github.com/gophercloud/gophercloud@v1.11.0/openstack/client.go (about)

     1  package openstack
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/gophercloud/gophercloud"
     9  	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
    10  	"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens"
    11  	"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1"
    12  	tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
    13  	"github.com/gophercloud/gophercloud/openstack/utils"
    14  )
    15  
    16  const (
    17  	// v2 represents Keystone v2.
    18  	// It should never increase beyond 2.0.
    19  	v2 = "v2.0"
    20  
    21  	// v3 represents Keystone v3.
    22  	// The version can be anything from v3 to v3.x.
    23  	v3 = "v3"
    24  )
    25  
    26  /*
    27  NewClient prepares an unauthenticated ProviderClient instance.
    28  Most users will probably prefer using the AuthenticatedClient function
    29  instead.
    30  
    31  This is useful if you wish to explicitly control the version of the identity
    32  service that's used for authentication explicitly, for example.
    33  
    34  A basic example of using this would be:
    35  
    36  	ao, err := openstack.AuthOptionsFromEnv()
    37  	provider, err := openstack.NewClient(ao.IdentityEndpoint)
    38  	client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
    39  */
    40  func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
    41  	base, err := utils.BaseEndpoint(endpoint)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	endpoint = gophercloud.NormalizeURL(endpoint)
    47  	base = gophercloud.NormalizeURL(base)
    48  
    49  	p := new(gophercloud.ProviderClient)
    50  	p.IdentityBase = base
    51  	p.IdentityEndpoint = endpoint
    52  	p.UseTokenLock()
    53  
    54  	return p, nil
    55  }
    56  
    57  /*
    58  AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
    59  specified by the options, acquires a token, and returns a Provider Client
    60  instance that's ready to operate.
    61  
    62  If the full path to a versioned identity endpoint was specified  (example:
    63  http://example.com:5000/v3), that path will be used as the endpoint to query.
    64  
    65  If a versionless endpoint was specified (example: http://example.com:5000/),
    66  the endpoint will be queried to determine which versions of the identity service
    67  are available, then chooses the most recent or most supported version.
    68  
    69  Example:
    70  
    71  	ao, err := openstack.AuthOptionsFromEnv()
    72  	provider, err := openstack.AuthenticatedClient(ao)
    73  	client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
    74  		Region: os.Getenv("OS_REGION_NAME"),
    75  	})
    76  */
    77  func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
    78  	client, err := NewClient(options.IdentityEndpoint)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	err = Authenticate(client, options)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return client, nil
    88  }
    89  
    90  // Authenticate or re-authenticate against the most recent identity service
    91  // supported at the provided endpoint.
    92  func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
    93  	versions := []*utils.Version{
    94  		{ID: v2, Priority: 20, Suffix: "/v2.0/"},
    95  		{ID: v3, Priority: 30, Suffix: "/v3/"},
    96  	}
    97  
    98  	chosen, endpoint, err := utils.ChooseVersion(client, versions)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	switch chosen.ID {
   104  	case v2:
   105  		return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
   106  	case v3:
   107  		return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
   108  	default:
   109  		// The switch statement must be out of date from the versions list.
   110  		return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
   111  	}
   112  }
   113  
   114  // AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
   115  func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
   116  	return v2auth(client, "", options, eo)
   117  }
   118  
   119  func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
   120  	v2Client, err := NewIdentityV2(client, eo)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	if endpoint != "" {
   126  		v2Client.Endpoint = endpoint
   127  	}
   128  
   129  	v2Opts := tokens2.AuthOptions{
   130  		IdentityEndpoint: options.IdentityEndpoint,
   131  		Username:         options.Username,
   132  		Password:         options.Password,
   133  		TenantID:         options.TenantID,
   134  		TenantName:       options.TenantName,
   135  		AllowReauth:      options.AllowReauth,
   136  		TokenID:          options.TokenID,
   137  	}
   138  
   139  	result := tokens2.Create(v2Client, v2Opts)
   140  
   141  	err = client.SetTokenAndAuthResult(result)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	catalog, err := result.ExtractServiceCatalog()
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	if options.AllowReauth {
   152  		// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
   153  		// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
   154  		// this should retry authentication only once
   155  		tac := *client
   156  		tac.SetThrowaway(true)
   157  		tac.ReauthFunc = nil
   158  		tac.SetTokenAndAuthResult(nil)
   159  		tao := options
   160  		tao.AllowReauth = false
   161  		client.ReauthFunc = func() error {
   162  			err := v2auth(&tac, endpoint, tao, eo)
   163  			if err != nil {
   164  				return err
   165  			}
   166  			client.CopyTokenFrom(&tac)
   167  			return nil
   168  		}
   169  	}
   170  	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
   171  		return V2EndpointURL(catalog, opts)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  // AuthenticateV3 explicitly authenticates against the identity v3 service.
   178  func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
   179  	return v3auth(client, "", options, eo)
   180  }
   181  
   182  func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
   183  	// Override the generated service endpoint with the one returned by the version endpoint.
   184  	v3Client, err := NewIdentityV3(client, eo)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	if endpoint != "" {
   190  		v3Client.Endpoint = endpoint
   191  	}
   192  
   193  	var catalog *tokens3.ServiceCatalog
   194  
   195  	var tokenID string
   196  	// passthroughToken allows to passthrough the token without a scope
   197  	var passthroughToken bool
   198  	switch v := opts.(type) {
   199  	case *gophercloud.AuthOptions:
   200  		tokenID = v.TokenID
   201  		passthroughToken = (v.Scope == nil || *v.Scope == gophercloud.AuthScope{})
   202  	case *tokens3.AuthOptions:
   203  		tokenID = v.TokenID
   204  		passthroughToken = (v.Scope == tokens3.Scope{})
   205  	}
   206  
   207  	if tokenID != "" && passthroughToken {
   208  		// passing through the token ID without requesting a new scope
   209  		if opts.CanReauth() {
   210  			return fmt.Errorf("cannot use AllowReauth, when the token ID is defined and auth scope is not set")
   211  		}
   212  
   213  		v3Client.SetToken(tokenID)
   214  		result := tokens3.Get(v3Client, tokenID)
   215  		if result.Err != nil {
   216  			return result.Err
   217  		}
   218  
   219  		err = client.SetTokenAndAuthResult(result)
   220  		if err != nil {
   221  			return err
   222  		}
   223  
   224  		catalog, err = result.ExtractServiceCatalog()
   225  		if err != nil {
   226  			return err
   227  		}
   228  	} else {
   229  		var result tokens3.CreateResult
   230  		switch opts.(type) {
   231  		case *ec2tokens.AuthOptions:
   232  			result = ec2tokens.Create(v3Client, opts)
   233  		case *oauth1.AuthOptions:
   234  			result = oauth1.Create(v3Client, opts)
   235  		default:
   236  			result = tokens3.Create(v3Client, opts)
   237  		}
   238  
   239  		err = client.SetTokenAndAuthResult(result)
   240  		if err != nil {
   241  			return err
   242  		}
   243  
   244  		catalog, err = result.ExtractServiceCatalog()
   245  		if err != nil {
   246  			return err
   247  		}
   248  	}
   249  
   250  	if opts.CanReauth() {
   251  		// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
   252  		// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
   253  		// this should retry authentication only once
   254  		tac := *client
   255  		tac.SetThrowaway(true)
   256  		tac.ReauthFunc = nil
   257  		tac.SetTokenAndAuthResult(nil)
   258  		var tao tokens3.AuthOptionsBuilder
   259  		switch ot := opts.(type) {
   260  		case *gophercloud.AuthOptions:
   261  			o := *ot
   262  			o.AllowReauth = false
   263  			tao = &o
   264  		case *tokens3.AuthOptions:
   265  			o := *ot
   266  			o.AllowReauth = false
   267  			tao = &o
   268  		case *ec2tokens.AuthOptions:
   269  			o := *ot
   270  			o.AllowReauth = false
   271  			tao = &o
   272  		case *oauth1.AuthOptions:
   273  			o := *ot
   274  			o.AllowReauth = false
   275  			tao = &o
   276  		default:
   277  			tao = opts
   278  		}
   279  		client.ReauthFunc = func() error {
   280  			err := v3auth(&tac, endpoint, tao, eo)
   281  			if err != nil {
   282  				return err
   283  			}
   284  			client.CopyTokenFrom(&tac)
   285  			return nil
   286  		}
   287  	}
   288  	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
   289  		return V3EndpointURL(catalog, opts)
   290  	}
   291  
   292  	return nil
   293  }
   294  
   295  // NewIdentityV2 creates a ServiceClient that may be used to interact with the
   296  // v2 identity service.
   297  func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   298  	endpoint := client.IdentityBase + "v2.0/"
   299  	clientType := "identity"
   300  	var err error
   301  	if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
   302  		eo.ApplyDefaults(clientType)
   303  		endpoint, err = client.EndpointLocator(eo)
   304  		if err != nil {
   305  			return nil, err
   306  		}
   307  	}
   308  
   309  	return &gophercloud.ServiceClient{
   310  		ProviderClient: client,
   311  		Endpoint:       endpoint,
   312  		Type:           clientType,
   313  	}, nil
   314  }
   315  
   316  // NewIdentityV3 creates a ServiceClient that may be used to access the v3
   317  // identity service.
   318  func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   319  	endpoint := client.IdentityBase + "v3/"
   320  	clientType := "identity"
   321  	var err error
   322  	if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
   323  		eo.ApplyDefaults(clientType)
   324  		endpoint, err = client.EndpointLocator(eo)
   325  		if err != nil {
   326  			return nil, err
   327  		}
   328  	}
   329  
   330  	// Ensure endpoint still has a suffix of v3.
   331  	// This is because EndpointLocator might have found a versionless
   332  	// endpoint or the published endpoint is still /v2.0. In both
   333  	// cases, we need to fix the endpoint to point to /v3.
   334  	base, err := utils.BaseEndpoint(endpoint)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	base = gophercloud.NormalizeURL(base)
   340  
   341  	endpoint = base + "v3/"
   342  
   343  	return &gophercloud.ServiceClient{
   344  		ProviderClient: client,
   345  		Endpoint:       endpoint,
   346  		Type:           clientType,
   347  	}, nil
   348  }
   349  
   350  func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
   351  	sc := new(gophercloud.ServiceClient)
   352  	eo.ApplyDefaults(clientType)
   353  	url, err := client.EndpointLocator(eo)
   354  	if err != nil {
   355  		return sc, err
   356  	}
   357  	sc.ProviderClient = client
   358  	sc.Endpoint = url
   359  	sc.Type = clientType
   360  	return sc, nil
   361  }
   362  
   363  // NewBareMetalV1 creates a ServiceClient that may be used with the v1
   364  // bare metal package.
   365  func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   366  	return initClientOpts(client, eo, "baremetal")
   367  }
   368  
   369  // NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1
   370  // bare metal introspection package.
   371  func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   372  	return initClientOpts(client, eo, "baremetal-introspection")
   373  }
   374  
   375  // NewObjectStorageV1 creates a ServiceClient that may be used with the v1
   376  // object storage package.
   377  func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   378  	return initClientOpts(client, eo, "object-store")
   379  }
   380  
   381  // NewComputeV2 creates a ServiceClient that may be used with the v2 compute
   382  // package.
   383  func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   384  	return initClientOpts(client, eo, "compute")
   385  }
   386  
   387  // NewNetworkV2 creates a ServiceClient that may be used with the v2 network
   388  // package.
   389  func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   390  	sc, err := initClientOpts(client, eo, "network")
   391  	sc.ResourceBase = sc.Endpoint + "v2.0/"
   392  	return sc, err
   393  }
   394  
   395  // NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
   396  // block storage service.
   397  func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   398  	return initClientOpts(client, eo, "volume")
   399  }
   400  
   401  // NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
   402  // block storage service.
   403  func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   404  	return initClientOpts(client, eo, "volumev2")
   405  }
   406  
   407  // NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
   408  func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   409  	return initClientOpts(client, eo, "volumev3")
   410  }
   411  
   412  // NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
   413  func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   414  	return initClientOpts(client, eo, "sharev2")
   415  }
   416  
   417  // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
   418  // CDN service.
   419  func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   420  	return initClientOpts(client, eo, "cdn")
   421  }
   422  
   423  // NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
   424  // orchestration service.
   425  func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   426  	return initClientOpts(client, eo, "orchestration")
   427  }
   428  
   429  // NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
   430  func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   431  	return initClientOpts(client, eo, "database")
   432  }
   433  
   434  // NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
   435  // service.
   436  func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   437  	sc, err := initClientOpts(client, eo, "dns")
   438  	sc.ResourceBase = sc.Endpoint + "v2/"
   439  	return sc, err
   440  }
   441  
   442  // NewImageServiceV2 creates a ServiceClient that may be used to access the v2
   443  // image service.
   444  func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   445  	sc, err := initClientOpts(client, eo, "image")
   446  	sc.ResourceBase = sc.Endpoint + "v2/"
   447  	return sc, err
   448  }
   449  
   450  // NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
   451  // load balancer service.
   452  func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   453  	sc, err := initClientOpts(client, eo, "load-balancer")
   454  
   455  	// Fixes edge case having an OpenStack lb endpoint with trailing version number.
   456  	endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1)
   457  
   458  	sc.ResourceBase = endpoint + "v2.0/"
   459  	return sc, err
   460  }
   461  
   462  // NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering
   463  // package.
   464  func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   465  	return initClientOpts(client, eo, "clustering")
   466  }
   467  
   468  // NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
   469  // service.
   470  func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   471  	sc, err := initClientOpts(client, eo, "messaging")
   472  	sc.MoreHeaders = map[string]string{"Client-ID": clientID}
   473  	return sc, err
   474  }
   475  
   476  // NewContainerV1 creates a ServiceClient that may be used with v1 container package
   477  func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   478  	return initClientOpts(client, eo, "container")
   479  }
   480  
   481  // NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
   482  // manager service.
   483  func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   484  	sc, err := initClientOpts(client, eo, "key-manager")
   485  	sc.ResourceBase = sc.Endpoint + "v1/"
   486  	return sc, err
   487  }
   488  
   489  // NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
   490  // package.
   491  func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   492  	return initClientOpts(client, eo, "container-infra")
   493  }
   494  
   495  // NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
   496  func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   497  	return initClientOpts(client, eo, "workflowv2")
   498  }
   499  
   500  // NewPlacementV1 creates a ServiceClient that may be used with the placement package.
   501  func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
   502  	return initClientOpts(client, eo, "placement")
   503  }