Merge pull request #468 from DSpeichert/role_assignments
[rfr] Keystone Identity /v3/role_assignments
diff --git a/openstack/identity/v3/roles/doc.go b/openstack/identity/v3/roles/doc.go
new file mode 100644
index 0000000..bdbc674
--- /dev/null
+++ b/openstack/identity/v3/roles/doc.go
@@ -0,0 +1,3 @@
+// Package roles provides information and interaction with the roles API
+// resource for the OpenStack Identity service.
+package roles
diff --git a/openstack/identity/v3/roles/requests.go b/openstack/identity/v3/roles/requests.go
new file mode 100644
index 0000000..d95c1e5
--- /dev/null
+++ b/openstack/identity/v3/roles/requests.go
@@ -0,0 +1,50 @@
+package roles
+
+import (
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// ListAssignmentsOptsBuilder allows extensions to add additional parameters to
+// the ListAssignments request.
+type ListAssignmentsOptsBuilder interface {
+ ToRolesListAssignmentsQuery() (string, error)
+}
+
+// ListAssignmentsOpts allows you to query the ListAssignments method.
+// Specify one of or a combination of GroupId, RoleId, ScopeDomainId, ScopeProjectId,
+// and/or UserId to search for roles assigned to corresponding entities.
+// Effective lists effective assignments at the user, project, and domain level,
+// allowing for the effects of group membership.
+type ListAssignmentsOpts struct {
+ GroupId string `q:"group.id"`
+ RoleId string `q:"role.id"`
+ ScopeDomainId string `q:"scope.domain.id"`
+ ScopeProjectId string `q:"scope.project.id"`
+ UserId string `q:"user.id"`
+ Effective bool `q:"effective"`
+}
+
+// ToRolesListAssignmentsQuery formats a ListAssignmentsOpts into a query string.
+func (opts ListAssignmentsOpts) ToRolesListAssignmentsQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// ListAssignments enumerates the roles assigned to a specified resource.
+func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOptsBuilder) pagination.Pager {
+ url := listAssignmentsURL(client)
+ query, err := opts.ToRolesListAssignmentsQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return RoleAssignmentsPage{pagination.LinkedPageBase{PageResult: r}}
+ }
+
+ return pagination.NewPager(client, url, createPage)
+}
diff --git a/openstack/identity/v3/roles/requests_test.go b/openstack/identity/v3/roles/requests_test.go
new file mode 100644
index 0000000..d62dbff
--- /dev/null
+++ b/openstack/identity/v3/roles/requests_test.go
@@ -0,0 +1,104 @@
+package roles
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+
+ "github.com/rackspace/gophercloud/pagination"
+ "github.com/rackspace/gophercloud/testhelper"
+ "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListSinglePage(t *testing.T) {
+ testhelper.SetupHTTP()
+ defer testhelper.TeardownHTTP()
+
+ testhelper.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) {
+ testhelper.TestMethod(t, r, "GET")
+ testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, `
+ {
+ "role_assignments": [
+ {
+ "links": {
+ "assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456"
+ },
+ "role": {
+ "id": "123456"
+ },
+ "scope": {
+ "domain": {
+ "id": "161718"
+ }
+ },
+ "user": {
+ "id": "313233"
+ }
+ },
+ {
+ "links": {
+ "assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456",
+ "membership": "http://identity:35357/v3/groups/101112/users/313233"
+ },
+ "role": {
+ "id": "123456"
+ },
+ "scope": {
+ "project": {
+ "id": "456789"
+ }
+ },
+ "user": {
+ "id": "313233"
+ }
+ }
+ ],
+ "links": {
+ "self": "http://identity:35357/v3/role_assignments?effective",
+ "previous": null,
+ "next": null
+ }
+ }
+ `)
+ })
+
+ count := 0
+ err := ListAssignments(client.ServiceClient(), ListAssignmentsOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractRoleAssignments(page)
+ if err != nil {
+ return false, err
+ }
+
+ expected := []RoleAssignment{
+ RoleAssignment{
+ Role: Role{ID: "123456"},
+ Scope: Scope{Domain: Domain{ID: "161718"}},
+ User: User{ID: "313233"},
+ Group: Group{},
+ },
+ RoleAssignment{
+ Role: Role{ID: "123456"},
+ Scope: Scope{Project: Project{ID: "456789"}},
+ User: User{ID: "313233"},
+ Group: Group{},
+ },
+ }
+
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("Expected %#v, got %#v", expected, actual)
+ }
+
+ return true, nil
+ })
+ if err != nil {
+ t.Errorf("Unexpected error while paging: %v", err)
+ }
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
diff --git a/openstack/identity/v3/roles/results.go b/openstack/identity/v3/roles/results.go
new file mode 100644
index 0000000..d25abd2
--- /dev/null
+++ b/openstack/identity/v3/roles/results.go
@@ -0,0 +1,81 @@
+package roles
+
+import (
+ "github.com/rackspace/gophercloud/pagination"
+
+ "github.com/mitchellh/mapstructure"
+)
+
+// RoleAssignment is the result of a role assignments query.
+type RoleAssignment struct {
+ Role Role `json:"role,omitempty"`
+ Scope Scope `json:"scope,omitempty"`
+ User User `json:"user,omitempty"`
+ Group Group `json:"group,omitempty"`
+}
+
+type Role struct {
+ ID string `json:"id,omitempty"`
+}
+
+type Scope struct {
+ Domain Domain `json:"domain,omitempty"`
+ Project Project `json:"domain,omitempty"`
+}
+
+type Domain struct {
+ ID string `json:"id,omitempty"`
+}
+
+type Project struct {
+ ID string `json:"id,omitempty"`
+}
+
+type User struct {
+ ID string `json:"id,omitempty"`
+}
+
+type Group struct {
+ ID string `json:"id,omitempty"`
+}
+
+// RoleAssignmentsPage is a single page of RoleAssignments results.
+type RoleAssignmentsPage struct {
+ pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if the page contains no results.
+func (p RoleAssignmentsPage) IsEmpty() (bool, error) {
+ roleAssignments, err := ExtractRoleAssignments(p)
+ if err != nil {
+ return true, err
+ }
+ return len(roleAssignments) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (page RoleAssignmentsPage) NextPageURL() (string, error) {
+ type resp struct {
+ Links struct {
+ Next string `mapstructure:"next"`
+ } `mapstructure:"links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(page.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ return r.Links.Next, nil
+}
+
+// ExtractRoleAssignments extracts a slice of RoleAssignments from a Collection acquired from List.
+func ExtractRoleAssignments(page pagination.Page) ([]RoleAssignment, error) {
+ var response struct {
+ RoleAssignments []RoleAssignment `mapstructure:"role_assignments"`
+ }
+
+ err := mapstructure.Decode(page.(RoleAssignmentsPage).Body, &response)
+ return response.RoleAssignments, err
+}
diff --git a/openstack/identity/v3/roles/urls.go b/openstack/identity/v3/roles/urls.go
new file mode 100644
index 0000000..b009340
--- /dev/null
+++ b/openstack/identity/v3/roles/urls.go
@@ -0,0 +1,7 @@
+package roles
+
+import "github.com/rackspace/gophercloud"
+
+func listAssignmentsURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("role_assignments")
+}
diff --git a/openstack/identity/v3/roles/urls_test.go b/openstack/identity/v3/roles/urls_test.go
new file mode 100644
index 0000000..04679da
--- /dev/null
+++ b/openstack/identity/v3/roles/urls_test.go
@@ -0,0 +1,15 @@
+package roles
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+)
+
+func TestListAssignmentsURL(t *testing.T) {
+ client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
+ url := listAssignmentsURL(&client)
+ if url != "http://localhost:5000/v3/role_assignments" {
+ t.Errorf("Unexpected list URL generated: [%s]", url)
+ }
+}