package openstack import ( "fmt" "net/url" "strconv" "strings" "github.com/rackspace/gophercloud" tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens" tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens" "github.com/rackspace/gophercloud/openstack/utils" ) const ( v20 = "v2.0" v30 = "v3.0" ) // NewClient prepares an unauthenticated ProviderClient instance. // Most users will probably prefer using the AuthenticatedClient function instead. // 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 } u.RawQuery, u.Fragment = "", "" // Base is url with path endpoint = gophercloud.NormalizeURL(endpoint) base := gophercloud.NormalizeURL(u.String()) path := u.Path if !strings.HasSuffix(path, "/") { path = path + "/" } parts := strings.Split(path[0:len(path)-1], "/") for index, version := range parts { if 2 <= len(version) && len(version) <= 4 && strings.HasPrefix(version, "v") { _, err := strconv.ParseFloat(version[1:], 64) if err == nil { // post version suffixes in path are not supported // version must be on the last index if index < len(parts)-1 { return nil, fmt.Errorf("Path suffixes (after version) are not supported.") } switch version { case "v2.0", "v3": // valid version found, strip from base return &gophercloud.ProviderClient{ IdentityBase: base[0 : len(base)-len(version)-1], IdentityEndpoint: endpoint, }, nil default: return nil, fmt.Errorf("Invalid identity endpoint version %v. Supported versions: v2.0, v3", version) } } } } 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 // returns a Client instance that's ready to operate. // It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses // the most recent identity service available to proceed. func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { client, err := NewClient(options.IdentityEndpoint) if err != nil { return nil, err } err = Authenticate(client, options) if err != nil { return nil, err } return client, nil } // 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{ {ID: v20, Priority: 20, Suffix: "/v2.0/"}, {ID: v30, Priority: 30, Suffix: "/v3/"}, } chosen, endpoint, err := utils.ChooseVersion(client, versions) if err != nil { return err } switch chosen.ID { case v20: return v2auth(client, endpoint, options) case v30: 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 := tokens2.Create(v2Client, tokens2.AuthOptions{AuthOptions: options}) token, err := result.ExtractToken() if err != nil { return err } catalog, err := result.ExtractServiceCatalog() if err != nil { return err } if options.AllowReauth { client.ReauthFunc = func() error { client.TokenID = "" return v2auth(client, endpoint, options) } } client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V2EndpointURL(catalog, opts) } return nil } // 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 } // copy the auth options to a local variable that we can change. `options` // needs to stay as-is for reauth purposes v3Options := options var scope *tokens3.Scope if options.TenantID != "" { scope = &tokens3.Scope{ ProjectID: options.TenantID, } v3Options.TenantID = "" v3Options.TenantName = "" } else { if options.TenantName != "" { scope = &tokens3.Scope{ ProjectName: options.TenantName, DomainID: options.DomainID, DomainName: options.DomainName, } v3Options.TenantName = "" } } result := tokens3.Create(v3Client, tokens3.AuthOptions{AuthOptions: v3Options}, scope) token, err := result.ExtractToken() if err != nil { return err } catalog, err := result.ExtractServiceCatalog() if err != nil { return err } client.TokenID = token.ID if options.AllowReauth { client.ReauthFunc = func() error { client.TokenID = "" return v3auth(client, endpoint, options) } } client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V3EndpointURL(catalog, opts) } return nil } // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service. func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient { v2Endpoint := client.IdentityBase + "v2.0/" return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: v2Endpoint, } } // NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service. func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient { v3Endpoint := client.IdentityBase + "v3/" return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: v3Endpoint, } } func NewIdentityAdminV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("identity") eo.Availability = gophercloud.AvailabilityAdmin url, err := client.EndpointLocator(eo) if err != nil { return nil, err } // Force using v2 API if strings.Contains(url, "/v3") { url = strings.Replace(url, "/v3", "/v2.0", -1) } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } func NewIdentityAdminV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("identity") eo.Availability = gophercloud.AvailabilityAdmin url, err := client.EndpointLocator(eo) if err != nil { return nil, err } // Force using v3 API if strings.Contains(url, "/v2.0") { url = strings.Replace(url, "/v2.0", "/v3", -1) } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package. func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("object-store") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewComputeV2 creates a ServiceClient that may be used with the v2 compute package. func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("compute") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewNetworkV2 creates a ServiceClient that may be used with the v2 network package. func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("network") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: url, ResourceBase: url + "v2.0/", }, nil } // NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service. func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("volume") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service. func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("volumev2") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // CDN service. func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("cdn") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("orchestration") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("database") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service. func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("image") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url, ResourceBase: url + "v2/"}, nil } // NewTelemetryV2 creates a ServiceClient that may be used to access the v2 telemetry service. func NewTelemetryV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { eo.ApplyDefaults("metering") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil }