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