• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gophercloud / gophercloud / 13809935483

12 Mar 2025 11:11AM UTC coverage: 78.687% (-0.05%) from 78.732%
13809935483

push

github

web-flow
Merge pull request #3327 from kayrus/backport-aliases

Merge pull request #3209 from shiftstack/proper-service-discovery

8 of 35 new or added lines in 5 files covered. (22.86%)

1 existing line in 1 file now uncovered.

21620 of 27476 relevant lines covered (78.69%)

58.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.56
/openstack/client.go
1
package openstack
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7
        "strings"
8

9
        "github.com/gophercloud/gophercloud/v2"
10
        tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens"
11
        "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens"
12
        "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1"
13
        tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens"
14
        "github.com/gophercloud/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) {
10✔
40
        base, err := utils.BaseEndpoint(endpoint)
10✔
41
        if err != nil {
10✔
42
                return nil, err
×
43
        }
×
44

45
        endpoint = gophercloud.NormalizeURL(endpoint)
10✔
46
        base = gophercloud.NormalizeURL(base)
10✔
47

10✔
48
        p := new(gophercloud.ProviderClient)
10✔
49
        p.IdentityBase = base
10✔
50
        p.IdentityEndpoint = endpoint
10✔
51
        p.UseTokenLock()
10✔
52

10✔
53
        return p, nil
10✔
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) {
10✔
75
        client, err := NewClient(options.IdentityEndpoint)
10✔
76
        if err != nil {
10✔
77
                return nil, err
×
78
        }
×
79

80
        err = Authenticate(ctx, client, options)
10✔
81
        if err != nil {
14✔
82
                return nil, err
4✔
83
        }
4✔
84
        return client, nil
6✔
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 {
10✔
90
        versions := []*utils.Version{
10✔
91
                {ID: v2, Priority: 20, Suffix: "/v2.0/"},
10✔
92
                {ID: v3, Priority: 30, Suffix: "/v3/"},
10✔
93
        }
10✔
94

10✔
95
        chosen, endpoint, err := utils.ChooseVersion(ctx, client, versions)
10✔
96
        if err != nil {
10✔
97
                return err
×
98
        }
×
99

100
        switch chosen.ID {
10✔
101
        case v2:
4✔
102
                return v2auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{})
4✔
103
        case v3:
6✔
104
                return v3auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{})
6✔
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 {
4✔
123
        v2Client, err := NewIdentityV2(client, eo)
4✔
124
        if err != nil {
4✔
125
                return err
×
126
        }
×
127

128
        if endpoint != "" {
8✔
129
                v2Client.Endpoint = endpoint
4✔
130
        }
4✔
131

132
        result := tokens2.Create(ctx, v2Client, options)
4✔
133

4✔
134
        err = client.SetTokenAndAuthResult(result)
4✔
135
        if err != nil {
6✔
136
                return err
2✔
137
        }
2✔
138

139
        catalog, err := result.ExtractServiceCatalog()
2✔
140
        if err != nil {
2✔
141
                return err
×
142
        }
×
143

144
        if options.CanReauth() {
2✔
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) {
2✔
165
                return V2EndpointURL(catalog, opts)
×
166
        }
×
167

168
        return nil
2✔
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 {
6✔
177
        // Override the generated service endpoint with the one returned by the version endpoint.
6✔
178
        v3Client, err := NewIdentityV3(client, eo)
6✔
179
        if err != nil {
6✔
180
                return err
×
181
        }
×
182

183
        if endpoint != "" {
12✔
184
                v3Client.Endpoint = endpoint
6✔
185
        }
6✔
186

187
        var catalog *tokens3.ServiceCatalog
6✔
188

6✔
189
        var tokenID string
6✔
190
        // passthroughToken allows to passthrough the token without a scope
6✔
191
        var passthroughToken bool
6✔
192
        switch v := opts.(type) {
6✔
193
        case *gophercloud.AuthOptions:
6✔
194
                tokenID = v.TokenID
6✔
195
                passthroughToken = (v.Scope == nil || *v.Scope == gophercloud.AuthScope{})
6✔
196
        case *tokens3.AuthOptions:
×
197
                tokenID = v.TokenID
×
198
                passthroughToken = (v.Scope == tokens3.Scope{})
×
199
        }
200

201
        if tokenID != "" && passthroughToken {
6✔
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 {
6✔
223
                var result tokens3.CreateResult
6✔
224
                switch opts.(type) {
6✔
225
                case *ec2tokens.AuthOptions:
×
226
                        result = ec2tokens.Create(ctx, v3Client, opts)
×
227
                case *oauth1.AuthOptions:
×
228
                        result = oauth1.Create(ctx, v3Client, opts)
×
229
                default:
6✔
230
                        result = tokens3.Create(ctx, v3Client, opts)
6✔
231
                }
232

233
                err = client.SetTokenAndAuthResult(result)
6✔
234
                if err != nil {
8✔
235
                        return err
2✔
236
                }
2✔
237

238
                catalog, err = result.ExtractServiceCatalog()
4✔
239
                if err != nil {
4✔
240
                        return err
×
241
                }
×
242
        }
243

244
        if opts.CanReauth() {
4✔
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) {
6✔
286
                return V3EndpointURL(catalog, opts)
2✔
287
        }
2✔
288

289
        return nil
4✔
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) {
4✔
295
        endpoint := client.IdentityBase + "v2.0/"
4✔
296
        clientType := "identity"
4✔
297
        var err error
4✔
298
        if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
4✔
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{
4✔
307
                ProviderClient: client,
4✔
308
                Endpoint:       endpoint,
4✔
309
                Type:           clientType,
4✔
310
        }, nil
4✔
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) {
8✔
316
        endpoint := client.IdentityBase + "v3/"
8✔
317
        clientType := "identity"
8✔
318
        var err error
8✔
319
        if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
10✔
320
                eo.ApplyDefaults(clientType)
2✔
321
                endpoint, err = client.EndpointLocator(eo)
2✔
322
                if err != nil {
2✔
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)
8✔
332
        if err != nil {
8✔
333
                return nil, err
×
334
        }
×
335

336
        base = gophercloud.NormalizeURL(base)
8✔
337

8✔
338
        endpoint = base + "v3/"
8✔
339

8✔
340
        return &gophercloud.ServiceClient{
8✔
341
                ProviderClient: client,
8✔
342
                Endpoint:       endpoint,
8✔
343
                Type:           clientType,
8✔
344
        }, nil
8✔
345
}
346

347
// TODO(stephenfin): Allow passing aliases to all New${SERVICE}V${VERSION} methods in v3
348
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
×
349
        sc := new(gophercloud.ServiceClient)
×
350
        eo.ApplyDefaults(clientType)
×
351
        url, err := client.EndpointLocator(eo)
×
352
        if err != nil {
×
353
                return sc, err
×
354
        }
×
355
        sc.ProviderClient = client
×
356
        sc.Endpoint = url
×
357
        sc.Type = clientType
×
358
        return sc, nil
×
359
}
360

361
// NewBareMetalV1 creates a ServiceClient that may be used with the v1
362
// bare metal package.
363
func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
364
        sc, err := initClientOpts(client, eo, "baremetal")
×
365
        if !strings.HasSuffix(strings.TrimSuffix(sc.Endpoint, "/"), "v1") {
×
366
                sc.ResourceBase = sc.Endpoint + "v1/"
×
367
        }
×
368
        return sc, err
×
369
}
370

371
// NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1
372
// bare metal introspection package.
373
func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
374
        return initClientOpts(client, eo, "baremetal-introspection")
×
375
}
×
376

