Add input validation into the 'checks' outputs and tracking (#33481)

This commit is contained in:
Liam Cervante 2023-07-10 12:33:45 +02:00 committed by GitHub
parent f74a8d16cf
commit c9bc7e8479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 501 additions and 250 deletions

View File

@ -44,6 +44,12 @@ func (c CheckRule) String() string {
return fmt.Sprintf("%s.postcondition[%d]", container, c.Index) return fmt.Sprintf("%s.postcondition[%d]", container, c.Index)
case OutputPrecondition: case OutputPrecondition:
return fmt.Sprintf("%s.precondition[%d]", container, c.Index) return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
case CheckDataResource:
return fmt.Sprintf("%s.data[%d]", container, c.Index)
case CheckAssertion:
return fmt.Sprintf("%s.assert[%d]", container, c.Index)
case InputValidation:
return fmt.Sprintf("%s.validation[%d]", container, c.Index)
default: default:
// This should not happen // This should not happen
return fmt.Sprintf("%s.condition[%d]", container, c.Index) return fmt.Sprintf("%s.condition[%d]", container, c.Index)
@ -85,6 +91,7 @@ const (
OutputPrecondition CheckRuleType = 3 OutputPrecondition CheckRuleType = 3
CheckDataResource CheckRuleType = 4 CheckDataResource CheckRuleType = 4
CheckAssertion CheckRuleType = 5 CheckAssertion CheckRuleType = 5
InputValidation CheckRuleType = 6
) )
// Description returns a human-readable description of the check type. This is // Description returns a human-readable description of the check type. This is
@ -101,6 +108,8 @@ func (c CheckRuleType) Description() string {
return "Check block data resource" return "Check block data resource"
case CheckAssertion: case CheckAssertion:
return "Check block assertion" return "Check block assertion"
case InputValidation:
return "Input variable validation"
default: default:
// This should not happen // This should not happen
return "Condition" return "Condition"

View File

@ -52,10 +52,11 @@ type CheckableKind rune
//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind checkable.go //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind checkable.go
const ( const (
CheckableKindInvalid CheckableKind = 0 CheckableKindInvalid CheckableKind = 0
CheckableResource CheckableKind = 'R' CheckableResource CheckableKind = 'R'
CheckableOutputValue CheckableKind = 'O' CheckableOutputValue CheckableKind = 'O'
CheckableCheck CheckableKind = 'C' CheckableCheck CheckableKind = 'C'
CheckableInputVariable CheckableKind = 'I'
) )
// ConfigCheckable is an interfaces implemented by address types that represent // ConfigCheckable is an interfaces implemented by address types that represent
@ -179,6 +180,14 @@ func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagn
} }
return Check{Name: name}.Absolute(path), diags return Check{Name: name}.Absolute(path), diags
case CheckableInputVariable:
name, nameDiags := getCheckableName("var", "variable value")
diags = diags.Append(nameDiags)
if diags.HasErrors() {
return nil, diags
}
return InputVariable{Name: name}.Absolute(path), diags
default: default:
panic(fmt.Sprintf("unsupported CheckableKind %s", kind)) panic(fmt.Sprintf("unsupported CheckableKind %s", kind))
} }

View File

