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 }