2017-01-19 20:10:52 +01:00
|
|
|
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], "/")
|
2017-01-20 02:10:17 +01:00
|
|
|
for index, version := range parts {
|
2017-01-19 20:10:52 +01:00
|
|
|
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
|
2017-01-20 02:10:17 +01:00
|
|
|
if index < len(parts)-1 {
|
2017-01-19 20:10:52 +01:00
|
|
|
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{
|
2017-01-20 02:10:17 +01:00
|
|
|
IdentityBase: base[0 : len(base)-len(version)-1],
|
2017-01-19 20:10:52 +01:00
|
|
|
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 = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 02:10:17 +01:00
|
|
|
result := tokens3.Create(v3Client, tokens3.AuthOptions{AuthOptions: v3Options}, scope)
|
2017-01-19 20:10:52 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2017-01-20 02:10:17 +01:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|