1package cfclient
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"io/ioutil"
8	"net/http"
9	"net/url"
10	"strconv"
11
12	"github.com/pkg/errors"
13)
14
15type SpaceRequest struct {
16	Name                 string   `json:"name"`
17	OrganizationGuid     string   `json:"organization_guid"`
18	DeveloperGuid        []string `json:"developer_guids,omitempty"`
19	ManagerGuid          []string `json:"manager_guids,omitempty"`
20	AuditorGuid          []string `json:"auditor_guids,omitempty"`
21	DomainGuid           []string `json:"domain_guids,omitempty"`
22	SecurityGroupGuids   []string `json:"security_group_guids,omitempty"`
23	SpaceQuotaDefGuid    string   `json:"space_quota_definition_guid,omitempty"`
24	IsolationSegmentGuid string   `json:"isolation_segment_guid,omitempty"`
25	AllowSSH             bool     `json:"allow_ssh"`
26}
27
28type SpaceResponse struct {
29	Count     int             `json:"total_results"`
30	Pages     int             `json:"total_pages"`
31	NextUrl   string          `json:"next_url"`
32	Resources []SpaceResource `json:"resources"`
33}
34
35type SpaceResource struct {
36	Meta   Meta  `json:"metadata"`
37	Entity Space `json:"entity"`
38}
39
40type ServicePlanEntity struct {
41	Name                string                  `json:"name"`
42	Free                bool                    `json:"free"`
43	Public              bool                    `json:"public"`
44	Active              bool                    `json:"active"`
45	Description         string                  `json:"description"`
46	ServiceOfferingGUID string                  `json:"service_guid"`
47	ServiceOffering     ServiceOfferingResource `json:"service"`
48}
49
50type ServiceOfferingExtra struct {
51	DisplayName      string `json:"displayName"`
52	DocumentationURL string `json:"documentationURL"`
53	LongDescription  string `json:"longDescription"`
54}
55
56type ServiceOfferingEntity struct {
57	Label        string
58	Description  string
59	Provider     string        `json:"provider"`
60	BrokerGUID   string        `json:"service_broker_guid"`
61	Requires     []string      `json:"requires"`
62	ServicePlans []interface{} `json:"service_plans"`
63	Extra        ServiceOfferingExtra
64}
65
66type ServiceOfferingResource struct {
67	Metadata Meta
68	Entity   ServiceOfferingEntity
69}
70
71type ServiceOfferingResponse struct {
72	Count     int                       `json:"total_results"`
73	Pages     int                       `json:"total_pages"`
74	NextUrl   string                    `json:"next_url"`
75	PrevUrl   string                    `json:"prev_url"`
76	Resources []ServiceOfferingResource `json:"resources"`
77}
78
79type SpaceUserResponse struct {
80	Count     int            `json:"total_results"`
81	Pages     int            `json:"total_pages"`
82	NextURL   string         `json:"next_url"`
83	Resources []UserResource `json:"resources"`
84}
85
86type Space struct {
87	Guid                 string      `json:"guid"`
88	CreatedAt            string      `json:"created_at"`
89	UpdatedAt            string      `json:"updated_at"`
90	Name                 string      `json:"name"`
91	OrganizationGuid     string      `json:"organization_guid"`
92	OrgURL               string      `json:"organization_url"`
93	OrgData              OrgResource `json:"organization"`
94	QuotaDefinitionGuid  string      `json:"space_quota_definition_guid"`
95	IsolationSegmentGuid string      `json:"isolation_segment_guid"`
96	AllowSSH             bool        `json:"allow_ssh"`
97	c                    *Client
98}
99
100type SpaceSummary struct {
101	Guid     string           `json:"guid"`
102	Name     string           `json:"name"`
103	Apps     []AppSummary     `json:"apps"`
104	Services []ServiceSummary `json:"services"`
105}
106
107type SpaceRoleResponse struct {
108	Count     int                 `json:"total_results"`
109	Pages     int                 `json:"total_pages"`
110	NextUrl   string              `json:"next_url"`
111	Resources []SpaceRoleResource `json:"resources"`
112}
113
114type SpaceRoleResource struct {
115	Meta   Meta      `json:"metadata"`
116	Entity SpaceRole `json:"entity"`
117}
118
119type SpaceRole struct {
120	Guid                           string   `json:"guid"`
121	Admin                          bool     `json:"admin"`
122	Active                         bool     `json:"active"`
123	DefaultSpaceGuid               string   `json:"default_space_guid"`
124	Username                       string   `json:"username"`
125	SpaceRoles                     []string `json:"space_roles"`
126	SpacesUrl                      string   `json:"spaces_url"`
127	OrganizationsUrl               string   `json:"organizations_url"`
128	ManagedOrganizationsUrl        string   `json:"managed_organizations_url"`
129	BillingManagedOrganizationsUrl string   `json:"billing_managed_organizations_url"`
130	AuditedOrganizationsUrl        string   `json:"audited_organizations_url"`
131	ManagedSpacesUrl               string   `json:"managed_spaces_url"`
132	AuditedSpacesUrl               string   `json:"audited_spaces_url"`
133	c                              *Client
134}
135
136func (s *Space) Org() (Org, error) {
137	var orgResource OrgResource
138	r := s.c.NewRequest("GET", s.OrgURL)
139	resp, err := s.c.DoRequest(r)
140	if err != nil {
141		return Org{}, errors.Wrap(err, "Error requesting org")
142	}
143	resBody, err := ioutil.ReadAll(resp.Body)
144	if err != nil {
145		return Org{}, errors.Wrap(err, "Error reading org request")
146	}
147
148	err = json.Unmarshal(resBody, &orgResource)
149	if err != nil {
150		return Org{}, errors.Wrap(err, "Error unmarshaling org")
151	}
152	return s.c.mergeOrgResource(orgResource), nil
153}
154
155func (s *Space) Quota() (*SpaceQuota, error) {
156	var spaceQuota *SpaceQuota
157	var spaceQuotaResource SpaceQuotasResource
158	if s.QuotaDefinitionGuid == "" {
159		return nil, nil
160	}
161	requestUrl := fmt.Sprintf("/v2/space_quota_definitions/%s", s.QuotaDefinitionGuid)
162	r := s.c.NewRequest("GET", requestUrl)
163	resp, err := s.c.DoRequest(r)
164	if err != nil {
165		return &SpaceQuota{}, errors.Wrap(err, "Error requesting space quota")
166	}
167	resBody, err := ioutil.ReadAll(resp.Body)
168	defer resp.Body.Close()
169	if err != nil {
170		return &SpaceQuota{}, errors.Wrap(err, "Error reading space quota body")
171	}
172	err = json.Unmarshal(resBody, &spaceQuotaResource)
173	if err != nil {
174		return &SpaceQuota{}, errors.Wrap(err, "Error unmarshalling space quota")
175	}
176	spaceQuota = &spaceQuotaResource.Entity
177	spaceQuota.Guid = spaceQuotaResource.Meta.Guid
178	spaceQuota.c = s.c
179	return spaceQuota, nil
180}
181
182func (s *Space) Summary() (SpaceSummary, error) {
183	var spaceSummary SpaceSummary
184	requestUrl := fmt.Sprintf("/v2/spaces/%s/summary", s.Guid)
185	r := s.c.NewRequest("GET", requestUrl)
186	resp, err := s.c.DoRequest(r)
187	if err != nil {
188		return SpaceSummary{}, errors.Wrap(err, "Error requesting space summary")
189	}
190	resBody, err := ioutil.ReadAll(resp.Body)
191	defer resp.Body.Close()
192	if err != nil {
193		return SpaceSummary{}, errors.Wrap(err, "Error reading space summary body")
194	}
195	err = json.Unmarshal(resBody, &spaceSummary)
196	if err != nil {
197		return SpaceSummary{}, errors.Wrap(err, "Error unmarshalling space summary")
198	}
199	return spaceSummary, nil
200}
201
202func (s *Space) Roles() ([]SpaceRole, error) {
203	var roles []SpaceRole
204	requestUrl := fmt.Sprintf("/v2/spaces/%s/user_roles", s.Guid)
205	for {
206		rolesResp, err := s.c.getSpaceRolesResponse(requestUrl)
207		if err != nil {
208			return roles, err
209		}
210		for _, role := range rolesResp.Resources {
211			role.Entity.Guid = role.Meta.Guid
212			role.Entity.c = s.c
213			roles = append(roles, role.Entity)
214		}
215		requestUrl = rolesResp.NextUrl
216		if requestUrl == "" {
217			break
218		}
219	}
220	return roles, nil
221}
222
223func (c *Client) CreateSpace(req SpaceRequest) (Space, error) {
224	buf := bytes.NewBuffer(nil)
225	err := json.NewEncoder(buf).Encode(req)
226	if err != nil {
227		return Space{}, err
228	}
229	r := c.NewRequestWithBody("POST", "/v2/spaces", buf)
230	resp, err := c.DoRequest(r)
231	if err != nil {
232		return Space{}, err
233	}
234	if resp.StatusCode != http.StatusCreated {
235		return Space{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
236	}
237	return c.handleSpaceResp(resp)
238}
239
240func (c *Client) UpdateSpace(spaceGUID string, req SpaceRequest) (Space, error) {
241	space := Space{Guid: spaceGUID, c: c}
242	return space.Update(req)
243}
244
245func (c *Client) DeleteSpace(guid string, recursive, async bool) error {
246	resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/spaces/%s?recursive=%t&async=%t", guid, recursive, async)))
247	if err != nil {
248		return err
249	}
250	if resp.StatusCode != http.StatusNoContent {
251		return errors.Wrapf(err, "Error deleting space %s, response code: %d", guid, resp.StatusCode)
252	}
253	return nil
254}
255
256func (c *Client) ListSpaceManagersByQuery(spaceGUID string, query url.Values) ([]User, error) {
257	return c.listSpaceUsersByRoleAndQuery(spaceGUID, "managers", query)
258}
259
260func (c *Client) ListSpaceManagers(spaceGUID string) ([]User, error) {
261	return c.ListSpaceManagersByQuery(spaceGUID, nil)
262}
263
264func (c *Client) ListSpaceAuditorsByQuery(spaceGUID string, query url.Values) ([]User, error) {
265	return c.listSpaceUsersByRoleAndQuery(spaceGUID, "auditors", query)
266}
267
268func (c *Client) ListSpaceAuditors(spaceGUID string) ([]User, error) {
269	return c.ListSpaceAuditorsByQuery(spaceGUID, nil)
270}
271
272func (c *Client) ListSpaceDevelopersByQuery(spaceGUID string, query url.Values) ([]User, error) {
273	return c.listSpaceUsersByRoleAndQuery(spaceGUID, "developers", query)
274}
275
276func (c *Client) listSpaceUsersByRoleAndQuery(spaceGUID, role string, query url.Values) ([]User, error) {
277	var users []User
278	requestURL := fmt.Sprintf("/v2/spaces/%s/%s?%s", spaceGUID, role, query.Encode())
279	for {
280		userResp, err := c.getUserResponse(requestURL)
281		if err != nil {
282			return []User{}, err
283		}
284		for _, u := range userResp.Resources {
285			users = append(users, c.mergeUserResource(u))
286		}
287		requestURL = userResp.NextUrl
288		if requestURL == "" {
289			break
290		}
291	}
292	return users, nil
293}
294
295func (c *Client) ListSpaceDevelopers(spaceGUID string) ([]User, error) {
296	return c.ListSpaceDevelopersByQuery(spaceGUID, nil)
297}
298
299func (c *Client) AssociateSpaceDeveloper(spaceGUID, userGUID string) (Space, error) {
300	space := Space{Guid: spaceGUID, c: c}
301	return space.AssociateDeveloper(userGUID)
302}
303
304func (c *Client) AssociateSpaceDeveloperByUsername(spaceGUID, name string) (Space, error) {
305	space := Space{Guid: spaceGUID, c: c}
306	return space.AssociateDeveloperByUsername(name)
307}
308
309func (c *Client) AssociateSpaceDeveloperByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
310	space := Space{Guid: spaceGUID, c: c}
311	return space.AssociateDeveloperByUsernameAndOrigin(name, origin)
312}
313
314func (c *Client) RemoveSpaceDeveloper(spaceGUID, userGUID string) error {
315	space := Space{Guid: spaceGUID, c: c}
316	return space.RemoveDeveloper(userGUID)
317}
318
319func (c *Client) RemoveSpaceDeveloperByUsername(spaceGUID, name string) error {
320	space := Space{Guid: spaceGUID, c: c}
321	return space.RemoveDeveloperByUsername(name)
322}
323
324func (c *Client) RemoveSpaceDeveloperByUsernameAndOrigin(spaceGUID, name, origin string) error {
325	space := Space{Guid: spaceGUID, c: c}
326	return space.RemoveDeveloperByUsernameAndOrigin(name, origin)
327}
328
329func (c *Client) AssociateSpaceAuditor(spaceGUID, userGUID string) (Space, error) {
330	space := Space{Guid: spaceGUID, c: c}
331	return space.AssociateAuditor(userGUID)
332}
333
334func (c *Client) AssociateSpaceAuditorByUsername(spaceGUID, name string) (Space, error) {
335	space := Space{Guid: spaceGUID, c: c}
336	return space.AssociateAuditorByUsername(name)
337}
338
339func (c *Client) AssociateSpaceAuditorByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
340	space := Space{Guid: spaceGUID, c: c}
341	return space.AssociateAuditorByUsernameAndOrigin(name, origin)
342}
343
344func (c *Client) RemoveSpaceAuditor(spaceGUID, userGUID string) error {
345	space := Space{Guid: spaceGUID, c: c}
346	return space.RemoveAuditor(userGUID)
347}
348
349func (c *Client) RemoveSpaceAuditorByUsername(spaceGUID, name string) error {
350	space := Space{Guid: spaceGUID, c: c}
351	return space.RemoveAuditorByUsername(name)
352}
353
354func (c *Client) RemoveSpaceAuditorByUsernameAndOrigin(spaceGUID, name, origin string) error {
355	space := Space{Guid: spaceGUID, c: c}
356	return space.RemoveAuditorByUsernameAndOrigin(name, origin)
357}
358
359func (c *Client) AssociateSpaceManager(spaceGUID, userGUID string) (Space, error) {
360	space := Space{Guid: spaceGUID, c: c}
361	return space.AssociateManager(userGUID)
362}
363
364func (c *Client) AssociateSpaceManagerByUsername(spaceGUID, name string) (Space, error) {
365	space := Space{Guid: spaceGUID, c: c}
366	return space.AssociateManagerByUsername(name)
367}
368
369func (c *Client) AssociateSpaceManagerByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
370	space := Space{Guid: spaceGUID, c: c}
371	return space.AssociateManagerByUsernameAndOrigin(name, origin)
372}
373
374func (c *Client) RemoveSpaceManager(spaceGUID, userGUID string) error {
375	space := Space{Guid: spaceGUID, c: c}
376	return space.RemoveManager(userGUID)
377}
378
379func (c *Client) RemoveSpaceManagerByUsername(spaceGUID, name string) error {
380	space := Space{Guid: spaceGUID, c: c}
381	return space.RemoveManagerByUsername(name)
382}
383
384func (c *Client) RemoveSpaceManagerByUsernameAndOrigin(spaceGUID, name, origin string) error {
385	space := Space{Guid: spaceGUID, c: c}
386	return space.RemoveManagerByUsernameAndOrigin(name, origin)
387}
388
389func (s *Space) AssociateDeveloper(userGUID string) (Space, error) {
390	return s.associateRole(userGUID, "developers")
391}
392
393func (s *Space) AssociateDeveloperByUsername(name string) (Space, error) {
394	return s.associateUserByRole(name, "developers", "")
395}
396
397func (s *Space) AssociateDeveloperByUsernameAndOrigin(name, origin string) (Space, error) {
398	return s.associateUserByRole(name, "developers", origin)
399}
400
401func (s *Space) RemoveDeveloper(userGUID string) error {
402	return s.removeRole(userGUID, "developers")
403}
404
405func (s *Space) RemoveDeveloperByUsername(name string) error {
406	return s.removeUserByRole(name, "developers", "")
407}
408
409func (s *Space) RemoveDeveloperByUsernameAndOrigin(name, origin string) error {
410	return s.removeUserByRole(name, "developers", origin)
411}
412
413func (s *Space) AssociateAuditor(userGUID string) (Space, error) {
414	return s.associateRole(userGUID, "auditors")
415}
416
417func (s *Space) AssociateAuditorByUsername(name string) (Space, error) {
418	return s.associateUserByRole(name, "auditors", "")
419}
420
421func (s *Space) AssociateAuditorByUsernameAndOrigin(name, origin string) (Space, error) {
422	return s.associateUserByRole(name, "auditors", origin)
423}
424
425func (s *Space) RemoveAuditor(userGUID string) error {
426	return s.removeRole(userGUID, "auditors")
427}
428
429func (s *Space) RemoveAuditorByUsername(name string) error {
430	return s.removeUserByRole(name, "auditors", "")
431}
432
433func (s *Space) RemoveAuditorByUsernameAndOrigin(name, origin string) error {
434	return s.removeUserByRole(name, "auditors", origin)
435}
436
437func (s *Space) AssociateManager(userGUID string) (Space, error) {
438	return s.associateRole(userGUID, "managers")
439}
440
441func (s *Space) AssociateManagerByUsername(name string) (Space, error) {
442	return s.associateUserByRole(name, "managers", "")
443}
444
445func (s *Space) AssociateManagerByUsernameAndOrigin(name, origin string) (Space, error) {
446	return s.associateUserByRole(name, "managers", origin)
447}
448
449func (s *Space) RemoveManager(userGUID string) error {
450	return s.removeRole(userGUID, "managers")
451}
452
453func (s *Space) RemoveManagerByUsername(name string) error {
454	return s.removeUserByRole(name, "managers", "")
455}
456func (s *Space) RemoveManagerByUsernameAndOrigin(name, origin string) error {
457	return s.removeUserByRole(name, "managers", origin)
458}
459
460func (s *Space) associateRole(userGUID, role string) (Space, error) {
461	requestUrl := fmt.Sprintf("/v2/spaces/%s/%s/%s", s.Guid, role, userGUID)
462	r := s.c.NewRequest("PUT", requestUrl)
463	resp, err := s.c.DoRequest(r)
464	if err != nil {
465		return Space{}, err
466	}
467	if resp.StatusCode != http.StatusCreated {
468		return Space{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, userGUID, resp.StatusCode)
469	}
470	return s.c.handleSpaceResp(resp)
471}
472
473func (s *Space) associateUserByRole(name, role, origin string) (Space, error) {
474	requestUrl := fmt.Sprintf("/v2/spaces/%s/%s", s.Guid, role)
475	buf := bytes.NewBuffer(nil)
476	payload := make(map[string]string)
477	payload["username"] = name
478	if origin != "" {
479		payload["origin"] = origin
480	}
481	err := json.NewEncoder(buf).Encode(payload)
482	if err != nil {
483		return Space{}, err
484	}
485	r := s.c.NewRequestWithBody("PUT", requestUrl, buf)
486	resp, err := s.c.DoRequest(r)
487	if err != nil {
488		return Space{}, err
489	}
490	if resp.StatusCode != http.StatusCreated {
491		return Space{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, name, resp.StatusCode)
492	}
493	return s.c.handleSpaceResp(resp)
494}
495
496func (s *Space) removeRole(userGUID, role string) error {
497	requestUrl := fmt.Sprintf("/v2/spaces/%s/%s/%s", s.Guid, role, userGUID)
498	r := s.c.NewRequest("DELETE", requestUrl)
499	resp, err := s.c.DoRequest(r)
500	if err != nil {
501		return err
502	}
503	if resp.StatusCode != http.StatusNoContent {
504		return errors.Wrapf(err, "Error removing %s %s, response code: %d", role, userGUID, resp.StatusCode)
505	}
506	return nil
507}
508
509func (s *Space) removeUserByRole(name, role, origin string) error {
510	var requestURL string
511	var method string
512
513	buf := bytes.NewBuffer(nil)
514	payload := make(map[string]string)
515	payload["username"] = name
516	if origin != "" {
517		payload["origin"] = origin
518		requestURL = fmt.Sprintf("/v2/spaces/%s/%s/remove", s.Guid, role)
519		method = "POST"
520	} else {
521		requestURL = fmt.Sprintf("/v2/spaces/%s/%s", s.Guid, role)
522		method = "DELETE"
523	}
524	err := json.NewEncoder(buf).Encode(payload)
525	if err != nil {
526		return err
527	}
528	r := s.c.NewRequestWithBody(method, requestURL, buf)
529	resp, err := s.c.DoRequest(r)
530	if err != nil {
531		return err
532	}
533	if resp.StatusCode != http.StatusOK {
534		return errors.Wrapf(err, "Error removing %s %s, response code: %d", role, name, resp.StatusCode)
535	}
536	return nil
537}
538
539func (c *Client) ListSpaceSecGroups(spaceGUID string) (secGroups []SecGroup, err error) {
540	space := Space{Guid: spaceGUID, c: c}
541	return space.ListSecGroups()
542}
543
544func (s *Space) ListSecGroups() (secGroups []SecGroup, err error) {
545	requestURL := fmt.Sprintf("/v2/spaces/%s/security_groups?inline-relations-depth=1", s.Guid)
546	for requestURL != "" {
547		var secGroupResp SecGroupResponse
548		r := s.c.NewRequest("GET", requestURL)
549		resp, err := s.c.DoRequest(r)
550
551		if err != nil {
552			return nil, errors.Wrap(err, "Error requesting sec groups")
553		}
554		resBody, err := ioutil.ReadAll(resp.Body)
555		if err != nil {
556			return nil, errors.Wrap(err, "Error reading sec group response body")
557		}
558
559		err = json.Unmarshal(resBody, &secGroupResp)
560		if err != nil {
561			return nil, errors.Wrap(err, "Error unmarshaling sec group")
562		}
563
564		for _, secGroup := range secGroupResp.Resources {
565			secGroup.Entity.Guid = secGroup.Meta.Guid
566			secGroup.Entity.c = s.c
567			for i, space := range secGroup.Entity.SpacesData {
568				space.Entity.Guid = space.Meta.Guid
569				secGroup.Entity.SpacesData[i] = space
570			}
571			if len(secGroup.Entity.SpacesData) == 0 {
572				spaces, err := secGroup.Entity.ListSpaceResources()
573				if err != nil {
574					return nil, err
575				}
576				for _, space := range spaces {
577					secGroup.Entity.SpacesData = append(secGroup.Entity.SpacesData, space)
578				}
579			}
580			secGroups = append(secGroups, secGroup.Entity)
581		}
582
583		requestURL = secGroupResp.NextUrl
584		resp.Body.Close()
585	}
586	return secGroups, nil
587}
588
589func (s *Space) GetServiceOfferings() (ServiceOfferingResponse, error) {
590	var response ServiceOfferingResponse
591	requestURL := fmt.Sprintf("/v2/spaces/%s/services", s.Guid)
592	req := s.c.NewRequest("GET", requestURL)
593
594	resp, err := s.c.DoRequest(req)
595	if err != nil {
596		return ServiceOfferingResponse{}, errors.Wrap(err, "Error requesting service offerings")
597	}
598
599	body, err := ioutil.ReadAll(resp.Body)
600	if err != nil {
601		return ServiceOfferingResponse{}, errors.Wrap(err, "Error reading service offering response")
602	}
603
604	err = json.Unmarshal(body, &response)
605	if err != nil {
606		return ServiceOfferingResponse{}, errors.Wrap(err, "Error unmarshalling service offering response")
607	}
608
609	return response, nil
610}
611
612func (s *Space) Update(req SpaceRequest) (Space, error) {
613	buf := bytes.NewBuffer(nil)
614	err := json.NewEncoder(buf).Encode(req)
615	if err != nil {
616		return Space{}, err
617	}
618	r := s.c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/spaces/%s", s.Guid), buf)
619	resp, err := s.c.DoRequest(r)
620	if err != nil {
621		return Space{}, err
622	}
623	if resp.StatusCode != http.StatusCreated {
624		return Space{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
625	}
626	return s.c.handleSpaceResp(resp)
627}
628
629func (c *Client) ListSpacesByQuery(query url.Values) ([]Space, error) {
630	return c.fetchSpaces("/v2/spaces?" + query.Encode())
631}
632
633func (c *Client) ListSpaces() ([]Space, error) {
634	return c.ListSpacesByQuery(nil)
635}
636
637func (c *Client) fetchSpaces(requestUrl string) ([]Space, error) {
638	var spaces []Space
639	for {
640		spaceResp, err := c.getSpaceResponse(requestUrl)
641		if err != nil {
642			return []Space{}, err
643		}
644		for _, space := range spaceResp.Resources {
645			spaces = append(spaces, c.mergeSpaceResource(space))
646		}
647		requestUrl = spaceResp.NextUrl
648		if requestUrl == "" {
649			break
650		}
651	}
652	return spaces, nil
653}
654
655func (c *Client) GetSpaceByName(spaceName string, orgGuid string) (space Space, err error) {
656	query := url.Values{}
657	query.Add("q", fmt.Sprintf("organization_guid:%s", orgGuid))
658	query.Add("q", fmt.Sprintf("name:%s", spaceName))
659	spaces, err := c.ListSpacesByQuery(query)
660	if err != nil {
661		return
662	}
663
664	if len(spaces) == 0 {
665		return space, fmt.Errorf("No space found with name: `%s` in org with GUID: `%s`", spaceName, orgGuid)
666	}
667
668	return spaces[0], nil
669
670}
671
672func (c *Client) GetSpaceByGuid(spaceGUID string) (Space, error) {
673	requestUrl := fmt.Sprintf("/v2/spaces/%s", spaceGUID)
674	r := c.NewRequest("GET", requestUrl)
675	resp, err := c.DoRequest(r)
676	if err != nil {
677		return Space{}, errors.Wrap(err, "Error requesting space info")
678	}
679	return c.handleSpaceResp(resp)
680}
681
682func (c *Client) getSpaceResponse(requestUrl string) (SpaceResponse, error) {
683	var spaceResp SpaceResponse
684	r := c.NewRequest("GET", requestUrl)
685	resp, err := c.DoRequest(r)
686	if err != nil {
687		return SpaceResponse{}, errors.Wrap(err, "Error requesting spaces")
688	}
689	resBody, err := ioutil.ReadAll(resp.Body)
690	defer resp.Body.Close()
691	if err != nil {
692		return SpaceResponse{}, errors.Wrap(err, "Error reading space request")
693	}
694	err = json.Unmarshal(resBody, &spaceResp)
695	if err != nil {
696		return SpaceResponse{}, errors.Wrap(err, "Error unmarshalling space")
697	}
698	return spaceResp, nil
699}
700
701func (c *Client) getSpaceRolesResponse(requestUrl string) (SpaceRoleResponse, error) {
702	var roleResp SpaceRoleResponse
703	r := c.NewRequest("GET", requestUrl)
704	resp, err := c.DoRequest(r)
705	if err != nil {
706		return roleResp, errors.Wrap(err, "Error requesting space roles")
707	}
708	resBody, err := ioutil.ReadAll(resp.Body)
709	defer resp.Body.Close()
710	if err != nil {
711		return roleResp, errors.Wrap(err, "Error reading space roles request")
712	}
713	err = json.Unmarshal(resBody, &roleResp)
714	if err != nil {
715		return roleResp, errors.Wrap(err, "Error unmarshalling space roles")
716	}
717	return roleResp, nil
718}
719
720func (c *Client) handleSpaceResp(resp *http.Response) (Space, error) {
721	body, err := ioutil.ReadAll(resp.Body)
722	defer resp.Body.Close()
723	if err != nil {
724		return Space{}, err
725	}
726	var spaceResource SpaceResource
727	err = json.Unmarshal(body, &spaceResource)
728	if err != nil {
729		return Space{}, err
730	}
731	return c.mergeSpaceResource(spaceResource), nil
732}
733
734func (c *Client) mergeSpaceResource(space SpaceResource) Space {
735	space.Entity.Guid = space.Meta.Guid
736	space.Entity.CreatedAt = space.Meta.CreatedAt
737	space.Entity.UpdatedAt = space.Meta.UpdatedAt
738	space.Entity.c = c
739	return space.Entity
740}
741
742type serviceOfferingExtra ServiceOfferingExtra
743
744func (resource *ServiceOfferingExtra) UnmarshalJSON(rawData []byte) error {
745	if string(rawData) == "null" {
746		return nil
747	}
748
749	extra := serviceOfferingExtra{}
750
751	unquoted, err := strconv.Unquote(string(rawData))
752	if err != nil {
753		return err
754	}
755
756	err = json.Unmarshal([]byte(unquoted), &extra)
757	if err != nil {
758		return err
759	}
760
761	*resource = ServiceOfferingExtra(extra)
762
763	return nil
764}
765
766func (c *Client) IsolationSegmentForSpace(spaceGUID, isolationSegmentGUID string) error {
767	return c.updateSpaceIsolationSegment(spaceGUID, map[string]interface{}{"guid": isolationSegmentGUID})
768}
769
770func (c *Client) ResetIsolationSegmentForSpace(spaceGUID string) error {
771	return c.updateSpaceIsolationSegment(spaceGUID, nil)
772}
773
774func (c *Client) updateSpaceIsolationSegment(spaceGUID string, data interface{}) error {
775	requestURL := fmt.Sprintf("/v3/spaces/%s/relationships/isolation_segment", spaceGUID)
776	buf := bytes.NewBuffer(nil)
777	err := json.NewEncoder(buf).Encode(map[string]interface{}{"data": data})
778	if err != nil {
779		return err
780	}
781	r := c.NewRequestWithBody("PATCH", requestURL, buf)
782	resp, err := c.DoRequest(r)
783	if err != nil {
784		return err
785	}
786	if resp.StatusCode != http.StatusOK {
787		return errors.Wrapf(err, "Error setting isolation segment for space %s, response code: %d", spaceGUID, resp.StatusCode)
788	}
789	return nil
790}
791