github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/openstack/client.go (about)

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