Support beta functionality (#412)

* support beta functionality

* add testing, improve logic and code quality

* improve testing and code quality

* Fix warning message
This commit is contained in:
Diogo Ferrão 2024-06-20 17:54:23 +01:00 committed by GitHub
parent 890e38f22a
commit a07ff3f9ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 288 additions and 0 deletions

View file

@ -34,6 +34,7 @@ type ProviderData struct {
SecretsManagerCustomEndpoint string
SQLServerFlexCustomEndpoint string
SKECustomEndpoint string
EnableBetaResources bool
}
// 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
tflog.Warn(ctx, fmt.Sprintf("%s | %s", summary, detail))
diags.AddWarning(summary, detail)
}
func LogAndAddWarningBeta(ctx context.Context, diags *diag.Diagnostics, name string) {
warnTitle := fmt.Sprintf("The resource %q is in BETA", name)
warnContent := fmt.Sprintf("The resource %q is in BETA and may be subject to breaking changes in the future. Use with caution.", name)
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
diags.AddWarning(warnTitle, warnContent)
}
func LogAndAddErrorBeta(ctx context.Context, diags *diag.Diagnostics, name string) {
errTitle := fmt.Sprintf("The resource %q is in BETA and BETA is not enabled", name)
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)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diags.AddError(errTitle, errContent)
}

View file

@ -0,0 +1,48 @@
package features
import (
"context"
"fmt"
"os"
"strings"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
)
// BetaResourcesEnabled returns whether this provider has BETA functionality enabled.
//
// In order of precedence, beta functionality can be managed by:
// - Environment Variable `STACKIT_TF_ENABLE_BETA_RESOURCES` - `true` is enabled, `false` is disabled.
// - Provider configuration feature flag `enable_beta` - `true` is enabled, `false` is disabled.
func BetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics) bool {
value, set := os.LookupEnv("STACKIT_TF_ENABLE_BETA_RESOURCES")
if set {
if strings.EqualFold(value, "true") {
return true
}
if strings.EqualFold(value, "false") {
return false
}
warnDetails := fmt.Sprintf(`The value of the environment variable that enables BETA functionality must be either "true" or "false", got %q.
Defaulting to the provider feature flag.`, value)
core.LogAndAddWarning(ctx, diags, "Invalid value for STACKIT_TF_ENABLE_BETA_RESOURCES environment variable.", warnDetails)
}
// ProviderData should always be set, but we check just in case
if data == nil {
return false
}
return data.EnableBetaResources
}
// CheckBetaResourcesEnabled is a helper function to log and add a warning or error if the BETA functionality is not enabled.
//
// Should be called in the Configure method of a BETA resource.
// Then, check for Errors in the diags using the diags.HasError() method.
func CheckBetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics, resourceName string) {
if !BetaResourcesEnabled(ctx, data, diags) {
core.LogAndAddErrorBeta(ctx, diags, resourceName)
return
}
core.LogAndAddWarningBeta(ctx, diags, resourceName)
}

View file

