diff --git a/internal/backend/local/backend_local.go b/internal/backend/local/backend_local.go index 82706857a7..9ae703eb4e 100644 --- a/internal/backend/local/backend_local.go +++ b/internal/backend/local/backend_local.go @@ -280,7 +280,7 @@ func (b *Local) localRunForPlanFile(op *backend.Operation, pf *planfile.Reader, return variable.Default, nil } - parsed, parsedErr := v.Decode(variable.Type) + parsed, parsedErr := v.Decode(cty.DynamicPseudoType) if parsedErr != nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, diff --git a/internal/command/e2etest/static_plan_test.go b/internal/command/e2etest/static_plan_test.go index f91b95ccd9..828fb7d1b7 100644 --- a/internal/command/e2etest/static_plan_test.go +++ b/internal/command/e2etest/static_plan_test.go @@ -6,6 +6,7 @@ package e2etest import ( + "fmt" "path/filepath" "testing" @@ -15,121 +16,129 @@ import ( // This is an e2e test as it relies on very specific configuration // within the meta object that is currently very hard to mock out. func TestStaticPlanVariables(t *testing.T) { - fixturePath := filepath.Join("testdata", "static_plan_variables") - tf := e2e.NewBinary(t, tofuBin, fixturePath) - - run := func(args ...string) tofuResult { - stdout, stderr, err := tf.Run(args...) - return tofuResult{t, stdout, stderr, err} + fixtures := []string{ + "static_plan_variables", + "static_plan_typed_variables", } + for _, fixture := range fixtures { + t.Run(fmt.Sprintf("TestStaticPlanVariables/%s", fixture), func(t *testing.T) { + fixturePath := filepath.Join("testdata", fixture) + tf := e2e.NewBinary(t, tofuBin, fixturePath) - statePath := "custom.tfstate" - stateVar := "-var=state_path=" + statePath - modVar := "-var=src=./mod" - planfile := "static.plan" + run := func(args ...string) tofuResult { + stdout, stderr, err := tf.Run(args...) + return tofuResult{t, stdout, stderr, err} + } - modErr := "module.mod.source depends on var.src which is not available" - backendErr := "backend.local depends on var.state_path which is not available" + statePath := "custom.tfstate" + stateVar := "-var=state_path=" + statePath + modVar := "-var=src=./mod" + planfile := "static.plan" - // Init - run("init").Failure().StderrContains(modErr) - run("init", stateVar, modVar).Success() + modErr := "module.mod.source depends on var.src which is not available" + backendErr := "backend.local depends on var.state_path which is not available" - // Get - run("get").Failure().StderrContains(modErr) - run("get", stateVar, modVar).Success() + // Init + run("init").Failure().StderrContains(modErr) + run("init", stateVar, modVar).Success() - // Validate - run("validate").Failure().StderrContains(modErr) - run("validate", stateVar, modVar).Success() + // Get + run("get").Failure().StderrContains(modErr) + run("get", stateVar, modVar).Success() - // Providers - run("providers").Failure().StderrContains(modErr) - run("providers", stateVar, modVar).Success() - run("providers", "lock").Failure().StderrContains(modErr) - run("providers", "lock", stateVar, modVar).Success() - run("providers", "mirror", "./tempproviders").Failure().StderrContains(modErr) - run("providers", "mirror", stateVar, modVar, "./tempproviders").Failure().StderrContains("Could not scan the output directory to get package metadata for the JSON") - run("providers", "schema", "-json").Failure().StderrContains(backendErr) - run("providers", "schema", "-json", stateVar, modVar).Success() + // Validate + run("validate").Failure().StderrContains(modErr) + run("validate", stateVar, modVar).Success() - // Check console init (early exits due to stdin setup) - run("console").Failure().StderrContains(backendErr) - run("console", stateVar, modVar).Success() + // Providers + run("providers").Failure().StderrContains(modErr) + run("providers", stateVar, modVar).Success() + run("providers", "lock").Failure().StderrContains(modErr) + run("providers", "lock", stateVar, modVar).Success() + run("providers", "mirror", "./tempproviders").Failure().StderrContains(modErr) + run("providers", "mirror", stateVar, modVar, "./tempproviders").Failure().StderrContains("Could not scan the output directory to get package metadata for the JSON") + run("providers", "schema", "-json").Failure().StderrContains(backendErr) + run("providers", "schema", "-json", stateVar, modVar).Success() - // Check graph (without plan) - run("graph").Failure().StderrContains(backendErr) - run("graph", stateVar, modVar).Success() + // Check console init (early exits due to stdin setup) + run("console").Failure().StderrContains(backendErr) + run("console", stateVar, modVar).Success() - // Plan with static variable - run("plan", stateVar, modVar, "-out="+planfile).Success() + // Check graph (without plan) + run("graph").Failure().StderrContains(backendErr) + run("graph", stateVar, modVar).Success() - // Show plan without static variable (embedded) - run("show", planfile).Success() + // Plan with static variable + run("plan", stateVar, modVar, "-out="+planfile).Success() - // Check graph (without plan) - run("graph", "-plan="+planfile).Success() + // Show plan without static variable (embedded) + run("show", planfile).Success() - // Apply plan without static variable (embedded) - run("apply", planfile).Success() + // Check graph (without plan) + run("graph", "-plan="+planfile).Success() - // Show State - run("show", statePath).Failure().StderrContains(modErr) - run("show", stateVar, modVar, statePath).Success().Contains(`out = "placeholder"`) + // Apply plan without static variable (embedded) + run("apply", planfile).Success() - // Force Unlock - run("force-unlock", "ident").Failure().StderrContains(backendErr) - run("force-unlock", stateVar, modVar, "ident").Failure().StderrContains("Local state cannot be unlocked by another process") + // Show State + run("show", statePath).Failure().StderrContains(modErr) + run("show", stateVar, modVar, statePath).Success().Contains(`out = "placeholder"`) - // Output values - run("output").Failure().StderrContains(backendErr) - run("output", stateVar, modVar).Success().Contains(`out = "placeholder"`) + // Force Unlock + run("force-unlock", "ident").Failure().StderrContains(backendErr) + run("force-unlock", stateVar, modVar, "ident").Failure().StderrContains("Local state cannot be unlocked by another process") - // Refresh - run("refresh").Failure().StderrContains(backendErr) - run("refresh", stateVar, modVar).Success().Contains("There are currently no remote objects tracked in the state") + // Output values + run("output").Failure().StderrContains(backendErr) + run("output", stateVar, modVar).Success().Contains(`out = "placeholder"`) - // Import - run("import", "resource.addr", "id").Failure().StderrContains(modErr) - run("import", stateVar, modVar, "resource.addr", "id").Failure().StderrContains("Before importing this resource, please create its configuration in the root module.") + // Refresh + run("refresh").Failure().StderrContains(backendErr) + run("refresh", stateVar, modVar).Success().Contains("There are currently no remote objects tracked in the state") - // Taint - run("taint", "resource.addr").Failure().StderrContains(modErr) - run("taint", stateVar, modVar, "resource.addr").Failure().StderrContains("There is no resource instance in the state with the address resource.addr.") - run("untaint", "resource.addr").Failure().StderrContains(backendErr) - run("untaint", stateVar, modVar, "resource.addr").Failure().StderrContains("There is no resource instance in the state with the address resource.addr.") + // Import + run("import", "resource.addr", "id").Failure().StderrContains(modErr) + run("import", stateVar, modVar, "resource.addr", "id").Failure().StderrContains("Before importing this resource, please create its configuration in the root module.") - // State - run("state", "list").Failure().StderrContains(backendErr) - run("state", "list", stateVar, modVar).Success() - run("state", "mv", "foo.bar", "foo.baz").Failure().StderrContains(modErr) - run("state", "mv", stateVar, modVar, "foo.bar", "foo.baz").Failure().StderrContains("Cannot move foo.bar: does not match anything in the current state.") - run("state", "pull").Failure().StderrContains(modErr) - run("state", "pull", stateVar, modVar).Success().Contains(`"outputs":{"out":{"value":"placeholder","type":"string"}}`) - run("state", "push", statePath).Failure().StderrContains(modErr) - run("state", "push", stateVar, modVar, statePath).Success() - run("state", "replace-provider", "foo", "bar").Failure().StderrContains(modErr) - run("state", "replace-provider", stateVar, modVar, "foo", "bar").Success().Contains("No matching resources found.") - run("state", "rm", "foo.bar").Failure().StderrContains(modErr) - run("state", "rm", stateVar, modVar, "foo.bar").Failure().StderrContains("No matching objects found.") - run("state", "show", "out").Failure().StderrContains(backendErr) - run("state", "show", stateVar, modVar, "invalid.resource").Failure().StderrContains("No instance found for the given address!") + // Taint + run("taint", "resource.addr").Failure().StderrContains(modErr) + run("taint", stateVar, modVar, "resource.addr").Failure().StderrContains("There is no resource instance in the state with the address resource.addr.") + run("untaint", "resource.addr").Failure().StderrContains(backendErr) + run("untaint", stateVar, modVar, "resource.addr").Failure().StderrContains("There is no resource instance in the state with the address resource.addr.") - // Workspace - run("workspace", "list").Failure().StderrContains(backendErr) - run("workspace", "list", stateVar, modVar).Success().Contains(`default`) - run("workspace", "new", "foo").Failure().StderrContains(backendErr) - run("workspace", "new", stateVar, modVar, "foo").Success().Contains(`foo`) - run("workspace", "select", "default").Failure().StderrContains(backendErr) - run("workspace", "select", stateVar, modVar, "default").Success().Contains(`default`) - run("workspace", "delete", "foo").Failure().StderrContains(backendErr) - run("workspace", "delete", stateVar, modVar, "foo").Success().Contains(`foo`) + // State + run("state", "list").Failure().StderrContains(backendErr) + run("state", "list", stateVar, modVar).Success() + run("state", "mv", "foo.bar", "foo.baz").Failure().StderrContains(modErr) + run("state", "mv", stateVar, modVar, "foo.bar", "foo.baz").Failure().StderrContains("Cannot move foo.bar: does not match anything in the current state.") + run("state", "pull").Failure().StderrContains(modErr) + run("state", "pull", stateVar, modVar).Success().Contains(`"outputs":{"out":{"value":"placeholder","type":"string"}}`) + run("state", "push", statePath).Failure().StderrContains(modErr) + run("state", "push", stateVar, modVar, statePath).Success() + run("state", "replace-provider", "foo", "bar").Failure().StderrContains(modErr) + run("state", "replace-provider", stateVar, modVar, "foo", "bar").Success().Contains("No matching resources found.") + run("state", "rm", "foo.bar").Failure().StderrContains(modErr) + run("state", "rm", stateVar, modVar, "foo.bar").Failure().StderrContains("No matching objects found.") + run("state", "show", "out").Failure().StderrContains(backendErr) + run("state", "show", stateVar, modVar, "invalid.resource").Failure().StderrContains("No instance found for the given address!") - // Test - run("test").Failure().StderrContains(modErr) - run("test", stateVar, modVar).Success().Contains(`Success!`) + // Workspace + run("workspace", "list").Failure().StderrContains(backendErr) + run("workspace", "list", stateVar, modVar).Success().Contains(`default`) + run("workspace", "new", "foo").Failure().StderrContains(backendErr) + run("workspace", "new", stateVar, modVar, "foo").Success().Contains(`foo`) + run("workspace", "select", "default").Failure().StderrContains(backendErr) + run("workspace", "select", stateVar, modVar, "default").Success().Contains(`default`) + run("workspace", "delete", "foo").Failure().StderrContains(backendErr) + run("workspace", "delete", stateVar, modVar, "foo").Success().Contains(`foo`) - // Destroy - run("destroy", "-auto-approve").Failure().StderrContains(backendErr) - run("destroy", stateVar, modVar, "-auto-approve").Success().Contains("You can apply this plan to save these new output values") + // Test + run("test").Failure().StderrContains(modErr) + run("test", stateVar, modVar).Success().Contains(`Success!`) + + // Destroy + run("destroy", "-auto-approve").Failure().StderrContains(backendErr) + run("destroy", stateVar, modVar, "-auto-approve").Success().Contains("You can apply this plan to save these new output values") + }) + } } diff --git a/internal/command/e2etest/testdata/static_plan_typed_variables/main.tf b/internal/command/e2etest/testdata/static_plan_typed_variables/main.tf new file mode 100644 index 0000000000..8e81a1a767 --- /dev/null +++ b/internal/command/e2etest/testdata/static_plan_typed_variables/main.tf @@ -0,0 +1,19 @@ +variable "state_path" {} + +variable "src" { + type = string +} + +terraform { + backend "local" { + path = var.state_path + } +} + +module "mod" { + source = var.src +} + +output "out" { + value = module.mod.out +} diff --git a/internal/command/e2etest/testdata/static_plan_typed_variables/mod/mod.tf b/internal/command/e2etest/testdata/static_plan_typed_variables/mod/mod.tf new file mode 100644 index 0000000000..95473f39da --- /dev/null +++ b/internal/command/e2etest/testdata/static_plan_typed_variables/mod/mod.tf @@ -0,0 +1,3 @@ +output "out" { + value = "placeholder" +} diff --git a/internal/command/show.go b/internal/command/show.go index 460e581839..2a783d9e17 100644 --- a/internal/command/show.go +++ b/internal/command/show.go @@ -13,6 +13,8 @@ import ( "strings" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/opentofu/opentofu/internal/backend" "github.com/opentofu/opentofu/internal/cloud" "github.com/opentofu/opentofu/internal/cloud/cloudplan" @@ -27,7 +29,6 @@ import ( "github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tofu" "github.com/opentofu/opentofu/internal/tofumigrate" - "github.com/zclconf/go-cty/cty" ) // Many of the methods we get data from can emit special error types if they're @@ -382,7 +383,7 @@ func getDataFromPlanfileReader(planReader *planfile.Reader, rootCall configs.Sta return variable.Default, nil } - parsed, parsedErr := v.Decode(variable.Type) + parsed, parsedErr := v.Decode(cty.DynamicPseudoType) if parsedErr != nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError,