Skip to content

Commit 20574d4

Browse files
authored
validation: don't strip marks from variables during validation (#37595)
* validation: don't strip marks from variables during validation * add regression test
1 parent 8097374 commit 20574d4

File tree

4 files changed

+79
-26
lines changed

4 files changed

+79
-26
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'variable validation: keep sensitive and ephemeral metadata when evaluating variable conditions.'
3+
time: 2025-09-11T14:20:38.411183+02:00
4+
custom:
5+
Issue: "37595"

internal/terraform/context_plan_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6974,7 +6974,7 @@ func TestContext2Plan_variableCustomValidationsCrossRef(t *testing.T) {
69746974
}
69756975
}
69766976

6977-
func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) {
6977+
func TestContext2Plan_variableCustomValidationsChildSensitive(t *testing.T) {
69786978
m := testModule(t, "validate-variable-custom-validations-child-sensitive")
69796979

69806980
p := testProvider("test")
@@ -6993,6 +6993,39 @@ func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) {
69936993
}
69946994
}
69956995

6996+
func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) {
6997+
m := testModule(t, "validate-variable-custom-validations-sensitive")
6998+
6999+
p := testProvider("test")
7000+
ctx := testContext2(t, &ContextOpts{
7001+
Providers: map[addrs.Provider]providers.Factory{
7002+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
7003+
},
7004+
})
7005+
7006+
_, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, InputValues{
7007+
"input": {
7008+
Value: cty.StringVal("short"),
7009+
},
7010+
}))
7011+
if len(diags) != 1 {
7012+
t.Fatal("wanted exactly one error")
7013+
}
7014+
if diff := cmp.Diff(diags[0].Description(), tfdiags.Description{
7015+
Summary: "Invalid value for variable",
7016+
Detail: "too short\n\nThis was checked by the validation rule at testdata/validate-variable-custom-validations-sensitive/validate-variable-custom-validations-sensitive.tf:4,3-13.",
7017+
}); len(diff) > 0 {
7018+
t.Error(diff)
7019+
}
7020+
7021+
vars := diags[0].FromExpr().EvalContext.Variables["var"].AsValueMap()
7022+
7023+
_, ms := vars["input"].Unmark()
7024+
if _, ok := ms[marks.Sensitive]; !ok {
7025+
t.Error("should have been marked as sensitive")
7026+
}
7027+
}
7028+
69967029
func TestContext2Plan_nullOutputNoOp(t *testing.T) {
69977030
// this should always plan a NoOp change for the output
69987031
m := testModuleInline(t, map[string]string{

internal/terraform/eval_variable.go

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -281,31 +281,33 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, ctx EvalContex
281281
// Although that behavior was accidental, it makes simple validation rules
282282
// more useful and is protected by compatibility promises, and so we'll
283283
// fake it here by overwriting the unknown value that scope.EvalContext
284-
// will have inserted with a possibly-more-known value using the same
285-
// strategy our special code used to use.
286-
ourVal := ctx.NamedValues().GetInputVariableValue(addr)
287-
if ourVal != cty.NilVal {
288-
// (it would be weird for ourVal to be nil here, but we'll tolerate it
289-
// because it was scope.EvalContext's responsibility to check for the
290-
// absent final value, and even if it didn't we'll just get an
291-
// evaluation error when evaluating the expressions below anyway.)
292-
293-
// Our goal here is to make sure that a reference to the variable
294-
// we're checking will evaluate to ourVal, regardless of what else
295-
// scope.EvalContext might have put in the variables table.
296-
if hclCtx.Variables == nil {
297-
hclCtx.Variables = make(map[string]cty.Value)
298-
}
299-
if varsVal, ok := hclCtx.Variables["var"]; ok {
300-
// Unfortunately we need to unpack and repack the object here,
301-
// because cty values are immutable.
302-
attrs := varsVal.AsValueMap()
303-
attrs[addr.Variable.Name] = ourVal
304-
hclCtx.Variables["var"] = cty.ObjectVal(attrs)
305-
} else {
306-
hclCtx.Variables["var"] = cty.ObjectVal(map[string]cty.Value{
307-
addr.Variable.Name: ourVal,
308-
})
284+
// will have inserted during validate walks with a possibly-more-known value
285+
// using the same strategy our special code used to use.
286+
if validateWalk {
287+
ourVal := ctx.NamedValues().GetInputVariableValue(addr)
288+
if ourVal != cty.NilVal {
289+
// (it would be weird for ourVal to be nil here, but we'll tolerate it
290+
// because it was scope.EvalContext's responsibility to check for the
291+
// absent final value, and even if it didn't we'll just get an
292+
// evaluation error when evaluating the expressions below anyway.)
293+
294+
// Our goal here is to make sure that a reference to the variable
295+
// we're checking will evaluate to ourVal, regardless of what else
296+
// scope.EvalContext might have put in the variables table.
297+
if hclCtx.Variables == nil {
298+
hclCtx.Variables = make(map[string]cty.Value)
299+
}
300+
if varsVal, ok := hclCtx.Variables["var"]; ok {
301+
// Unfortunately we need to unpack and repack the object here,
302+
// because cty values are immutable.
303+
attrs := varsVal.AsValueMap()
304+
attrs[addr.Variable.Name] = ourVal
305+
hclCtx.Variables["var"] = cty.ObjectVal(attrs)
306+
} else {
307+
hclCtx.Variables["var"] = cty.ObjectVal(map[string]cty.Value{
308+
addr.Variable.Name: ourVal,
309+
})
310+
}
309311
}
310312
}
311313

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
variable "input" {
3+
type = string
4+
validation {
5+
condition = length(var.input) > 5
6+
error_message = "too short"
7+
}
8+
sensitive = true
9+
}
10+
11+
output "value" {
12+
value = var.input
13+
}

0 commit comments

Comments
 (0)