@ -0,0 +1,216 @@
package features
import (
"context"
"testing"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
)
func TestBetaResourcesEnabled(t *testing.T) {
tests := []struct {
description string
data *core.ProviderData
envSet bool
envValue string
expected bool
expectWarn bool
}{
{
description: "Feature flag enabled, env var not set",
data: &core.ProviderData{
EnableBetaResources: true,
},
expected: true,
},
{
description: "Feature flag is disabled, env var not set",
data: &core.ProviderData{
EnableBetaResources: false,
},
expected: false,
},
{
description: "Feature flag, Env var not set",
data: &core.ProviderData{},
expected: false,
},
{
description: "Feature flag not set, Env var is true",
data: &core.ProviderData{},
envSet: true,
envValue: "true",
expected: true,
},
{
description: "Feature flag not set, Env var is false",
data: &core.ProviderData{},
envSet: true,
envValue: "false",
expected: false,
},
{
description: "Feature flag not set, Env var is empty",
data: &core.ProviderData{},
envSet: true,
envValue: "",
expectWarn: true,
expected: false,
},
{
description: "Feature flag not set, Env var is gibberish",
data: &core.ProviderData{},
envSet: true,
envValue: "gibberish",
expectWarn: true,
expected: false,
},
{
description: "Feature flag enabled, Env var is true",
data: &core.ProviderData{
EnableBetaResources: true,
},
envSet: true,
envValue: "true",
expected: true,
},
{
description: "Feature flag enabled, Env var is false",
data: &core.ProviderData{
EnableBetaResources: true,
},
envSet: true,
envValue: "false",
expected: false,
},
{
description: "Feature flag enabled, Env var is empty",
data: &core.ProviderData{
EnableBetaResources: true,
},
envSet: true,
envValue: "",
expectWarn: true,
expected: true,
},
{
description: "Feature flag enabled, Env var is gibberish",
data: &core.ProviderData{
EnableBetaResources: true,
},
envSet: true,
envValue: "gibberish",
expectWarn: true,
expected: true,
},
{
description: "Feature flag disabled, Env var is true",
data: &core.ProviderData{
EnableBetaResources: false,
},
envSet: true,
envValue: "true",
expected: true,
},
{
description: "Feature flag disabled, Env var is false",
data: &core.ProviderData{
EnableBetaResources: false,
},
envSet: true,
envValue: "false",
expected: false,
},
{
description: "Feature flag disabled, Env var is empty",
data: &core.ProviderData{
EnableBetaResources: false,
},
envSet: true,
envValue: "",
expectWarn: true,
expected: false,
},
{
description: "Feature flag disabled, Env var is gibberish",
data: &core.ProviderData{
EnableBetaResources: false,
},
envSet: true,
envValue: "gibberish",
expectWarn: true,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
if tt.envSet {
t.Setenv("STACKIT_TF_ENABLE_BETA_RESOURCES", tt.envValue)
}
diags := diag.Diagnostics{}
result := BetaResourcesEnabled(context.Background(), tt.data, &diags)
if result != tt.expected {
t.Fatalf("Expected %t, got %t", tt.expected, result)
}
if tt.expectWarn && diags.WarningsCount() == 0 {
t.Fatalf("Expected warning, got none")
}
if !tt.expectWarn && diags.WarningsCount() > 0 {
t.Fatalf("Expected no warning, got %d", diags.WarningsCount())
}
})
}
}
func TestCheckBetaResourcesEnabled(t *testing.T) {
tests := []struct {
description string
betaEnabled bool
expectError bool
expectWarn bool
}{
{
description: "Beta enabled, show warning",
betaEnabled: true,
expectWarn: true,
},
{
description: "Beta disabled, show error",
betaEnabled: false,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
var envValue string
if tt.betaEnabled {
envValue = "true"
} else {
envValue = "false"
}
t.Setenv("STACKIT_TF_ENABLE_BETA_RESOURCES", envValue)
diags := diag.Diagnostics{}
CheckBetaResourcesEnabled(context.Background(), &core.ProviderData{}, &diags, "test")
if tt.expectError && diags.ErrorsCount() == 0 {
t.Fatalf("Expected error, got none")
}
if !tt.expectError && diags.ErrorsCount() > 0 {
t.Fatalf("Expected no error, got %d", diags.ErrorsCount())
}
if tt.expectWarn && diags.WarningsCount() == 0 {
t.Fatalf("Expected warning, got none")
}
if !tt.expectWarn && diags.WarningsCount() > 0 {
t.Fatalf("Expected no warning, got %d", diags.WarningsCount())
}
})
}
}

View file

@ -103,6 +103,7 @@ type providerModel struct {
ResourceManagerCustomEndpoint types.String `tfsdk:"resourcemanager_custom_endpoint"`
TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"`
JWKSCustomEndpoint types.String `tfsdk:"jwks_custom_endpoint"`
EnableBetaResources types.Bool `tfsdk:"enable_beta_resources"`
}
// Schema defines the provider-level schema for configuration data.
@ -135,6 +136,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
"ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service",
"token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow",
"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",
"enable_beta_resources": "Enable beta resources. Default is false.",
}
resp.Schema = schema.Schema{
@ -248,6 +250,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
Description: descriptions["jwks_custom_endpoint"],
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",
},
"enable_beta_resources": schema.BoolAttribute{
Optional: true,
Description: descriptions["enable_beta_resources"],
},
},
}
}
@ -344,6 +350,9 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
if !(providerConfig.TokenCustomEndpoint.IsUnknown() || providerConfig.TokenCustomEndpoint.IsNull()) {
sdkConfig.TokenCustomUrl = providerConfig.TokenCustomEndpoint.ValueString()
}
if !(providerConfig.EnableBetaResources.IsUnknown() || providerConfig.EnableBetaResources.IsNull()) {
providerData.EnableBetaResources = providerConfig.EnableBetaResources.ValueBool()
}
roundTripper, err := sdkauth.SetupAuth(sdkConfig)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring provider", fmt.Sprintf("Setting up authentication: %v", err))