Merge pull request #220 from jrperritt/os-blockstorage
OS blockstorage v1
diff --git a/acceptance/openstack/identity/v3/token_test.go b/acceptance/openstack/identity/v3/token_test.go
index d5f9ea6..341acb7 100644
--- a/acceptance/openstack/identity/v3/token_test.go
+++ b/acceptance/openstack/identity/v3/token_test.go
@@ -34,15 +34,10 @@
service := openstack.NewIdentityV3(provider)
// Use the service to create a token.
- result, err := tokens3.Create(service, ao, nil)
+ token, err := tokens3.Create(service, ao, nil).Extract()
if err != nil {
t.Fatalf("Unable to get token: %v", err)
}
- token, err := result.TokenID()
- if err != nil {
- t.Fatalf("Unable to extract token from response: %v", err)
- }
-
- t.Logf("Acquired token: %s", token)
+ t.Logf("Acquired token: %s", token.ID)
}
diff --git a/openstack/client.go b/openstack/client.go
index b916dd9..1b057d0 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -175,15 +175,12 @@
v3Client.Endpoint = endpoint
}
- result, err := tokens3.Create(v3Client, options, nil)
+ token, err := tokens3.Create(v3Client, options, nil).Extract()
if err != nil {
return err
}
+ client.TokenID = token.ID
- client.TokenID, err = result.TokenID()
- if err != nil {
- return err
- }
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return v3endpointLocator(v3Client, opts)
}
diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go
index 186d0fc..eb52573 100644
--- a/openstack/identity/v3/endpoints/requests.go
+++ b/openstack/identity/v3/endpoints/requests.go
@@ -9,14 +9,6 @@
"github.com/rackspace/gophercloud/pagination"
)
-// maybeString returns nil for empty strings and nil for empty.
-func maybeString(original string) *string {
- if original != "" {
- return &original
- }
- return nil
-}
-
// EndpointOpts contains the subset of Endpoint attributes that should be used to create or update an Endpoint.
type EndpointOpts struct {
Availability gophercloud.Availability
@@ -28,7 +20,7 @@
// Create inserts a new Endpoint into the service catalog.
// Within EndpointOpts, Region may be omitted by being left as "", but all other fields are required.
-func Create(client *gophercloud.ServiceClient, opts EndpointOpts) (*Endpoint, error) {
+func Create(client *gophercloud.ServiceClient, opts EndpointOpts) CreateResult {
// Redefined so that Region can be re-typed as a *string, which can be omitted from the JSON output.
type endpoint struct {
Interface string `json:"interface"`
@@ -42,22 +34,18 @@
Endpoint endpoint `json:"endpoint"`
}
- type response struct {
- Endpoint Endpoint `json:"endpoint"`
- }
-
// Ensure that EndpointOpts is fully populated.
if opts.Availability == "" {
- return nil, ErrAvailabilityRequired
+ return createErr(ErrAvailabilityRequired)
}
if opts.Name == "" {
- return nil, ErrNameRequired
+ return createErr(ErrNameRequired)
}
if opts.URL == "" {
- return nil, ErrURLRequired
+ return createErr(ErrURLRequired)
}
if opts.ServiceID == "" {
- return nil, ErrServiceIDRequired
+ return createErr(ErrServiceIDRequired)
}
// Populate the request body.
@@ -69,20 +57,16 @@
ServiceID: opts.ServiceID,
},
}
- reqBody.Endpoint.Region = maybeString(opts.Region)
+ reqBody.Endpoint.Region = gophercloud.MaybeString(opts.Region)
- var respBody response
- _, err := perigee.Request("POST", getListURL(client), perigee.Options{
+ var result CreateResult
+ _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
ReqBody: &reqBody,
- Results: &respBody,
+ Results: &result.Resp,
OkCodes: []int{201},
})
- if err != nil {
- return nil, err
- }
-
- return &respBody.Endpoint, nil
+ return result
}
// ListOpts allows finer control over the the endpoints returned by a List call.
@@ -114,13 +98,13 @@
return EndpointPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
- u := getListURL(client) + utils.BuildQuery(q)
+ u := listURL(client) + utils.BuildQuery(q)
return pagination.NewPager(client, u, createPage)
}
// Update changes an existing endpoint with new data.
// All fields are optional in the provided EndpointOpts.
-func Update(client *gophercloud.ServiceClient, endpointID string, opts EndpointOpts) (*Endpoint, error) {
+func Update(client *gophercloud.ServiceClient, endpointID string, opts EndpointOpts) UpdateResult {
type endpoint struct {
Interface *string `json:"interface,omitempty"`
Name *string `json:"name,omitempty"`
@@ -133,34 +117,26 @@
Endpoint endpoint `json:"endpoint"`
}
- type response struct {
- Endpoint Endpoint `json:"endpoint"`
- }
-
reqBody := request{Endpoint: endpoint{}}
- reqBody.Endpoint.Interface = maybeString(string(opts.Availability))
- reqBody.Endpoint.Name = maybeString(opts.Name)
- reqBody.Endpoint.Region = maybeString(opts.Region)
- reqBody.Endpoint.URL = maybeString(opts.URL)
- reqBody.Endpoint.ServiceID = maybeString(opts.ServiceID)
+ reqBody.Endpoint.Interface = gophercloud.MaybeString(string(opts.Availability))
+ reqBody.Endpoint.Name = gophercloud.MaybeString(opts.Name)
+ reqBody.Endpoint.Region = gophercloud.MaybeString(opts.Region)
+ reqBody.Endpoint.URL = gophercloud.MaybeString(opts.URL)
+ reqBody.Endpoint.ServiceID = gophercloud.MaybeString(opts.ServiceID)
- var respBody response
- _, err := perigee.Request("PATCH", getEndpointURL(client, endpointID), perigee.Options{
+ var result UpdateResult
+ _, result.Err = perigee.Request("PATCH", endpointURL(client, endpointID), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
ReqBody: &reqBody,
- Results: &respBody,
+ Results: &result.Resp,
OkCodes: []int{200},
})
- if err != nil {
- return nil, err
- }
-
- return &respBody.Endpoint, nil
+ return result
}
// Delete removes an endpoint from the service catalog.
func Delete(client *gophercloud.ServiceClient, endpointID string) error {
- _, err := perigee.Request("DELETE", getEndpointURL(client, endpointID), perigee.Options{
+ _, err := perigee.Request("DELETE", endpointURL(client, endpointID), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
diff --git a/openstack/identity/v3/endpoints/requests_test.go b/openstack/identity/v3/endpoints/requests_test.go
index 241d175..c30bd55 100644
--- a/openstack/identity/v3/endpoints/requests_test.go
+++ b/openstack/identity/v3/endpoints/requests_test.go
@@ -59,13 +59,13 @@
client := serviceClient()
- result, err := Create(client, EndpointOpts{
+ actual, err := Create(client, EndpointOpts{
Availability: gophercloud.AvailabilityPublic,
Name: "the-endiest-of-points",
Region: "underground",
URL: "https://1.2.3.4:9000/",
ServiceID: "asdfasdfasdfasdf",
- })
+ }).Extract()
if err != nil {
t.Fatalf("Unable to create an endpoint: %v", err)
}
@@ -79,8 +79,8 @@
URL: "https://1.2.3.4:9000/",
}
- if !reflect.DeepEqual(result, expected) {
- t.Errorf("Expected %#v, was %#v", expected, result)
+ if !reflect.DeepEqual(actual, expected) {
+ t.Errorf("Expected %#v, was %#v", expected, actual)
}
}
@@ -205,7 +205,7 @@
actual, err := Update(client, "12", EndpointOpts{
Name: "renamed",
Region: "somewhere-else",
- })
+ }).Extract()
if err != nil {
t.Fatalf("Unexpected error from Update: %v", err)
}
diff --git a/openstack/identity/v3/endpoints/results.go b/openstack/identity/v3/endpoints/results.go
index 8da90f3..2dd2357 100644
--- a/openstack/identity/v3/endpoints/results.go
+++ b/openstack/identity/v3/endpoints/results.go
@@ -1,11 +1,51 @@
package endpoints
import (
+ "fmt"
+
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
+type commonResult struct {
+ gophercloud.CommonResult
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete Endpoint.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*Endpoint, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Endpoint `json:"endpoint"`
+ }
+
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Endpoint: %v", err)
+ }
+
+ return &res.Endpoint, nil
+}
+
+// CreateResult is the deferred result of a Create call.
+type CreateResult struct {
+ commonResult
+}
+
+// createErr quickly wraps an error in a CreateResult.
+func createErr(err error) CreateResult {
+ return CreateResult{commonResult{gophercloud.CommonResult{Err: err}}}
+}
+
+// UpdateResult is the deferred result of an Update call.
+type UpdateResult struct {
+ commonResult
+}
+
// Endpoint describes the entry point for another service's API.
type Endpoint struct {
ID string `mapstructure:"id" json:"id"`
diff --git a/openstack/identity/v3/endpoints/urls.go b/openstack/identity/v3/endpoints/urls.go
index 011cc01..547d7b1 100644
--- a/openstack/identity/v3/endpoints/urls.go
+++ b/openstack/identity/v3/endpoints/urls.go
@@ -2,10 +2,10 @@
import "github.com/rackspace/gophercloud"
-func getListURL(client *gophercloud.ServiceClient) string {
+func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("endpoints")
}
-func getEndpointURL(client *gophercloud.ServiceClient, endpointID string) string {
+func endpointURL(client *gophercloud.ServiceClient, endpointID string) string {
return client.ServiceURL("endpoints", endpointID)
}
diff --git a/openstack/identity/v3/endpoints/urls_test.go b/openstack/identity/v3/endpoints/urls_test.go
index fe1fb4a..0b183b7 100644
--- a/openstack/identity/v3/endpoints/urls_test.go
+++ b/openstack/identity/v3/endpoints/urls_test.go
@@ -8,7 +8,7 @@
func TestGetListURL(t *testing.T) {
client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
- url := getListURL(&client)
+ url := listURL(&client)
if url != "http://localhost:5000/v3/endpoints" {
t.Errorf("Unexpected list URL generated: [%s]", url)
}
@@ -16,7 +16,7 @@
func TestGetEndpointURL(t *testing.T) {
client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
- url := getEndpointURL(&client, "1234")
+ url := endpointURL(&client, "1234")
if url != "http://localhost:5000/v3/endpoints/1234" {
t.Errorf("Unexpected service URL generated: [%s]", url)
}
diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go
index 405a9a6..7816aca 100644
--- a/openstack/identity/v3/services/requests.go
+++ b/openstack/identity/v3/services/requests.go
@@ -14,25 +14,21 @@
}
// Create adds a new service of the requested type to the catalog.
-func Create(client *gophercloud.ServiceClient, serviceType string) (*Service, error) {
+func Create(client *gophercloud.ServiceClient, serviceType string) CreateResult {
type request struct {
Type string `json:"type"`
}
req := request{Type: serviceType}
- var resp response
- _, err := perigee.Request("POST", getListURL(client), perigee.Options{
+ var result CreateResult
+ _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
ReqBody: &req,
- Results: &resp,
+ Results: &result.Resp,
OkCodes: []int{201},
})
- if err != nil {
- return nil, err
- }
-
- return &resp.Service, nil
+ return result
}
// ListOpts allows you to query the List method.
@@ -54,7 +50,7 @@
if opts.PerPage != 0 {
q["perPage"] = strconv.Itoa(opts.PerPage)
}
- u := getListURL(client) + utils.BuildQuery(q)
+ u := listURL(client) + utils.BuildQuery(q)
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
return ServicePage{pagination.LinkedPageBase{LastHTTPResponse: r}}
@@ -64,45 +60,38 @@
}
// Get returns additional information about a service, given its ID.
-func Get(client *gophercloud.ServiceClient, serviceID string) (*Service, error) {
- var resp response
- _, err := perigee.Request("GET", getServiceURL(client, serviceID), perigee.Options{
+func Get(client *gophercloud.ServiceClient, serviceID string) GetResult {
+ var result GetResult
+ _, result.Err = perigee.Request("GET", serviceURL(client, serviceID), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
- Results: &resp,
+ Results: &result.Resp,
OkCodes: []int{200},
})
- if err != nil {
- return nil, err
- }
- return &resp.Service, nil
+ return result
}
-// Update changes the service type of an existing service.s
-func Update(client *gophercloud.ServiceClient, serviceID string, serviceType string) (*Service, error) {
+// Update changes the service type of an existing service.
+func Update(client *gophercloud.ServiceClient, serviceID string, serviceType string) UpdateResult {
type request struct {
Type string `json:"type"`
}
req := request{Type: serviceType}
- var resp response
- _, err := perigee.Request("PATCH", getServiceURL(client, serviceID), perigee.Options{
+ var result UpdateResult
+ _, result.Err = perigee.Request("PATCH", serviceURL(client, serviceID), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
ReqBody: &req,
- Results: &resp,
+ Results: &result.Resp,
OkCodes: []int{200},
})
- if err != nil {
- return nil, err
- }
-
- return &resp.Service, nil
+ return result
}
// Delete removes an existing service.
// It either deletes all associated endpoints, or fails until all endpoints are deleted.
func Delete(client *gophercloud.ServiceClient, serviceID string) error {
- _, err := perigee.Request("DELETE", getServiceURL(client, serviceID), perigee.Options{
+ _, err := perigee.Request("DELETE", serviceURL(client, serviceID), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
diff --git a/openstack/identity/v3/services/requests_test.go b/openstack/identity/v3/services/requests_test.go
index 804f034..a3d345b 100644
--- a/openstack/identity/v3/services/requests_test.go
+++ b/openstack/identity/v3/services/requests_test.go
@@ -45,7 +45,7 @@
client := serviceClient()
- result, err := Create(client, "compute")
+ result, err := Create(client, "compute").Extract()
if err != nil {
t.Fatalf("Unexpected error from Create: %v", err)
}
@@ -161,7 +161,7 @@
client := serviceClient()
- result, err := Get(client, "12345")
+ result, err := Get(client, "12345").Extract()
if err != nil {
t.Fatalf("Error fetching service information: %v", err)
}
@@ -202,13 +202,13 @@
client := serviceClient()
- result, err := Update(client, "12345", "lasermagic")
+ result, err := Update(client, "12345", "lasermagic").Extract()
if err != nil {
t.Fatalf("Unable to update service: %v", err)
}
if result.ID != "12345" {
-
+ t.Fatalf("Expected ID 12345, was %s", result.ID)
}
}
diff --git a/openstack/identity/v3/services/results.go b/openstack/identity/v3/services/results.go
index cccea8e..b4e7bd2 100644
--- a/openstack/identity/v3/services/results.go
+++ b/openstack/identity/v3/services/results.go
@@ -1,11 +1,52 @@
package services
import (
+ "fmt"
+
+ "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
+type commonResult struct {
+ gophercloud.CommonResult
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete Service.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*Service, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Service `json:"service"`
+ }
+
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Service: %v", err)
+ }
+
+ return &res.Service, nil
+}
+
+// CreateResult is the deferred result of a Create call.
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult is the deferred result of a Get call.
+type GetResult struct {
+ commonResult
+}
+
+// UpdateResult is the deferred result of an Update call.
+type UpdateResult struct {
+ commonResult
+}
+
// Service is the result of a list or information query.
type Service struct {
Description *string `json:"description,omitempty"`
diff --git a/openstack/identity/v3/services/urls.go b/openstack/identity/v3/services/urls.go
index 3556238..85443a4 100644
--- a/openstack/identity/v3/services/urls.go
+++ b/openstack/identity/v3/services/urls.go
@@ -2,10 +2,10 @@
import "github.com/rackspace/gophercloud"
-func getListURL(client *gophercloud.ServiceClient) string {
+func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("services")
}
-func getServiceURL(client *gophercloud.ServiceClient, serviceID string) string {
+func serviceURL(client *gophercloud.ServiceClient, serviceID string) string {
return client.ServiceURL("services", serviceID)
}
diff --git a/openstack/identity/v3/services/urls_test.go b/openstack/identity/v3/services/urls_test.go
index deded69..5a31b32 100644
--- a/openstack/identity/v3/services/urls_test.go
+++ b/openstack/identity/v3/services/urls_test.go
@@ -6,17 +6,17 @@
"github.com/rackspace/gophercloud"
)
-func TestGetListURL(t *testing.T) {
+func TestListURL(t *testing.T) {
client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
- url := getListURL(&client)
+ url := listURL(&client)
if url != "http://localhost:5000/v3/services" {
t.Errorf("Unexpected list URL generated: [%s]", url)
}
}
-func TestGetServiceURL(t *testing.T) {
+func TestServiceURL(t *testing.T) {
client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
- url := getServiceURL(&client, "1234")
+ url := serviceURL(&client, "1234")
if url != "http://localhost:5000/v3/services/1234" {
t.Errorf("Unexpected service URL generated: [%s]", url)
}
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index ab4ae05..c8587b6 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -20,7 +20,7 @@
}
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
-func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) (gophercloud.AuthResults, error) {
+func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
type domainReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
@@ -73,13 +73,13 @@
// Test first for unrecognized arguments.
if options.APIKey != "" {
- return nil, ErrAPIKeyProvided
+ return createErr(ErrAPIKeyProvided)
}
if options.TenantID != "" {
- return nil, ErrTenantIDProvided
+ return createErr(ErrTenantIDProvided)
}
if options.TenantName != "" {
- return nil, ErrTenantNameProvided
+ return createErr(ErrTenantNameProvided)
}
if options.Password == "" {
@@ -87,16 +87,16 @@
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
if options.Username != "" {
- return nil, ErrUsernameWithToken
+ return createErr(ErrUsernameWithToken)
}
if options.UserID != "" {
- return nil, ErrUserIDWithToken
+ return createErr(ErrUserIDWithToken)
}
if options.DomainID != "" {
- return nil, ErrDomainIDWithToken
+ return createErr(ErrDomainIDWithToken)
}
if options.DomainName != "" {
- return nil, ErrDomainNameWithToken
+ return createErr(ErrDomainNameWithToken)
}
// Configure the request for Token authentication.
@@ -106,7 +106,7 @@
}
} else {
// If no password or token ID are available, authentication can't continue.
- return nil, ErrMissingPassword
+ return createErr(ErrMissingPassword)
}
} else {
// Password authentication.
@@ -114,23 +114,23 @@
// At least one of Username and UserID must be specified.
if options.Username == "" && options.UserID == "" {
- return nil, ErrUsernameOrUserID
+ return createErr(ErrUsernameOrUserID)
}
if options.Username != "" {
// If Username is provided, UserID may not be provided.
if options.UserID != "" {
- return nil, ErrUsernameOrUserID
+ return createErr(ErrUsernameOrUserID)
}
// Either DomainID or DomainName must also be specified.
if options.DomainID == "" && options.DomainName == "" {
- return nil, ErrDomainIDOrDomainName
+ return createErr(ErrDomainIDOrDomainName)
}
if options.DomainID != "" {
if options.DomainName != "" {
- return nil, ErrDomainIDOrDomainName
+ return createErr(ErrDomainIDOrDomainName)
}
// Configure the request for Username and Password authentication with a DomainID.
@@ -158,10 +158,10 @@
if options.UserID != "" {
// If UserID is specified, neither DomainID nor DomainName may be.
if options.DomainID != "" {
- return nil, ErrDomainIDWithUserID
+ return createErr(ErrDomainIDWithUserID)
}
if options.DomainName != "" {
- return nil, ErrDomainNameWithUserID
+ return createErr(ErrDomainNameWithUserID)
}
// Configure the request for UserID and Password authentication.
@@ -177,10 +177,10 @@
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" {
- return nil, ErrScopeDomainIDOrDomainName
+ return createErr(ErrScopeDomainIDOrDomainName)
}
if scope.ProjectID != "" {
- return nil, ErrScopeProjectIDOrProjectName
+ return createErr(ErrScopeProjectIDOrProjectName)
}
if scope.DomainID != "" {
@@ -205,10 +205,10 @@
} else if scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" {
- return nil, ErrScopeProjectIDAlone
+ return createErr(ErrScopeProjectIDAlone)
}
if scope.DomainName != "" {
- return nil, ErrScopeProjectIDAlone
+ return createErr(ErrScopeProjectIDAlone)
}
// ProjectID
@@ -218,7 +218,7 @@
} else if scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" {
- return nil, ErrScopeDomainIDOrDomainName
+ return createErr(ErrScopeDomainIDOrDomainName)
}
// DomainID
@@ -226,51 +226,45 @@
Domain: &domainReq{ID: &scope.DomainID},
}
} else if scope.DomainName != "" {
- return nil, ErrScopeDomainName
+ return createErr(ErrScopeDomainName)
} else {
- return nil, ErrScopeEmpty
+ return createErr(ErrScopeEmpty)
}
}
- var result TokenCreateResult
- response, err := perigee.Request("POST", getTokenURL(c), perigee.Options{
+ var result CreateResult
+ var response *perigee.Response
+ response, result.Err = perigee.Request("POST", tokenURL(c), perigee.Options{
ReqBody: &req,
- Results: &result.response,
+ Results: &result.Resp,
OkCodes: []int{201},
})
- if err != nil {
- return nil, err
+ if result.Err != nil {
+ return result
}
-
- // Extract the token ID from the response, if present.
- result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
-
- return &result, nil
+ result.header = response.HttpResponse.Header
+ return result
}
// Get validates and retrieves information about another token.
-func Get(c *gophercloud.ServiceClient, token string) (*TokenCreateResult, error) {
- var result TokenCreateResult
-
- response, err := perigee.Request("GET", getTokenURL(c), perigee.Options{
+func Get(c *gophercloud.ServiceClient, token string) GetResult {
+ var result GetResult
+ var response *perigee.Response
+ response, result.Err = perigee.Request("GET", tokenURL(c), perigee.Options{
MoreHeaders: subjectTokenHeaders(c, token),
- Results: &result.response,
+ Results: &result.Resp,
OkCodes: []int{200, 203},
})
-
- if err != nil {
- return nil, err
+ if result.Err != nil {
+ return result
}
-
- // Extract the token ID from the response, if present.
- result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
-
- return &result, nil
+ result.header = response.HttpResponse.Header
+ return result
}
// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
- response, err := perigee.Request("HEAD", getTokenURL(c), perigee.Options{
+ response, err := perigee.Request("HEAD", tokenURL(c), perigee.Options{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404},
})
@@ -283,7 +277,7 @@
// Revoke immediately makes specified token invalid.
func Revoke(c *gophercloud.ServiceClient, token string) error {
- _, err := perigee.Request("DELETE", getTokenURL(c), perigee.Options{
+ _, err := perigee.Request("DELETE", tokenURL(c), perigee.Options{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204},
})
diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go
index d0813ac..367c73c 100644
--- a/openstack/identity/v3/tokens/requests_test.go
+++ b/openstack/identity/v3/tokens/requests_test.go
@@ -29,10 +29,14 @@
testhelper.TestJSONRequest(t, r, requestJSON)
w.WriteHeader(http.StatusCreated)
- fmt.Fprintf(w, `{}`)
+ fmt.Fprintf(w, `{
+ "token": {
+ "expires_at": "2014-10-02T13:45:00.000000Z"
+ }
+ }`)
})
- _, err := Create(&client, options, scope)
+ _, err := Create(&client, options, scope).Extract()
if err != nil {
t.Errorf("Create returned an error: %v", err)
}
@@ -50,7 +54,7 @@
client.Provider.TokenID = "abcdef123456"
}
- _, err := Create(&client, options, scope)
+ _, err := Create(&client, options, scope).Extract()
if err == nil {
t.Errorf("Create did NOT return an error")
}
@@ -250,18 +254,21 @@
w.Header().Add("X-Subject-Token", "aaa111")
w.WriteHeader(http.StatusCreated)
- fmt.Fprintf(w, `{}`)
+ fmt.Fprintf(w, `{
+ "token": {
+ "expires_at": "2014-10-02T13:45:00.000000Z"
+ }
+ }`)
})
options := gophercloud.AuthOptions{UserID: "me", Password: "shhh"}
- result, err := Create(&client, options, nil)
+ token, err := Create(&client, options, nil).Extract()
if err != nil {
- t.Errorf("Create returned an error: %v", err)
+ t.Fatalf("Create returned an error: %v", err)
}
- token, _ := result.TokenID()
- if token != "aaa111" {
- t.Errorf("Expected token to be aaa111, but was %s", token)
+ if token.ID != "aaa111" {
+ t.Errorf("Expected token to be aaa111, but was %s", token.ID)
}
}
@@ -413,19 +420,14 @@
`)
})
- result, err := Get(&client, "abcdef12345")
+ token, err := Get(&client, "abcdef12345").Extract()
if err != nil {
t.Errorf("Info returned an error: %v", err)
}
- expires, err := result.ExpiresAt()
- if err != nil {
- t.Errorf("Error extracting token expiration time: %v", err)
- }
-
expected, _ := time.Parse(time.UnixDate, "Fri Aug 29 13:10:01 UTC 2014")
- if expires != expected {
- t.Errorf("Expected expiration time %s, but was %s", expected.Format(time.UnixDate), expires.Format(time.UnixDate))
+ if token.ExpiresAt != expected {
+ t.Errorf("Expected expiration time %s, but was %s", expected.Format(time.UnixDate), token.ExpiresAt.Format(time.UnixDate))
}
}
diff --git a/openstack/identity/v3/tokens/results.go b/openstack/identity/v3/tokens/results.go
index 8e0f018..c970c67 100644
--- a/openstack/identity/v3/tokens/results.go
+++ b/openstack/identity/v3/tokens/results.go
@@ -1,46 +1,81 @@
package tokens
import (
+ "net/http"
"time"
"github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
)
// RFC3339Milli describes the time format used by identity API responses.
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
-// TokenCreateResult contains the document structure returned from a Create call.
-type TokenCreateResult struct {
- response map[string]interface{}
- tokenID string
+// commonResult is the deferred result of a Create or a Get call.
+type commonResult struct {
+ gophercloud.CommonResult
+
+ // header stores the headers from the original HTTP response because token responses are returned in an X-Subject-Token header.
+ header http.Header
}
-// TokenID retrieves a token generated by a Create call from an token creation response.
-func (r *TokenCreateResult) TokenID() (string, error) {
- return r.tokenID, nil
-}
-
-// ExpiresAt retrieves the token expiration time.
-func (r *TokenCreateResult) ExpiresAt() (time.Time, error) {
- type tokenResp struct {
- ExpiresAt string `mapstructure:"expires_at"`
+// Extract interprets a commonResult as a Token.
+func (r commonResult) Extract() (*Token, error) {
+ if r.Err != nil {
+ return nil, r.Err
}
- type response struct {
- Token tokenResp `mapstructure:"token"`
+ var response struct {
+ Token struct {
+ ExpiresAt string `mapstructure:"expires_at"`
+ } `mapstructure:"token"`
}
- var resp response
- err := mapstructure.Decode(r.response, &resp)
+ var token Token
+
+ // Parse the token itself from the stored headers.
+ token.ID = r.header.Get("X-Subject-Token")
+
+ err := mapstructure.Decode(r.Resp, &response)
if err != nil {
- return time.Time{}, err
+ return nil, err
}
// Attempt to parse the timestamp.
- ts, err := time.Parse(RFC3339Milli, resp.Token.ExpiresAt)
+ token.ExpiresAt, err = time.Parse(RFC3339Milli, response.Token.ExpiresAt)
if err != nil {
- return time.Time{}, err
+ return nil, err
}
- return ts, nil
+ return &token, nil
+}
+
+// CreateResult is the deferred response from a Create call.
+type CreateResult struct {
+ commonResult
+}
+
+// createErr quickly creates a CreateResult that reports an error.
+func createErr(err error) CreateResult {
+ return CreateResult{
+ commonResult: commonResult{
+ CommonResult: gophercloud.CommonResult{Err: err},
+ header: nil,
+ },
+ }
+}
+
+// GetResult is the deferred response from a Get call.
+type GetResult struct {
+ commonResult
+}
+
+// Token is a string that grants a user access to a controlled set of services in an OpenStack provider.
+// Each Token is valid for a set length of time.
+type Token struct {
+ // ID is the issued token.
+ ID string
+
+ // ExpiresAt is the timestamp at which this token will no longer be accepted.
+ ExpiresAt time.Time
}
diff --git a/openstack/identity/v3/tokens/results_test.go b/openstack/identity/v3/tokens/results_test.go
deleted file mode 100644
index 669db61..0000000
--- a/openstack/identity/v3/tokens/results_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package tokens
-
-import (
- "testing"
- "time"
-)
-
-func TestTokenID(t *testing.T) {
- result := TokenCreateResult{tokenID: "1234"}
-
- token, _ := result.TokenID()
- if token != "1234" {
- t.Errorf("Expected tokenID of 1234, got %s", token)
- }
-}
-
-func TestExpiresAt(t *testing.T) {
- resp := map[string]interface{}{
- "token": map[string]string{
- "expires_at": "2013-02-02T18:30:59.000000Z",
- },
- }
-
- result := TokenCreateResult{
- tokenID: "1234",
- response: resp,
- }
-
- expected, _ := time.Parse(time.UnixDate, "Sat Feb 2 18:30:59 UTC 2013")
- actual, err := result.ExpiresAt()
- if err != nil {
- t.Errorf("Error extraction expiration time: %v", err)
- }
- if actual != expected {
- t.Errorf("Expected expiration time %s, but was %s", expected.Format(time.UnixDate), actual.Format(time.UnixDate))
- }
-}
diff --git a/openstack/identity/v3/tokens/urls.go b/openstack/identity/v3/tokens/urls.go
index 5b47c02..360b60a 100644
--- a/openstack/identity/v3/tokens/urls.go
+++ b/openstack/identity/v3/tokens/urls.go
@@ -2,6 +2,6 @@
import "github.com/rackspace/gophercloud"
-func getTokenURL(c *gophercloud.ServiceClient) string {
+func tokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("auth", "tokens")
}
diff --git a/openstack/identity/v3/tokens/urls_test.go b/openstack/identity/v3/tokens/urls_test.go
index 5ff8bc6..549c398 100644
--- a/openstack/identity/v3/tokens/urls_test.go
+++ b/openstack/identity/v3/tokens/urls_test.go
@@ -14,7 +14,7 @@
client := gophercloud.ServiceClient{Endpoint: testhelper.Endpoint()}
expected := testhelper.Endpoint() + "auth/tokens"
- actual := getTokenURL(&client)
+ actual := tokenURL(&client)
if actual != expected {
t.Errorf("Expected URL %s, but was %s", expected, actual)
}