@ -12,13 +12,15 @@ func _() {
_ = x[CheckableResource-82] _ = x[CheckableResource-82]
_ = x[CheckableOutputValue-79] _ = x[CheckableOutputValue-79]
_ = x[CheckableCheck-67] _ = x[CheckableCheck-67]
_ = x[CheckableInputVariable-73]
} }
const ( const (
_CheckableKind_name_0 = "CheckableKindInvalid" _CheckableKind_name_0 = "CheckableKindInvalid"
_CheckableKind_name_1 = "CheckableCheck" _CheckableKind_name_1 = "CheckableCheck"
_CheckableKind_name_2 = "CheckableOutputValue" _CheckableKind_name_2 = "CheckableInputVariable"
_CheckableKind_name_3 = "CheckableResource" _CheckableKind_name_3 = "CheckableOutputValue"
_CheckableKind_name_4 = "CheckableResource"
) )
func (i CheckableKind) String() string { func (i CheckableKind) String() string {
@ -27,10 +29,12 @@ func (i CheckableKind) String() string {
return _CheckableKind_name_0 return _CheckableKind_name_0
case i == 67: case i == 67:
return _CheckableKind_name_1 return _CheckableKind_name_1
case i == 79: case i == 73:
return _CheckableKind_name_2 return _CheckableKind_name_2
case i == 82: case i == 79:
return _CheckableKind_name_3 return _CheckableKind_name_3
case i == 82:
return _CheckableKind_name_4
default: default:
return "CheckableKind(" + strconv.FormatInt(int64(i), 10) + ")" return "CheckableKind(" + strconv.FormatInt(int64(i), 10) + ")"
} }

View File

@ -14,11 +14,12 @@ func _() {
_ = x[OutputPrecondition-3] _ = x[OutputPrecondition-3]
_ = x[CheckDataResource-4] _ = x[CheckDataResource-4]
_ = x[CheckAssertion-5] _ = x[CheckAssertion-5]
_ = x[InputValidation-6]
} }
const _CheckRuleType_name = "InvalidConditionResourcePreconditionResourcePostconditionOutputPreconditionCheckDataResourceCheckAssertion" const _CheckRuleType_name = "InvalidConditionResourcePreconditionResourcePostconditionOutputPreconditionCheckDataResourceCheckAssertionInputValidation"
var _CheckRuleType_index = [...]uint8{0, 16, 36, 57, 75, 92, 106} var _CheckRuleType_index = [...]uint8{0, 16, 36, 57, 75, 92, 106, 121}
func (i CheckRuleType) String() string { func (i CheckRuleType) String() string {
if i < 0 || i >= CheckRuleType(len(_CheckRuleType_index)-1) { if i < 0 || i >= CheckRuleType(len(_CheckRuleType_index)-1) {

View File

@ -32,6 +32,13 @@ func (v InputVariable) Absolute(m ModuleInstance) AbsInputVariableInstance {
} }
} }
func (v InputVariable) InModule(module Module) ConfigInputVariable {
return ConfigInputVariable{
Module: module,
Variable: v,
}
}
// AbsInputVariableInstance is the address of an input variable within a // AbsInputVariableInstance is the address of an input variable within a
// particular module instance. // particular module instance.
type AbsInputVariableInstance struct { type AbsInputVariableInstance struct {
@ -39,6 +46,8 @@ type AbsInputVariableInstance struct {
Variable InputVariable Variable InputVariable
} }
var _ Checkable = AbsInputVariableInstance{}
// InputVariable returns the absolute address of the input variable of the // InputVariable returns the absolute address of the input variable of the
// given name inside the receiving module instance. // given name inside the receiving module instance.
func (m ModuleInstance) InputVariable(name string) AbsInputVariableInstance { func (m ModuleInstance) InputVariable(name string) AbsInputVariableInstance {
@ -57,3 +66,61 @@ func (v AbsInputVariableInstance) String() string {
return fmt.Sprintf("%s.%s", v.Module.String(), v.Variable.String()) return fmt.Sprintf("%s.%s", v.Module.String(), v.Variable.String())
} }
func (v AbsInputVariableInstance) UniqueKey() UniqueKey {
return absInputVariableInstanceUniqueKey(v.String())
}
func (v AbsInputVariableInstance) checkableSigil() {}
func (v AbsInputVariableInstance) CheckRule(typ CheckRuleType, i int) CheckRule {
return CheckRule{
Container: v,
Type: typ,
Index: i,
}
}
func (v AbsInputVariableInstance) ConfigCheckable() ConfigCheckable {
return ConfigInputVariable{
Module: v.Module.Module(),
Variable: v.Variable,
}
}
func (v AbsInputVariableInstance) CheckableKind() CheckableKind {
return CheckableInputVariable
}
type ConfigInputVariable struct {
Module Module
Variable InputVariable
}
var _ ConfigCheckable = ConfigInputVariable{}
func (v ConfigInputVariable) UniqueKey() UniqueKey {
return configInputVariableUniqueKey(v.String())
}
func (v ConfigInputVariable) configCheckableSigil() {}
func (v ConfigInputVariable) CheckableKind() CheckableKind {
return CheckableInputVariable
}
func (v ConfigInputVariable) String() string {
if len(v.Module) == 0 {
return v.Variable.String()
}
return fmt.Sprintf("%s.%s", v.Module.String(), v.Variable.String())
}
type configInputVariableUniqueKey string
func (k configInputVariableUniqueKey) uniqueKeySigil() {}
type absInputVariableInstanceUniqueKey string
func (k absInputVariableInstanceUniqueKey) uniqueKeySigil() {}

View File

@ -68,6 +68,22 @@ func collectInitialStatuses(into addrs.Map[addrs.ConfigCheckable, *configCheckab
into.Put(addr, st) into.Put(addr, st)
} }
for _, v := range cfg.Module.Variables {
addr := v.Addr().InModule(moduleAddr)
vs := len(v.Validations)
if vs == 0 {
continue
}
st := &configCheckableState{}
st.checkTypes = map[addrs.CheckRuleType]int{
addrs.InputValidation: vs,
}
into.Put(addr, st)
}
// Must also visit child modules to collect everything // Must also visit child modules to collect everything
for _, child := range cfg.Children { for _, child := range cfg.Children {
collectInitialStatuses(into, child) collectInitialStatuses(into, child)

View File

@ -59,6 +59,17 @@ func makeStaticObjectAddr(addr addrs.ConfigCheckable) staticObjectAddr {
if !addr.Module.IsRoot() { if !addr.Module.IsRoot() {
ret["module"] = addr.Module.String() ret["module"] = addr.Module.String()
} }
case addrs.ConfigInputVariable:
if kind := addr.CheckableKind(); kind != addrs.CheckableInputVariable {
// Something has gone very wrong
panic(fmt.Sprintf("%T has CheckableKind %s", addr, kind))
}
ret["kind"] = "var"
ret["name"] = addr.Variable.Name
if !addr.Module.IsRoot() {
ret["module"] = addr.Module.String()
}
default: default:
panic(fmt.Sprintf("unsupported ConfigCheckable implementation %T", addr)) panic(fmt.Sprintf("unsupported ConfigCheckable implementation %T", addr))
} }
@ -89,6 +100,10 @@ func makeDynamicObjectAddr(addr addrs.Checkable) dynamicObjectAddr {
if !addr.Module.IsRoot() { if !addr.Module.IsRoot() {
ret["module"] = addr.Module.String() ret["module"] = addr.Module.String()
} }
case addrs.AbsInputVariableInstance:
if !addr.Module.IsRoot() {
ret["module"] = addr.Module.String()
}
default: default:
panic(fmt.Sprintf("unsupported Checkable implementation %T", addr)) panic(fmt.Sprintf("unsupported Checkable implementation %T", addr))
} }

View File

@ -279,10 +279,11 @@ func (CheckResults_Status) EnumDescriptor() ([]byte, []int) {
type CheckResults_ObjectKind int32 type CheckResults_ObjectKind int32
const ( const (
CheckResults_UNSPECIFIED CheckResults_ObjectKind = 0 CheckResults_UNSPECIFIED CheckResults_ObjectKind = 0
CheckResults_RESOURCE CheckResults_ObjectKind = 1 CheckResults_RESOURCE CheckResults_ObjectKind = 1
CheckResults_OUTPUT_VALUE CheckResults_ObjectKind = 2 CheckResults_OUTPUT_VALUE CheckResults_ObjectKind = 2
CheckResults_CHECK CheckResults_ObjectKind = 3 CheckResults_CHECK CheckResults_ObjectKind = 3
CheckResults_INPUT_VARIABLE CheckResults_ObjectKind = 4
) )
// Enum value maps for CheckResults_ObjectKind. // Enum value maps for CheckResults_ObjectKind.
@ -292,12 +293,14 @@ var (
1: "RESOURCE", 1: "RESOURCE",
2: "OUTPUT_VALUE", 2: "OUTPUT_VALUE",
3: "CHECK", 3: "CHECK",
4: "INPUT_VARIABLE",
} }
CheckResults_ObjectKind_value = map[string]int32{ CheckResults_ObjectKind_value = map[string]int32{
"UNSPECIFIED": 0, "UNSPECIFIED": 0,
"RESOURCE": 1, "RESOURCE": 1,
"OUTPUT_VALUE": 2, "OUTPUT_VALUE": 2,
"CHECK": 3, "CHECK": 3,
"INPUT_VARIABLE": 4,
} }
) )
@ -1449,7 +1452,7 @@ var file_planfile_proto_rawDesc = []byte{
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65,
0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73,
0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xe8, 0x03, 0x0a, 0x0c, 0x43, 0x68, 0x65, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x6b, 0x69, 0x6e,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62,
@ -1475,70 +1478,72 @@ var file_planfile_proto_rawDesc = []byte{
0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a,
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x41, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x41,
0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x09, 0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x09,
0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x48, 0x0a, 0x0a, 0x4f, 0x62, 0x6a, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x5c, 0x0a, 0x0a, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45,
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x4f, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x4f,
0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54,
0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x45, 0x43, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x45, 0x43,
0x4b, 0x10, 0x03, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x4b, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x56, 0x41, 0x52,
0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x04, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x22, 0xa5, 0x01, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61,
0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c,
0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74,
0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61,
0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c,
0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48,
0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b, 0x0a, 0x09, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a,
0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b, 0x0a, 0x09, 0x49, 0x6d, 0x70,
0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a,
0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45,
0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, 0x45,
0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, 0x74,
0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a,
0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41,
0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12,
0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44,
0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54,
0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x2a, 0xc8, 0x03, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48,
0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x2a, 0xc8, 0x03, 0x0a, 0x1c,
0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04,
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43,
0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x52, 0x45, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, 0x45,
0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42,
0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x4e, 0x4f, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52,
0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43,
0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x25,
0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x0a, 0x21, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45,
0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e,
0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x54, 0x49, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f,
0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, 0x45,
0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x50, 0x45, 0x54, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, 0x45,
0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x55,
0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x4b, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45,
0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, 0x43,
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54,
0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44,
0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45,
0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, 0x1f,
0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43,
0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x45, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, 0x12,
0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x0b, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f,
0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x44, 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49,
0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0d, 0x12, 0x4e, 0x47, 0x10, 0x0b, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43,
0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45,
0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x44, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45,
0x10, 0x0c, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54, 0x41,
0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x52, 0x47, 0x45, 0x54, 0x10, 0x0c, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74,
0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
} }
var ( var (

View File

@ -253,6 +253,7 @@ message CheckResults {
RESOURCE = 1; RESOURCE = 1;
OUTPUT_VALUE = 2; OUTPUT_VALUE = 2;
CHECK = 3; CHECK = 3;
INPUT_VARIABLE = 4;
} }
message ObjectResult { message ObjectResult {

View File

@ -120,6 +120,8 @@ func readTfplan(r io.Reader) (*plans.Plan, error) {
objKind = addrs.CheckableOutputValue objKind = addrs.CheckableOutputValue
case planproto.CheckResults_CHECK: case planproto.CheckResults_CHECK:
objKind = addrs.CheckableCheck objKind = addrs.CheckableCheck
case planproto.CheckResults_INPUT_VARIABLE:
objKind = addrs.CheckableInputVariable
default: default:
return nil, fmt.Errorf("aggregate check results for %s have unsupported object kind %s", rawCRs.ConfigAddr, objKind) return nil, fmt.Errorf("aggregate check results for %s have unsupported object kind %s", rawCRs.ConfigAddr, objKind)
} }
@ -545,6 +547,8 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error {
pcrs.Kind = planproto.CheckResults_OUTPUT_VALUE pcrs.Kind = planproto.CheckResults_OUTPUT_VALUE
case addrs.CheckableCheck: case addrs.CheckableCheck:
pcrs.Kind = planproto.CheckResults_CHECK pcrs.Kind = planproto.CheckResults_CHECK
case addrs.CheckableInputVariable:
pcrs.Kind = planproto.CheckResults_INPUT_VARIABLE
default: default:
return fmt.Errorf("checkable configuration %s has unsupported object type kind %s", configElem.Key, kind) return fmt.Errorf("checkable configuration %s has unsupported object type kind %s", configElem.Key, kind)
} }

View File

@ -643,6 +643,8 @@ func decodeCheckableObjectKindV4(in string) addrs.CheckableKind {
return addrs.CheckableOutputValue return addrs.CheckableOutputValue
case "check": case "check":
return addrs.CheckableCheck return addrs.CheckableCheck
case "var":
return addrs.CheckableInputVariable
default: default:
// We'll treat anything else as invalid just as a concession to // We'll treat anything else as invalid just as a concession to
// forward-compatible parsing, in case a later version of Terraform // forward-compatible parsing, in case a later version of Terraform
@ -659,6 +661,8 @@ func encodeCheckableObjectKindV4(in addrs.CheckableKind) string {
return "output" return "output"
case addrs.CheckableCheck: case addrs.CheckableCheck:
return "check" return "check"
case addrs.CheckableInputVariable:
return "var"
default: default:
panic(fmt.Sprintf("unsupported checkable object kind %s", in)) panic(fmt.Sprintf("unsupported checkable object kind %s", in))
} }

View File

@ -10,12 +10,14 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/gohcl"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/tfdiags" "github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
) )
func prepareFinalInputVariableValue(addr addrs.AbsInputVariableInstance, raw *InputValue, cfg *configs.Variable) (cty.Value, tfdiags.Diagnostics) { func prepareFinalInputVariableValue(addr addrs.AbsInputVariableInstance, raw *InputValue, cfg *configs.Variable) (cty.Value, tfdiags.Diagnostics) {
@ -202,6 +204,16 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
} }
log.Printf("[TRACE] evalVariableValidations: validating %s", addr) log.Printf("[TRACE] evalVariableValidations: validating %s", addr)
checkState := ctx.Checks()
if !checkState.ConfigHasChecks(addr.ConfigCheckable()) {
// We have nothing to do if this object doesn't have any checks,
// but the "rules" slice should agree that we don't.
if ct := len(config.Validations); ct != 0 {
panic(fmt.Sprintf("check state says that %s should have no rules, but it has %d", addr, ct))
}
return diags
}
// Variable nodes evaluate in the parent module to where they were declared // Variable nodes evaluate in the parent module to where they were declared
// because the value expression (n.Expr, if set) comes from the calling // because the value expression (n.Expr, if set) comes from the calling
// "module" block in the parent module. // "module" block in the parent module.
@ -229,169 +241,186 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
Functions: ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).Functions(), Functions: ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).Functions(),
} }
for _, validation := range config.Validations { for ix, validation := range config.Validations {
const errInvalidCondition = "Invalid variable validation result" result, ruleDiags := evalVariableValidation(validation, hclCtx, addr, config, expr)
const errInvalidValue = "Invalid value for variable"
var ruleDiags tfdiags.Diagnostics
result, moreDiags := validation.Condition.Value(hclCtx)
ruleDiags = ruleDiags.Append(moreDiags)
errorValue, errorDiags := validation.ErrorMessage.Value(hclCtx)
// The following error handling is a workaround to preserve backwards
// compatibility. Due to an implementation quirk, all prior versions of
// Terraform would treat error messages specified using JSON
// configuration syntax (.tf.json) as string literals, even if they
// contained the "${" template expression operator. This behaviour did
// not match that of HCL configuration syntax, where a template
// expression would result in a validation error.
//
// As a result, users writing or generating JSON configuration syntax
// may have specified error messages which are invalid template
// expressions. As we add support for error message expressions, we are
// unable to perfectly distinguish between these two cases.
//
// To ensure that we don't break backwards compatibility, we have the
// below fallback logic if the error message fails to evaluate. This
// should only have any effect for JSON configurations. The gohcl
// DecodeExpression function behaves differently when the source of the
// expression is a JSON configuration file and a nil context is passed.
if errorDiags.HasErrors() {
// Attempt to decode the expression as a string literal. Passing
// nil as the context forces a JSON syntax string value to be
// interpreted as a string literal.
var errorString string
moreErrorDiags := gohcl.DecodeExpression(validation.ErrorMessage, nil, &errorString)
if !moreErrorDiags.HasErrors() {
// Decoding succeeded, meaning that this is a JSON syntax
// string value. We rewrap that as a cty value to allow later
// decoding to succeed.
errorValue = cty.StringVal(errorString)
// This warning diagnostic explains this odd behaviour, while
// giving us an escape hatch to change this to a hard failure
// in some future Terraform 1.x version.
errorDiags = hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Validation error message expression is invalid",
Detail: fmt.Sprintf("The error message provided could not be evaluated as an expression, so Terraform is interpreting it as a string literal.\n\nIn future versions of Terraform, this will be considered an error. Please file a GitHub issue if this would break your workflow.\n\n%s", errorDiags.Error()),
Subject: validation.ErrorMessage.Range().Ptr(),
Context: validation.DeclRange.Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
},
}
}
// We want to either report the original diagnostics if the
// fallback failed, or the warning generated above if it succeeded.
ruleDiags = ruleDiags.Append(errorDiags)
}
diags = diags.Append(ruleDiags) diags = diags.Append(ruleDiags)
if ruleDiags.HasErrors() { log.Printf("[TRACE] evalVariableValidations: %s status is now %s", addr, result.Status)
log.Printf("[TRACE] evalVariableValidations: %s rule %s check rule evaluation failed: %s", addr, validation.DeclRange, ruleDiags.Err().Error()) if result.Status == checks.StatusFail {
} checkState.ReportCheckFailure(addr, addrs.InputValidation, ix, result.FailureMessage)
if !result.IsKnown() {
log.Printf("[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now", addr, validation.DeclRange)
continue // We'll wait until we've learned more, then.
}
if result.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidCondition,
Detail: "Validation condition expression must return either true or false, not null.",
Subject: validation.Condition.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
continue
}
var err error
result, err = convert.Convert(result, cty.Bool)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidCondition,
Detail: fmt.Sprintf("Invalid validation condition result value: %s.", tfdiags.FormatError(err)),
Subject: validation.Condition.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
continue
}
// Validation condition may be marked if the input variable is bound to
// a sensitive value. This is irrelevant to the validation process, so
// we discard the marks now.
result, _ = result.Unmark()
if result.True() {
continue
}
var errorMessage string
if !errorDiags.HasErrors() && errorValue.IsKnown() && !errorValue.IsNull() {
var err error
errorValue, err = convert.Convert(errorValue, cty.String)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid error message",
Detail: fmt.Sprintf("Unsuitable value for error message: %s.", tfdiags.FormatError(err)),
Subject: validation.ErrorMessage.Range().Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
})
} else {
if marks.Has(errorValue, marks.Sensitive) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Error message refers to sensitive values",
Detail: `The error expression used to explain this condition refers to sensitive values. Terraform will not display the resulting message.
You can correct this by removing references to sensitive values, or by carefully using the nonsensitive() function if the expression will not reveal the sensitive data.`,
Subject: validation.ErrorMessage.Range().Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
})
errorMessage = "The error message included a sensitive value, so it will not be displayed."
} else {
errorMessage = strings.TrimSpace(errorValue.AsString())
}
}
}
if errorMessage == "" {
errorMessage = "Failed to evaluate condition error message."
}
if expr != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidValue,
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", errorMessage, validation.DeclRange.String()),
Subject: expr.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
} else { } else {
// Since we don't have a source expression for a root module checkState.ReportCheckResult(addr, addrs.InputValidation, ix, result.Status)
// variable, we'll just report the error from the perspective
// of the variable declaration itself.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidValue,
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", errorMessage, validation.DeclRange.String()),
Subject: config.DeclRange.Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
} }
} }
return diags return diags
} }
func evalVariableValidation(validation *configs.CheckRule, hclCtx *hcl.EvalContext, addr addrs.AbsInputVariableInstance, config *configs.Variable, expr hcl.Expression) (checkResult, tfdiags.Diagnostics) {
const errInvalidCondition = "Invalid variable validation result"
const errInvalidValue = "Invalid value for variable"
var diags tfdiags.Diagnostics
result, moreDiags := validation.Condition.Value(hclCtx)
diags = diags.Append(moreDiags)
errorValue, errorDiags := validation.ErrorMessage.Value(hclCtx)
// The following error handling is a workaround to preserve backwards
// compatibility. Due to an implementation quirk, all prior versions of
// Terraform would treat error messages specified using JSON
// configuration syntax (.tf.json) as string literals, even if they
// contained the "${" template expression operator. This behaviour did
// not match that of HCL configuration syntax, where a template
// expression would result in a validation error.
//
// As a result, users writing or generating JSON configuration syntax
// may have specified error messages which are invalid template
// expressions. As we add support for error message expressions, we are
// unable to perfectly distinguish between these two cases.
//
// To ensure that we don't break backwards compatibility, we have the
// below fallback logic if the error message fails to evaluate. This
// should only have any effect for JSON configurations. The gohcl
// DecodeExpression function behaves differently when the source of the
// expression is a JSON configuration file and a nil context is passed.
if errorDiags.HasErrors() {
// Attempt to decode the expression as a string literal. Passing
// nil as the context forces a JSON syntax string value to be
// interpreted as a string literal.
var errorString string
moreErrorDiags := gohcl.DecodeExpression(validation.ErrorMessage, nil, &errorString)
if !moreErrorDiags.HasErrors() {
// Decoding succeeded, meaning that this is a JSON syntax
// string value. We rewrap that as a cty value to allow later
// decoding to succeed.
errorValue = cty.StringVal(errorString)
// This warning diagnostic explains this odd behaviour, while
// giving us an escape hatch to change this to a hard failure
// in some future Terraform 1.x version.
errorDiags = hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Validation error message expression is invalid",
Detail: fmt.Sprintf("The error message provided could not be evaluated as an expression, so Terraform is interpreting it as a string literal.\n\nIn future versions of Terraform, this will be considered an error. Please file a GitHub issue if this would break your workflow.\n\n%s", errorDiags.Error()),
Subject: validation.ErrorMessage.Range().Ptr(),
Context: validation.DeclRange.Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
},
}
}
// We want to either report the original diagnostics if the
// fallback failed, or the warning generated above if it succeeded.
diags = diags.Append(errorDiags)
}
if diags.HasErrors() {
log.Printf("[TRACE] evalVariableValidations: %s rule %s check rule evaluation failed: %s", addr, validation.DeclRange, diags.Err().Error())
}
if !result.IsKnown() {
log.Printf("[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now", addr, validation.DeclRange)
return checkResult{Status: checks.StatusUnknown}, diags // We'll wait until we've learned more, then.
}
if result.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidCondition,
Detail: "Validation condition expression must return either true or false, not null.",
Subject: validation.Condition.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
return checkResult{Status: checks.StatusError}, diags
}
var err error
result, err = convert.Convert(result, cty.Bool)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidCondition,
Detail: fmt.Sprintf("Invalid validation condition result value: %s.", tfdiags.FormatError(err)),
Subject: validation.Condition.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
return checkResult{Status: checks.StatusError}, diags
}
// Validation condition may be marked if the input variable is bound to
// a sensitive value. This is irrelevant to the validation process, so
// we discard the marks now.
result, _ = result.Unmark()
status := checks.StatusForCtyValue(result)
if status != checks.StatusFail {
return checkResult{Status: status}, diags
}
var errorMessage string
if !errorDiags.HasErrors() && errorValue.IsKnown() && !errorValue.IsNull() {
var err error
errorValue, err = convert.Convert(errorValue, cty.String)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid error message",
Detail: fmt.Sprintf("Unsuitable value for error message: %s.", tfdiags.FormatError(err)),
Subject: validation.ErrorMessage.Range().Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
})
} else {
if marks.Has(errorValue, marks.Sensitive) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Error message refers to sensitive values",
Detail: `The error expression used to explain this condition refers to sensitive values. Terraform will not display the resulting message.
You can correct this by removing references to sensitive values, or by carefully using the nonsensitive() function if the expression will not reveal the sensitive data.`,
Subject: validation.ErrorMessage.Range().Ptr(),
Expression: validation.ErrorMessage,
EvalContext: hclCtx,
})
errorMessage = "The error message included a sensitive value, so it will not be displayed."
} else {
errorMessage = strings.TrimSpace(errorValue.AsString())
}
}
}
if errorMessage == "" {
errorMessage = "Failed to evaluate condition error message."
}
if expr != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidValue,
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", errorMessage, validation.DeclRange.String()),
Subject: expr.Range().Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
} else {
// Since we don't have a source expression for a root module
// variable, we'll just report the error from the perspective
// of the variable declaration itself.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errInvalidValue,
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", errorMessage, validation.DeclRange.String()),
Subject: config.DeclRange.Ptr(),
Expression: validation.Condition,
EvalContext: hclCtx,
})
}
return checkResult{
Status: status,
FailureMessage: errorMessage,
}, diags
}

