Delegate to OS for Rackspace Identity.
But handle the APIKey authentication manually, first.
diff --git a/rackspace/client.go b/rackspace/client.go
new file mode 100644
index 0000000..ed053e0
--- /dev/null
+++ b/rackspace/client.go
@@ -0,0 +1,44 @@
+package rackspace
+
+import (
+ "errors"
+
+ "github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack"
+)
+
+const (
+ // RackspaceUSIdentity is an identity endpoint located in the United States.
+ RackspaceUSIdentity = "https://identity.api.rackspacecloud.com/v2.0/"
+
+ // RackspaceUKIdentity is an identity endpoint located in the UK.
+ RackspaceUKIdentity = "https://lon.identity.api.rackspacecloud.com/v2.0/"
+)
+
+// NewClient creates a client that's prepared to communicate with the Rackspace API, but is not
+// yet authenticated. Most users will probably prefer using the AuthenticatedClient function
+// instead.
+//
+// Provide the base URL of the identity endpoint you wish to authenticate against as "endpoint".
+// Often, this will be either RackspaceUSIdentity or RackspaceUKIdentity.
+func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
+ return os.NewClient(endpoint)
+}
+
+// AuthenticatedClient logs in to Rackspace with the provided credentials and constructs a
+// ProviderClient that's ready to operate.
+//
+// If the provided AuthOptions does not specify an explicit IdentityEndpoint, it will default to
+// the canonical, production Rackspace US identity endpoint.
+func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
+ if options.IdentityEndpoint == "" {
+ options.IdentityEndpoint = RackspaceUSIdentity
+ }
+
+ _, err := NewClient(options.IdentityEndpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ return nil, errors.New("Incomplete")
+}
diff --git a/rackspace/identity/v2/tokens/delegate.go b/rackspace/identity/v2/tokens/delegate.go
new file mode 100644
index 0000000..6a5c1b3
--- /dev/null
+++ b/rackspace/identity/v2/tokens/delegate.go
@@ -0,0 +1,50 @@
+package tokens
+
+import (
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
+)
+
+// Create authenticates to Rackspace's identity service and attempts to acquire a Token. Rather
+// than interact with this service directly, users should generally call
+// rackspace.AuthenticatedClient().
+func Create(client *gophercloud.ServiceClient, auth gophercloud.AuthOptions) os.CreateResult {
+ if auth.APIKey != "" {
+ // Authenticate with the provided API key.
+
+ if auth.Username == "" {
+ return createErr(os.ErrUsernameRequired)
+ }
+
+ var request struct {
+ Auth struct {
+ APIKeyCredentials struct {
+ Username string `json:"username"`
+ APIKey string `json:"apiKey"`
+ } `json:"RAX-KSKEY:apiKeyCredentials"`
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+ } `json:"auth"`
+ }
+
+ request.Auth.APIKeyCredentials.Username = auth.Username
+ request.Auth.APIKeyCredentials.APIKey = auth.APIKey
+ request.Auth.TenantID = auth.TenantID
+ request.Auth.TenantName = auth.TenantName
+
+ var result os.CreateResult
+ _, result.Err = perigee.Request("POST", os.CreateURL(client), perigee.Options{
+ ReqBody: &request,
+ Results: &result.Resp,
+ OkCodes: []int{200, 203},
+ })
+ return result
+ }
+
+ return os.Create(client, auth)
+}
+
+func createErr(err error) os.CreateResult {
+ return os.CreateResult{CommonResult: gophercloud.CommonResult{Err: err}}
+}
diff --git a/rackspace/identity/v2/tokens/delegate_test.go b/rackspace/identity/v2/tokens/delegate_test.go
new file mode 100644
index 0000000..c289947
--- /dev/null
+++ b/rackspace/identity/v2/tokens/delegate_test.go
@@ -0,0 +1,161 @@
+package tokens
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
+ os "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+var expectedToken = &os.Token{
+ ID: "aaaabbbbccccdddd",
+ ExpiresAt: time.Date(2014, time.January, 31, 15, 30, 58, 0, time.UTC),
+ Tenant: tenants.Tenant{
+ ID: "fc394f2ab2df4114bde39905f800dc57",
+ Name: "test",
+ Description: "There are many tenants. This one is yours.",
+ Enabled: true,
+ },
+}
+
+var expectedServiceCatalog = &os.ServiceCatalog{
+ Entries: []os.CatalogEntry{
+ os.CatalogEntry{
+ Name: "inscrutablewalrus",
+ Type: "something",
+ Endpoints: []os.Endpoint{
+ os.Endpoint{
+ PublicURL: "http://something0:1234/v2/",
+ Region: "region0",
+ },
+ os.Endpoint{
+ PublicURL: "http://something1:1234/v2/",
+ Region: "region1",
+ },
+ },
+ },
+ os.CatalogEntry{
+ Name: "arbitrarypenguin",
+ Type: "else",
+ Endpoints: []os.Endpoint{
+ os.Endpoint{
+ PublicURL: "http://else0:4321/v3/",
+ Region: "region0",
+ },
+ },
+ },
+ },
+}
+
+func tokenPost(t *testing.T, options gophercloud.AuthOptions, requestJSON string) os.CreateResult {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ client := gophercloud.ServiceClient{Endpoint: th.Endpoint()}
+
+ th.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, requestJSON)
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `
+{
+ "access": {
+ "token": {
+ "issued_at": "2014-01-30T15:30:58.000000Z",
+ "expires": "2014-01-31T15:30:58Z",
+ "id": "aaaabbbbccccdddd",
+ "tenant": {
+ "description": "There are many tenants. This one is yours.",
+ "enabled": true,
+ "id": "fc394f2ab2df4114bde39905f800dc57",
+ "name": "test"
+ }
+ },
+ "serviceCatalog": [
+ {
+ "endpoints": [
+ {
+ "publicURL": "http://something0:1234/v2/",
+ "region": "region0"
+ },
+ {
+ "publicURL": "http://something1:1234/v2/",
+ "region": "region1"
+ }
+ ],
+ "type": "something",
+ "name": "inscrutablewalrus"
+ },
+ {
+ "endpoints": [
+ {
+ "publicURL": "http://else0:4321/v3/",
+ "region": "region0"
+ }
+ ],
+ "type": "else",
+ "name": "arbitrarypenguin"
+ }
+ ]
+ }
+}
+ `)
+ })
+
+ return Create(&client, options)
+}
+
+func tokenPostErr(t *testing.T, options gophercloud.AuthOptions, expectedErr error) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ client := gophercloud.ServiceClient{Endpoint: th.Endpoint()}
+
+ th.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{}`)
+ })
+
+ actualErr := Create(&client, options).Err
+ th.CheckEquals(t, expectedErr, actualErr)
+}
+
+func isSuccessful(t *testing.T, result os.CreateResult) {
+ token, err := result.ExtractToken()
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, expectedToken, token)
+
+ serviceCatalog, err := result.ExtractServiceCatalog()
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, expectedServiceCatalog, serviceCatalog)
+}
+
+func TestCreateTokenWithAPIKey(t *testing.T) {
+ options := gophercloud.AuthOptions{
+ Username: "me",
+ APIKey: "1234567890abcdef",
+ }
+
+ isSuccessful(t, tokenPost(t, options, `
+ {
+ "auth": {
+ "RAX-KSKEY:apiKeyCredentials": {
+ "username": "me",
+ "apiKey": "1234567890abcdef"
+ }
+ }
+ }
+ `))
+}