Skip to content

Commit bcccae7

Browse files
authored
Add support for lazily authenticating to Vault (hashicorp#2049)
1 parent 6b5cef1 commit bcccae7

31 files changed

+167
-95
lines changed

internal/provider/meta.go

+95-27
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,39 @@ type ProviderMeta struct {
5151
client *api.Client
5252
resourceData *schema.ResourceData
5353
clientCache map[string]*api.Client
54-
m sync.RWMutex
5554
vaultVersion *version.Version
55+
mu sync.RWMutex
5656
}
5757

5858
// GetClient returns the providers default Vault client.
59-
func (p *ProviderMeta) GetClient() *api.Client {
60-
return p.client
59+
func (p *ProviderMeta) GetClient() (*api.Client, error) {
60+
p.mu.Lock()
61+
defer p.mu.Unlock()
62+
63+
return p.getClient()
64+
}
65+
66+
// MustGetClient returns the providers default Vault client. Panics on any error.
67+
func (p *ProviderMeta) MustGetClient() *api.Client {
68+
client, err := p.GetClient()
69+
if err != nil {
70+
panic(err)
71+
}
72+
73+
return client
6174
}
6275

6376
// GetNSClient returns a namespaced Vault client.
6477
// The provided namespace will always be set relative to the default client's
6578
// namespace.
6679
func (p *ProviderMeta) GetNSClient(ns string) (*api.Client, error) {
67-
p.m.Lock()
68-
defer p.m.Unlock()
80+
p.mu.Lock()
81+
defer p.mu.Unlock()
82+
83+
client, err := p.getClient()
84+
if err != nil {
85+
return nil, err
86+
}
6987

7088
if err := p.validate(); err != nil {
7189
return nil, err
@@ -88,7 +106,7 @@ func (p *ProviderMeta) GetNSClient(ns string) (*api.Client, error) {
88106
return v, nil
89107
}
90108

91-
c, err := p.client.Clone()
109+
c, err := client.Clone()
92110
if err != nil {
93111
return nil, err
94112
}
@@ -122,12 +140,21 @@ func (p *ProviderMeta) IsEnterpriseSupported() bool {
122140
if ver == nil {
123141
return false
124142
}
143+
125144
return strings.Contains(ver.Metadata(), enterpriseMetadata)
126145
}
127146

128147
// GetVaultVersion returns the providerMeta
129148
// vaultVersion attribute.
130149
func (p *ProviderMeta) GetVaultVersion() *version.Version {
150+
p.mu.Lock()
151+
defer p.mu.Unlock()
152+
153+
err := p.setVaultVersion()
154+
if err != nil {
155+
return nil
156+
}
157+
131158
return p.vaultVersion
132159
}
133160

@@ -143,12 +170,19 @@ func (p *ProviderMeta) validate() error {
143170
return nil
144171
}
145172

146-
// NewProviderMeta sets up the Provider to service Vault requests.
147-
// It is meant to be used as a schema.ConfigureFunc.
148-
func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
149-
if d == nil {
150-
return nil, fmt.Errorf("nil ResourceData provided")
173+
// setClient sets up an authenticated Vault client based on the
174+
// ProviderMeta.resourceData configuration. It should typically only need to be
175+
// called once per ProviderMeta instance. Must be called with a lock.
176+
func (p *ProviderMeta) setClient() error {
177+
if p.client != nil {
178+
return nil
179+
}
180+
181+
if p.resourceData == nil {
182+
return fmt.Errorf("nil ResourceData provided")
151183
}
184+
185+
d := p.resourceData
152186
clientConfig := api.DefaultConfig()
153187
addr := d.Get(consts.FieldAddress).(string)
154188
if addr != "" {
@@ -175,7 +209,7 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
175209

176210
err := clientConfig.ConfigureTLS(tlsConfig)
177211
if err != nil {
178-
return nil, fmt.Errorf("failed to configure TLS for Vault API: %s", err)
212+
return fmt.Errorf("failed to configure TLS for Vault API: %s", err)
179213
}
180214

181215
clientConfig.HttpClient.Transport = helper.NewTransport(
@@ -192,7 +226,7 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
192226

193227
client, err := api.NewClient(clientConfig)
194228
if err != nil {
195-
return nil, fmt.Errorf("failed to configure Vault API: %s", err)
229+
return fmt.Errorf("failed to configure Vault API: %s", err)
196230
}
197231

198232
// setting this is critical for proper namespace handling
@@ -226,15 +260,15 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
226260

227261
authLogin, err := GetAuthLogin(d)
228262
if err != nil {
229-
return nil, err
263+
return err
230264
}
231265

232266
var token string
233267
if authLogin != nil {
234268
// the clone is only used to auth to Vault
235269
clone, err := client.Clone()
236270
if err != nil {
237-
return nil, err
271+
return err
238272
}
239273

240274
if clone.Token() != "" {
@@ -256,15 +290,15 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
256290

257291
secret, err := authLogin.Login(clone)
258292
if err != nil {
259-
return nil, err
293+
return err
260294
}
261295

262296
token = secret.Auth.ClientToken
263297
} else {
264298
// try and get the token from the config or token helper
265299
token, err = GetToken(d)
266300
if err != nil {
267-
return nil, err
301+
return err
268302
}
269303
}
270304

@@ -273,15 +307,15 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
273307
}
274308

275309
if client.Token() == "" {
276-
return nil, errors.New("no vault token set on Client")
310+
return errors.New("no vault token set on Client")
277311
}
278312

279313
tokenInfo, err := client.Auth().Token().LookupSelf()
280314
if err != nil {
281-
return nil, fmt.Errorf("failed to lookup token, err=%w", err)
315+
return fmt.Errorf("failed to lookup token, err=%w", err)
282316
}
283317
if tokenInfo == nil {
284-
return nil, fmt.Errorf("no token information returned from self lookup")
318+
return fmt.Errorf("no token information returned from self lookup")
285319
}
286320

287321
warnMinTokenTTL(tokenInfo)
@@ -295,7 +329,7 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
295329
// a child token is always created in the namespace of the parent token.
296330
token, err = createChildToken(d, client, tokenNamespace)
297331
if err != nil {
298-
return nil, err
332+
return err
299333
}
300334

301335
client.SetToken(token)
@@ -316,7 +350,7 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
316350
// namespace paths are properly honoured.
317351
if v, ok := d.Get(consts.FieldSetNamespaceFromToken).(bool); ok && v {
318352
if err := d.Set(consts.FieldNamespace, namespace); err != nil {
319-
return nil, err
353+
return err
320354
}
321355
}
322356
}
@@ -326,27 +360,61 @@ func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
326360
client.SetNamespace(namespace)
327361
}
328362

363+
p.client = client
364+
return nil
365+
}
366+
367+
func (p *ProviderMeta) setVaultVersion() error {
368+
if p.vaultVersion != nil {
369+
return nil
370+
}
371+
372+
d := p.resourceData
329373
var vaultVersion *version.Version
330374
if v, ok := d.GetOk(consts.FieldVaultVersionOverride); ok {
331375
ver, err := version.NewVersion(v.(string))
332376
if err != nil {
333-
return nil, fmt.Errorf("invalid value for %q, err=%w",
377+
return fmt.Errorf("invalid value for %q, err=%w",
334378
consts.FieldVaultVersionOverride, err)
335379
}
336380
vaultVersion = ver
337381
} else if !d.Get(consts.FieldSkipGetVaultVersion).(bool) {
338382
// Set the Vault version to *ProviderMeta object
383+
client, err := p.getClient()
384+
if err != nil {
385+
return err
386+
}
387+
339388
ver, err := getVaultVersion(client)
340389
if err != nil {
341-
return nil, err
390+
return err
342391
}
343392
vaultVersion = ver
344393
}
345394

395+
p.vaultVersion = vaultVersion
396+
397+
return nil
398+
}
399+
400+
// getClient returns the provider's default Vault client. Must be called with ProviderMeta.mu
401+
func (p *ProviderMeta) getClient() (*api.Client, error) {
402+
if err := p.setClient(); err != nil {
403+
return nil, err
404+
}
405+
406+
return p.client, nil
407+
}
408+
409+
// NewProviderMeta sets up the Provider to service Vault requests.
410+
// It is meant to be used as a schema.ConfigureFunc.
411+
func NewProviderMeta(d *schema.ResourceData) (interface{}, error) {
412+
if d == nil {
413+
return nil, fmt.Errorf("nil ResourceData provided")
414+
}
415+
346416
return &ProviderMeta{
347417
resourceData: d,
348-
client: client,
349-
vaultVersion: vaultVersion,
350418
}, nil
351419
}
352420

@@ -416,7 +484,7 @@ func GetClient(i interface{}, meta interface{}) (*api.Client, error) {
416484
return p.GetNSClient(ns)
417485
}
418486

419-
return p.GetClient(), nil
487+
return p.GetClient()
420488
}
421489

422490
func GetClientDiag(i interface{}, meta interface{}) (*api.Client, diag.Diagnostics) {

internal/provider/meta_test.go

+31-27
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ func TestProviderMeta_GetNSClient(t *testing.T) {
4040
expectErr error
4141
calls int
4242
}{
43-
{
44-
name: "no-client",
45-
client: nil,
46-
resourceData: &schema.ResourceData{},
47-
wantErr: true,
48-
expectErr: errors.New("root api.Client not set, init with NewProviderMeta()"),
49-
},
5043
{
5144
name: "no-resource-data",
5245
client: &api.Client{},
@@ -368,6 +361,9 @@ func TestGetClient(t *testing.T) {
368361
}
369362

370363
func TestIsAPISupported(t *testing.T) {
364+
testutil.SkipTestAcc(t)
365+
testutil.TestAccPreCheck(t)
366+
371367
rootClient, err := api.NewClient(api.DefaultConfig())
372368
if err != nil {
373369
t.Fatalf("error initializing root client, err=%s", err)
@@ -416,15 +412,6 @@ func TestIsAPISupported(t *testing.T) {
416412
vaultVersion: VaultVersion10,
417413
},
418414
},
419-
{
420-
name: "unsupported-unset",
421-
minVersion: version.Must(version.NewSemver("1.12.0")),
422-
expected: false,
423-
meta: &ProviderMeta{
424-
client: rootClient,
425-
vaultVersion: nil,
426-
},
427-
},
428415
}
429416

430417
for _, tt := range testCases {
@@ -437,6 +424,12 @@ func TestIsAPISupported(t *testing.T) {
437424
Type: schema.TypeString,
438425
Required: true,
439426
},
427+
consts.FieldVaultVersionOverride: {
428+
Type: schema.TypeString,
429+
},
430+
consts.FieldSkipGetVaultVersion: {
431+
Type: schema.TypeBool,
432+
},
440433
},
441434
map[string]interface{}{},
442435
)
@@ -453,6 +446,9 @@ func TestIsAPISupported(t *testing.T) {
453446
}
454447

455448
func TestIsEnterpriseSupported(t *testing.T) {
449+
testutil.SkipTestAcc(t)
450+
testutil.TestAccPreCheck(t)
451+
456452
rootClient, err := api.NewClient(api.DefaultConfig())
457453
if err != nil {
458454
t.Fatalf("error initializing root client, err=%s", err)
@@ -502,14 +498,6 @@ func TestIsEnterpriseSupported(t *testing.T) {
502498
vaultVersion: VaultVersion12,
503499
},
504500
},
505-
{
506-
name: "unsupported unset",
507-
expected: false,
508-
meta: &ProviderMeta{
509-
client: rootClient,
510-
vaultVersion: nil,
511-
},
512-
},
513501
}
514502

515503
for _, tt := range testCases {
@@ -522,6 +510,12 @@ func TestIsEnterpriseSupported(t *testing.T) {
522510
Type: schema.TypeString,
523511
Required: true,
524512
},
513+
consts.FieldVaultVersionOverride: {
514+
Type: schema.TypeString,
515+
},
516+
consts.FieldSkipGetVaultVersion: {
517+
Type: schema.TypeBool,
518+
},
525519
},
526520
map[string]interface{}{},
527521
)
@@ -787,8 +781,13 @@ func TestNewProviderMeta(t *testing.T) {
787781
t.Fatalf("invalid type got %T, expected %T", got, &ProviderMeta{})
788782
}
789783

790-
if !reflect.DeepEqual(p.client.Namespace(), tt.wantNamespace) {
791-
t.Errorf("NewProviderMeta() got ns = %q, want ns %q", p.client.Namespace(), tt.wantNamespace)
784+
client, err := p.GetClient()
785+
if err != nil {
786+
t.Fatalf("got unexpected error %s", err)
787+
}
788+
789+
if !reflect.DeepEqual(client.Namespace(), tt.wantNamespace) {
790+
t.Errorf("NewProviderMeta() got ns = %v, want ns %v", p.client.Namespace(), tt.wantNamespace)
792791
}
793792

794793
if tt.checkSetSetTokenNamespace && tt.wantNamespaceFromToken != tt.d.Get(consts.FieldNamespace).(string) {
@@ -1010,7 +1009,12 @@ func TestNewProviderMeta_Cert(t *testing.T) {
10101009
t.Fatalf("invalid type got %T, expected %T", got, &ProviderMeta{})
10111010
}
10121011

1013-
if !reflect.DeepEqual(p.client.Namespace(), tt.wantNamespace) {
1012+
pClient, err := p.GetClient()
1013+
if err != nil {
1014+
t.Fatalf("got unexpected error %s", err)
1015+
}
1016+
1017+
if !reflect.DeepEqual(pClient.Namespace(), tt.wantNamespace) {
10141018
t.Errorf("NewProviderMeta() got ns = %v, want ns %v", p.client.Namespace(), tt.wantNamespace)
10151019
}
10161020

vault/data_source_kv_secret_v2_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestDataSourceKVV2Secret_deletedSecret(t *testing.T) {
7272
Steps: []resource.TestStep{
7373
{
7474
PreConfig: func() {
75-
client := testProvider.Meta().(*provider.ProviderMeta).GetClient()
75+
client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient()
7676

7777
err := client.Sys().Mount(mount, &api.MountInput{
7878
Type: "kv-v2",

0 commit comments

Comments
 (0)