From e4b319401e121d89ca074eda838d7a9aed86c0ef Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Wed, 13 Dec 2023 19:38:22 -0500 Subject: [PATCH] stacksruntime: Add sensitive outputs tests --- internal/stacks/stackruntime/plan_test.go | 147 ++++++++++++++++++ .../sensitive-output-nested.tfstack.hcl | 11 ++ .../test/sensitive-output/sensitive-output.tf | 4 + .../sensitive-output.tfstack.hcl | 10 ++ 4 files changed, 172 insertions(+) create mode 100644 internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output-nested/sensitive-output-nested.tfstack.hcl create mode 100644 internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tf create mode 100644 internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tfstack.hcl diff --git a/internal/stacks/stackruntime/plan_test.go b/internal/stacks/stackruntime/plan_test.go index c5ffe77d9b..96dc6ab418 100644 --- a/internal/stacks/stackruntime/plan_test.go +++ b/internal/stacks/stackruntime/plan_test.go @@ -15,6 +15,7 @@ import ( terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform" "github.com/hashicorp/terraform/internal/collections" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/stacks/stackaddrs" @@ -236,6 +237,152 @@ func TestPlanVariableOutputRoundtripNested(t *testing.T) { } } +var cmpCollectionsSet = cmp.Comparer(func(x, y collections.Set[stackaddrs.AbsComponent]) bool { + if x.Len() != y.Len() { + return false + } + + for _, v := range x.Elems() { + if !y.Has(v) { + return false + } + } + + return true +}) + +func TestPlanSensitiveOutput(t *testing.T) { + ctx := context.Background() + cfg := loadMainBundleConfigForTest(t, "sensitive-output") + + fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z") + if err != nil { + t.Fatal(err) + } + + changesCh := make(chan stackplan.PlannedChange, 8) + diagsCh := make(chan tfdiags.Diagnostic, 2) + req := PlanRequest{ + Config: cfg, + ForcePlanTimestamp: &fakePlanTimestamp, + } + resp := PlanResponse{ + PlannedChanges: changesCh, + Diagnostics: diagsCh, + } + + go Plan(ctx, &req, &resp) + gotChanges, diags := collectPlanOutput(changesCh, diagsCh) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics\n%s", diags.ErrWithWarnings().Error()) + } + + wantChanges := []stackplan.PlannedChange{ + &stackplan.PlannedChangeApplyable{ + Applyable: true, + }, + &stackplan.PlannedChangeComponentInstance{ + Addr: stackaddrs.Absolute( + stackaddrs.RootStackInstance, + stackaddrs.ComponentInstance{ + Component: stackaddrs.Component{Name: "self"}, + }, + ), + Action: plans.Create, + PlannedInputValues: make(map[string]plans.DynamicValue), + PlannedOutputValues: map[string]cty.Value{ + "out": cty.StringVal("secret").Mark(marks.Sensitive), + }, + PlanTimestamp: fakePlanTimestamp, + }, + &stackplan.PlannedChangeHeader{ + TerraformVersion: version.SemVer, + }, + &stackplan.PlannedChangeOutputValue{ + Addr: stackaddrs.OutputValue{Name: "result"}, + Action: plans.Create, + OldValue: plans.DynamicValue{0xc0}, // MessagePack nil + NewValue: mustPlanDynamicValue(cty.StringVal("secret")), + NewValueMarks: []cty.PathValueMarks{{Marks: cty.NewValueMarks(marks.Sensitive)}}, + }, + } + sort.SliceStable(gotChanges, func(i, j int) bool { + // An arbitrary sort just to make the result stable for comparison. + return fmt.Sprintf("%T", gotChanges[i]) < fmt.Sprintf("%T", gotChanges[j]) + }) + + if diff := cmp.Diff(wantChanges, gotChanges, ctydebug.CmpOptions, cmpCollectionsSet); diff != "" { + t.Errorf("wrong changes\n%s", diff) + } +} + +func TestPlanSensitiveOutputNested(t *testing.T) { + ctx := context.Background() + cfg := loadMainBundleConfigForTest(t, "sensitive-output-nested") + + fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z") + if err != nil { + t.Fatal(err) + } + + changesCh := make(chan stackplan.PlannedChange, 8) + diagsCh := make(chan tfdiags.Diagnostic, 2) + req := PlanRequest{ + Config: cfg, + ForcePlanTimestamp: &fakePlanTimestamp, + } + resp := PlanResponse{ + PlannedChanges: changesCh, + Diagnostics: diagsCh, + } + + go Plan(ctx, &req, &resp) + gotChanges, diags := collectPlanOutput(changesCh, diagsCh) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics\n%s", diags.ErrWithWarnings().Error()) + } + + wantChanges := []stackplan.PlannedChange{ + &stackplan.PlannedChangeApplyable{ + Applyable: true, + }, + &stackplan.PlannedChangeComponentInstance{ + Addr: stackaddrs.Absolute( + stackaddrs.RootStackInstance.Child("child", addrs.NoKey), + stackaddrs.ComponentInstance{ + Component: stackaddrs.Component{Name: "self"}, + }, + ), + Action: plans.Create, + PlannedInputValues: make(map[string]plans.DynamicValue), + PlannedOutputValues: map[string]cty.Value{ + "out": cty.StringVal("secret").Mark(marks.Sensitive), + }, + PlanTimestamp: fakePlanTimestamp, + }, + &stackplan.PlannedChangeHeader{ + TerraformVersion: version.SemVer, + }, + &stackplan.PlannedChangeOutputValue{ + Addr: stackaddrs.OutputValue{Name: "result"}, + Action: plans.Create, + OldValue: plans.DynamicValue{0xc0}, // MessagePack nil + NewValue: mustPlanDynamicValue(cty.StringVal("secret")), + NewValueMarks: []cty.PathValueMarks{{Marks: cty.NewValueMarks(marks.Sensitive)}}, + }, + } + sort.SliceStable(gotChanges, func(i, j int) bool { + // An arbitrary sort just to make the result stable for comparison. + return fmt.Sprintf("%T", gotChanges[i]) < fmt.Sprintf("%T", gotChanges[j]) + }) + + if diff := cmp.Diff(wantChanges, gotChanges, ctydebug.CmpOptions, cmpCollectionsSet); diff != "" { + t.Errorf("wrong changes\n%s", diff) + } +} + func TestPlanWithProviderConfig(t *testing.T) { ctx := context.Background() cfg := loadMainBundleConfigForTest(t, "with-provider-config") diff --git a/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output-nested/sensitive-output-nested.tfstack.hcl b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output-nested/sensitive-output-nested.tfstack.hcl new file mode 100644 index 0000000000..58aa9630bb --- /dev/null +++ b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output-nested/sensitive-output-nested.tfstack.hcl @@ -0,0 +1,11 @@ +stack "child" { + source = "../sensitive-output" + + inputs = { + } +} + +output "result" { + type = string + value = stack.child.result +} diff --git a/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tf b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tf new file mode 100644 index 0000000000..1031c01a58 --- /dev/null +++ b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tf @@ -0,0 +1,4 @@ +output "out" { + value = sensitive("secret") + sensitive = true +} diff --git a/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tfstack.hcl b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tfstack.hcl new file mode 100644 index 0000000000..1df80f4d0c --- /dev/null +++ b/internal/stacks/stackruntime/testdata/mainbundle/test/sensitive-output/sensitive-output.tfstack.hcl @@ -0,0 +1,10 @@ +component "self" { + source = "./" + inputs = { + } +} + +output "result" { + type = string + value = component.self.out +}