diff --git a/internal/configs/module_merge.go b/internal/configs/module_merge.go index b281b6f100..ff27d502e3 100644 --- a/internal/configs/module_merge.go +++ b/internal/configs/module_merge.go @@ -53,6 +53,10 @@ func (v *Variable) merge(ov *Variable) hcl.Diagnostics { v.Ephemeral = ov.Ephemeral v.EphemeralSet = ov.EphemeralSet } + if ov.ConstSet { + v.Const = ov.Const + v.ConstSet = ov.ConstSet + } if ov.Default != cty.NilVal { v.Default = ov.Default } diff --git a/internal/configs/module_merge_test.go b/internal/configs/module_merge_test.go index 67c091a6c9..78b2825e21 100644 --- a/internal/configs/module_merge_test.go +++ b/internal/configs/module_merge_test.go @@ -308,7 +308,117 @@ func TestModuleOverrideSensitiveVariable(t *testing.T) { } if got[v].SensitiveSet != want.sensitiveSet { - t.Errorf("wrong result for sensitive set\ngot: %t want: %t", got[v].Sensitive, want.sensitive) + t.Errorf("wrong result for sensitive set\ngot: %t want: %t", got[v].SensitiveSet, want.sensitiveSet) + } + }) + } +} + +func TestModuleOverrideEphemeralVariable(t *testing.T) { + type testCase struct { + ephemeral bool + ephemeralSet bool + } + cases := map[string]testCase{ + "false_true": { + ephemeral: true, + ephemeralSet: true, + }, + "true_false": { + ephemeral: false, + ephemeralSet: true, + }, + "false_false_true": { + ephemeral: true, + ephemeralSet: true, + }, + "true_true_false": { + ephemeral: false, + ephemeralSet: true, + }, + "false_true_false": { + ephemeral: false, + ephemeralSet: true, + }, + "true_false_true": { + ephemeral: true, + ephemeralSet: true, + }, + } + + mod, diags := testModuleFromDir("testdata/valid-modules/override-variable-ephemeral") + + assertNoDiagnostics(t, diags) + + if mod == nil { + t.Fatalf("module is nil") + } + + got := mod.Variables + + for v, want := range cases { + t.Run(fmt.Sprintf("variable %s", v), func(t *testing.T) { + if got[v].Ephemeral != want.ephemeral { + t.Errorf("wrong result for ephemeral\ngot: %t want: %t", got[v].Ephemeral, want.ephemeral) + } + + if got[v].EphemeralSet != want.ephemeralSet { + t.Errorf("wrong result for ephemeral set\ngot: %t want: %t", got[v].EphemeralSet, want.ephemeralSet) + } + }) + } +} + +func TestModuleOverrideConstVariable(t *testing.T) { + type testCase struct { + constV bool + constSet bool + } + cases := map[string]testCase{ + "false_true": { + constV: true, + constSet: true, + }, + "true_false": { + constV: false, + constSet: true, + }, + "false_false_true": { + constV: true, + constSet: true, + }, + "true_true_false": { + constV: false, + constSet: true, + }, + "false_true_false": { + constV: false, + constSet: true, + }, + "true_false_true": { + constV: true, + constSet: true, + }, + } + + mod, diags := testModuleFromDir("testdata/valid-modules/override-variable-const") + + assertNoDiagnostics(t, diags) + + if mod == nil { + t.Fatalf("module is nil") + } + + got := mod.Variables + + for v, want := range cases { + t.Run(fmt.Sprintf("variable %s", v), func(t *testing.T) { + if got[v].Const != want.constV { + t.Errorf("wrong result for const\ngot: %t want: %t", got[v].Const, want.constV) + } + + if got[v].ConstSet != want.constSet { + t.Errorf("wrong result for const set\ngot: %t want: %t", got[v].ConstSet, want.constSet) } }) } diff --git a/internal/configs/named_values.go b/internal/configs/named_values.go index 1267864710..bd5dedec63 100644 --- a/internal/configs/named_values.go +++ b/internal/configs/named_values.go @@ -38,9 +38,14 @@ type Variable struct { Sensitive bool Ephemeral bool + // Const indicates that this variable can be used during early evaluation + // work and configuration loading, for example in module sources + Const bool + DescriptionSet bool SensitiveSet bool EphemeralSet bool + ConstSet bool // Nullable indicates that null is a valid value for this variable. Setting // Nullable to false means that the module can expect this variable to @@ -133,6 +138,12 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno v.EphemeralSet = true } + if attr, exists := content.Attributes["const"]; exists { + valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Const) + diags = append(diags, valDiags...) + v.ConstSet = true + } + if attr, exists := content.Attributes["nullable"]; exists { valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Nullable) diags = append(diags, valDiags...) @@ -525,6 +536,9 @@ var variableBlockSchema = &hcl.BodySchema{ { Name: "deprecated", }, + { + Name: "const", + }, }, Blocks: []hcl.BlockHeaderSchema{ { diff --git a/internal/configs/testdata/valid-modules/override-variable-const/a_override.tf b/internal/configs/testdata/valid-modules/override-variable-const/a_override.tf new file mode 100644 index 0000000000..86a236ff04 --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-const/a_override.tf @@ -0,0 +1,23 @@ +variable "false_true" { + const = true +} + +variable "true_false" { + const = false +} + +variable "false_false_true" { + const = false +} + +variable "true_true_false" { + const = true +} + +variable "false_true_false" { + const = true +} + +variable "true_false_true" { + const = false +} diff --git a/internal/configs/testdata/valid-modules/override-variable-const/b_override.tf b/internal/configs/testdata/valid-modules/override-variable-const/b_override.tf new file mode 100644 index 0000000000..0709cd6a1e --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-const/b_override.tf @@ -0,0 +1,21 @@ +variable "false_true" { +} + +variable "true_false" { +} + +variable "false_false_true" { + const = true +} + +variable "true_true_false" { + const = false +} + +variable "false_true_false" { + const = false +} + +variable "true_false_true" { + const = true +} diff --git a/internal/configs/testdata/valid-modules/override-variable-const/primary.tf b/internal/configs/testdata/valid-modules/override-variable-const/primary.tf new file mode 100644 index 0000000000..bd1e631c40 --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-const/primary.tf @@ -0,0 +1,23 @@ +variable "false_true" { + const = false +} + +variable "true_false" { + const = true +} + +variable "false_false_true" { + const = false +} + +variable "true_true_false" { + const = true +} + +variable "false_true_false" { + const = false +} + +variable "true_false_true" { + const = true +} diff --git a/internal/configs/testdata/valid-modules/override-variable-ephemeral/a_override.tf b/internal/configs/testdata/valid-modules/override-variable-ephemeral/a_override.tf new file mode 100644 index 0000000000..f7cb24a8e3 --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-ephemeral/a_override.tf @@ -0,0 +1,23 @@ +variable "false_true" { + ephemeral = true +} + +variable "true_false" { + ephemeral = false +} + +variable "false_false_true" { + ephemeral = false +} + +variable "true_true_false" { + ephemeral = true +} + +variable "false_true_false" { + ephemeral = true +} + +variable "true_false_true" { + ephemeral = false +} diff --git a/internal/configs/testdata/valid-modules/override-variable-ephemeral/b_override.tf b/internal/configs/testdata/valid-modules/override-variable-ephemeral/b_override.tf new file mode 100644 index 0000000000..223af930a9 --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-ephemeral/b_override.tf @@ -0,0 +1,21 @@ +variable "false_true" { +} + +variable "true_false" { +} + +variable "false_false_true" { + ephemeral = true +} + +variable "true_true_false" { + ephemeral = false +} + +variable "false_true_false" { + ephemeral = false +} + +variable "true_false_true" { + ephemeral = true +} diff --git a/internal/configs/testdata/valid-modules/override-variable-ephemeral/primary.tf b/internal/configs/testdata/valid-modules/override-variable-ephemeral/primary.tf new file mode 100644 index 0000000000..422f62c820 --- /dev/null +++ b/internal/configs/testdata/valid-modules/override-variable-ephemeral/primary.tf @@ -0,0 +1,23 @@ +variable "false_true" { + ephemeral = false +} + +variable "true_false" { + ephemeral = true +} + +variable "false_false_true" { + ephemeral = false +} + +variable "true_true_false" { + ephemeral = true +} + +variable "false_true_false" { + ephemeral = false +} + +variable "true_false_true" { + ephemeral = true +}