diff --git a/internal/configs/module_merge.go b/internal/configs/module_merge.go index 014d2329c..7dc380114 100644 --- a/internal/configs/module_merge.go +++ b/internal/configs/module_merge.go @@ -56,6 +56,10 @@ func (v *Variable) merge(ov *Variable) hcl.Diagnostics { if ov.ParsingMode != 0 { v.ParsingMode = ov.ParsingMode } + if ov.NullableSet { + v.Nullable = ov.Nullable + v.NullableSet = ov.NullableSet + } // If the override file overrode type without default or vice-versa then // it may have created an invalid situation, which we'll catch now by @@ -100,6 +104,16 @@ func (v *Variable) merge(ov *Variable) hcl.Diagnostics { } else { v.Default = val } + + // ensure a null default wasn't merged in when it is not allowed + if !v.Nullable && v.Default.IsNull() { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid default value for variable", + Detail: "A null default value is not valid when nullable=false.", + Subject: &ov.DeclRange, + }) + } } return diags diff --git a/internal/configs/module_merge_test.go b/internal/configs/module_merge_test.go index d6df20381..d95e62c3c 100644 --- a/internal/configs/module_merge_test.go +++ b/internal/configs/module_merge_test.go @@ -24,7 +24,8 @@ func TestModuleOverrideVariable(t *testing.T) { Description: "b_override description", DescriptionSet: true, Default: cty.StringVal("b_override"), - Nullable: true, + Nullable: false, + NullableSet: true, Type: cty.String, ConstraintType: cty.String, ParsingMode: VariableParseLiteral, @@ -48,6 +49,7 @@ func TestModuleOverrideVariable(t *testing.T) { DescriptionSet: true, Default: cty.StringVal("b_override partial"), Nullable: true, + NullableSet: false, Type: cty.String, ConstraintType: cty.String, ParsingMode: VariableParseLiteral, diff --git a/internal/configs/named_values.go b/internal/configs/named_values.go index 8f3db453e..9dcd10b40 100644 --- a/internal/configs/named_values.go +++ b/internal/configs/named_values.go @@ -39,7 +39,8 @@ type Variable struct { // Nullable indicates that null is a valid value for this variable. Setting // Nullable to false means that the module can expect this variable to // never be null. - Nullable bool + Nullable bool + NullableSet bool DeclRange hcl.Range } @@ -118,6 +119,7 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno if attr, exists := content.Attributes["nullable"]; exists { valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Nullable) diags = append(diags, valDiags...) + v.NullableSet = true } else { // The current default is true, which is subject to change in a future // language edition. diff --git a/internal/configs/testdata/valid-modules/override-variable/b_override.tf b/internal/configs/testdata/valid-modules/override-variable/b_override.tf index 21dbe82e9..f09ce5380 100644 --- a/internal/configs/testdata/valid-modules/override-variable/b_override.tf +++ b/internal/configs/testdata/valid-modules/override-variable/b_override.tf @@ -1,4 +1,5 @@ variable "fully_overridden" { + nullable = false default = "b_override" description = "b_override description" type = string