377
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1
378
// object storage package.
379
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
380
        return initClientOpts(client, eo, "object-store")
×
381
}
×
382

383
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute
384
// package.
385
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
386
        return initClientOpts(client, eo, "compute")
×
387
}
×
388

389
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network
390
// package.
391
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
392
        sc, err := initClientOpts(client, eo, "network")
×
393
        sc.ResourceBase = sc.Endpoint + "v2.0/"
×
394
        return sc, err
×
395
}
×
396

397
// TODO(stephenfin): Remove this in v3. We no longer support the V1 Block Storage service.
398
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
399
// block storage service.
400
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
401
        return initClientOpts(client, eo, "volume")
×
402
}
×
403

404
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
405
// block storage service.
406
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
407
        return initClientOpts(client, eo, "block-storage")
×
408
}
×
409

410
// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
411
func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
412
        return initClientOpts(client, eo, "block-storage")
×
413
}
×
414

415
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
416
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
417
        return initClientOpts(client, eo, "shared-file-system")
×
418
}
×
419

420
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
421
// orchestration service.
422
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
423
        return initClientOpts(client, eo, "orchestration")
×
424
}
×
425

426
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
427
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
428
        return initClientOpts(client, eo, "database")
×
429
}
×
430

431
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
432
// service.
433
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
434
        sc, err := initClientOpts(client, eo, "dns")
×
435
        sc.ResourceBase = sc.Endpoint + "v2/"
×
436
        return sc, err
×
437
}
×
438

439
// NewImageV2 creates a ServiceClient that may be used to access the v2 image
440
// service.
441
func NewImageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
442
        sc, err := initClientOpts(client, eo, "image")
×
443
        sc.ResourceBase = sc.Endpoint + "v2/"
×
444
        return sc, err
×
445
}
×
446

447
// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
448
// load balancer service.
449
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
450
        sc, err := initClientOpts(client, eo, "load-balancer")
×
451

×
452
        // Fixes edge case having an OpenStack lb endpoint with trailing version number.
×
453
        endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1)
×
454

×
455
        sc.ResourceBase = endpoint + "v2.0/"
×
456
        return sc, err
×
457
}
×
458

459
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
460
// service.
461
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
462
        sc, err := initClientOpts(client, eo, "message")
×
463
        sc.MoreHeaders = map[string]string{"Client-ID": clientID}
×
464
        return sc, err
×
465
}
×
466

467
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
468
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
469
        return initClientOpts(client, eo, "application-container")
×
470
}
×
471

472
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
473
// manager service.
474
func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
475
        sc, err := initClientOpts(client, eo, "key-manager")
×
476
        sc.ResourceBase = sc.Endpoint + "v1/"
×
477
        return sc, err
×
478
}
×
479

480
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
481
// package.
482
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
483
        return initClientOpts(client, eo, "container-infrastructure-management")
×
484
}
×
485

486
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
487
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
NEW
488
        return initClientOpts(client, eo, "workflow")
×
489
}
×
490

491
// NewPlacementV1 creates a ServiceClient that may be used with the placement package.
492
func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
×
493
        return initClientOpts(client, eo, "placement")
×
494
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc