1package cfclient
2
3import (
4	"bytes"
5	"crypto/tls"
6	"encoding/json"
7	"fmt"
8	"io"
9	"io/ioutil"
10	"mime/multipart"
11	"net/http"
12	"net/url"
13	"os"
14	"strconv"
15	"strings"
16	"time"
17
18	"github.com/pkg/errors"
19)
20
21type AppResponse struct {
22	Count     int           `json:"total_results"`
23	Pages     int           `json:"total_pages"`
24	NextUrl   string        `json:"next_url"`
25	Resources []AppResource `json:"resources"`
26}
27
28type AppResource struct {
29	Meta   Meta `json:"metadata"`
30	Entity App  `json:"entity"`
31}
32
33type AppState string
34
35const (
36	APP_STOPPED AppState = "STOPPED"
37	APP_STARTED AppState = "STARTED"
38)
39
40type HealthCheckType string
41
42const (
43	HEALTH_HTTP    HealthCheckType = "http"
44	HEALTH_PORT    HealthCheckType = "port"
45	HEALTH_PROCESS HealthCheckType = "process"
46)
47
48type DockerCredentials struct {
49	Username string `json:"username,omitempty"`
50	Password string `json:"password,omitempty"`
51}
52
53type AppCreateRequest struct {
54	Name      string `json:"name"`
55	SpaceGuid string `json:"space_guid"`
56	// Memory for the app, in MB
57	Memory int `json:"memory,omitempty"`
58	// Instances to startup
59	Instances int `json:"instances,omitempty"`
60	// Disk quota in MB
61	DiskQuota int    `json:"disk_quota,omitempty"`
62	StackGuid string `json:"stack_guid,omitempty"`
63	// Desired state of the app. Either "STOPPED" or "STARTED"
64	State AppState `json:"state,omitempty"`
65	// Command to start an app
66	Command string `json:"command,omitempty"`
67	// Buildpack to build the app. Three options:
68	// 1. Blank for autodetection
69	// 2. GIT url
70	// 3. Name of an installed buildpack
71	Buildpack string `json:"buildpack,omitempty"`
72	// Endpoint to check if an app is healthy
73	HealthCheckHttpEndpoint string `json:"health_check_http_endpoint,omitempty"`
74	// How to check if an app is healthy. Defaults to HEALTH_PORT if not specified
75	HealthCheckType   HealthCheckType        `json:"health_check_type,omitempty"`
76	Diego             bool                   `json:"diego,omitempty"`
77	EnableSSH         bool                   `json:"enable_ssh,omitempty"`
78	DockerImage       string                 `json:"docker_image,omitempty"`
79	DockerCredentials DockerCredentials      `json:"docker_credentials,omitempty"`
80	Environment       map[string]interface{} `json:"environment_json,omitempty"`
81}
82
83type App struct {
84	Guid                     string                 `json:"guid"`
85	CreatedAt                string                 `json:"created_at"`
86	UpdatedAt                string                 `json:"updated_at"`
87	Name                     string                 `json:"name"`
88	Memory                   int                    `json:"memory"`
89	Instances                int                    `json:"instances"`
90	DiskQuota                int                    `json:"disk_quota"`
91	SpaceGuid                string                 `json:"space_guid"`
92	StackGuid                string                 `json:"stack_guid"`
93	State                    string                 `json:"state"`
94	PackageState             string                 `json:"package_state"`
95	Command                  string                 `json:"command"`
96	Buildpack                string                 `json:"buildpack"`
97	DetectedBuildpack        string                 `json:"detected_buildpack"`
98	DetectedBuildpackGuid    string                 `json:"detected_buildpack_guid"`
99	HealthCheckHttpEndpoint  string                 `json:"health_check_http_endpoint"`
100	HealthCheckType          string                 `json:"health_check_type"`
101	HealthCheckTimeout       int                    `json:"health_check_timeout"`
102	Diego                    bool                   `json:"diego"`
103	EnableSSH                bool                   `json:"enable_ssh"`
104	DetectedStartCommand     string                 `json:"detected_start_command"`
105	DockerImage              string                 `json:"docker_image"`
106	DockerCredentials        map[string]interface{} `json:"docker_credentials_json"`
107	Environment              map[string]interface{} `json:"environment_json"`
108	StagingFailedReason      string                 `json:"staging_failed_reason"`
109	StagingFailedDescription string                 `json:"staging_failed_description"`
110	Ports                    []int                  `json:"ports"`
111	SpaceURL                 string                 `json:"space_url"`
112	SpaceData                SpaceResource          `json:"space"`
113	PackageUpdatedAt         string                 `json:"package_updated_at"`
114	c                        *Client
115}
116
117type AppInstance struct {
118	State string    `json:"state"`
119	Since sinceTime `json:"since"`
120}
121
122type AppStats struct {
123	State string `json:"state"`
124	Stats struct {
125		Name      string   `json:"name"`
126		Uris      []string `json:"uris"`
127		Host      string   `json:"host"`
128		Port      int      `json:"port"`
129		Uptime    int      `json:"uptime"`
130		MemQuota  int      `json:"mem_quota"`
131		DiskQuota int      `json:"disk_quota"`
132		FdsQuota  int      `json:"fds_quota"`
133		Usage     struct {
134			Time statTime `json:"time"`
135			CPU  float64  `json:"cpu"`
136			Mem  int      `json:"mem"`
137			Disk int      `json:"disk"`
138		} `json:"usage"`
139	} `json:"stats"`
140}
141
142type AppSummary struct {
143	Guid                     string                 `json:"guid"`
144	Name                     string                 `json:"name"`
145	ServiceCount             int                    `json:"service_count"`
146	RunningInstances         int                    `json:"running_instances"`
147	SpaceGuid                string                 `json:"space_guid"`
148	StackGuid                string                 `json:"stack_guid"`
149	Buildpack                string                 `json:"buildpack"`
150	DetectedBuildpack        string                 `json:"detected_buildpack"`
151	Environment              map[string]interface{} `json:"environment_json"`
152	Memory                   int                    `json:"memory"`
153	Instances                int                    `json:"instances"`
154	DiskQuota                int                    `json:"disk_quota"`
155	State                    string                 `json:"state"`
156	Command                  string                 `json:"command"`
157	PackageState             string                 `json:"package_state"`
158	HealthCheckType          string                 `json:"health_check_type"`
159	HealthCheckTimeout       int                    `json:"health_check_timeout"`
160	StagingFailedReason      string                 `json:"staging_failed_reason"`
161	StagingFailedDescription string                 `json:"staging_failed_description"`
162	Diego                    bool                   `json:"diego"`
163	DockerImage              string                 `json:"docker_image"`
164	DetectedStartCommand     string                 `json:"detected_start_command"`
165	EnableSSH                bool                   `json:"enable_ssh"`
166	DockerCredentials        map[string]interface{} `json:"docker_credentials_json"`
167}
168
169type AppEnv struct {
170	// These can have arbitrary JSON so need to map to interface{}
171	Environment    map[string]interface{} `json:"environment_json"`
172	StagingEnv     map[string]interface{} `json:"staging_env_json"`
173	RunningEnv     map[string]interface{} `json:"running_env_json"`
174	SystemEnv      map[string]interface{} `json:"system_env_json"`
175	ApplicationEnv map[string]interface{} `json:"application_env_json"`
176}
177
178// Custom time types to handle non-RFC3339 formatting in API JSON
179
180type sinceTime struct {
181	time.Time
182}
183
184func (s *sinceTime) UnmarshalJSON(b []byte) (err error) {
185	timeFlt, err := strconv.ParseFloat(string(b), 64)
186	if err != nil {
187		return err
188	}
189	time := time.Unix(int64(timeFlt), 0)
190	*s = sinceTime{time}
191	return nil
192}
193
194func (s sinceTime) ToTime() time.Time {
195	t, _ := time.Parse(time.UnixDate, s.Format(time.UnixDate))
196	return t
197}
198
199type statTime struct {
200	time.Time
201}
202
203func (s *statTime) UnmarshalJSON(b []byte) (err error) {
204	timeString, err := strconv.Unquote(string(b))
205	if err != nil {
206		return err
207	}
208
209	possibleFormats := [...]string{time.RFC3339, time.RFC3339Nano, "2006-01-02 15:04:05 -0700", "2006-01-02 15:04:05 MST"}
210
211	var value time.Time
212	for _, possibleFormat := range possibleFormats {
213		if value, err = time.Parse(possibleFormat, timeString); err == nil {
214			*s = statTime{value}
215			return nil
216		}
217	}
218
219	return fmt.Errorf("%s was not in any of the expected Date Formats %v", timeString, possibleFormats)
220}
221
222func (s statTime) ToTime() time.Time {
223	t, _ := time.Parse(time.UnixDate, s.Format(time.UnixDate))
224	return t
225}
226
227func (a *App) Space() (Space, error) {
228	var spaceResource SpaceResource
229	r := a.c.NewRequest("GET", a.SpaceURL)
230	resp, err := a.c.DoRequest(r)
231	if err != nil {
232		return Space{}, errors.Wrap(err, "Error requesting space")
233	}
234	defer resp.Body.Close()
235	resBody, err := ioutil.ReadAll(resp.Body)
236	if err != nil {
237		return Space{}, errors.Wrap(err, "Error reading space response")
238	}
239
240	err = json.Unmarshal(resBody, &spaceResource)
241	if err != nil {
242		return Space{}, errors.Wrap(err, "Error unmarshalling body")
243	}
244	return a.c.mergeSpaceResource(spaceResource), nil
245}
246
247func (a *App) Summary() (AppSummary, error) {
248	var appSummary AppSummary
249	requestUrl := fmt.Sprintf("/v2/apps/%s/summary", a.Guid)
250	r := a.c.NewRequest("GET", requestUrl)
251	resp, err := a.c.DoRequest(r)
252	if err != nil {
253		return AppSummary{}, errors.Wrap(err, "Error requesting app summary")
254	}
255	resBody, err := ioutil.ReadAll(resp.Body)
256	defer resp.Body.Close()
257	if err != nil {
258		return AppSummary{}, errors.Wrap(err, "Error reading app summary body")
259	}
260	err = json.Unmarshal(resBody, &appSummary)
261	if err != nil {
262		return AppSummary{}, errors.Wrap(err, "Error unmarshalling app summary")
263	}
264	return appSummary, nil
265}
266
267// ListAppsByQueryWithLimits queries totalPages app info. When totalPages is
268// less and equal than 0, it queries all app info
269// When there are no more than totalPages apps on server side, all apps info will be returned
270func (c *Client) ListAppsByQueryWithLimits(query url.Values, totalPages int) ([]App, error) {
271	return c.listApps("/v2/apps?"+query.Encode(), totalPages)
272}
273
274func (c *Client) ListAppsByQuery(query url.Values) ([]App, error) {
275	return c.listApps("/v2/apps?"+query.Encode(), -1)
276}
277
278// GetAppByGuidNoInlineCall will fetch app info including space and orgs information
279// Without using inline-relations-depth=2 call
280func (c *Client) GetAppByGuidNoInlineCall(guid string) (App, error) {
281	var appResource AppResource
282	r := c.NewRequest("GET", "/v2/apps/"+guid)
283	resp, err := c.DoRequest(r)
284	if err != nil {
285		return App{}, errors.Wrap(err, "Error requesting apps")
286	}
287	defer resp.Body.Close()
288	resBody, err := ioutil.ReadAll(resp.Body)
289	if err != nil {
290		return App{}, errors.Wrap(err, "Error reading app response body")
291	}
292
293	err = json.Unmarshal(resBody, &appResource)
294	if err != nil {
295		return App{}, errors.Wrap(err, "Error unmarshalling app")
296	}
297	app := c.mergeAppResource(appResource)
298
299	// If no Space Information no need to check org.
300	if app.SpaceGuid != "" {
301		//Getting Spaces Resource
302		space, err := app.Space()
303		if err != nil {
304			errors.Wrap(err, "Unable to get the Space for the apps "+app.Name)
305		} else {
306			app.SpaceData.Entity = space
307
308		}
309
310		//Getting orgResource
311		org, err := app.SpaceData.Entity.Org()
312		if err != nil {
313			errors.Wrap(err, "Unable to get the Org for the apps "+app.Name)
314		} else {
315			app.SpaceData.Entity.OrgData.Entity = org
316		}
317	}
318
319	return app, nil
320}
321
322func (c *Client) ListApps() ([]App, error) {
323	q := url.Values{}
324	q.Set("inline-relations-depth", "2")
325	return c.ListAppsByQuery(q)
326}
327
328func (c *Client) ListAppsByRoute(routeGuid string) ([]App, error) {
329	return c.listApps(fmt.Sprintf("/v2/routes/%s/apps", routeGuid), -1)
330}
331
332func (c *Client) listApps(requestUrl string, totalPages int) ([]App, error) {
333	pages := 0
334	apps := []App{}
335	for {
336		var appResp AppResponse
337		r := c.NewRequest("GET", requestUrl)
338		resp, err := c.DoRequest(r)
339
340		if err != nil {
341			return nil, errors.Wrap(err, "Error requesting apps")
342		}
343		defer resp.Body.Close()
344		resBody, err := ioutil.ReadAll(resp.Body)
345		if err != nil {
346			return nil, errors.Wrap(err, "Error reading app request")
347		}
348
349		err = json.Unmarshal(resBody, &appResp)
350		if err != nil {
351			return nil, errors.Wrap(err, "Error unmarshalling app")
352		}
353		for _, app := range appResp.Resources {
354			apps = append(apps, c.mergeAppResource(app))
355		}
356
357		requestUrl = appResp.NextUrl
358		if requestUrl == "" {
359			break
360		}
361
362		pages += 1
363		if totalPages > 0 && pages >= totalPages {
364			break
365		}
366	}
367	return apps, nil
368}
369
370func (c *Client) GetAppInstances(guid string) (map[string]AppInstance, error) {
371	var appInstances map[string]AppInstance
372
373	requestURL := fmt.Sprintf("/v2/apps/%s/instances", guid)
374	r := c.NewRequest("GET", requestURL)
375	resp, err := c.DoRequest(r)
376	if err != nil {
377		return nil, errors.Wrap(err, "Error requesting app instances")
378	}
379	defer resp.Body.Close()
380	resBody, err := ioutil.ReadAll(resp.Body)
381	if err != nil {
382		return nil, errors.Wrap(err, "Error reading app instances")
383	}
384	err = json.Unmarshal(resBody, &appInstances)
385	if err != nil {
386		return nil, errors.Wrap(err, "Error unmarshalling app instances")
387	}
388	return appInstances, nil
389}
390
391func (c *Client) GetAppEnv(guid string) (AppEnv, error) {
392	var appEnv AppEnv
393
394	requestURL := fmt.Sprintf("/v2/apps/%s/env", guid)
395	r := c.NewRequest("GET", requestURL)
396	resp, err := c.DoRequest(r)
397	if err != nil {
398		return appEnv, errors.Wrap(err, "Error requesting app env")
399	}
400	defer resp.Body.Close()
401	resBody, err := ioutil.ReadAll(resp.Body)
402	if err != nil {
403		return appEnv, errors.Wrap(err, "Error reading app env")
404	}
405	err = json.Unmarshal(resBody, &appEnv)
406	if err != nil {
407		return appEnv, errors.Wrap(err, "Error unmarshalling app env")
408	}
409	return appEnv, nil
410}
411
412func (c *Client) GetAppRoutes(guid string) ([]Route, error) {
413	return c.fetchRoutes(fmt.Sprintf("/v2/apps/%s/routes", guid))
414}
415
416func (c *Client) GetAppStats(guid string) (map[string]AppStats, error) {
417	var appStats map[string]AppStats
418
419	requestURL := fmt.Sprintf("/v2/apps/%s/stats", guid)
420	r := c.NewRequest("GET", requestURL)
421	resp, err := c.DoRequest(r)
422	if err != nil {
423		return nil, errors.Wrap(err, "Error requesting app stats")
424	}
425	defer resp.Body.Close()
426	resBody, err := ioutil.ReadAll(resp.Body)
427	if err != nil {
428		return nil, errors.Wrap(err, "Error reading app stats")
429	}
430	err = json.Unmarshal(resBody, &appStats)
431	if err != nil {
432		return nil, errors.Wrap(err, "Error unmarshalling app stats")
433	}
434	return appStats, nil
435}
436
437func (c *Client) KillAppInstance(guid string, index string) error {
438	requestURL := fmt.Sprintf("/v2/apps/%s/instances/%s", guid, index)
439	r := c.NewRequest("DELETE", requestURL)
440	resp, err := c.DoRequest(r)
441	if err != nil {
442		return errors.Wrapf(err, "Error stopping app %s at index %s", guid, index)
443	}
444	defer resp.Body.Close()
445	if resp.StatusCode != 204 {
446		return errors.Wrapf(err, "Error stopping app %s at index %s", guid, index)
447	}
448	return nil
449}
450
451func (c *Client) GetAppByGuid(guid string) (App, error) {
452	var appResource AppResource
453	r := c.NewRequest("GET", "/v2/apps/"+guid+"?inline-relations-depth=2")
454	resp, err := c.DoRequest(r)
455	if err != nil {
456		return App{}, errors.Wrap(err, "Error requesting apps")
457	}
458	defer resp.Body.Close()
459	resBody, err := ioutil.ReadAll(resp.Body)
460	if err != nil {
461		return App{}, errors.Wrap(err, "Error reading app response body")
462	}
463
464	err = json.Unmarshal(resBody, &appResource)
465	if err != nil {
466		return App{}, errors.Wrap(err, "Error unmarshalling app")
467	}
468	return c.mergeAppResource(appResource), nil
469}
470
471func (c *Client) AppByGuid(guid string) (App, error) {
472	return c.GetAppByGuid(guid)
473}
474
475//AppByName takes an appName, and GUIDs for a space and org, and performs
476// the API lookup with those query parameters set to return you the desired
477// App object.
478func (c *Client) AppByName(appName, spaceGuid, orgGuid string) (app App, err error) {
479	query := url.Values{}
480	query.Add("q", fmt.Sprintf("organization_guid:%s", orgGuid))
481	query.Add("q", fmt.Sprintf("space_guid:%s", spaceGuid))
482	query.Add("q", fmt.Sprintf("name:%s", appName))
483	apps, err := c.ListAppsByQuery(query)
484	if err != nil {
485		return
486	}
487	if len(apps) == 0 {
488		err = fmt.Errorf("No app found with name: `%s` in space with GUID `%s` and org with GUID `%s`", appName, spaceGuid, orgGuid)
489		return
490	}
491	app = apps[0]
492	return
493}
494
495// UploadAppBits uploads the application's contents
496func (c *Client) UploadAppBits(file io.Reader, appGUID string) error {
497	requestFile, err := ioutil.TempFile("", "requests")
498
499	defer func() {
500		requestFile.Close()
501		os.Remove(requestFile.Name())
502	}()
503
504	writer := multipart.NewWriter(requestFile)
505	err = writer.WriteField("resources", "[]")
506	if err != nil {
507		return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
508	}
509
510	part, err := writer.CreateFormFile("application", "application.zip")
511	if err != nil {
512		return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
513	}
514
515	_, err = io.Copy(part, file)
516	if err != nil {
517		return errors.Wrapf(err, "Error uploading app %s bits, failed to copy all bytes", appGUID)
518	}
519
520	err = writer.Close()
521	if err != nil {
522		return errors.Wrapf(err, "Error uploading app %s bits, failed to close multipart writer", appGUID)
523	}
524
525	requestFile.Seek(0, 0)
526	fileStats, err := requestFile.Stat()
527	if err != nil {
528		return errors.Wrapf(err, "Error uploading app %s bits, failed to get temp file stats", appGUID)
529	}
530
531	requestURL := fmt.Sprintf("/v2/apps/%s/bits", appGUID)
532	r := c.NewRequestWithBody("PUT", requestURL, requestFile)
533	req, err := r.toHTTP()
534	if err != nil {
535		return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
536	}
537
538	req.ContentLength = fileStats.Size()
539	contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())
540	req.Header.Set("Content-Type", contentType)
541
542	resp, err := c.Do(req)
543	if err != nil {
544		return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
545	}
546	if resp.StatusCode != http.StatusCreated {
547		return errors.Wrapf(err, "Error uploading app %s bits, response code: %d", appGUID, resp.StatusCode)
548	}
549
550	return nil
551}
552
553// GetAppBits downloads the application's bits as a tar file
554func (c *Client) GetAppBits(guid string) (io.ReadCloser, error) {
555	requestURL := fmt.Sprintf("/v2/apps/%s/download", guid)
556	req := c.NewRequest("GET", requestURL)
557	resp, err := c.DoRequestWithoutRedirects(req)
558	if err != nil {
559		return nil, errors.Wrapf(err, "Error downloading app %s bits, API request failed", guid)
560	}
561	if isResponseRedirect(resp) {
562		// directly download the bits from blobstore using a non cloud controller transport
563		// some blobstores will return a 400 if an Authorization header is sent
564		blobStoreLocation := resp.Header.Get("Location")
565		tr := &http.Transport{
566			TLSClientConfig: &tls.Config{InsecureSkipVerify: c.Config.SkipSslValidation},
567		}
568		client := &http.Client{Transport: tr}
569		resp, err = client.Get(blobStoreLocation)
570		if err != nil {
571			return nil, errors.Wrapf(err, "Error downloading app %s bits from blobstore", guid)
572		}
573	} else {
574		return nil, errors.Wrapf(err, "Error downloading app %s bits, expected redirect to blobstore", guid)
575	}
576	return resp.Body, nil
577}
578
579// CreateApp creates a new empty application that still needs it's
580// app bit uploaded and to be started
581func (c *Client) CreateApp(req AppCreateRequest) (App, error) {
582	var appResp AppResource
583	buf := bytes.NewBuffer(nil)
584	err := json.NewEncoder(buf).Encode(req)
585	if err != nil {
586		return App{}, err
587	}
588	r := c.NewRequestWithBody("POST", "/v2/apps", buf)
589	resp, err := c.DoRequest(r)
590	if err != nil {
591		return App{}, errors.Wrapf(err, "Error creating app %s", req.Name)
592	}
593	if resp.StatusCode != http.StatusCreated {
594		return App{}, errors.Wrapf(err, "Error creating app %s, response code: %d", req.Name, resp.StatusCode)
595	}
596	resBody, err := ioutil.ReadAll(resp.Body)
597	defer resp.Body.Close()
598	if err != nil {
599		return App{}, errors.Wrapf(err, "Error reading app %s http response body", req.Name)
600	}
601	err = json.Unmarshal(resBody, &appResp)
602	if err != nil {
603		return App{}, errors.Wrapf(err, "Error deserializing app %s response", req.Name)
604	}
605	return c.mergeAppResource(appResp), nil
606}
607
608func (c *Client) StartApp(guid string) error {
609	startRequest := strings.NewReader(`{ "state": "STARTED" }`)
610	resp, err := c.DoRequest(c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/apps/%s", guid), startRequest))
611	if err != nil {
612		return err
613	}
614	if resp.StatusCode != http.StatusNoContent {
615		return errors.Wrapf(err, "Error starting app %s, response code: %d", guid, resp.StatusCode)
616	}
617	return nil
618}
619
620func (c *Client) StopApp(guid string) error {
621	stopRequest := strings.NewReader(`{ "state": "STOPPED" }`)
622	resp, err := c.DoRequest(c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/apps/%s", guid), stopRequest))
623	if err != nil {
624		return err
625	}
626	if resp.StatusCode != http.StatusNoContent {
627		return errors.Wrapf(err, "Error stopping app %s, response code: %d", guid, resp.StatusCode)
628	}
629	return nil
630}
631
632func (c *Client) DeleteApp(guid string) error {
633	resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/apps/%s", guid)))
634	if err != nil {
635		return err
636	}
637	if resp.StatusCode != http.StatusNoContent {
638		return errors.Wrapf(err, "Error deleting app %s, response code: %d", guid, resp.StatusCode)
639	}
640	return nil
641}
642
643func (c *Client) mergeAppResource(app AppResource) App {
644	app.Entity.Guid = app.Meta.Guid
645	app.Entity.CreatedAt = app.Meta.CreatedAt
646	app.Entity.UpdatedAt = app.Meta.UpdatedAt
647	app.Entity.SpaceData.Entity.Guid = app.Entity.SpaceData.Meta.Guid
648	app.Entity.SpaceData.Entity.OrgData.Entity.Guid = app.Entity.SpaceData.Entity.OrgData.Meta.Guid
649	app.Entity.c = c
650	return app.Entity
651}
652
653func isResponseRedirect(res *http.Response) bool {
654	switch res.StatusCode {
655	case http.StatusTemporaryRedirect, http.StatusPermanentRedirect, http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther:
656		return true
657	}
658	return false
659}
660