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 }