diff --git a/acceptance/openstack/client_test.go b/acceptance/openstack/client_test.go index 895b23666..a5350ae21 100644 --- a/acceptance/openstack/client_test.go +++ b/acceptance/openstack/client_test.go @@ -18,6 +18,8 @@ func TestAuthenticatedClient(t *testing.T) { t.Fatalf("Unable to acquire credentials: %v", err) } + t.Logf("AuthOptionsFromEnv: %+v", ao) + client, err := openstack.AuthenticatedClient(ao) if err != nil { t.Fatalf("Unable to authenticate: %v", err) diff --git a/acceptance/openstack/identity/v2/tenant_test.go b/acceptance/openstack/identity/v2/tenant_test.go index caea3b516..45a2683cd 100644 --- a/acceptance/openstack/identity/v2/tenant_test.go +++ b/acceptance/openstack/identity/v2/tenant_test.go @@ -13,7 +13,7 @@ import ( func TestTenantsList(t *testing.T) { client, err := clients.NewIdentityV2Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } allPages, err := tenants.List(client, nil).AllPages() @@ -34,7 +34,7 @@ func TestTenantsList(t *testing.T) { func TestTenantsCRUD(t *testing.T) { client, err := clients.NewIdentityV2AdminClient() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } tenant, err := CreateTenant(t, client, nil) diff --git a/acceptance/openstack/identity/v3/endpoint_test.go b/acceptance/openstack/identity/v3/endpoint_test.go index ded3b4013..8ac65e77c 100644 --- a/acceptance/openstack/identity/v3/endpoint_test.go +++ b/acceptance/openstack/identity/v3/endpoint_test.go @@ -15,7 +15,7 @@ import ( func TestEndpointsList(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } allPages, err := endpoints.List(client, nil).AllPages() @@ -33,10 +33,38 @@ func TestEndpointsList(t *testing.T) { } } +func TestEndpointsGet(t *testing.T) { + + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + allPages, err := endpoints.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list endpoints: %v", err) + } + + allEndpoints, err := endpoints.ExtractEndpoints(allPages) + if err != nil { + t.Fatalf("Unable to extract endpoints: %v", err) + } + + if len(allEndpoints) > 0 { + endpoint := allEndpoints[0] + p, err := endpoints.Get(client, endpoint.ID).Extract() + if err != nil { + t.Fatalf("Unable to get endpoint: %v", err) + } + + tools.PrintResource(t, p) + } +} + func TestEndpointsNavigateCatalog(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } // Discover the service we're interested in. @@ -51,7 +79,7 @@ func TestEndpointsNavigateCatalog(t *testing.T) { allServices, err := services.ExtractServices(allPages) if err != nil { - t.Fatalf("Unable to extract service: %v") + t.Fatalf("Unable to extract service: %v", err) } if len(allServices) != 1 { @@ -74,7 +102,7 @@ func TestEndpointsNavigateCatalog(t *testing.T) { allEndpoints, err := endpoints.ExtractEndpoints(allPages) if err != nil { - t.Fatalf("Unable to extract endpoint: %v") + t.Fatalf("Unable to extract endpoint: %v", err) } if len(allEndpoints) != 1 { diff --git a/acceptance/openstack/identity/v3/group_users_test.go b/acceptance/openstack/identity/v3/group_users_test.go new file mode 100644 index 000000000..27482b9c8 --- /dev/null +++ b/acceptance/openstack/identity/v3/group_users_test.go @@ -0,0 +1,85 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/identity/v3/groups" + "github.com/huaweicloud/golangsdk/openstack/identity/v3/users" +) + +func TestGroupUsersCRUD(t *testing.T) { + + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + // Create Group in the default domain + createGroupOpts := groups.CreateOpts{ + Name: "testgroup", + DomainID: "default", + Extra: map[string]interface{}{ + "email": "testgroup@example.com", + }, + } + group, err := CreateGroup(t, client, &createGroupOpts) + if err != nil { + t.Fatalf("Unable to create group: %v", err) + } + defer DeleteGroup(t, client, group.ID) + + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + // Create a test user + createUserOpts := users.CreateOpts{ + Password: "foobar", + DomainID: "default", + Options: map[users.Option]interface{}{ + users.IgnorePasswordExpiry: true, + users.MultiFactorAuthRules: []interface{}{ + []string{"password", "totp"}, + []string{"password", "custom-auth-method"}, + }, + }, + Extra: map[string]interface{}{ + "email": "jsmith@example.com", + }, + } + user, err := CreateUser(t, client, &createUserOpts) + if err != nil { + t.Fatalf("Unable to create user: %v", err) + } + defer DeleteUser(t, client, user.ID) + + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + err = users.AddUserToGroup(client, group.ID, user.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to add user to group: %v", err) + } + + allPages, err := users.ListInGroup(client, group.ID, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list users: %v", err) + } + _, err = users.ExtractUsers(allPages) + if err != nil { + t.Fatalf("Unable to extract users: %v", err) + } + + err = users.CheckGroupUser(client, group.ID, user.ID).ExtractErr() + if err != nil { + t.Fatalf("Didn't successfully add user to group") + } + + err = users.DeleteGroupUser(client, group.ID, user.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to remove user from group: %v", err) + } +} diff --git a/acceptance/openstack/identity/v3/projects_test.go b/acceptance/openstack/identity/v3/projects_test.go index 545059b9e..af3f032ff 100644 --- a/acceptance/openstack/identity/v3/projects_test.go +++ b/acceptance/openstack/identity/v3/projects_test.go @@ -64,7 +64,7 @@ func TestProjectsGet(t *testing.T) { func TestProjectsCRUD(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } project, err := CreateProject(t, client, nil) @@ -91,7 +91,7 @@ func TestProjectsCRUD(t *testing.T) { func TestProjectsDomain(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } var iTrue = true @@ -126,14 +126,14 @@ func TestProjectsDomain(t *testing.T) { _, err = projects.Update(client, projectDomain.ID, updateOpts).Extract() if err != nil { - t.Fatalf("Unable to disable domain: %v") + t.Fatalf("Unable to disable domain: %v", err) } } func TestProjectsNested(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } projectMain, err := CreateProject(t, client, nil) diff --git a/acceptance/openstack/identity/v3/roles_test.go b/acceptance/openstack/identity/v3/roles_test.go index 24a8bff2f..43196151e 100644 --- a/acceptance/openstack/identity/v3/roles_test.go +++ b/acceptance/openstack/identity/v3/roles_test.go @@ -268,6 +268,30 @@ func TestRoleAssignToGroupOnDomain(t *testing.T) { for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) } + + err = roles.CheckRoleOf(client, role.ID, roles.CheckRoleOfOpts{ + GroupID: group.ID, + DomainID: domain.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to check role of domain: %v", err) + } + + allPages, err = roles.ListRolesOf(client, roles.ListRolesOfOpts{ + GroupID: group.ID, + DomainID: domain.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list roles of domain: %v", err) + } + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + t.Fatalf("Unable to extract roles: %v", err) + } + + for _, role := range allRoles { + tools.PrintResource(t, role) + } } func TestRoleAssignToGroupOnProject(t *testing.T) { @@ -325,4 +349,28 @@ func TestRoleAssignToGroupOnProject(t *testing.T) { for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) } + + err = roles.CheckRoleOf(client, role.ID, roles.CheckRoleOfOpts{ + GroupID: group.ID, + ProjectID: project.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to check role of project: %v", err) + } + + allPages, err = roles.ListRolesOf(client, roles.ListRolesOfOpts{ + GroupID: group.ID, + ProjectID: project.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list roles of project: %v", err) + } + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + t.Fatalf("Unable to extract roles: %v", err) + } + + for _, role := range allRoles { + tools.PrintResource(t, role) + } } diff --git a/acceptance/openstack/identity/v3/service_test.go b/acceptance/openstack/identity/v3/service_test.go index 2eaf0595b..13d60f370 100644 --- a/acceptance/openstack/identity/v3/service_test.go +++ b/acceptance/openstack/identity/v3/service_test.go @@ -13,7 +13,7 @@ import ( func TestServicesList(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } listOpts := services.ListOpts{ @@ -36,6 +36,39 @@ func TestServicesList(t *testing.T) { } +func TestServicesGet(t *testing.T) { + + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + listOpts := services.ListOpts{ + ServiceType: "identity", + } + + allPages, err := services.List(client, listOpts).AllPages() + if err != nil { + t.Fatalf("Unable to list services: %v", err) + } + + allServices, err := services.ExtractServices(allPages) + if err != nil { + t.Fatalf("Unable to extract services: %v", err) + } + + if len(allServices) > 0 { + + service := allServices[0] + p, err := services.Get(client, service.ID).Extract() + if err != nil { + t.Fatalf("Unable to get service: %v", err) + } + + tools.PrintResource(t, p) + } +} + func TestServicesCRUD(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { @@ -56,6 +89,11 @@ func TestServicesCRUD(t *testing.T) { } defer DeleteService(t, client, service.ID) + service, err = services.Get(client, service.ID).Extract() + if err != nil { + t.Fatalf("Unable to read service: %v", err) + } + tools.PrintResource(t, service) tools.PrintResource(t, service.Extra) diff --git a/acceptance/openstack/identity/v3/token_test.go b/acceptance/openstack/identity/v3/token_test.go index a32553d0e..979840191 100644 --- a/acceptance/openstack/identity/v3/token_test.go +++ b/acceptance/openstack/identity/v3/token_test.go @@ -14,7 +14,7 @@ import ( func TestGetToken(t *testing.T) { client, err := clients.NewIdentityV3Client() if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") + t.Fatalf("Unable to obtain an identity client: %v", err) } ao, err := openstack.AuthOptionsFromEnv() @@ -25,7 +25,7 @@ func TestGetToken(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, Password: ao.Password, - DomainName: "default", + DomainName: ao.DomainName, } token, err := tokens.Create(client, &authOptions).Extract() diff --git a/acceptance/openstack/identity/v3/users_test.go b/acceptance/openstack/identity/v3/users_test.go index 40b0aae85..3879da6cb 100644 --- a/acceptance/openstack/identity/v3/users_test.go +++ b/acceptance/openstack/identity/v3/users_test.go @@ -121,6 +121,15 @@ func TestUserCRUD(t *testing.T) { tools.PrintResource(t, newUser) tools.PrintResource(t, newUser.Extra) + + updatePasswdOpts := users.UpdatePasswdOpts{ + OriginalPassword: "foobar", + Password: "barfoo", + } + err = users.UpdatePasswd(client, user.ID, updatePasswdOpts).ExtractErr() + if err != nil { + t.Fatalf("Unable to update user password: %v", err) + } } func TestUsersListGroups(t *testing.T) { diff --git a/openstack/identity/v3/domains/results.go b/openstack/identity/v3/domains/results.go index 5d84e8ca0..e6d4e8130 100644 --- a/openstack/identity/v3/domains/results.go +++ b/openstack/identity/v3/domains/results.go @@ -21,6 +21,9 @@ type Domain struct { // Name is the name of the domain. Name string `json:"name"` + + // EnterpriseName is the name of company user. + EnterpriseName string `json:"enterpriseName"` } type domainResult struct { diff --git a/openstack/identity/v3/domains/testing/fixtures.go b/openstack/identity/v3/domains/testing/fixtures.go index 60d904325..cc89617a0 100644 --- a/openstack/identity/v3/domains/testing/fixtures.go +++ b/openstack/identity/v3/domains/testing/fixtures.go @@ -125,7 +125,7 @@ var ExpectedDomainsSlice = []domains.Domain{FirstDomain, SecondDomain} // HandleListDomainsSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a list of two domains. func HandleListDomainsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/auth/domains", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -139,7 +139,7 @@ func HandleListDomainsSuccessfully(t *testing.T) { // HandleGetDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a single domain. func HandleGetDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/auth/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -153,7 +153,7 @@ func HandleGetDomainSuccessfully(t *testing.T) { // HandleCreateDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain creation. func HandleCreateDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/auth/domains", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) @@ -166,7 +166,7 @@ func HandleCreateDomainSuccessfully(t *testing.T) { // HandleDeleteDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain deletion. func HandleDeleteDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/auth/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -177,7 +177,7 @@ func HandleDeleteDomainSuccessfully(t *testing.T) { // HandleUpdateDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain update. func HandleUpdateDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/auth/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) diff --git a/openstack/identity/v3/domains/urls.go b/openstack/identity/v3/domains/urls.go index fad3eee89..4e202ccbc 100644 --- a/openstack/identity/v3/domains/urls.go +++ b/openstack/identity/v3/domains/urls.go @@ -3,21 +3,21 @@ package domains import "github.com/huaweicloud/golangsdk" func listURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL("domains") + return client.ServiceURL("auth/domains") } func getURL(client *golangsdk.ServiceClient, domainID string) string { - return client.ServiceURL("domains", domainID) + return client.ServiceURL("auth/domains", domainID) } func createURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL("domains") + return client.ServiceURL("auth/domains") } func deleteURL(client *golangsdk.ServiceClient, domainID string) string { - return client.ServiceURL("domains", domainID) + return client.ServiceURL("auth/domains", domainID) } func updateURL(client *golangsdk.ServiceClient, domainID string) string { - return client.ServiceURL("domains", domainID) + return client.ServiceURL("auth/domains", domainID) } diff --git a/openstack/identity/v3/endpoints/doc.go b/openstack/identity/v3/endpoints/doc.go index 380a3f38e..41d6ae6c8 100644 --- a/openstack/identity/v3/endpoints/doc.go +++ b/openstack/identity/v3/endpoints/doc.go @@ -65,5 +65,12 @@ Example to Delete an Endpoint if err != nil { panic(err) } + +Example to Get an Endpoint + endpointID := "ad59deeec5154d1fa0dcff518596f499" + endpoint, err := endpoints.Get(identityClient, endpointID).ExtractErr() + if err != nil { + panic(err) + } */ package endpoints diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go index 54f101de9..df717c59f 100644 --- a/openstack/identity/v3/endpoints/requests.go +++ b/openstack/identity/v3/endpoints/requests.go @@ -137,3 +137,9 @@ func Delete(client *golangsdk.ServiceClient, endpointID string) (r DeleteResult) _, r.Err = client.Delete(endpointURL(client, endpointID), nil) return } + +// Get retrieves details on a single Endpoint, by endpointID +func Get(client *golangsdk.ServiceClient, endpointID string) (r GetResult) { + _, r.Err = client.Get(endpointURL(client, endpointID), &r.Body, nil) + return +} diff --git a/openstack/identity/v3/endpoints/results.go b/openstack/identity/v3/endpoints/results.go index fccf7350d..b88ecf7b3 100644 --- a/openstack/identity/v3/endpoints/results.go +++ b/openstack/identity/v3/endpoints/results.go @@ -37,6 +37,12 @@ type DeleteResult struct { golangsdk.ErrResult } +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as an Endpoint. +type GetResult struct { + commonResult +} + // Endpoint describes the entry point for another service's API. type Endpoint struct { // ID is the unique ID of the endpoint. @@ -57,6 +63,15 @@ type Endpoint struct { // URL is the url of the Endpoint. URL string `json:"url"` + + // RegionID is the ID of the region the Endpoint is located in. + RegionID string `json:"region_id"` + + // Enabled is the availablity of the Endpoint. + Enabled bool `json:"enabled"` + + // Links is the links of the Endpoint + Links map[string]interface{} `json:"links"` } // EndpointPage is a single page of Endpoint results. diff --git a/openstack/identity/v3/endpoints/testing/requests_test.go b/openstack/identity/v3/endpoints/testing/requests_test.go index 3783d9fa7..7715ce925 100644 --- a/openstack/identity/v3/endpoints/testing/requests_test.go +++ b/openstack/identity/v3/endpoints/testing/requests_test.go @@ -65,6 +65,9 @@ func TestCreateSuccessful(t *testing.T) { Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Links: map[string]interface{}{ + "self": "https://localhost:5000/v3/endpoints/12", + }, } th.AssertDeepEquals(t, expected, actual) @@ -130,6 +133,9 @@ func TestListEndpoints(t *testing.T) { Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Links: map[string]interface{}{ + "self": "https://localhost:5000/v3/endpoints/12", + }, }, { ID: "13", @@ -138,6 +144,9 @@ func TestListEndpoints(t *testing.T) { Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9001/", + Links: map[string]interface{}{ + "self": "https://localhost:5000/v3/endpoints/13", + }, }, } th.AssertDeepEquals(t, expected, actual) @@ -194,6 +203,9 @@ func TestUpdateEndpoint(t *testing.T) { Region: "somewhere-else", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Links: map[string]interface{}{ + "self": "https://localhost:5000/v3/endpoints/12", + }, } th.AssertDeepEquals(t, expected, actual) } @@ -212,3 +224,51 @@ func TestDeleteEndpoint(t *testing.T) { res := endpoints.Delete(client.ServiceClient(), "34") th.AssertNoErr(t, res.Err) } + +func TestGetEnpoint(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + fmt.Fprintf(w, ` + { + "endpoint": { + "id": "12", + "interface": "public", + "links": { + "self": "https://localhost:5000/v3/endpoints/12" + }, + "name": "renamed", + "region": "somewhere-else", + "service_id": "asdfasdfasdfasdf", + "url": "https://1.2.3.4:9000/", + "region_id": "qwerqwerqwer", + "enabled": true + } + } + `) + }) + + actual, err := endpoints.Get(client.ServiceClient(), "12").Extract() + if err != nil { + t.Fatalf("Unexpected error from Get: %v", err) + } + + expected := &endpoints.Endpoint{ + ID: "12", + Availability: golangsdk.AvailabilityPublic, + Name: "renamed", + Region: "somewhere-else", + ServiceID: "asdfasdfasdfasdf", + URL: "https://1.2.3.4:9000/", + RegionID: "qwerqwerqwer", + Enabled: true, + Links: map[string]interface{}{ + "self": "https://localhost:5000/v3/endpoints/12", + }, + } + th.AssertDeepEquals(t, expected, actual) +} diff --git a/openstack/identity/v3/regions/doc.go b/openstack/identity/v3/regions/doc.go index a37b05a54..5e98bdcbe 100644 --- a/openstack/identity/v3/regions/doc.go +++ b/openstack/identity/v3/regions/doc.go @@ -59,5 +59,13 @@ Example to Delete a Region if err != nil { panic(err) } + +Example to Get a Region + + regionID := "TestRegion" + region, err := regions.Get(identityClient, regionID).Extract() + if err != nil { + panic(err) + } */ package regions diff --git a/openstack/identity/v3/regions/results.go b/openstack/identity/v3/regions/results.go index aa66f5252..70aa553e2 100644 --- a/openstack/identity/v3/regions/results.go +++ b/openstack/identity/v3/regions/results.go @@ -24,6 +24,12 @@ type Region struct { // ParentRegionID is the ID of the parent region. ParentRegionID string `json:"parent_region_id"` + + // Locales is the names of the region + Locales map[string]interface{} `json:"locales"` + + // Type is the type of the region + Type string `json:"type"` } func (r *Region) UnmarshalJSON(b []byte) error { diff --git a/openstack/identity/v3/roles/doc.go b/openstack/identity/v3/roles/doc.go index 2886a872d..f290e5492 100644 --- a/openstack/identity/v3/roles/doc.go +++ b/openstack/identity/v3/roles/doc.go @@ -105,6 +105,44 @@ Example to Unassign a Role From a User in a Project ProjectID: projectID, }).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Role of a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + allPages, err := roles.ListRolesOf(identityClient, roles.ListRolesOfOpts{ + UserID: userID, + ProjectID: projectID, + }).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + +Example to Check a Role Of a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + err := roles.CheckRoleOf(identityClient, roleID, roles.CheckRoleOfOpts{ + UserID: userID, + ProjectID: projectID, + }).ExtractErr() + if err != nil { panic(err) } diff --git a/openstack/identity/v3/roles/requests.go b/openstack/identity/v3/roles/requests.go index 80f944da3..8ae2c795d 100644 --- a/openstack/identity/v3/roles/requests.go +++ b/openstack/identity/v3/roles/requests.go @@ -312,3 +312,123 @@ func Unassign(client *golangsdk.ServiceClient, roleID string, opts UnassignOpts) }) return } + +// ListRolesOfOpts provides options to list roles +type ListRolesOfOpts struct { + // UserID is the ID of a user to unassign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to unassign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// GetRolesFromGroup list all roles assigned to the group +func ListRolesOf( + client *golangsdk.ServiceClient, opts ListRolesOfOpts) pagination.Pager { + + // Check xor conditions + _, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return pagination.Pager{Err: err} + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + return pagination.NewPager( + client, + listRolesOfURL(client, targetType, targetID, actorType, actorID), + func(r pagination.PageResult) pagination.Page { + return RolePage{pagination.LinkedPageBase{PageResult: r}} + }, + ) +} + +// CheckRoleOfOpts provides options to check role existed +type CheckRoleOfOpts struct { + // UserID is the ID of a user to unassign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to unassign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// CheckRoleOf check a role existed in a group of a domain or project +func CheckRoleOf(client *golangsdk.ServiceClient, + roleID string, opts CheckRoleOfOpts) (r CheckRoleOfResult) { + + // Check xor conditions + _, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + _, r.Err = client.Head( + checkRoleOfURL(client, + targetType, targetID, actorType, actorID, roleID), + &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }, + ) + return +} diff --git a/openstack/identity/v3/roles/results.go b/openstack/identity/v3/roles/results.go index 2e4cec087..20ce10b99 100644 --- a/openstack/identity/v3/roles/results.go +++ b/openstack/identity/v3/roles/results.go @@ -24,6 +24,21 @@ type Role struct { // Extra is a collection of miscellaneous key/values. Extra map[string]interface{} `json:"-"` + + // Type is type of the role. + Type string `json:"type"` + + // DisplayName is the displayed name of the role + DisplayName string `json:"display_name"` + + // Catalog is the catelog of the role. + Catalog string `json:"catalog"` + + // Policy contains detail policies of the role + Policy map[string]interface{} `json:"policy"` + + // Description is the description of the role + Description string `json:"description"` } func (r *Role) UnmarshalJSON(b []byte) error { @@ -212,3 +227,9 @@ type AssignmentResult struct { type UnassignmentResult struct { golangsdk.ErrResult } + +// CheckRoleOfResult is the response of checking a role existed in a group +// of a domain or project request. +type CheckRoleOfResult struct { + golangsdk.ErrResult +} diff --git a/openstack/identity/v3/roles/testing/fixtures.go b/openstack/identity/v3/roles/testing/fixtures.go index ce80c1344..0e6d52eff 100644 --- a/openstack/identity/v3/roles/testing/fixtures.go +++ b/openstack/identity/v3/roles/testing/fixtures.go @@ -331,3 +331,91 @@ func HandleListRoleAssignmentsSuccessfully(t *testing.T) { fmt.Fprintf(w, ListAssignmentOutput) }) } + +// HandleCheckRoleOfSuccessfully creates an HTTP handler for CheckRoleOf api +func HandleCheckRoleOfSuccessfully(t *testing.T) { + + th.Mux.HandleFunc( + "/domains/{domain_id}/groups/{group_id}/roles/{role_id}", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc( + "/domains/{domain_id}/users/{user_id}/roles/{role_id}", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc( + "/projects/{project_id}/groups/{group_id}/roles/{role_id}", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc( + "/projects/{project_id}/users/{user_id}/roles/{role_id}", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleListRolesOfSuccessfully creates an HTTP handler for ListRolesOf api +func HandleListRolesOfSuccessfully(t *testing.T) { + + th.Mux.HandleFunc( + "/domains/{domain_id}/groups/{group_id}/roles", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) + + th.Mux.HandleFunc( + "/domains/{domain_id}/users/{user_id}/roles", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) + + th.Mux.HandleFunc( + "/projects/{project_id}/groups/{group_id}/roles", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) + + th.Mux.HandleFunc( + "/projects/{project_id}/users/{user_id}/roles", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} diff --git a/openstack/identity/v3/roles/testing/requests_test.go b/openstack/identity/v3/roles/testing/requests_test.go index 318d36883..f3bb2b54b 100644 --- a/openstack/identity/v3/roles/testing/requests_test.go +++ b/openstack/identity/v3/roles/testing/requests_test.go @@ -174,3 +174,65 @@ func TestUnassign(t *testing.T) { }).ExtractErr() th.AssertNoErr(t, err) } + +func TestCheckRoleOf(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCheckRoleOfSuccessfully(t) + + err := roles.CheckRoleOf(client.ServiceClient(), "{role_id}", + roles.CheckRoleOfOpts{ + UserID: "{user_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.CheckRoleOf(client.ServiceClient(), "{role_id}", + roles.CheckRoleOfOpts{ + GroupID: "{group_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.CheckRoleOf(client.ServiceClient(), "{role_id}", + roles.CheckRoleOfOpts{ + UserID: "{user_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.CheckRoleOf(client.ServiceClient(), "{role_id}", + roles.CheckRoleOfOpts{ + GroupID: "{group_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestListRolesOf(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListRolesOfSuccessfully(t) + + count := 0 + err := roles.ListRolesOf( + client.ServiceClient(), + roles.ListRolesOfOpts{ + UserID: "{user_id}", + ProjectID: "{project_id}", + }, + ).EachPage(func(page pagination.Page) (bool, error) { + + count++ + + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedRolesSlice, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} diff --git a/openstack/identity/v3/roles/urls.go b/openstack/identity/v3/roles/urls.go index e7975d8f9..dca23dc25 100644 --- a/openstack/identity/v3/roles/urls.go +++ b/openstack/identity/v3/roles/urls.go @@ -33,3 +33,23 @@ func listAssignmentsURL(client *golangsdk.ServiceClient) string { func assignURL(client *golangsdk.ServiceClient, targetType, targetID, actorType, actorID, roleID string) string { return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath, roleID) } + +func checkRoleOfURL(client *golangsdk.ServiceClient, + targetType, targetID, actorType, actorID, roleID string) string { + + return client.ServiceURL( + targetType, targetID, + actorType, actorID, + "roles", roleID, + ) +} + +func listRolesOfURL(client *golangsdk.ServiceClient, + targetType, targetID, actorType, actorID string) string { + + return client.ServiceURL( + targetType, targetID, + actorType, actorID, + "roles", + ) +} diff --git a/openstack/identity/v3/services/doc.go b/openstack/identity/v3/services/doc.go index 81702359a..8cddd0dff 100644 --- a/openstack/identity/v3/services/doc.go +++ b/openstack/identity/v3/services/doc.go @@ -62,5 +62,13 @@ Example to Delete a Service panic(err) } +Example to Get a service + + serviceID := "3c7bbe9a6ecb453ca1789586291380ed" + service, err := services.Get(identityClient, serviceID).Extract() + if err != nil { + panic(err) + } + */ package services diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go index cf3fae6be..fb3017647 100644 --- a/openstack/identity/v3/services/requests.go +++ b/openstack/identity/v3/services/requests.go @@ -64,9 +64,6 @@ type ListOptsBuilder interface { type ListOpts struct { // ServiceType filter the response by a type of service. ServiceType string `q:"type"` - - // Name filters the response by a service name. - Name string `q:"name"` } // ToServiceListMap builds a list query from the list options. diff --git a/openstack/identity/v3/services/results.go b/openstack/identity/v3/services/results.go index f49f00ef6..2db066316 100644 --- a/openstack/identity/v3/services/results.go +++ b/openstack/identity/v3/services/results.go @@ -62,6 +62,12 @@ type Service struct { // Extra is a collection of miscellaneous key/values. Extra map[string]interface{} `json:"-"` + + // Description is the description of the service. + Description string `json:"description"` + + // Name is the name of the service. + Name string `json:"name"` } func (r *Service) UnmarshalJSON(b []byte) error { diff --git a/openstack/identity/v3/services/testing/fixtures.go b/openstack/identity/v3/services/testing/fixtures.go index e89eb3669..547cd937d 100644 --- a/openstack/identity/v3/services/testing/fixtures.go +++ b/openstack/identity/v3/services/testing/fixtures.go @@ -12,58 +12,65 @@ import ( // ListOutput provides a single page of Service results. const ListOutput = ` -{ - "links": { - "next": null, - "previous": null - }, - "services": [ - { - "id": "1234", - "links": { - "self": "https://example.com/identity/v3/services/1234" - }, - "type": "identity", - "enabled": false, +{ + "services": [ + { + "name": "service-one", + "links": { + "self": "https://iamcore_links.com/v3/services/053d21d488d1463c818132d9d08fb617" + }, + "enabled": true, + "type": "compute", + "id": "053d21d488d1463c818132d9d08fb617", + "description": "Service One", "extra": { "name": "service-one", "description": "Service One" } - }, - { - "id": "9876", - "links": { - "self": "https://example.com/identity/v3/services/9876" - }, - "type": "compute", - "enabled": false, + }, + { + "name": "service-two", + "links": { + "self": "https://iamcore_links.com/v3/services/c2474183dca7453bbd73123a0b78feae" + }, + "enabled": true, + "type": "compute", + "id": "c2474183dca7453bbd73123a0b78feae", + "description": "Service Two", "extra": { "name": "service-two", - "description": "Service Two", - "email": "service@example.com" + "description": "Service Two" } } - ] + ], + "links": { + "self": "https://iamcore_links.com/v3/services?type=compute", + "previous": null, + "next": null + } } + ` // GetOutput provides a Get result. const GetOutput = ` -{ - "service": { - "id": "9876", - "links": { - "self": "https://example.com/identity/v3/services/9876" - }, - "type": "compute", - "enabled": false, - "extra": { - "name": "service-two", - "description": "Service Two", - "email": "service@example.com" - } - } +{ + "service": { + "name": "service-two", + "links": { + "self": "https://iamcore_links.com/v3/services/c2474183dca7453bbd73123a0b78feae" + }, + "enabled": true, + "type": "compute", + "id": "c2474183dca7453bbd73123a0b78feae", + "description": "Service Two", + "extra": { + "name": "service-two", + "description": "Service Two" + } + } } + ` // CreateRequest provides the input to a Create request. @@ -71,9 +78,9 @@ const CreateRequest = ` { "service": { "description": "Service Two", - "email": "service@example.com", "name": "service-two", - "type": "compute" + "type": "compute", + "email": "service@example.com" } } ` @@ -92,29 +99,32 @@ const UpdateRequest = ` const UpdateOutput = ` { "service": { - "id": "9876", - "links": { - "self": "https://example.com/identity/v3/services/9876" - }, - "type": "compute2", - "enabled": false, - "extra": { - "name": "service-two", - "description": "Service Two Updated", - "email": "service@example.com" - } + "name": "service-two", + "links": { + "self": "https://iamcore_links.com/v3/services/c2474183dca7453bbd73123a0b78feae" + }, + "enabled": true, + "type": "compute2", + "id": "c2474183dca7453bbd73123a0b78feae", + "description": "Service Two Updated", + "extra": { + "name": "service-two", + "description": "Service Two Updated" + } } } ` // FirstService is the first service in the List request. var FirstService = services.Service{ - ID: "1234", + ID: "053d21d488d1463c818132d9d08fb617", Links: map[string]interface{}{ - "self": "https://example.com/identity/v3/services/1234", + "self": "https://iamcore_links.com/v3/services/053d21d488d1463c818132d9d08fb617", }, - Type: "identity", - Enabled: false, + Type: "compute", + Enabled: true, + Name: "service-one", + Description: "Service One", Extra: map[string]interface{}{ "name": "service-one", "description": "Service One", @@ -123,31 +133,33 @@ var FirstService = services.Service{ // SecondService is the second service in the List request. var SecondService = services.Service{ - ID: "9876", + ID: "c2474183dca7453bbd73123a0b78feae", Links: map[string]interface{}{ - "self": "https://example.com/identity/v3/services/9876", + "self": "https://iamcore_links.com/v3/services/c2474183dca7453bbd73123a0b78feae", }, - Type: "compute", - Enabled: false, + Type: "compute", + Enabled: true, + Name: "service-two", + Description: "Service Two", Extra: map[string]interface{}{ "name": "service-two", "description": "Service Two", - "email": "service@example.com", }, } // SecondServiceUpdated is the SecondService should look after an Update. var SecondServiceUpdated = services.Service{ - ID: "9876", + ID: "c2474183dca7453bbd73123a0b78feae", Links: map[string]interface{}{ - "self": "https://example.com/identity/v3/services/9876", + "self": "https://iamcore_links.com/v3/services/c2474183dca7453bbd73123a0b78feae", }, - Type: "compute2", - Enabled: false, + Type: "compute2", + Enabled: true, + Name: "service-two", + Description: "Service Two Updated", Extra: map[string]interface{}{ "name": "service-two", "description": "Service Two Updated", - "email": "service@example.com", }, } @@ -171,7 +183,7 @@ func HandleListServicesSuccessfully(t *testing.T) { // HandleGetServiceSuccessfully creates an HTTP handler at `/services` on the // test handler mux that responds with a single service. func HandleGetServiceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/services/c2474183dca7453bbd73123a0b78feae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -198,7 +210,7 @@ func HandleCreateServiceSuccessfully(t *testing.T) { // HandleUpdateServiceSuccessfully creates an HTTP handler at `/services` on the // test handler mux that tests service update. func HandleUpdateServiceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/services/c2474183dca7453bbd73123a0b78feae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) diff --git a/openstack/identity/v3/services/testing/requests_test.go b/openstack/identity/v3/services/testing/requests_test.go index 9777c7497..3722da40e 100644 --- a/openstack/identity/v3/services/testing/requests_test.go +++ b/openstack/identity/v3/services/testing/requests_test.go @@ -59,8 +59,6 @@ func TestListServicesAllPages(t *testing.T) { actual, err := services.ExtractServices(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedServicesSlice, actual) - th.AssertEquals(t, ExpectedServicesSlice[0].Extra["name"], "service-one") - th.AssertEquals(t, ExpectedServicesSlice[1].Extra["email"], "service@example.com") } func TestGetSuccessful(t *testing.T) { @@ -68,11 +66,10 @@ func TestGetSuccessful(t *testing.T) { defer th.TeardownHTTP() HandleGetServiceSuccessfully(t) - actual, err := services.Get(client.ServiceClient(), "9876").Extract() + actual, err := services.Get(client.ServiceClient(), "c2474183dca7453bbd73123a0b78feae").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondService, *actual) - th.AssertEquals(t, SecondService.Extra["email"], "service@example.com") } func TestUpdateSuccessful(t *testing.T) { @@ -86,22 +83,21 @@ func TestUpdateSuccessful(t *testing.T) { "description": "Service Two Updated", }, } - actual, err := services.Update(client.ServiceClient(), "9876", updateOpts).Extract() + actual, err := services.Update(client.ServiceClient(), "c2474183dca7453bbd73123a0b78feae", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondServiceUpdated, *actual) - th.AssertEquals(t, SecondServiceUpdated.Extra["description"], "Service Two Updated") } func TestDeleteSuccessful(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - th.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/services/053d21d488d1463c818132d9d08fb617", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := services.Delete(client.ServiceClient(), "12345") + res := services.Delete(client.ServiceClient(), "053d21d488d1463c818132d9d08fb617") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/users/doc.go b/openstack/identity/v3/users/doc.go index 2b3b2e390..c41511ab4 100644 --- a/openstack/identity/v3/users/doc.go +++ b/openstack/identity/v3/users/doc.go @@ -119,5 +119,49 @@ Example to List Users in a Group fmt.Printf("%+v\n", user) } +Example to Update User Password + + userID := "0fe36e73809d46aeae6705c39077b1b3" + + opts := users.UpdatePasswdOpts { + OriginalPassword: "old-user-password", + Password: "new-user-password", + } + + err := users.UpdatePasswd(identityClient, userID, opts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Check User in a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + + err := users.CheckGroupUser(identityClient, groupID, userID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Delete User from a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + + err := users.DeleteGroupUser(identityClient, groupID, userID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Add a User to a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + + err := users.AddUserToGroup(identityClient, groupID, userID).ExtractErr() + if err != nil { + panic(err) + } + */ package users diff --git a/openstack/identity/v3/users/requests.go b/openstack/identity/v3/users/requests.go index dea3a9852..7afee92e9 100644 --- a/openstack/identity/v3/users/requests.go +++ b/openstack/identity/v3/users/requests.go @@ -1,6 +1,8 @@ package users import ( + "net/http" + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/openstack/identity/v3/groups" "github.com/huaweicloud/golangsdk/openstack/identity/v3/projects" @@ -240,3 +242,74 @@ func ListInGroup(client *golangsdk.ServiceClient, groupID string, opts ListOptsB return UserPage{pagination.LinkedPageBase{PageResult: r}} }) } + +// UpdatePasswdOptsBuilder is the interface for password updating parameters +type UpdatePasswdOptsBuilder interface { + ToPasswdUpdateMap() (map[string]interface{}, error) +} + +// UpdatePasswdOpts provides options used to update user password +type UpdatePasswdOpts struct { + OriginalPassword string `json:"original_password"` + Password string `json:"password"` +} + +// ToPasswdUpdateMap formats a UpdatePasswdOpts into an http request +func (opts UpdatePasswdOpts) ToPasswdUpdateMap() ( + map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "user") +} + +// UpdatePasswd update password by user itself +func UpdatePasswd(client *golangsdk.ServiceClient, + userID string, opts UpdatePasswdOptsBuilder) (r UpdatePasswdResult) { + + b, err := opts.ToPasswdUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(updatePasswdURL(client, userID), &b, nil, + &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }, + ) + return +} + +// CheckGroupUser check if the user does exist in the group +func CheckGroupUser(client *golangsdk.ServiceClient, + groupID string, userID string) (r CheckGroupUserResult) { + + _, r.Err = client.Head(operateOnGroupUserURL(client, groupID, userID), + &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }, + ) + return +} + +// DeleteGroupUser deletes a user from a group. +func DeleteGroupUser(client *golangsdk.ServiceClient, + groupID string, userID string) (r DeleteGroupUserResult) { + + _, r.Err = client.Delete(operateOnGroupUserURL(client, groupID, userID), + &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }, + ) + return +} + +// AddUserToGroup add a user to a group +func AddUserToGroup(client *golangsdk.ServiceClient, + groupID, userID string) (r AddUserToGroupResult) { + + _, r.Err = client.Put(operateOnGroupUserURL(client, groupID, userID), + nil, nil, + &golangsdk.RequestOpts{ + OkCodes: []int{http.StatusNoContent}, + }, + ) + return +} diff --git a/openstack/identity/v3/users/results.go b/openstack/identity/v3/users/results.go index d3e434b97..c6855d137 100644 --- a/openstack/identity/v3/users/results.go +++ b/openstack/identity/v3/users/results.go @@ -147,3 +147,25 @@ func (r userResult) Extract() (*User, error) { err := r.ExtractInto(&s) return s.User, err } + +// UpdatePasswordResult is the response from updating password operation. Call +// its ExtractErr to determine the http response status. +type UpdatePasswdResult struct { + golangsdk.ErrResult +} + +// CheckGroupUserResult is the response from checking the user is existed in +// the group. +type CheckGroupUserResult struct { + golangsdk.ErrResult +} + +// DeleteGroupUserResult is the response of deleting the user from a group. +type DeleteGroupUserResult struct { + golangsdk.ErrResult +} + +// AddUserToGroupResult is the response of adding a user to a group. +type AddUserToGroupResult struct { + golangsdk.ErrResult +} diff --git a/openstack/identity/v3/users/testing/fixtures.go b/openstack/identity/v3/users/testing/fixtures.go index 7b5067dc7..362924721 100644 --- a/openstack/identity/v3/users/testing/fixtures.go +++ b/openstack/identity/v3/users/testing/fixtures.go @@ -231,6 +231,17 @@ const ListProjectsOutput = ` } ` +// UpdatePasswdReUpdatePasswdRequest provides a demo update password request +// body. +const UpdatePasswdRequest = ` +{ + "user": { + "original_password": "secretsecret", + "password": "notthatsecret" + } +} +` + // FirstUser is the first user in the List request. var nilTime time.Time var FirstUser = users.User{ @@ -475,3 +486,57 @@ func HandleListInGroupSuccessfully(t *testing.T) { fmt.Fprintf(w, ListOutput) }) } + +// HandleUpdateUserPasswdSuccessfully creates an HTTP handler at +// "/users/{user_id}/password" on the test handler mux. +func HandleUpdateUserPasswdSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/9fe1d3/password", + func(w http.ResponseWriter, r *http.Request) { + + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdatePasswdRequest) + + w.WriteHeader(http.StatusNoContent) + }, + ) +} + +// HandleCheckGroupUserSuccessfully creates an HTTP handler at +// "/groups/{group_id}/users/{user_id}" on the test handler mux. +func HandleCheckGroupUserSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", + func(w http.ResponseWriter, r *http.Request) { + + th.TestMethod(t, r, "HEAD") + + w.WriteHeader(http.StatusNoContent) + }, + ) +} + +// HandleDeleteGroupUserSuccessfully creates an HTTP handler at +// "/groups/{group_id}/users/{user_id}" on the test handler mux. +func HandleDeleteGroupUserSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", + func(w http.ResponseWriter, r *http.Request) { + + th.TestMethod(t, r, "DELETE") + + w.WriteHeader(http.StatusNoContent) + }, + ) +} + +// HandleAddUserToGroupSuccessfully creates an HTTP handler at +// "/groups/{group_id}/users/{user_id}" on the test handler mux. +func HandleAddUserToGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", + func(w http.ResponseWriter, r *http.Request) { + + th.TestMethod(t, r, "PUT") + + w.WriteHeader(http.StatusNoContent) + }, + ) +} diff --git a/openstack/identity/v3/users/testing/requests_test.go b/openstack/identity/v3/users/testing/requests_test.go index bbef95fc6..09a29fb50 100644 --- a/openstack/identity/v3/users/testing/requests_test.go +++ b/openstack/identity/v3/users/testing/requests_test.go @@ -175,3 +175,43 @@ func TestListInGroup(t *testing.T) { th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedUsersSlice, actual) } + +func TestUpdateUserPasswd(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateUserPasswdSuccessfully(t) + + opts := users.UpdatePasswdOpts{ + OriginalPassword: "secretsecret", + Password: "notthatsecret", + } + res := users.UpdatePasswd(client.ServiceClient(), "9fe1d3", opts) + th.AssertNoErr(t, res.Err) +} + +func TestCheckGroupUser(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCheckGroupUserSuccessfully(t) + + res := users.CheckGroupUser(client.ServiceClient(), "ea167b", "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestDeleteGroupUser(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteGroupUserSuccessfully(t) + + res := users.DeleteGroupUser(client.ServiceClient(), "ea167b", "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestAddUserToGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAddUserToGroupSuccessfully(t) + + res := users.AddUserToGroup(client.ServiceClient(), "ea167b", "9fe1d3") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/identity/v3/users/urls.go b/openstack/identity/v3/users/urls.go index d3ae819c6..148547c75 100644 --- a/openstack/identity/v3/users/urls.go +++ b/openstack/identity/v3/users/urls.go @@ -30,6 +30,16 @@ func listProjectsURL(client *golangsdk.ServiceClient, userID string) string { return client.ServiceURL("users", userID, "projects") } +func updatePasswdURL(client *golangsdk.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "password") +} + func listInGroupURL(client *golangsdk.ServiceClient, groupID string) string { return client.ServiceURL("groups", groupID, "users") } + +func operateOnGroupUserURL(client *golangsdk.ServiceClient, + groupID string, userID string) string { + + return client.ServiceURL("groups", groupID, "users", userID) +} diff --git a/script/acceptancetest b/script/acceptancetest index 5ed5b6d2f..1b534f290 100755 --- a/script/acceptancetest +++ b/script/acceptancetest @@ -2,4 +2,4 @@ # # Run the acceptance tests. -exec go test -p=1 github.com/huaweicloud/golangsdk/acceptance/... $@ +exec go test -tags 'acceptance' -p=1 github.com/huaweicloud/golangsdk/acceptance/... $@ diff --git a/service_client.go b/service_client.go index df6eaa23f..98d6c7b75 100644 --- a/service_client.go +++ b/service_client.go @@ -72,6 +72,17 @@ func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *Req return client.Request("GET", url, opts) } +// Head calls "Request" with the "HEAD" HTTP method. +func (client *ServiceClient) Head( + url string, opts *RequestOpts) (*http.Response, error) { + + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, nil, opts) + return client.Request("HEAD", url, opts) +} + // Post calls `Request` with the "POST" HTTP verb. func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil {