diff --git a/payloads_test.go b/payloads_test.go index d97c447f..e1b8ffcd 100644 --- a/payloads_test.go +++ b/payloads_test.go @@ -8388,3 +8388,94 @@ const GetV3ServiceCredentialBindingsByGUIDPayload = `{ } } }` + +const listV3UsersPayload = `{ + "pagination": { + "total_results": 3, + "total_pages": 2, + "first": { + "href": "https://api.example.org/v3/users?page=1&per_page=2" + }, + "last": { + "href": "https://api.example.org/v3/users?page=2&per_page=2" + }, + "next": { + "href": "https://api.example.org/v3/userspage2?page=2&per_page=2" + }, + "previous": null + }, + "resources": [ + { + "guid": "16f43d50-43a2-4981-bae8-633e8248a637", + "created_at": "2022-08-02T21:37:52Z", + "updated_at": "2022-08-02T21:37:52Z", + "username": "smoke_tests", + "presentation_name": "smoke_tests", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/16f43d50-43a2-4981-bae8-633e8248a637" + } + } + }, + { + "guid": "test1", + "created_at": "2022-08-02T21:40:34Z", + "updated_at": "2022-08-02T21:40:34Z", + "username": "test1", + "presentation_name": "test1", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/test1" + } + } + } + ] +}` + +const listV3UsersPayloadPage2 = `{ + "pagination": { + "total_results": 3, + "total_pages": 2, + "first": { + "href": "https://api.example.org/v3/users?page=1&per_page=2" + }, + "last": { + "href": "https://api.example.org/v3/users?page=2&per_page=2" + }, + "next": { + "href": "" + }, + "previous": { + "href": "https://api.example.org/v3/users?page=1&per_page=2" + } + }, + "resources": [ + { + "guid": "test2", + "created_at": "2022-08-02T21:41:59Z", + "updated_at": "2022-08-02T21:41:59Z", + "username": "test2", + "presentation_name": "test2", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/test2" + } + } + } + ] +}` diff --git a/v3roles.go b/v3roles.go index c4cccb19..dce289f2 100644 --- a/v3roles.go +++ b/v3roles.go @@ -182,12 +182,53 @@ func (c *Client) ListV3RoleUsersByQuery(query url.Values) ([]V3User, error) { return users, nil } +func (c *Client) ListV3RoleAndUsersByQuery(query url.Values) ([]V3Role, []V3User, error) { + var roles []V3Role + var users []V3User + requestURL, err := url.Parse("/v3/roles") + if err != nil { + return nil, nil, err + } + requestURL.RawQuery = query.Encode() + + for { + r := c.NewRequest("GET", fmt.Sprintf("%s?%s", requestURL.Path, requestURL.RawQuery)) + resp, err := c.DoRequest(r) + if err != nil { + return nil, nil, errors.Wrap(err, "Error requesting v3 roles") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("Error listing v3 roles, response code: %d", resp.StatusCode) + } + + var data listV3RolesResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, nil, errors.Wrap(err, "Error parsing JSON from list v3 roles") + } + + roles = append(roles, data.Resources...) + users = append(users, data.Included.Users...) + + requestURL, err = url.Parse(data.Pagination.Next.Href) + if err != nil { + return nil, nil, errors.Wrap(err, "Error parsing next page URL") + } + if requestURL.String() == "" { + break + } + } + + return roles, users, nil +} + // ListV3SpaceRolesByGUID retrieves roles based on query -func (c *Client) ListV3SpaceRolesByGUID(spaceGUID string) ([]V3User, error) { +func (c *Client) ListV3SpaceRolesByGUID(spaceGUID string) ([]V3Role, []V3User, error) { query := url.Values{} query["space_guids"] = []string{spaceGUID} query["include"] = []string{"user"} - return c.ListV3RoleUsersByQuery(query) + return c.ListV3RoleAndUsersByQuery(query) } // ListV3SpaceRolesByGUIDAndType retrieves roles based on query @@ -209,11 +250,11 @@ func (c *Client) ListV3OrganizationRolesByGUIDAndType(orgGUID string, roleType s } // ListV3OrganizationRolesByGUID retrieves roles based on query -func (c *Client) ListV3OrganizationRolesByGUID(orgGUID string) ([]V3User, error) { +func (c *Client) ListV3OrganizationRolesByGUID(orgGUID string) ([]V3Role, []V3User, error) { query := url.Values{} query["organization_guids"] = []string{orgGUID} query["include"] = []string{"user"} - return c.ListV3RoleUsersByQuery(query) + return c.ListV3RoleAndUsersByQuery(query) } func (c *Client) DeleteV3Role(roleGUID string) error { diff --git a/v3roles_test.go b/v3roles_test.go index 4bfd08ff..91c60717 100644 --- a/v3roles_test.go +++ b/v3roles_test.go @@ -113,8 +113,9 @@ func TestListV3SpaceRolesByGUID(t *testing.T) { client, err := NewClient(c) So(err, ShouldBeNil) - users, err := client.ListV3SpaceRolesByGUID("spaceGUID1") + roles, users, err := client.ListV3SpaceRolesByGUID("spaceGUID1") So(err, ShouldBeNil) + So(roles, ShouldHaveLength, 3) So(users, ShouldHaveLength, 3) So(users[0].Username, ShouldEqual, "user1") @@ -159,8 +160,9 @@ func TestListV3OrgRolesByGUID(t *testing.T) { client, err := NewClient(c) So(err, ShouldBeNil) - users, err := client.ListV3OrganizationRolesByGUID("orgGUID1") + roles, users, err := client.ListV3OrganizationRolesByGUID("orgGUID1") So(err, ShouldBeNil) + So(roles, ShouldHaveLength, 3) So(users, ShouldHaveLength, 3) So(users[0].Username, ShouldEqual, "user1") diff --git a/v3users.go b/v3users.go index 6ba2ffb7..c66aaa54 100644 --- a/v3users.go +++ b/v3users.go @@ -1,5 +1,14 @@ package cfclient +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pkg/errors" +) + // V3User implements the user object type V3User struct { GUID string `json:"guid,omitempty"` @@ -11,3 +20,48 @@ type V3User struct { Links map[string]Link `json:"links,omitempty"` Metadata V3Metadata `json:"metadata,omitempty"` } + +type listV3UsersResponse struct { + Pagination Pagination `json:"pagination,omitempty"` + Resources []V3User `json:"resources,omitempty"` +} + +// ListV3UsersByQuery by query +func (c *Client) ListV3UsersByQuery(query url.Values) ([]V3User, error) { + var users []V3User + requestURL, err := url.Parse("/v3/users") + if err != nil { + return nil, err + } + requestURL.RawQuery = query.Encode() + + for { + r := c.NewRequest("GET", fmt.Sprintf("%s?%s", requestURL.Path, requestURL.RawQuery)) + resp, err := c.DoRequest(r) + if err != nil { + return nil, errors.Wrap(err, "Error requesting v3 users") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Error listing v3 users, response code: %d", resp.StatusCode) + } + + var data listV3UsersResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, errors.Wrap(err, "Error parsing JSON from list v3 users") + } + + users = append(users, data.Resources...) + + requestURL, err = url.Parse(data.Pagination.Next.Href) + if err != nil { + return nil, errors.Wrap(err, "Error parsing next page URL") + } + if requestURL.String() == "" { + break + } + } + + return users, nil +} diff --git a/v3users_test.go b/v3users_test.go new file mode 100644 index 00000000..05d4df97 --- /dev/null +++ b/v3users_test.go @@ -0,0 +1,34 @@ +package cfclient + +import ( + "net/http" + "net/url" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestListV3UserByQuery(t *testing.T) { + Convey("List V3 Users by Query", t, func() { + mocks := []MockRoute{ + {"GET", "/v3/users", []string{listV3UsersPayload}, "", http.StatusOK, "", nil}, + {"GET", "/v3/userspage2", []string{listV3UsersPayloadPage2}, "", http.StatusOK, "page=2&per_page=2", nil}, + } + setupMultiple(mocks, t) + defer teardown() + + c := &Config{ApiAddress: server.URL, Token: "foobar"} + client, err := NewClient(c) + So(err, ShouldBeNil) + + query := url.Values{} + users, err := client.ListV3UsersByQuery(query) + So(err, ShouldBeNil) + So(users, ShouldHaveLength, 3) + + So(users[0].Username, ShouldEqual, "smoke_tests") + So(users[1].Username, ShouldEqual, "test1") + So(users[2].Username, ShouldEqual, "test2") + }) + +}