Skip to content

Commit a07ff3f

Browse files
authored
Support beta functionality (#412)
* support beta functionality * add testing, improve logic and code quality * improve testing and code quality * Fix warning message
1 parent 890e38f commit a07ff3f

4 files changed

Lines changed: 288 additions & 0 deletions

File tree

stackit/internal/core/core.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type ProviderData struct {
3434
SecretsManagerCustomEndpoint string
3535
SQLServerFlexCustomEndpoint string
3636
SKECustomEndpoint string
37+
EnableBetaResources bool
3738
}
3839

3940
// DiagsToError Converts TF diagnostics' errors into an error with a human-readable description.
@@ -66,3 +67,17 @@ func LogAndAddWarning(ctx context.Context, diags *diag.Diagnostics, summary, det
6667
tflog.Warn(ctx, fmt.Sprintf("%s | %s", summary, detail))
6768
diags.AddWarning(summary, detail)
6869
}
70+
71+
func LogAndAddWarningBeta(ctx context.Context, diags *diag.Diagnostics, name string) {
72+
warnTitle := fmt.Sprintf("The resource %q is in BETA", name)
73+
warnContent := fmt.Sprintf("The resource %q is in BETA and may be subject to breaking changes in the future. Use with caution.", name)
74+
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
75+
diags.AddWarning(warnTitle, warnContent)
76+
}
77+
78+
func LogAndAddErrorBeta(ctx context.Context, diags *diag.Diagnostics, name string) {
79+
errTitle := fmt.Sprintf("The resource %q is in BETA and BETA is not enabled", name)
80+
errContent := fmt.Sprintf("The resource %q is in BETA and the BETA functionality is currently not enabled. Please refer to the documentation on how to enable the BETA functionality.", name)
81+
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
82+
diags.AddError(errTitle, errContent)
83+
}

stackit/internal/features/beta.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package features
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
11+
)
12+
13+
// BetaResourcesEnabled returns whether this provider has BETA functionality enabled.
14+
//
15+
// In order of precedence, beta functionality can be managed by:
16+
// - Environment Variable `STACKIT_TF_ENABLE_BETA_RESOURCES` - `true` is enabled, `false` is disabled.
17+
// - Provider configuration feature flag `enable_beta` - `true` is enabled, `false` is disabled.
18+
func BetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics) bool {
19+
value, set := os.LookupEnv("STACKIT_TF_ENABLE_BETA_RESOURCES")
20+
if set {
21+
if strings.EqualFold(value, "true") {
22+
return true
23+
}
24+
if strings.EqualFold(value, "false") {
25+
return false
26+
}
27+
warnDetails := fmt.Sprintf(`The value of the environment variable that enables BETA functionality must be either "true" or "false", got %q.
28+
Defaulting to the provider feature flag.`, value)
29+
core.LogAndAddWarning(ctx, diags, "Invalid value for STACKIT_TF_ENABLE_BETA_RESOURCES environment variable.", warnDetails)
30+
}
31+
// ProviderData should always be set, but we check just in case
32+
if data == nil {
33+
return false
34+
}
35+
return data.EnableBetaResources
36+
}
37+
38+
// CheckBetaResourcesEnabled is a helper function to log and add a warning or error if the BETA functionality is not enabled.
39+
//
40+
// Should be called in the Configure method of a BETA resource.
41+
// Then, check for Errors in the diags using the diags.HasError() method.
42+
func CheckBetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics, resourceName string) {
43+
if !BetaResourcesEnabled(ctx, data, diags) {
44+
core.LogAndAddErrorBeta(ctx, diags, resourceName)
45+
return
46+
}
47+
core.LogAndAddWarningBeta(ctx, diags, resourceName)
48+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package features
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/diag"
8+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
9+
)
10+
11+
func TestBetaResourcesEnabled(t *testing.T) {
12+
tests := []struct {
13+
description string
14+
data *core.ProviderData
15+
envSet bool
16+
envValue string
17+
expected bool
18+
expectWarn bool
19+
}{
20+
{
21+
description: "Feature flag enabled, env var not set",
22+
data: &core.ProviderData{
23+
EnableBetaResources: true,
24+
},
25+
expected: true,
26+
},
27+
{
28+
description: "Feature flag is disabled, env var not set",
29+
data: &core.ProviderData{
30+
EnableBetaResources: false,
31+
},
32+
expected: false,
33+
},
34+
{
35+
description: "Feature flag, Env var not set",
36+
data: &core.ProviderData{},
37+
expected: false,
38+
},
39+
{
40+
description: "Feature flag not set, Env var is true",
41+
data: &core.ProviderData{},
42+
envSet: true,
43+
envValue: "true",
44+
expected: true,
45+
},
46+
{
47+
description: "Feature flag not set, Env var is false",
48+
data: &core.ProviderData{},
49+
envSet: true,
50+
envValue: "false",
51+
expected: false,
52+
},
53+
{
54+
description: "Feature flag not set, Env var is empty",
55+
data: &core.ProviderData{},
56+
envSet: true,
57+
envValue: "",
58+
expectWarn: true,
59+
expected: false,
60+
},
61+
{
62+
description: "Feature flag not set, Env var is gibberish",
63+
data: &core.ProviderData{},
64+
envSet: true,
65+
envValue: "gibberish",
66+
expectWarn: true,
67+
expected: false,
68+
},
69+
{
70+
description: "Feature flag enabled, Env var is true",
71+
data: &core.ProviderData{
72+
EnableBetaResources: true,
73+
},
74+
envSet: true,
75+
envValue: "true",
76+
expected: true,
77+
},
78+
{
79+
description: "Feature flag enabled, Env var is false",
80+
data: &core.ProviderData{
81+
EnableBetaResources: true,
82+
},
83+
envSet: true,
84+
envValue: "false",
85+
expected: false,
86+
},
87+
{
88+
description: "Feature flag enabled, Env var is empty",
89+
data: &core.ProviderData{
90+
EnableBetaResources: true,
91+
},
92+
envSet: true,
93+
envValue: "",
94+
expectWarn: true,
95+
expected: true,
96+
},
97+
{
98+
description: "Feature flag enabled, Env var is gibberish",
99+
data: &core.ProviderData{
100+
EnableBetaResources: true,
101+
},
102+
envSet: true,
103+
envValue: "gibberish",
104+
expectWarn: true,
105+
expected: true,
106+
},
107+
{
108+
description: "Feature flag disabled, Env var is true",
109+
data: &core.ProviderData{
110+
EnableBetaResources: false,
111+
},
112+
envSet: true,
113+
envValue: "true",
114+
expected: true,
115+
},
116+
{
117+
description: "Feature flag disabled, Env var is false",
118+
data: &core.ProviderData{
119+
EnableBetaResources: false,
120+
},
121+
envSet: true,
122+
envValue: "false",
123+
expected: false,
124+
},
125+
{
126+
description: "Feature flag disabled, Env var is empty",
127+
data: &core.ProviderData{
128+
EnableBetaResources: false,
129+
},
130+
envSet: true,
131+
envValue: "",
132+
expectWarn: true,
133+
expected: false,
134+
},
135+
{
136+
description: "Feature flag disabled, Env var is gibberish",
137+
data: &core.ProviderData{
138+
EnableBetaResources: false,
139+
},
140+
envSet: true,
141+
envValue: "gibberish",
142+
expectWarn: true,
143+
expected: false,
144+
},
145+
}
146+
147+
for _, tt := range tests {
148+
t.Run(tt.description, func(t *testing.T) {
149+
if tt.envSet {
150+
t.Setenv("STACKIT_TF_ENABLE_BETA_RESOURCES", tt.envValue)
151+
}
152+
diags := diag.Diagnostics{}
153+
154+
result := BetaResourcesEnabled(context.Background(), tt.data, &diags)
155+
if result != tt.expected {
156+
t.Fatalf("Expected %t, got %t", tt.expected, result)
157+
}
158+
159+
if tt.expectWarn && diags.WarningsCount() == 0 {
160+
t.Fatalf("Expected warning, got none")
161+
}
162+
if !tt.expectWarn && diags.WarningsCount() > 0 {
163+
t.Fatalf("Expected no warning, got %d", diags.WarningsCount())
164+
}
165+
})
166+
}
167+
}
168+
169+
func TestCheckBetaResourcesEnabled(t *testing.T) {
170+
tests := []struct {
171+
description string
172+
betaEnabled bool
173+
expectError bool
174+
expectWarn bool
175+
}{
176+
{
177+
description: "Beta enabled, show warning",
178+
betaEnabled: true,
179+
expectWarn: true,
180+
},
181+
{
182+
description: "Beta disabled, show error",
183+
betaEnabled: false,
184+
expectError: true,
185+
},
186+
}
187+
188+
for _, tt := range tests {
189+
t.Run(tt.description, func(t *testing.T) {
190+
var envValue string
191+
if tt.betaEnabled {
192+
envValue = "true"
193+
} else {
194+
envValue = "false"
195+
}
196+
t.Setenv("STACKIT_TF_ENABLE_BETA_RESOURCES", envValue)
197+
198+
diags := diag.Diagnostics{}
199+
CheckBetaResourcesEnabled(context.Background(), &core.ProviderData{}, &diags, "test")
200+
201+
if tt.expectError && diags.ErrorsCount() == 0 {
202+
t.Fatalf("Expected error, got none")
203+
}
204+
if !tt.expectError && diags.ErrorsCount() > 0 {
205+
t.Fatalf("Expected no error, got %d", diags.ErrorsCount())
206+
}
207+
208+
if tt.expectWarn && diags.WarningsCount() == 0 {
209+
t.Fatalf("Expected warning, got none")
210+
}
211+
if !tt.expectWarn && diags.WarningsCount() > 0 {
212+
t.Fatalf("Expected no warning, got %d", diags.WarningsCount())
213+
}
214+
})
215+
}
216+
}

