diff --git a/stackit/internal/services/sfs/resourcepool/resource.go b/stackit/internal/services/sfs/resourcepool/resource.go index 16fdf3579..096510ceb 100644 --- a/stackit/internal/services/sfs/resourcepool/resource.go +++ b/stackit/internal/services/sfs/resourcepool/resource.go @@ -237,7 +237,10 @@ func (r *resourcePoolResource) Schema(_ context.Context, _ resource.SchemaReques Description: "Name of the snapshot policy.", Computed: true, PlanModifiers: []planmodifier.String{ - stringplanmodifierUtils.UseStateForUnknownIf(stringplanmodifierUtils.StringUnchanged(path.Root("snapshot_policy").AtName("id")), "sets `UseStateForUnknown` only if `id` has not changed"), + stringplanmodifierUtils.UseStateForUnknownIf( + stringplanmodifierUtils.UnchangedPaths(path.MatchRoot("snapshot_policy").AtName("id")), + "sets `UseStateForUnknown` only if `id` has not changed", + ), }, }, }, diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index 2fd10bee1..393c4c50f 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -489,7 +489,10 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re Description: "Full Kubernetes version used. For example, if 1.22 was set in `kubernetes_version_min`, this value may result to 1.22.15. " + SKEUpdateDoc, Computed: true, PlanModifiers: []planmodifier.String{ - stringplanmodifierUtils.UseStateForUnknownIf(stringplanmodifierUtils.StringUnchanged(path.Root("kubernetes_version_min")), "sets `UseStateForUnknown` only if `kubernetes_min_version` has not changed"), + stringplanmodifierUtils.UseStateForUnknownIf( + stringplanmodifierUtils.UnchangedPaths(path.MatchRoot("kubernetes_version_min")), + "sets `UseStateForUnknown` only if `kubernetes_min_version` has not changed", + ), }, }, "egress_address_ranges": schema.ListAttribute{ @@ -580,7 +583,10 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re Description: "Full OS image version used. For example, if 3815.2 was set in `os_version_min`, this value may result to 3815.2.2. " + SKEUpdateDoc, Computed: true, PlanModifiers: []planmodifier.String{ - stringplanmodifierUtils.UseStateForUnknownIf(skeUtils.HasOsVersionMinChanged, "sets `UseStateForUnknown` only if `os_version_min` has not changed"), //nolint:staticcheck // temporary fix for issue with StringUnchanged + stringplanmodifierUtils.UseStateForUnknownIf( + stringplanmodifierUtils.UnchangedPaths(path.MatchRelative().AtParent().AtName("os_version_min")), + "sets `UseStateForUnknown` only if `os_version_min` has not changed", + ), }, }, "volume_type": schema.StringAttribute{ diff --git a/stackit/internal/services/ske/ske_acc_test.go b/stackit/internal/services/ske/ske_acc_test.go index 452c53d15..a003e36fd 100644 --- a/stackit/internal/services/ske/ske_acc_test.go +++ b/stackit/internal/services/ske/ske_acc_test.go @@ -23,6 +23,8 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) +const ephemeralKubeconfigAddress = "ephemeral.stackit_ske_kubeconfig.ephemeral_kubeconfig" + var ( minTestName = "acc-min" + acctest.RandStringFromCharSet(3, acctest.CharSetAlpha) maxTestName = "acc-max" + acctest.RandStringFromCharSet(3, acctest.CharSetAlpha) @@ -138,6 +140,11 @@ func TestAccSKEMin(t *testing.T) { { Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentSKE).BuildProviderConfig() + "\n" + resourceMin, ConfigVariables: testConfigVarsMin, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), @@ -189,6 +196,11 @@ func TestAccSKEMin(t *testing.T) { { Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentSKE).BuildProviderConfig() + "\n" + resourceMin, ConfigVariables: testConfigVarsMin, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data @@ -247,7 +259,10 @@ func TestAccSKEMin(t *testing.T) { PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction("stackit_ske_cluster.cluster", plancheck.ResourceActionUpdate), }, + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", testutil.ConvertConfigVariable(configVarsMinUpdated()["project_id"])), @@ -299,6 +314,11 @@ func TestAccSKEMax(t *testing.T) { { Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentSKE).BuildProviderConfig() + "\n" + resourceMax, ConfigVariables: testConfigVarsMax, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), @@ -389,6 +409,11 @@ func TestAccSKEMax(t *testing.T) { { Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentSKE).BuildProviderConfig() + "\n" + resourceMax, ConfigVariables: testConfigVarsMax, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data @@ -486,7 +511,10 @@ func TestAccSKEMax(t *testing.T) { PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction("stackit_ske_cluster.cluster", plancheck.ResourceActionUpdate), }, + PostApplyPreRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, + PostApplyPostRefresh: []plancheck.PlanCheck{testutil.ExpectOnlyEphemeralOpen(ephemeralKubeconfigAddress)}, }, + ExpectNonEmptyPlan: testutil.UsingOpenTofu(), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", testutil.ConvertConfigVariable(configVarsMaxUpdated()["project_id"])), diff --git a/stackit/internal/services/ske/utils/util.go b/stackit/internal/services/ske/utils/util.go index 6b4aca343..ef4581caa 100644 --- a/stackit/internal/services/ske/utils/util.go +++ b/stackit/internal/services/ske/utils/util.go @@ -5,14 +5,11 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/config" ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils/planmodifiers/stringplanmodifier" ) func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *ske.APIClient { @@ -45,27 +42,3 @@ func IsEmptyExtension(extension *ske.Extension) bool { } return false } - -// Deprecated: HasOsVersionMinChanged -func HasOsVersionMinChanged(ctx context.Context, request planmodifier.StringRequest, response *stringplanmodifier.UseStateForUnknownFuncResponse) { // nolint:gocritic // function signature required by Terraform - dependencyPath := request.Path.ParentPath().AtName("os_version_min") - - var minVersionPlan types.String - diags := request.Plan.GetAttribute(ctx, dependencyPath, &minVersionPlan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - var minVersionState types.String - diags = request.State.GetAttribute(ctx, dependencyPath, &minVersionState) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - if minVersionState == minVersionPlan { - response.UseStateForUnknown = true - return - } -} diff --git a/stackit/internal/testutil/plancheck.go b/stackit/internal/testutil/plancheck.go new file mode 100644 index 000000000..ae9f3e074 --- /dev/null +++ b/stackit/internal/testutil/plancheck.go @@ -0,0 +1,45 @@ +package testutil + +import ( + "context" + "fmt" + "slices" + + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +// ExpectOnlyEphemeralOpen returns a plan check that allows no-op actions and +// OpenTofu "open" actions for the given ephemeral resource addresses. +func ExpectOnlyEphemeralOpen(configAddresses ...string) plancheck.PlanCheck { + return expectOnlyEphemeralOpen{configAddresses: configAddresses} +} + +type expectOnlyEphemeralOpen struct { + configAddresses []string +} + +func (c expectOnlyEphemeralOpen) CheckPlan(_ context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) { + for _, resourceChange := range req.Plan.ResourceChanges { + if resourceChange.Change == nil || resourceChange.Change.Actions.NoOp() { + continue + } + + actions := resourceChange.Change.Actions + isSpecificResource := slices.Contains(c.configAddresses, resourceChange.Address) + if isSpecificResource && + string(resourceChange.Mode) == "ephemeral" && + len(actions) == 1 && string(actions[0]) == "open" { + continue + } + + resp.Error = fmt.Errorf("unexpected planned action(s) %v for %s", actions, resourceChange.Address) + return + } + + for name, outputChange := range req.Plan.OutputChanges { + if outputChange != nil && !outputChange.Actions.NoOp() { + resp.Error = fmt.Errorf("unexpected planned action(s) %v for output %s", outputChange.Actions, name) + return + } + } +} diff --git a/stackit/internal/testutil/plancheck_test.go b/stackit/internal/testutil/plancheck_test.go new file mode 100644 index 000000000..d0ce430d1 --- /dev/null +++ b/stackit/internal/testutil/plancheck_test.go @@ -0,0 +1,307 @@ +package testutil + +import ( + "context" + "strings" + "testing" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestExpectOnlyEphemeralOpen(t *testing.T) { + tests := []struct { + name string + configAddresses []string + plan tfjson.Plan + wantErr string + }{ + { + name: "empty plan", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{}, + }, + { + name: "resource with nil change is ignored", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_cluster.test", + Mode: tfjson.ManagedResourceMode, + Change: nil, + }, + }, + }, + }, + { + name: "resource with no-op actions is ignored", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_cluster.test", + Mode: tfjson.ManagedResourceMode, + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.ActionNoop}, + }, + }, + }, + }, + }, + { + name: "ephemeral resource with single open action matching configured address succeeds", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + }, + { + name: "multiple configured ephemeral resources with open action succeed", + configAddresses: []string{"stackit_ske_kubeconfig.test1", "stackit_ske_kubeconfig.test2"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test1", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + { + Address: "stackit_ske_kubeconfig.test2", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + }, + { + name: "ephemeral open resource not in configAddresses fails", + configAddresses: []string{"stackit_ske_kubeconfig.other"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [open] for stackit_ske_kubeconfig.test", + }, + { + name: "managed resource matching address and open action fails due to mode", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ManagedResourceMode, + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [open] for stackit_ske_kubeconfig.test", + }, + { + name: "data resource matching address and open action fails due to mode", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.DataResourceMode, + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [open] for stackit_ske_kubeconfig.test", + }, + { + name: "ephemeral resource matching address but with create action fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.ActionCreate}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [create] for stackit_ske_kubeconfig.test", + }, + { + name: "ephemeral resource matching address but with multiple actions fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open"), tfjson.ActionUpdate}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [open update] for stackit_ske_kubeconfig.test", + }, + { + name: "ephemeral resource matching address but with empty actions fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [] for stackit_ske_kubeconfig.test", + }, + { + name: "managed resource with create action fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_cluster.test", + Mode: tfjson.ManagedResourceMode, + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.ActionCreate}, + }, + }, + }, + }, + wantErr: "unexpected planned action(s) [create] for stackit_ske_cluster.test", + }, + { + name: "mixed resource changes: nil, no-op, and allowed ephemeral open succeed", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_cluster.nil_change", + Mode: tfjson.ManagedResourceMode, + Change: nil, + }, + { + Address: "stackit_ske_cluster.noop_change", + Mode: tfjson.ManagedResourceMode, + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.ActionNoop}, + }, + }, + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + }, + }, + { + name: "nil output change is ignored", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + OutputChanges: map[string]*tfjson.Change{ + "test_output": nil, + }, + }, + }, + { + name: "no-op output change is ignored", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + OutputChanges: map[string]*tfjson.Change{ + "test_output": { + Actions: tfjson.Actions{tfjson.ActionNoop}, + }, + }, + }, + }, + { + name: "output change with create action fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + OutputChanges: map[string]*tfjson.Change{ + "kubeconfig": { + Actions: tfjson.Actions{tfjson.ActionCreate}, + }, + }, + }, + wantErr: "unexpected planned action(s) [create] for output kubeconfig", + }, + { + name: "valid ephemeral open but output change with create action fails", + configAddresses: []string{"stackit_ske_kubeconfig.test"}, + plan: tfjson.Plan{ + ResourceChanges: []*tfjson.ResourceChange{ + { + Address: "stackit_ske_kubeconfig.test", + Mode: tfjson.ResourceMode("ephemeral"), + Change: &tfjson.Change{ + Actions: tfjson.Actions{tfjson.Action("open")}, + }, + }, + }, + OutputChanges: map[string]*tfjson.Change{ + "kubeconfig": { + Actions: tfjson.Actions{tfjson.ActionCreate}, + }, + }, + }, + wantErr: "unexpected planned action(s) [create] for output kubeconfig", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + check := ExpectOnlyEphemeralOpen(tt.configAddresses...) + req := plancheck.CheckPlanRequest{ + Plan: &tt.plan, + } + resp := &plancheck.CheckPlanResponse{} + + check.CheckPlan(context.Background(), req, resp) + + if tt.wantErr == "" { + if resp.Error != nil { + t.Fatalf("unexpected error: %v", resp.Error) + } + } else { + if resp.Error == nil { + t.Fatalf("expected error containing %q, got nil", tt.wantErr) + } + if !strings.Contains(resp.Error.Error(), tt.wantErr) { + t.Errorf("error %q does not contain %q", resp.Error.Error(), tt.wantErr) + } + } + }) + } +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index ea4ac6060..bcf1eda62 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -27,6 +27,11 @@ const ( credentialsFilePath = ".stackit/credentials.json" //nolint:gosec // linter false positive ) +// UsingOpenTofu reports whether acceptance tests are running with the OpenTofu CLI. +func UsingOpenTofu() bool { + return strings.HasSuffix(os.Getenv("TF_ACC_TERRAFORM_PATH"), "tofu") +} + var ( // TestAccProtoV6ProviderFactories is used to instantiate a provider during // acceptance testing. The factory function will be invoked for every Terraform diff --git a/stackit/internal/testutil/testutil_test.go b/stackit/internal/testutil/testutil_test.go index cabfaf01f..291c279c2 100644 --- a/stackit/internal/testutil/testutil_test.go +++ b/stackit/internal/testutil/testutil_test.go @@ -49,6 +49,18 @@ func newTestState(resourceName string, attributes map[string]string) *terraform. } } +func TestUsingOpenTofu(t *testing.T) { + t.Setenv("TF_ACC_TERRAFORM_PATH", "/usr/local/bin/tofu") + if !UsingOpenTofu() { + t.Error("UsingOpenTofu() = false, want true") + } + + t.Setenv("TF_ACC_TERRAFORM_PATH", "/usr/local/bin/terraform") + if UsingOpenTofu() { + t.Error("UsingOpenTofu() = true, want false") + } +} + func TestConvertConfigVariable(t *testing.T) { tests := []struct { name string diff --git a/stackit/internal/utils/planmodifiers/stringplanmodifier/use_state_for_unknown_if.go b/stackit/internal/utils/planmodifiers/stringplanmodifier/use_state_for_unknown_if.go index 08847b46d..30c39f358 100644 --- a/stackit/internal/utils/planmodifiers/stringplanmodifier/use_state_for_unknown_if.go +++ b/stackit/internal/utils/planmodifiers/stringplanmodifier/use_state_for_unknown_if.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" ) type UseStateForUnknownFuncResponse struct { @@ -71,30 +70,6 @@ func (m useStateForUnknownIf) PlanModifyString(ctx context.Context, req planmodi } } -// StringUnchanged sets UseStateForUnkown to true if the attribute's planned value matches the current state -func StringUnchanged(attributePath path.Path) UseStateForUnknownIfFunc { // nolint:gocritic // function signature required by Terraform - return func(ctx context.Context, request planmodifier.StringRequest, response *UseStateForUnknownFuncResponse) { - var attributePlan types.String - diags := request.Plan.GetAttribute(ctx, attributePath, &attributePlan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - var attributeState types.String - diags = request.State.GetAttribute(ctx, attributePath, &attributeState) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - if attributeState == attributePlan { - response.UseStateForUnknown = true - return - } - } -} - // UnchangedPaths sets UseStateForUnknown to true if all values matched by paths are equal in Plan & State func UnchangedPaths(paths ...path.Expression) UseStateForUnknownIfFunc { return func(ctx context.Context, req planmodifier.StringRequest, resp *UseStateForUnknownFuncResponse) {