Ookay, last reshuffle.
* openstack.NewClient() normalizes the identity endpoint with a trailing slash, and sets base and endpoint.
* utils.ChooseVersion() checks suffixes first to short-circuit actual version calls.
* gophercloud.ProviderClient distinguishes between the root of all identity services (IdentityBase)
and the endpoint of the requested auth service (IdentityEndpoint).
diff --git a/openstack/client.go b/openstack/client.go
index d244053..7e9b628 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -2,6 +2,7 @@
import (
"fmt"
+ "net/url"
"strings"
"github.com/rackspace/gophercloud"
@@ -22,11 +23,32 @@
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return nil, err
+ }
+ hadPath := u.Path != ""
+ u.Path, u.RawQuery, u.Fragment = "", "", ""
+ base := u.String()
+
if !strings.HasSuffix(endpoint, "/") {
endpoint = endpoint + "/"
}
+ if !strings.HasSuffix(base, "/") {
+ base = base + "/"
+ }
- return &gophercloud.ProviderClient{IdentityEndpoint: endpoint}, nil
+ if hadPath {
+ return &gophercloud.ProviderClient{
+ IdentityBase: base,
+ IdentityEndpoint: endpoint,
+ }, nil
+ }
+
+ return &gophercloud.ProviderClient{
+ IdentityBase: base,
+ IdentityEndpoint: "",
+ }, nil
}
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
@@ -49,61 +71,55 @@
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
- &utils.Version{ID: v20, Priority: 20},
- &utils.Version{ID: v30, Priority: 30},
+ &utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
+ &utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
}
- chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions)
+ chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions)
if err != nil {
return err
}
switch chosen.ID {
case v20:
- v2Client := NewIdentityV2(client)
- v2Client.Endpoint = endpoint
-
- result, err := identity2.Authenticate(v2Client, options)
- if err != nil {
- return err
- }
-
- token, err := identity2.GetToken(result)
- if err != nil {
- return err
- }
-
- client.TokenID = token.ID
- client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
- return v2endpointLocator(result, opts)
- }
-
- return nil
+ return v2auth(client, endpoint, options)
case v30:
- // Override the generated service endpoint with the one returned by the version endpoint.
- v3Client := NewIdentityV3(client)
- v3Client.Endpoint = endpoint
-
- result, err := tokens3.Create(v3Client, options, nil)
- if err != nil {
- return err
- }
-
- client.TokenID, err = result.TokenID()
- if err != nil {
- return err
- }
- client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
- return v3endpointLocator(v3Client, opts)
- }
-
- return nil
+ return v3auth(client, endpoint, options)
default:
// The switch statement must be out of date from the versions list.
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
}
}
+// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
+func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
+ return v2auth(client, "", options)
+}
+
+func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
+ v2Client := NewIdentityV2(client)
+ if endpoint != "" {
+ v2Client.Endpoint = endpoint
+ }
+
+ result, err := identity2.Authenticate(v2Client, options)
+ if err != nil {
+ return err
+ }
+
+ token, err := identity2.GetToken(result)
+ if err != nil {
+ return err
+ }
+
+ client.TokenID = token.ID
+ client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+ return v2endpointLocator(result, opts)
+ }
+
+ return nil
+}
+
func v2endpointLocator(authResults identity2.AuthResults, opts gophercloud.EndpointOpts) (string, error) {
catalog, err := identity2.GetServiceCatalog(authResults)
if err != nil {
@@ -150,6 +166,34 @@
return "", gophercloud.ErrEndpointNotFound
}
+// AuthenticateV3 explicitly authenticates against the identity v3 service.
+func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
+ return v3auth(client, "", options)
+}
+
+func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
+ // Override the generated service endpoint with the one returned by the version endpoint.
+ v3Client := NewIdentityV3(client)
+ if endpoint != "" {
+ v3Client.Endpoint = endpoint
+ }
+
+ result, err := tokens3.Create(v3Client, options, nil)
+ if err != nil {
+ return err
+ }
+
+ client.TokenID, err = result.TokenID()
+ if err != nil {
+ return err
+ }
+ client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+ return v3endpointLocator(v3Client, opts)
+ }
+
+ return nil
+}
+
func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
// Transform URLType into an Interface.
var endpointInterface = endpoints3.InterfacePublic
@@ -233,7 +277,7 @@
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
- v2Endpoint := client.IdentityEndpoint + "v2.0/"
+ v2Endpoint := client.IdentityBase + "v2.0/"
return &gophercloud.ServiceClient{
Provider: client,
@@ -243,7 +287,7 @@
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
- v3Endpoint := client.IdentityEndpoint + "v3/"
+ v3Endpoint := client.IdentityBase + "v3/"
return &gophercloud.ServiceClient{
Provider: client,