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