427 lines
14 KiB
Go
427 lines
14 KiB
Go
package openstack
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/gophercloud/gophercloud"
|
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
|
)
|
|
|
|
const (
|
|
// v2 represents Keystone v2.
|
|
// It should never increase beyond 2.0.
|
|
v2 = "v2.0"
|
|
|
|
// v3 represents Keystone v3.
|
|
// The version can be anything from v3 to v3.x.
|
|
v3 = "v3"
|
|
)
|
|
|
|
/*
|
|
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.
|
|
|
|
A basic example of using this would be:
|
|
|
|
ao, err := openstack.AuthOptionsFromEnv()
|
|
provider, err := openstack.NewClient(ao.IdentityEndpoint)
|
|
client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
|
|
*/
|
|
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
|
|
base, err := utils.BaseEndpoint(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
endpoint = gophercloud.NormalizeURL(endpoint)
|
|
base = gophercloud.NormalizeURL(base)
|
|
|
|
p := new(gophercloud.ProviderClient)
|
|
p.IdentityBase = base
|
|
p.IdentityEndpoint = endpoint
|
|
p.UseTokenLock()
|
|
|
|
return p, nil
|
|
}
|
|
|
|
/*
|
|
AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
|
|
specified by the options, acquires a token, and returns a Provider Client
|
|
instance that's ready to operate.
|
|
|
|
If the full path to a versioned identity endpoint was specified (example:
|
|
http://example.com:5000/v3), that path will be used as the endpoint to query.
|
|
|
|
If a versionless endpoint was specified (example: http://example.com:5000/),
|
|
the endpoint will be queried to determine which versions of the identity service
|
|
are available, then chooses the most recent or most supported version.
|
|
|
|
Example:
|
|
|
|
ao, err := openstack.AuthOptionsFromEnv()
|
|
provider, err := openstack.AuthenticatedClient(ao)
|
|
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
|
|
Region: os.Getenv("OS_REGION_NAME"),
|
|
})
|
|
*/
|
|
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: v2, Priority: 20, Suffix: "/v2.0/"},
|
|
{ID: v3, Priority: 30, Suffix: "/v3/"},
|
|
}
|
|
|
|
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch chosen.ID {
|
|
case v2:
|
|
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
|
|
case v3:
|
|
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
|
|
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, eo gophercloud.EndpointOpts) error {
|
|
return v2auth(client, "", options, eo)
|
|
}
|
|
|
|
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
|
v2Client, err := NewIdentityV2(client, eo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if endpoint != "" {
|
|
v2Client.Endpoint = endpoint
|
|
}
|
|
|
|
v2Opts := tokens2.AuthOptions{
|
|
IdentityEndpoint: options.IdentityEndpoint,
|
|
Username: options.Username,
|
|
Password: options.Password,
|
|
TenantID: options.TenantID,
|
|
TenantName: options.TenantName,
|
|
AllowReauth: options.AllowReauth,
|
|
TokenID: options.TokenID,
|
|
}
|
|
|
|
result := tokens2.Create(v2Client, v2Opts)
|
|
|
|
err = client.SetTokenAndAuthResult(result)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
catalog, err := result.ExtractServiceCatalog()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if options.AllowReauth {
|
|
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
|
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
|
|
// this should retry authentication only once
|
|
tac := *client
|
|
tac.SetThrowaway(true)
|
|
tac.ReauthFunc = nil
|
|
tac.SetTokenAndAuthResult(nil)
|
|
tao := options
|
|
tao.AllowReauth = false
|
|
client.ReauthFunc = func() error {
|
|
err := v2auth(&tac, endpoint, tao, eo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client.CopyTokenFrom(&tac)
|
|
return nil
|
|
}
|
|
}
|
|
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 tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
|
return v3auth(client, "", options, eo)
|
|
}
|
|
|
|
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
|
// Override the generated service endpoint with the one returned by the version endpoint.
|
|
v3Client, err := NewIdentityV3(client, eo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if endpoint != "" {
|
|
v3Client.Endpoint = endpoint
|
|
}
|
|
|
|
result := tokens3.Create(v3Client, opts)
|
|
|
|
err = client.SetTokenAndAuthResult(result)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
catalog, err := result.ExtractServiceCatalog()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.CanReauth() {
|
|
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
|
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
|
|
// this should retry authentication only once
|
|
tac := *client
|
|
tac.SetThrowaway(true)
|
|
tac.ReauthFunc = nil
|
|
tac.SetTokenAndAuthResult(nil)
|
|
var tao tokens3.AuthOptionsBuilder
|
|
switch ot := opts.(type) {
|
|
case *gophercloud.AuthOptions:
|
|
o := *ot
|
|
o.AllowReauth = false
|
|
tao = &o
|
|
case *tokens3.AuthOptions:
|
|
o := *ot
|
|
o.AllowReauth = false
|
|
tao = &o
|
|
default:
|
|
tao = opts
|
|
}
|
|
client.ReauthFunc = func() error {
|
|
err := v3auth(&tac, endpoint, tao, eo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client.CopyTokenFrom(&tac)
|
|
return nil
|
|
}
|
|
}
|
|
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, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
endpoint := client.IdentityBase + "v2.0/"
|
|
clientType := "identity"
|
|
var err error
|
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
|
eo.ApplyDefaults(clientType)
|
|
endpoint, err = client.EndpointLocator(eo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &gophercloud.ServiceClient{
|
|
ProviderClient: client,
|
|
Endpoint: endpoint,
|
|
Type: clientType,
|
|
}, nil
|
|
}
|
|
|
|
// NewIdentityV3 creates a ServiceClient that may be used to access the v3
|
|
// identity service.
|
|
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
endpoint := client.IdentityBase + "v3/"
|
|
clientType := "identity"
|
|
var err error
|
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
|
eo.ApplyDefaults(clientType)
|
|
endpoint, err = client.EndpointLocator(eo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Ensure endpoint still has a suffix of v3.
|
|
// This is because EndpointLocator might have found a versionless
|
|
// endpoint or the published endpoint is still /v2.0. In both
|
|
// cases, we need to fix the endpoint to point to /v3.
|
|
base, err := utils.BaseEndpoint(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base = gophercloud.NormalizeURL(base)
|
|
|
|
endpoint = base + "v3/"
|
|
|
|
return &gophercloud.ServiceClient{
|
|
ProviderClient: client,
|
|
Endpoint: endpoint,
|
|
Type: clientType,
|
|
}, nil
|
|
}
|
|
|
|
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
|
|
sc := new(gophercloud.ServiceClient)
|
|
eo.ApplyDefaults(clientType)
|
|
url, err := client.EndpointLocator(eo)
|
|
if err != nil {
|
|
return sc, err
|
|
}
|
|
sc.ProviderClient = client
|
|
sc.Endpoint = url
|
|
sc.Type = clientType
|
|
return sc, 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) {
|
|
return initClientOpts(client, eo, "object-store")
|
|
}
|
|
|
|
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute
|
|
// package.
|
|
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "compute")
|
|
}
|
|
|
|
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network
|
|
// package.
|
|
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
sc, err := initClientOpts(client, eo, "network")
|
|
sc.ResourceBase = sc.Endpoint + "v2.0/"
|
|
return sc, err
|
|
}
|
|
|
|
// 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) {
|
|
return initClientOpts(client, eo, "volume")
|
|
}
|
|
|
|
// 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) {
|
|
return initClientOpts(client, eo, "volumev2")
|
|
}
|
|
|
|
// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
|
|
func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "volumev3")
|
|
}
|
|
|
|
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
|
|
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "sharev2")
|
|
}
|
|
|
|
// 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) {
|
|
return initClientOpts(client, eo, "cdn")
|
|
}
|
|
|
|
// 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) {
|
|
return initClientOpts(client, eo, "orchestration")
|
|
}
|
|
|
|
// 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) {
|
|
return initClientOpts(client, eo, "database")
|
|
}
|
|
|
|
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
|
|
// service.
|
|
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
sc, err := initClientOpts(client, eo, "dns")
|
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
|
return sc, err
|
|
}
|
|
|
|
// 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) {
|
|
sc, err := initClientOpts(client, eo, "image")
|
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
|
return sc, err
|
|
}
|
|
|
|
// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
|
|
// load balancer service.
|
|
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
sc, err := initClientOpts(client, eo, "load-balancer")
|
|
sc.ResourceBase = sc.Endpoint + "v2.0/"
|
|
return sc, err
|
|
}
|
|
|
|
// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering
|
|
// package.
|
|
func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "clustering")
|
|
}
|
|
|
|
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
|
|
// service.
|
|
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
sc, err := initClientOpts(client, eo, "messaging")
|
|
sc.MoreHeaders = map[string]string{"Client-ID": clientID}
|
|
return sc, err
|
|
}
|
|
|
|
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
|
|
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "container")
|
|
}
|
|
|
|
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
|
|
// manager service.
|
|
func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
sc, err := initClientOpts(client, eo, "key-manager")
|
|
sc.ResourceBase = sc.Endpoint + "v1/"
|
|
return sc, err
|
|
}
|
|
|
|
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
|
|
// package.
|
|
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "container-infra")
|
|
}
|
|
|
|
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
|
|
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
return initClientOpts(client, eo, "workflowv2")
|
|
}
|