View File

@ -12,6 +12,7 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/tfdiags" "github.com/hashicorp/terraform/internal/tfdiags"
@ -1107,12 +1108,14 @@ func TestEvalVariableValidations_jsonErrorMessageEdgeCase(t *testing.T) {
given cty.Value given cty.Value
wantErr []string wantErr []string
wantWarn []string wantWarn []string
status checks.Status
}{ }{
// Valid variable validation declaration, assigned value which passes // Valid variable validation declaration, assigned value which passes
// the condition generates no diagnostics. // the condition generates no diagnostics.
{ {
varName: "valid", varName: "valid",
given: cty.StringVal("foo"), given: cty.StringVal("foo"),
status: checks.StatusPass,
}, },
// Assigning a value which fails the condition generates an error // Assigning a value which fails the condition generates an error
// message with the expression successfully evaluated. // message with the expression successfully evaluated.
@ -1123,6 +1126,7 @@ func TestEvalVariableValidations_jsonErrorMessageEdgeCase(t *testing.T) {
"Invalid value for variable", "Invalid value for variable",
"Valid template string bar", "Valid template string bar",
}, },
status: checks.StatusFail,
}, },
// Invalid variable validation declaration due to an unparseable // Invalid variable validation declaration due to an unparseable
// template string. Assigning a value which passes the condition // template string. Assigning a value which passes the condition
@ -1134,6 +1138,7 @@ func TestEvalVariableValidations_jsonErrorMessageEdgeCase(t *testing.T) {
"Validation error message expression is invalid", "Validation error message expression is invalid",
"Missing expression; Expected the start of an expression, but found the end of the file.", "Missing expression; Expected the start of an expression, but found the end of the file.",
}, },
status: checks.StatusPass,
}, },
// Assigning a value which fails the condition generates an error // Assigning a value which fails the condition generates an error
// message including the configured string interpreted as a literal // message including the configured string interpreted as a literal
@ -1149,6 +1154,7 @@ func TestEvalVariableValidations_jsonErrorMessageEdgeCase(t *testing.T) {
"Validation error message expression is invalid", "Validation error message expression is invalid",
"Missing expression; Expected the start of an expression, but found the end of the file.", "Missing expression; Expected the start of an expression, but found the end of the file.",
}, },
status: checks.StatusFail,
}, },
} }
@ -1173,11 +1179,17 @@ func TestEvalVariableValidations_jsonErrorMessageEdgeCase(t *testing.T) {
} }
return test.given return test.given
} }
ctx.ChecksState = checks.NewState(cfg)
ctx.ChecksState.ReportCheckableObjects(varAddr.ConfigCheckable(), addrs.MakeSet[addrs.Checkable](varAddr))
gotDiags := evalVariableValidations( gotDiags := evalVariableValidations(
varAddr, varCfg, nil, ctx, varAddr, varCfg, nil, ctx,
) )
if ctx.ChecksState.ObjectCheckStatus(varAddr) != test.status {
t.Errorf("expected check result %s but instead %s", test.status, ctx.ChecksState.ObjectCheckStatus(varAddr))
}
if len(test.wantErr) == 0 && len(test.wantWarn) == 0 { if len(test.wantErr) == 0 && len(test.wantWarn) == 0 {
if len(gotDiags) > 0 { if len(gotDiags) > 0 {
t.Errorf("no diags expected, got %s", gotDiags.Err().Error()) t.Errorf("no diags expected, got %s", gotDiags.Err().Error())
@ -1258,12 +1270,14 @@ variable "bar" {
varName string varName string
given cty.Value given cty.Value
wantErr []string wantErr []string
status checks.Status
}{ }{
// Validations pass on a sensitive variable with an error message which // Validations pass on a sensitive variable with an error message which
// would generate a sensitive value // would generate a sensitive value
{ {
varName: "foo", varName: "foo",
given: cty.StringVal("boop"), given: cty.StringVal("boop"),
status: checks.StatusPass,
}, },
// Assigning a value which fails the condition generates a sensitive // Assigning a value which fails the condition generates a sensitive
// error message, which is elided and generates another error // error message, which is elided and generates another error
@ -1275,12 +1289,14 @@ variable "bar" {
"The error message included a sensitive value, so it will not be displayed.", "The error message included a sensitive value, so it will not be displayed.",
"Error message refers to sensitive values", "Error message refers to sensitive values",
}, },
status: checks.StatusFail,
}, },
// Validations pass on a sensitive variable with a correctly defined // Validations pass on a sensitive variable with a correctly defined
// error message // error message
{ {
varName: "bar", varName: "bar",
given: cty.StringVal("boop"), given: cty.StringVal("boop"),
status: checks.StatusPass,
}, },
// Assigning a value which fails the condition generates a nonsensitive // Assigning a value which fails the condition generates a nonsensitive
// error message, which is displayed // error message, which is displayed
@ -1291,6 +1307,7 @@ variable "bar" {
"Invalid value for variable", "Invalid value for variable",
"Bar must be 4 characters, not 3.", "Bar must be 4 characters, not 3.",
}, },
status: checks.StatusFail,
}, },
} }
@ -1319,11 +1336,17 @@ variable "bar" {
return test.given return test.given
} }
} }
ctx.ChecksState = checks.NewState(cfg)
ctx.ChecksState.ReportCheckableObjects(varAddr.ConfigCheckable(), addrs.MakeSet[addrs.Checkable](varAddr))
gotDiags := evalVariableValidations( gotDiags := evalVariableValidations(
varAddr, varCfg, nil, ctx, varAddr, varCfg, nil, ctx,
) )
if ctx.ChecksState.ObjectCheckStatus(varAddr) != test.status {
t.Errorf("expected check result %s but instead %s", test.status, ctx.ChecksState.ObjectCheckStatus(varAddr))
}
if len(test.wantErr) == 0 { if len(test.wantErr) == 0 {
if len(gotDiags) > 0 { if len(gotDiags) > 0 {
t.Errorf("no diags expected, got %s", gotDiags.Err().Error()) t.Errorf("no diags expected, got %s", gotDiags.Err().Error())

View File

@ -67,8 +67,8 @@ func (b *EvalGraphBuilder) Steps() []GraphTransformer {
}, },
// Add dynamic values // Add dynamic values
&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues}, &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues, Planning: true},
&ModuleVariableTransformer{Config: b.Config}, &ModuleVariableTransformer{Config: b.Config, Planning: true},
&LocalTransformer{Config: b.Config}, &LocalTransformer{Config: b.Config},
&OutputTransformer{ &OutputTransformer{
Config: b.Config, Config: b.Config,

View File

@ -128,8 +128,8 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
}, },
// Add dynamic values // Add dynamic values
&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues}, &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues, Planning: true},
&ModuleVariableTransformer{Config: b.Config}, &ModuleVariableTransformer{Config: b.Config, Planning: true},
&LocalTransformer{Config: b.Config}, &LocalTransformer{Config: b.Config},
&OutputTransformer{ &OutputTransformer{
Config: b.Config, Config: b.Config,

View File

@ -25,6 +25,10 @@ type nodeExpandModuleVariable struct {
Module addrs.Module Module addrs.Module
Config *configs.Variable Config *configs.Variable
Expr hcl.Expression Expr hcl.Expression
// Planning must be set to true when building a planning graph, and must be
// false when building an apply graph.
Planning bool
} }
var ( var (
@ -44,10 +48,27 @@ func (n *nodeExpandModuleVariable) temporaryValue() bool {
func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) { func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) {
var g Graph var g Graph
// If this variable has preconditions, we need to report these checks now.
//
// We should only do this during planning as the apply phase starts with
// all the same checkable objects that were registered during the plan.
var checkableAddrs addrs.Set[addrs.Checkable]
if n.Planning {
if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(n.Module)) {
checkableAddrs = addrs.MakeSet[addrs.Checkable]()
}
}
expander := ctx.InstanceExpander() expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(n.Module) { for _, module := range expander.ExpandModule(n.Module) {
addr := n.Addr.Absolute(module)
if checkableAddrs != nil {
checkableAddrs.Add(addr)
}
o := &nodeModuleVariable{ o := &nodeModuleVariable{
Addr: n.Addr.Absolute(module), Addr: addr,
Config: n.Config, Config: n.Config,
Expr: n.Expr, Expr: n.Expr,
ModuleInstance: module, ModuleInstance: module,
@ -55,6 +76,11 @@ func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error
g.Add(o) g.Add(o)
} }
addRootNodeToGraph(&g) addRootNodeToGraph(&g)
if checkableAddrs != nil {
ctx.Checks().ReportCheckableObjects(n.Addr.InModule(n.Module), checkableAddrs)
}
return &g, nil return &g, nil
} }

View File

@ -6,11 +6,12 @@ package terraform
import ( import (
"log" "log"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag" "github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/tfdiags" "github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
) )
// NodeRootVariable represents a root variable input. // NodeRootVariable represents a root variable input.
@ -24,6 +25,10 @@ type NodeRootVariable struct {
// converted or validated, and can be nil for a variable that isn't // converted or validated, and can be nil for a variable that isn't
// set at all. // set at all.
RawValue *InputValue RawValue *InputValue
// Planning must be set to true when building a planning graph, and must be
// false when building an apply graph.
Planning bool
} }
var ( var (
@ -82,6 +87,14 @@ func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Di
} }
} }
if n.Planning {
if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(addrs.RootModule)) {
ctx.Checks().ReportCheckableObjects(
n.Addr.InModule(addrs.RootModule),
addrs.MakeSet[addrs.Checkable](n.Addr.Absolute(addrs.RootModuleInstance)))
}
}
finalVal, moreDiags := prepareFinalInputVariableValue( finalVal, moreDiags := prepareFinalInputVariableValue(
addr, addr,
givenVal, givenVal,

View File

@ -11,6 +11,7 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/lang"
) )
@ -115,8 +116,17 @@ func TestNodeRootVariableExecute(t *testing.T) {
Value: cty.StringVal("5"), Value: cty.StringVal("5"),
SourceType: ValueFromUnknown, SourceType: ValueFromUnknown,
}, },
Planning: true,
} }
ctx.ChecksState = checks.NewState(&configs.Config{
Module: &configs.Module{
Variables: map[string]*configs.Variable{
"foo": n.Config,
},
},
})
diags := n.Execute(ctx, walkApply) diags := n.Execute(ctx, walkApply)
if diags.HasErrors() { if diags.HasErrors() {
t.Fatalf("unexpected error: %s", diags.Err()) t.Fatalf("unexpected error: %s", diags.Err())
@ -134,6 +144,9 @@ func TestNodeRootVariableExecute(t *testing.T) {
// as part of preparing the "final value". // as part of preparing the "final value".
t.Errorf("wrong value for ctx.SetRootModuleArgument\ngot: %#v\nwant: %#v", got, want) t.Errorf("wrong value for ctx.SetRootModuleArgument\ngot: %#v\nwant: %#v", got, want)
} }
if status := ctx.Checks().ObjectCheckStatus(n.Addr.Absolute(addrs.RootModuleInstance)); status != checks.StatusPass {
t.Errorf("expected checks to pass but go %s instead", status)
}
}) })
} }

