diff --git a/openstack/auth_env.go b/openstack/auth_env.go index a4402b6f..2d226d60 100644 --- a/openstack/auth_env.go +++ b/openstack/auth_env.go @@ -13,14 +13,15 @@ var nilOptions = gophercloud.AuthOptions{} // environment variables, respectively, remain undefined. See the AuthOptions() function for more details. var ( ErrNoAuthURL = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.") - ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.") - ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD needs to be set.") + ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME, OS_USERID, or OS_TOKEN needs to be set.") + ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_TOKEN needs to be set.") ) -// AuthOptions fills out an identity.AuthOptions structure with the settings found on the various OpenStack -// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME, -// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must -// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional. +// AuthOptionsFromEnv fills out an AuthOptions structure from the environment +// variables: OS_AUTH_URL, OS_USERNAME, OS_USERID, OS_PASSWORD, OS_TENANT_ID, +// OS_TENANT_NAME, OS_DOMAIN_ID, OS_DOMAIN_NAME, OS_TOKEN. It checks that +// (1) OS_AUTH_URL is set, (2) OS_USERNAME, OS_USERID, or OS_TOKEN is set, +// (3) OS_PASSWORD or OS_TOKEN is set. func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { authURL := os.Getenv("OS_AUTH_URL") username := os.Getenv("OS_USERNAME") @@ -30,16 +31,17 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { tenantName := os.Getenv("OS_TENANT_NAME") domainID := os.Getenv("OS_DOMAIN_ID") domainName := os.Getenv("OS_DOMAIN_NAME") + tokenID := os.Getenv("OS_TOKEN") if authURL == "" { return nilOptions, ErrNoAuthURL } - if username == "" && userID == "" { + if username == "" && userID == "" && tokenID == "" { return nilOptions, ErrNoUsername } - if password == "" { + if password == "" && tokenID == "" { return nilOptions, ErrNoPassword } @@ -52,6 +54,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { TenantName: tenantName, DomainID: domainID, DomainName: domainName, + TokenID: tokenID, } return ao, nil diff --git a/openstack/identity/v3/tokens/errors.go b/openstack/identity/v3/tokens/errors.go index 44761092..a9e1d5c2 100644 --- a/openstack/identity/v3/tokens/errors.go +++ b/openstack/identity/v3/tokens/errors.go @@ -52,8 +52,8 @@ var ( // It may also indicate that both a DomainID and a DomainName were provided at once. ErrDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName to authenticate by Username") - // ErrMissingPassword indicates that no password was provided and no token is available. - ErrMissingPassword = errors.New("You must provide a password to authenticate") + // ErrMissingPassword indicates that no password and no token were provided and no token is available. + ErrMissingPassword = errors.New("You must provide a password or a token to authenticate") // ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present. ErrScopeDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName in a Scope with ProjectName") diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go index d63b1bb5..2810f2fa 100644 --- a/openstack/identity/v3/tokens/requests.go +++ b/openstack/identity/v3/tokens/requests.go @@ -84,6 +84,9 @@ func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope } if options.Password == "" { + if options.TokenID != "" { + c.TokenID = options.TokenID + } if c.TokenID != "" { // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication // parameters. @@ -93,12 +96,6 @@ func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope if options.UserID != "" { return createErr(ErrUserIDWithToken) } - if options.DomainID != "" { - return createErr(ErrDomainIDWithToken) - } - if options.DomainName != "" { - return createErr(ErrDomainNameWithToken) - } // Configure the request for Token authentication. req.Auth.Identity.Methods = []string{"token"} diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go index 2b26e4ad..46ba5864 100644 --- a/openstack/identity/v3/tokens/requests_test.go +++ b/openstack/identity/v3/tokens/requests_test.go @@ -135,6 +135,21 @@ func TestCreateTokenID(t *testing.T) { `) } +func TestCreateNewTokenID(t *testing.T) { + authTokenPost(t, gophercloud.AuthOptions{TokenID: "asdf"}, nil, ` + { + "auth": { + "identity": { + "methods": ["token"], + "token": { + "id": "asdf" + } + } + } + } + `) +} + func TestCreateProjectIDScope(t *testing.T) { options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"} scope := &Scope{ProjectID: "123456"} @@ -296,12 +311,54 @@ func TestCreateFailureTokenIDUserID(t *testing.T) { authTokenPostErr(t, gophercloud.AuthOptions{UserID: "something"}, nil, true, ErrUserIDWithToken) } -func TestCreateFailureTokenIDDomainID(t *testing.T) { - authTokenPostErr(t, gophercloud.AuthOptions{DomainID: "something"}, nil, true, ErrDomainIDWithToken) +func TestCreateTokenIDDomainID(t *testing.T) { + scope := &Scope{ProjectName: "world-domination", DomainID: "1000"} + authTokenPost(t, gophercloud.AuthOptions{DomainID: "something"}, scope, ` + { + "auth": { + "identity": { + "methods": [ + "token" + ], + "token": { + "id": "12345abcdef" + } + }, + "scope": { + "project": { + "domain": { + "id": "1000" + }, + "name": "world-domination" + } + } + } + }`) } -func TestCreateFailureTokenIDDomainName(t *testing.T) { - authTokenPostErr(t, gophercloud.AuthOptions{DomainName: "something"}, nil, true, ErrDomainNameWithToken) +func TestCreateTokenIDDomainName(t *testing.T) { + scope := &Scope{ProjectName: "world-domination", DomainName: "evil-plans"} + authTokenPost(t, gophercloud.AuthOptions{DomainName: "something"}, scope, ` + { + "auth": { + "identity": { + "methods": [ + "token" + ], + "token": { + "id": "12345abcdef" + } + }, + "scope": { + "project": { + "domain": { + "name": "evil-plans" + }, + "name": "world-domination" + } + } + } + }`) } func TestCreateFailureMissingUser(t *testing.T) {