Skip to content

Commit 7afd97a

Browse files
authored
allow late config binding (dynamic_config flag) (#58)
* allow late config binding (dynamic_config flag) * bump gocmlclient to 0.0.19
1 parent 6760f2a commit 7afd97a

File tree

8 files changed

+129
-89
lines changed

8 files changed

+129
-89
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
Lists the changes in the provider.
44

5+
## Version 0.6.1
6+
7+
- allow dynamic configuration of the provider by introducing a "dynamic_config"
8+
provider config flag. This defaults to `false`. When set to `true` then the
9+
provider configuration is only validated when actual resources are read or
10+
created. This is for specific use cases like AWS deployments where the CML2
11+
instance IP is only known after the EC2 instance has been created.
12+
- bump the gocmlclient to 0.0.18
13+
514
## Version 0.6.0
615

716
- allow empty node configurations (fixes #50)

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ provider "cml2" {
6262
### Optional
6363

6464
- `cacert` (String) A CA CERT, PEM encoded. When provided, the controller cert will be checked against it. Otherwise, the system trust anchors will be used.
65+
- `dynamic_config` (Boolean) Does late binding of the provider configuration. If set to `true` then provider configuration errors will only be caught when resources and data sources are actually created/read. Defaults to `false`
6566
- `password` (String, Sensitive) CML2 password.
6667
- `skip_verify` (Boolean) Disables TLS certificate verification (default is false -- will not skip / it will verify the certificate!)
6768
- `token` (String, Sensitive) CML2 API token (JWT).

go.mod

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/hashicorp/terraform-plugin-go v0.15.0
1010
github.com/hashicorp/terraform-plugin-log v0.8.0
1111
github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1
12-
github.com/rschmied/gocmlclient v0.0.17
12+
github.com/rschmied/gocmlclient v0.0.19
1313
github.com/stretchr/testify v1.8.2
1414
)
1515

@@ -58,7 +58,6 @@ require (
5858
github.com/russross/blackfriday v1.6.0 // indirect
5959
github.com/shopspring/decimal v1.3.1 // indirect
6060
github.com/spf13/cast v1.5.0 // indirect
61-
github.com/stretchr/objx v0.5.0 // indirect
6261
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
6362
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
6463
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect

go.sum

+2-4
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
146146
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
147147
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
148148
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
149-
github.com/rschmied/gocmlclient v0.0.16 h1:dWlXb4IJfwyuEqfI1/8RbebKHe+CkBEBsthq61NI6pY=
150-
github.com/rschmied/gocmlclient v0.0.16/go.mod h1:j5oDyPNNiM/Q1rOE3IKqTJW2lt5ANDcGPolWyr9ddMc=
151-
github.com/rschmied/gocmlclient v0.0.17 h1:I2G6jo4O6gkhME19QIIgzuE0WE25P3NFyOKWxGQPFuM=
152-
github.com/rschmied/gocmlclient v0.0.17/go.mod h1:AxuKqPKsWwqRtThJk5Ozi8B2Hp7/dR/A94zDq3gLQ90=
149+
github.com/rschmied/gocmlclient v0.0.19 h1:mrC2boo67juuzJPCinmV9EIu8Dlwm9n0vii3vQUePhQ=
150+
github.com/rschmied/gocmlclient v0.0.19/go.mod h1:AxuKqPKsWwqRtThJk5Ozi8B2Hp7/dR/A94zDq3gLQ90=
153151
github.com/rschmied/mockresponder v1.0.4 h1:VFXa9Y9QJ/5oZFhKoqh9u3HQlbjcBfE9pxI+BanMlEs=
154152
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
155153
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=

internal/cmlschema/provider.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import (
77

88
// ProviderModel describes the provider configuration data model.
99
type ProviderModel struct {
10-
Address types.String `tfsdk:"address"`
11-
Username types.String `tfsdk:"username"`
12-
Password types.String `tfsdk:"password"`
13-
Token types.String `tfsdk:"token"`
14-
CAcert types.String `tfsdk:"cacert"`
15-
SkipVerify types.Bool `tfsdk:"skip_verify"`
16-
UseCache types.Bool `tfsdk:"use_cache"`
10+
Address types.String `tfsdk:"address"`
11+
Username types.String `tfsdk:"username"`
12+
Password types.String `tfsdk:"password"`
13+
Token types.String `tfsdk:"token"`
14+
CAcert types.String `tfsdk:"cacert"`
15+
SkipVerify types.Bool `tfsdk:"skip_verify"`
16+
UseCache types.Bool `tfsdk:"use_cache"`
17+
DynamicConfig types.Bool `tfsdk:"dynamic_config"`
1718
}
1819

1920
func Provider() map[string]schema.Attribute {
@@ -49,5 +50,9 @@ func Provider() map[string]schema.Attribute {
4950
Description: "Enables the client cache, this is considered experimental (default is false -- will not use the cache!)",
5051
Optional: true,
5152
},
53+
"dynamic_config": schema.BoolAttribute{
54+
MarkdownDescription: "Does late binding of the provider configuration. If set to `true` then provider configuration errors will only be caught when resources and data sources are actually created/read. Defaults to `false`",
55+
Optional: true,
56+
},
5257
}
5358
}

internal/cmlschema/provider_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestProviderAttrs(t *testing.T) {
1818

1919
got, diag := schema.TypeAtPath(context.TODO(), path.Root("address"))
2020
assert.Equal(t, types.StringType, got)
21-
assert.Equal(t, 7, len(schema.Attributes))
21+
assert.Equal(t, 8, len(schema.Attributes))
2222
assert.False(t, diag.HasError())
2323
t.Log(diag.Errors())
2424
}

internal/common/configure.go

+91-4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ import (
66
"sync"
77

88
"github.com/hashicorp/terraform-plugin-framework/datasource"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/hashicorp/terraform-plugin-log/tflog"
1013
cmlclient "github.com/rschmied/gocmlclient"
14+
"github.com/rschmied/terraform-provider-cml2/internal/cmlschema"
1115
)
1216

1317
type ProviderConfig struct {
1418
client *cmlclient.Client
19+
data *cmlschema.ProviderModel
1520
mu *sync.Mutex
1621
}
1722

@@ -27,13 +32,95 @@ func (r *ProviderConfig) Unlock() {
2732
r.mu.Unlock()
2833
}
2934

30-
func NewProviderConfig(client *cmlclient.Client) *ProviderConfig {
35+
func NewProviderConfig(data *cmlschema.ProviderModel) *ProviderConfig {
3136
return &ProviderConfig{
32-
client: client,
37+
client: nil,
3338
mu: new(sync.Mutex),
39+
data: data,
3440
}
3541
}
3642

43+
func (r *ProviderConfig) Initialize(ctx context.Context, data *cmlschema.ProviderModel, diag diag.Diagnostics) *ProviderConfig {
44+
r.mu.Lock()
45+
defer r.mu.Unlock()
46+
47+
if r.client != nil {
48+
return r
49+
}
50+
51+
// check if provided auth configuration makes sense
52+
if data.Token.IsNull() &&
53+
(data.Username.IsNull() || data.Password.IsNull()) {
54+
diag.AddError(
55+
"Required configuration missing",
56+
"null check: either username and password or a token must be provided",
57+
)
58+
}
59+
60+
if len(data.Token.ValueString()) == 0 &&
61+
(len(data.Username.ValueString()) == 0 || len(data.Password.ValueString()) == 0) {
62+
diag.AddError(
63+
"Required configuration missing",
64+
"value check: either username and password or a token must be provided",
65+
)
66+
}
67+
68+
if len(data.Token.ValueString()) > 0 && len(data.Username.ValueString()) > 0 {
69+
diag.AddWarning(
70+
"Conflicting configuration",
71+
"both token and username / password were provided")
72+
}
73+
74+
// an address must be specified
75+
if len(data.Address.ValueString()) == 0 {
76+
diag.AddError(
77+
"Required configuration missing",
78+
"A server address must be configured to use th CML2 provider",
79+
)
80+
}
81+
if data.SkipVerify.IsNull() {
82+
tflog.Warn(ctx, "unspecified certificate verification, will verify")
83+
data.SkipVerify = types.BoolValue(false)
84+
}
85+
86+
if data.UseCache.IsNull() {
87+
data.UseCache = types.BoolValue(false)
88+
} else if data.UseCache.ValueBool() {
89+
diag.AddWarning(
90+
"Experimental feature enabled",
91+
"\"use_cache\" is considered experimental and may not work as expected; use with care",
92+
)
93+
}
94+
95+
// create a new CML2 client
96+
client := cmlclient.New(
97+
data.Address.ValueString(),
98+
data.SkipVerify.ValueBool(),
99+
data.UseCache.ValueBool(),
100+
)
101+
if len(data.Username.ValueString()) > 0 {
102+
client.SetUsernamePassword(
103+
data.Username.ValueString(),
104+
data.Password.ValueString(),
105+
)
106+
}
107+
if len(data.Token.ValueString()) > 0 {
108+
client.SetToken(data.Token.ValueString())
109+
}
110+
111+
if len(data.CAcert.ValueString()) > 0 {
112+
err := client.SetCACert([]byte(data.CAcert.ValueString()))
113+
if err != nil {
114+
diag.AddError(
115+
"Configuration issue",
116+
fmt.Sprintf("Provided certificate could not be used: %s", err),
117+
)
118+
}
119+
}
120+
r.client = client
121+
return r
122+
}
123+
37124
func DatasourceConfigure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) *ProviderConfig {
38125
// Prevent panic if the provider has not been configured.
39126
if req.ProviderData == nil {
@@ -47,7 +134,7 @@ func DatasourceConfigure(ctx context.Context, req datasource.ConfigureRequest, r
47134
)
48135
return nil
49136
}
50-
return config
137+
return config.Initialize(ctx, config.data, resp.Diagnostics)
51138
}
52139

53140
func ResourceConfigure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) *ProviderConfig {
@@ -64,5 +151,5 @@ func ResourceConfigure(ctx context.Context, req resource.ConfigureRequest, resp
64151
)
65152
return nil
66153
}
67-
return config
154+
return config.Initialize(ctx, config.data, resp.Diagnostics)
68155
}

internal/provider/provider.go

+12-71
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@ package provider
22

33
import (
44
"context"
5-
"fmt"
65

76
"github.com/hashicorp/terraform-plugin-framework/datasource"
87
"github.com/hashicorp/terraform-plugin-framework/provider"
98
"github.com/hashicorp/terraform-plugin-framework/resource"
109
"github.com/hashicorp/terraform-plugin-framework/types"
11-
"github.com/hashicorp/terraform-plugin-log/tflog"
12-
13-
cmlclient "github.com/rschmied/gocmlclient"
1410

1511
"github.com/rschmied/terraform-provider-cml2/internal/common"
1612
d_groups "github.com/rschmied/terraform-provider-cml2/internal/provider/datasource/groups"
@@ -51,76 +47,21 @@ func (p *CML2Provider) Configure(ctx context.Context, req provider.ConfigureRequ
5147
return
5248
}
5349

54-
// check if provided auth configuration makes sense
55-
if data.Token.IsNull() &&
56-
(data.Username.IsNull() || data.Password.IsNull()) {
57-
resp.Diagnostics.AddError(
58-
"Required configuration missing",
59-
fmt.Sprintf("null check: either username and password or a token must be provided %T", p),
60-
)
61-
}
62-
63-
if len(data.Token.ValueString()) == 0 &&
64-
(len(data.Username.ValueString()) == 0 || len(data.Password.ValueString()) == 0) {
65-
resp.Diagnostics.AddError(
66-
"Required configuration missing",
67-
fmt.Sprintf("value check: either username and password or a token must be provided %T", p),
68-
)
69-
}
70-
71-
if len(data.Token.ValueString()) > 0 && len(data.Username.ValueString()) > 0 {
72-
resp.Diagnostics.AddWarning(
73-
"Conflicting configuration",
74-
"both token and username / password were provided")
75-
}
76-
77-
// an address must be specified
78-
if len(data.Address.ValueString()) == 0 {
79-
resp.Diagnostics.AddError(
80-
"Required configuration missing",
81-
fmt.Sprintf("A server address must be configured to use provider %s", p.name),
82-
)
83-
}
84-
if data.SkipVerify.IsNull() {
85-
tflog.Warn(ctx, "unspecified certificate verification, will verify")
86-
data.SkipVerify = types.BoolValue(false)
87-
}
88-
89-
if data.UseCache.IsNull() {
90-
data.UseCache = types.BoolValue(false)
91-
} else if data.UseCache.ValueBool() {
92-
resp.Diagnostics.AddWarning(
93-
"Experimental feature enabled",
94-
"\"use_cache\" is considered experimental and may not work as expected; use with care",
95-
)
50+
dynamic_config := false
51+
if data.DynamicConfig.IsNull() {
52+
data.DynamicConfig = types.BoolValue(false)
53+
} else if data.DynamicConfig.ValueBool() {
54+
dynamic_config = true
55+
// resp.Diagnostics.AddWarning(
56+
// "Dynamic configuration",
57+
// "\"dynamic_config\" does late binding of the provider configuration",
58+
// )
9659
}
9760

98-
// create a new CML2 client
99-
client := cmlclient.New(
100-
data.Address.ValueString(),
101-
data.SkipVerify.ValueBool(),
102-
data.UseCache.ValueBool(),
103-
)
104-
if len(data.Username.ValueString()) > 0 {
105-
client.SetUsernamePassword(
106-
data.Username.ValueString(),
107-
data.Password.ValueString(),
108-
)
109-
}
110-
if len(data.Token.ValueString()) > 0 {
111-
client.SetToken(data.Token.ValueString())
112-
}
113-
114-
if len(data.CAcert.ValueString()) > 0 {
115-
err := client.SetCACert([]byte(data.CAcert.ValueString()))
116-
if err != nil {
117-
resp.Diagnostics.AddError(
118-
"Configuration issue",
119-
fmt.Sprintf("Provided certificate could not be used: %s", err),
120-
)
121-
}
61+
config := common.NewProviderConfig(&data)
62+
if !dynamic_config {
63+
config.Initialize(ctx, &data, resp.Diagnostics)
12264
}
123-
config := common.NewProviderConfig(client)
12465
resp.DataSourceData = config
12566
resp.ResourceData = config
12667
}

0 commit comments

Comments
 (0)