View File

@ -6,11 +6,13 @@ package terraform
import ( import (
"fmt" "fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs"
) )
@ -26,6 +28,10 @@ import (
// steps for validating module blocks, separate from this transform. // steps for validating module blocks, separate from this transform.
type ModuleVariableTransformer struct { type ModuleVariableTransformer struct {
Config *configs.Config Config *configs.Config
// Planning must be set to true when building a planning graph, and must be
// false when building an apply graph.
Planning bool
} }
func (t *ModuleVariableTransformer) Transform(g *Graph) error { func (t *ModuleVariableTransformer) Transform(g *Graph) error {
@ -104,9 +110,10 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs
Addr: addrs.InputVariable{ Addr: addrs.InputVariable{
Name: v.Name, Name: v.Name,
}, },
Module: c.Path, Module: c.Path,
Config: v, Config: v,
Expr: expr, Expr: expr,
Planning: t.Planning,
} }
g.Add(node) g.Add(node)
} }

View File

@ -18,6 +18,10 @@ type RootVariableTransformer struct {
Config *configs.Config Config *configs.Config
RawValues InputValues RawValues InputValues
// Planning must be set to true when building a planning graph, and must be
// false when building an apply graph.
Planning bool
} }
func (t *RootVariableTransformer) Transform(g *Graph) error { func (t *RootVariableTransformer) Transform(g *Graph) error {
@ -38,6 +42,7 @@ func (t *RootVariableTransformer) Transform(g *Graph) error {
}, },
Config: v, Config: v,
RawValue: t.RawValues[v.Name], RawValue: t.RawValues[v.Name],
Planning: t.Planning,
} }
g.Add(node) g.Add(node)
} }