From 16d84a43905f8b2dfa40de6b27a67eaa15ff38b9 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 4 Sep 2024 12:54:31 +0100 Subject: [PATCH] internal/command: Ensure ephemeral variables are validated accordingly --- internal/command/apply_test.go | 1 + internal/command/plan_test.go | 93 ++++++++++++++++++- .../invalid-variable-in-output.tf | 8 ++ .../.terraform/modules/modules.json | 1 + .../eph-module/outputs.tf | 8 ++ .../eph-module/variables.tf | 4 + .../invalid-module-output.tf | 9 ++ .../.terraform/modules/modules.json | 1 + .../eph-module/variables.tf | 3 + .../variable-in-module-input.tf | 10 ++ .../.terraform/modules/modules.json | 1 + .../eph-module/outputs.tf | 8 ++ .../eph-module/variables.tf | 4 + .../invalid-module-output.tf | 8 ++ .../invalid-variable-in-output.tf | 8 ++ .../.terraform/modules/modules.json | 1 + .../eph-module/variables.tf | 4 + .../variable-in-module-input.tf | 10 ++ .../variable-in-output.tf | 9 ++ .../variable-in-provider.tf | 8 ++ .../variable-validation.tf | 9 ++ 21 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-eph-var-in-non-eph-output/invalid-variable-in-output.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/.terraform/modules/modules.json create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/outputs.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/variables.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/invalid-module-output.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-input/.terraform/modules/modules.json create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-input/eph-module/variables.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-input/variable-in-module-input.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/.terraform/modules/modules.json create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/outputs.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/variables.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/invalid-module-output.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-invalid-noneph-var-in-eph-output/invalid-variable-in-output.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-valid-module-input/.terraform/modules/modules.json create mode 100644 internal/command/testdata/plan-ephemeral-values-valid-module-input/eph-module/variables.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-valid-module-input/variable-in-module-input.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-valid/variable-in-output.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-valid/variable-in-provider.tf create mode 100644 internal/command/testdata/plan-ephemeral-values-variable-validation/variable-validation.tf diff --git a/internal/command/apply_test.go b/internal/command/apply_test.go index d304095b436f..f4bbbc7fc757 100644 --- a/internal/command/apply_test.go +++ b/internal/command/apply_test.go @@ -292,6 +292,7 @@ func TestApply_parallelism(t *testing.T) { // called once we reach the desired concurrency, allowing all apply calls // to proceed in unison. beginCtx, begin := context.WithCancel(context.Background()) + t.Cleanup(begin) // Since our mock provider has its own mutex preventing concurrent calls // to ApplyResourceChange, we need to use a number of separate providers diff --git a/internal/command/plan_test.go b/internal/command/plan_test.go index 12643e5659b4..f2bb7d29578a 100644 --- a/internal/command/plan_test.go +++ b/internal/command/plan_test.go @@ -22,11 +22,13 @@ import ( "github.com/hashicorp/terraform/internal/addrs" backendinit "github.com/hashicorp/terraform/internal/backend/init" "github.com/hashicorp/terraform/internal/checks" + "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" testing_provider "github.com/hashicorp/terraform/internal/providers/testing" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -1428,7 +1430,7 @@ func TestPlan_parallelism(t *testing.T) { // called once we reach the desired concurrency, allowing all apply calls // to proceed in unison. beginCtx, begin := context.WithCancel(context.Background()) - + t.Cleanup(begin) // Since our mock provider has its own mutex preventing concurrent calls // to ApplyResourceChange, we need to use a number of separate providers // here. They will all have the same mock implementation function assigned @@ -1583,6 +1585,95 @@ func TestPlan_jsonGoldenReference(t *testing.T) { checkGoldenReference(t, output, "plan") } +func TestPlan_ephemeralValues(t *testing.T) { + testCases := []struct { + fixturePath string + expectedCode int + expectedOutput string + }{ + { + "plan-ephemeral-values-invalid-eph-var-in-non-eph-output", + 1, + `Error: Ephemeral value not allowed`, + }, + { + "plan-ephemeral-values-invalid-module-eph-output", + 1, + `Error: Value not allowed in ephemeral output`, + }, + { + "plan-ephemeral-values-invalid-module-input", + 1, + "Error: Ephemeral value not allowed", + }, + { + "plan-ephemeral-values-invalid-module-non-eph-output", + 1, + `Error: Ephemeral value not allowed`, + }, + { + "plan-ephemeral-values-invalid-noneph-var-in-eph-output", + 1, + `Error: Value not allowed in ephemeral output`, + }, + { + "plan-ephemeral-values-valid", + 0, + "", + }, + { + "plan-ephemeral-values-valid-module-input", + 0, + "", + }, + { + "plan-ephemeral-values-variable-validation", + 1, + "Error: Invalid value for variable", + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%2d-%s", i, tc.fixturePath), func(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath(tc.fixturePath), td) + defer testChdir(t, td)() + + p := planFixtureProvider() + p.GetProviderSchemaResponse.Provider = providers.Schema{ + Version: 0, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "token": { + Type: cty.String, + Optional: true, + Sensitive: true, + }, + }, + }, + } + + streams, done := terminal.StreamsForTesting(t) + c := &PlanCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + View: views.NewView(streams), + }, + } + code := c.Run([]string{"-json"}) + out := done(t) + if code != tc.expectedCode { + t.Fatalf("expected exit code %d, given %d\noutput: %q", tc.expectedCode, code, out.All()) + } + + stdout := out.Stdout() + if tc.expectedOutput != "" && !strings.Contains(stdout, tc.expectedOutput) { + t.Fatalf("unexpected output: %s", stdout) + } + }) + } +} + // planFixtureSchema returns a schema suitable for processing the // configuration in testdata/plan . This schema should be // assigned to a mock provider named "test". diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-eph-var-in-non-eph-output/invalid-variable-in-output.tf b/internal/command/testdata/plan-ephemeral-values-invalid-eph-var-in-non-eph-output/invalid-variable-in-output.tf new file mode 100644 index 000000000000..69db3c697895 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-eph-var-in-non-eph-output/invalid-variable-in-output.tf @@ -0,0 +1,8 @@ +variable "eph" { + ephemeral = true + default = "foo" +} + +output "not-eph" { + value = var.eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/.terraform/modules/modules.json b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/.terraform/modules/modules.json new file mode 100644 index 000000000000..4bfd4402fc1f --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"test","Source":"./eph-module","Dir":"eph-module"}]} \ No newline at end of file diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/outputs.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/outputs.tf new file mode 100644 index 000000000000..c109f4f37279 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/outputs.tf @@ -0,0 +1,8 @@ +output "eph" { + ephemeral = true + value = var.eph +} + +output "not-eph" { + value = "foo" +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/variables.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/variables.tf new file mode 100644 index 000000000000..da007aa974f8 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/eph-module/variables.tf @@ -0,0 +1,4 @@ +variable "eph" { + type = string + ephemeral = true +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/invalid-module-output.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/invalid-module-output.tf new file mode 100644 index 000000000000..b89f52e5d6d3 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-eph-output/invalid-module-output.tf @@ -0,0 +1,9 @@ +module "test" { + source = "./eph-module" + eph = "foo" +} + +output "eph" { + ephemeral = true + value = module.test.not-eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-input/.terraform/modules/modules.json b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/.terraform/modules/modules.json new file mode 100644 index 000000000000..4bfd4402fc1f --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"test","Source":"./eph-module","Dir":"eph-module"}]} \ No newline at end of file diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-input/eph-module/variables.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/eph-module/variables.tf new file mode 100644 index 000000000000..8ebf45fb074f --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/eph-module/variables.tf @@ -0,0 +1,3 @@ +variable "eph" { + type = string +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-input/variable-in-module-input.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/variable-in-module-input.tf new file mode 100644 index 000000000000..08192f44dc67 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-input/variable-in-module-input.tf @@ -0,0 +1,10 @@ +variable "test-eph" { + type = string + default = "foo" + ephemeral = true +} + +module "test" { + source = "./eph-module" + eph = var.test-eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/.terraform/modules/modules.json b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/.terraform/modules/modules.json new file mode 100644 index 000000000000..4bfd4402fc1f --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"test","Source":"./eph-module","Dir":"eph-module"}]} \ No newline at end of file diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/outputs.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/outputs.tf new file mode 100644 index 000000000000..c109f4f37279 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/outputs.tf @@ -0,0 +1,8 @@ +output "eph" { + ephemeral = true + value = var.eph +} + +output "not-eph" { + value = "foo" +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/variables.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/variables.tf new file mode 100644 index 000000000000..da007aa974f8 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/eph-module/variables.tf @@ -0,0 +1,4 @@ +variable "eph" { + type = string + ephemeral = true +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/invalid-module-output.tf b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/invalid-module-output.tf new file mode 100644 index 000000000000..f44fc1c8a119 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-module-non-eph-output/invalid-module-output.tf @@ -0,0 +1,8 @@ +module "test" { + source = "./eph-module" + eph = "foo" +} + +output "eph" { + value = module.test.eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-invalid-noneph-var-in-eph-output/invalid-variable-in-output.tf b/internal/command/testdata/plan-ephemeral-values-invalid-noneph-var-in-eph-output/invalid-variable-in-output.tf new file mode 100644 index 000000000000..b91fb22aad42 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-invalid-noneph-var-in-eph-output/invalid-variable-in-output.tf @@ -0,0 +1,8 @@ +variable "not-eph" { + default = "foo" +} + +output "eph" { + ephemeral = true + value = var.not-eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-valid-module-input/.terraform/modules/modules.json b/internal/command/testdata/plan-ephemeral-values-valid-module-input/.terraform/modules/modules.json new file mode 100644 index 000000000000..4bfd4402fc1f --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-valid-module-input/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"test","Source":"./eph-module","Dir":"eph-module"}]} \ No newline at end of file diff --git a/internal/command/testdata/plan-ephemeral-values-valid-module-input/eph-module/variables.tf b/internal/command/testdata/plan-ephemeral-values-valid-module-input/eph-module/variables.tf new file mode 100644 index 000000000000..a2fc57638e65 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-valid-module-input/eph-module/variables.tf @@ -0,0 +1,4 @@ +variable "eph" { + ephemeral = true + type = string +} diff --git a/internal/command/testdata/plan-ephemeral-values-valid-module-input/variable-in-module-input.tf b/internal/command/testdata/plan-ephemeral-values-valid-module-input/variable-in-module-input.tf new file mode 100644 index 000000000000..08192f44dc67 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-valid-module-input/variable-in-module-input.tf @@ -0,0 +1,10 @@ +variable "test-eph" { + type = string + default = "foo" + ephemeral = true +} + +module "test" { + source = "./eph-module" + eph = var.test-eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-valid/variable-in-output.tf b/internal/command/testdata/plan-ephemeral-values-valid/variable-in-output.tf new file mode 100644 index 000000000000..78b9c576ed90 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-valid/variable-in-output.tf @@ -0,0 +1,9 @@ +variable "valid-eph" { + ephemeral = true + default = "foo" +} + +output "valid-eph" { + ephemeral = true + value = var.valid-eph +} diff --git a/internal/command/testdata/plan-ephemeral-values-valid/variable-in-provider.tf b/internal/command/testdata/plan-ephemeral-values-valid/variable-in-provider.tf new file mode 100644 index 000000000000..d9027f398e27 --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-valid/variable-in-provider.tf @@ -0,0 +1,8 @@ +variable "token" { + ephemeral = true + default = "insecure" +} + +provider "test" { + token = var.token +} diff --git a/internal/command/testdata/plan-ephemeral-values-variable-validation/variable-validation.tf b/internal/command/testdata/plan-ephemeral-values-variable-validation/variable-validation.tf new file mode 100644 index 000000000000..df61516b3c7c --- /dev/null +++ b/internal/command/testdata/plan-ephemeral-values-variable-validation/variable-validation.tf @@ -0,0 +1,9 @@ +variable "test" { + ephemeral = true + default = "foo" + + validation { + condition = var.test != "foo" + error_message = "value" + } +}