stackit/provider.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type providerModel struct {
103103
ResourceManagerCustomEndpoint types.String `tfsdk:"resourcemanager_custom_endpoint"`
104104
TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"`
105105
JWKSCustomEndpoint types.String `tfsdk:"jwks_custom_endpoint"`
106+
EnableBetaResources types.Bool `tfsdk:"enable_beta_resources"`
106107
}
107108

108109
// Schema defines the provider-level schema for configuration data.
@@ -135,6 +136,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
135136
"ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service",
136137
"token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow",
137138
"jwks_custom_endpoint": "Custom endpoint for the jwks API, which is used to get the json web key sets (jwks) to validate tokens when using the key flow",
139+
"enable_beta_resources": "Enable beta resources. Default is false.",
138140
}
139141

140142
resp.Schema = schema.Schema{
@@ -248,6 +250,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
248250
Description: descriptions["jwks_custom_endpoint"],
249251
DeprecationMessage: "Validation using JWKS was removed, for being redundant with token validation done in the APIs. This field has no effect, and will be removed in a later update",
250252
},
253+
"enable_beta_resources": schema.BoolAttribute{
254+
Optional: true,
255+
Description: descriptions["enable_beta_resources"],
256+
},
251257
},
252258
}
253259
}
@@ -344,6 +350,9 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
344350
if !(providerConfig.TokenCustomEndpoint.IsUnknown() || providerConfig.TokenCustomEndpoint.IsNull()) {
345351
sdkConfig.TokenCustomUrl = providerConfig.TokenCustomEndpoint.ValueString()
346352
}
353+
if !(providerConfig.EnableBetaResources.IsUnknown() || providerConfig.EnableBetaResources.IsNull()) {
354+
providerData.EnableBetaResources = providerConfig.EnableBetaResources.ValueBool()
355+
}
347356
roundTripper, err := sdkauth.SetupAuth(sdkConfig)
348357
if err != nil {
349358
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring provider", fmt.Sprintf("Setting up authentication: %v", err))

0 commit comments

Comments
 (0)