we only want to send diagnostics for deeply nested deprecations in certain locations

Mainly terminal locations for the value where they are used, such as the config of a resource, for_each, outputs.
We don't want to evaluate the deprecation deeply when it comes to values where the value is not yet used, e.g. locals
This is because if e.g. a deeply nested value is deprecated it should still be ok for the entire object to be in a local
whereas the same object should give a warning in e.g. an output
This commit is contained in:
Daniel Schmidt 2026-01-14 15:18:18 +01:00
parent 2f392d904e
commit e6d969a2eb
6 changed files with 58 additions and 15 deletions

View file

@ -37,19 +37,31 @@ func (d *Deprecations) SuppressModuleCallDeprecation(addr addrs.Module) {
// Validate checks the given value for deprecation marks and returns diagnostics
// for each deprecation found, unless deprecation warnings are suppressed for the given module.
// It does not check for deeply nested deprecation marks.
func (d *Deprecations) Validate(value cty.Value, module addrs.Module, rng *hcl.Range) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
deprecationMarks := marks.GetDeprecationMarks(value)
if len(deprecationMarks) == 0 {
return value, diags
}
// This is appropriate for non-terminal values (values that can be referenced) only.
// If the value can not be referenced, use ValidateDeep or ValidateAsConfig instead.
func (d *Deprecations) Validate(value cty.Value, module addrs.Module, rng *hcl.Range) (cty.Value, tfdiags.Diagnostics) {
deprecationMarks := marks.GetDeprecationMarks(value)
notDeprecatedValue := marks.RemoveDeprecationMarks(value)
return notDeprecatedValue, d.deprecationMarksToDiagnostics(deprecationMarks, module, rng)
}
// ValidateDeep does the same as Validate but checks deeply nested deprecation marks as well.
func (d *Deprecations) ValidateDeep(value cty.Value, module addrs.Module, rng *hcl.Range) (cty.Value, tfdiags.Diagnostics) {
deprecationMarks := marks.GetDeprecationMarksDeep(value)
notDeprecatedValue := marks.RemoveDeprecationMarksDeep(value)
return notDeprecatedValue, d.deprecationMarksToDiagnostics(deprecationMarks, module, rng)
}
func (d *Deprecations) deprecationMarksToDiagnostics(deprecationMarks []marks.DeprecationMark, module addrs.Module, rng *hcl.Range) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
if len(deprecationMarks) == 0 {
return diags
}
// Check if we need to suppress deprecation warnings for this module call.
if d.IsModuleCallDeprecationSuppressed(module) {
return notDeprecatedValue, diags
return diags
}
for _, depMark := range deprecationMarks {
@ -66,8 +78,7 @@ func (d *Deprecations) Validate(value cty.Value, module addrs.Module, rng *hcl.R
}
diags = diags.Append(diag)
}
return notDeprecatedValue, diags
return diags
}
// ValidateAsConfig checks the given value for deprecation marks and returns diagnostics

View file

@ -68,9 +68,30 @@ func GetDeprecationMarks(val cty.Value) []DeprecationMark {
return FilterDeprecationMarks(marks)
}
// GetDeprecationMarksDeep returns all deprecation marks present on the given
// cty.Value or any nested values.
func GetDeprecationMarksDeep(val cty.Value) []DeprecationMark {
_, marks := val.UnmarkDeep()
return FilterDeprecationMarks(marks)
}
// RemoveDeprecationMarks returns a copy of the given cty.Value with all
// deprecation marks removed.
func RemoveDeprecationMarks(val cty.Value) cty.Value {
newVal, marks := val.Unmark()
for mark := range marks {
if _, ok := mark.(DeprecationMark); !ok {
newVal = newVal.Mark(mark)
}
}
return newVal
}
// RemoveDeprecationMarksDeep returns a copy of the given cty.Value with all
// deprecation marks deeply removed.
func RemoveDeprecationMarksDeep(val cty.Value) cty.Value {
newVal, pvms := val.UnmarkDeepWithPaths()
otherPvms := RemoveAll(pvms, Deprecation)
return newVal.MarkWithPaths(otherPvms)

View file

@ -7477,6 +7477,17 @@ output "test_output2" {
End: hcl.Pos{Line: 7, Column: 27, Byte: 97},
},
},
).Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 10, Byte: 80},
End: hcl.Pos{Line: 7, Column: 27, Byte: 97},
},
},
))
}

View file

@ -107,7 +107,7 @@ func validateCheckRule(addr addrs.CheckRule, rule *configs.CheckRule, ctx EvalCo
errorMessage, moreDiags := lang.EvalCheckErrorMessage(rule.ErrorMessage, hclCtx, &addr)
diags = diags.Append(moreDiags)
_, deprecationDiags := ctx.Deprecations().Validate(errorMessage, ctx.Path().Module(), rule.ErrorMessage.Range().Ptr())
_, deprecationDiags := ctx.Deprecations().ValidateDeep(errorMessage, ctx.Path().Module(), rule.ErrorMessage.Range().Ptr())
diags = diags.Append(deprecationDiags)
// NOTE: We've discarded any other marks the string might have been carrying,
@ -170,7 +170,7 @@ func evalCheckRule(addr addrs.CheckRule, rule *configs.CheckRule, ctx EvalContex
}
// We don't care about the returned value here, only the diagnostics
_, deprecationDiags := ctx.Deprecations().Validate(resultVal, addr.ModuleInstance().Module(), rule.Condition.Range().Ptr())
_, deprecationDiags := ctx.Deprecations().ValidateDeep(resultVal, addr.ModuleInstance().Module(), rule.Condition.Range().Ptr())
diags = diags.Append(deprecationDiags)
var err error

View file

@ -102,7 +102,7 @@ func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Val
})
}
countVal, deprecationDiags := ctx.Deprecations().Validate(countVal, ctx.Path().Module(), expr.Range().Ptr())
countVal, deprecationDiags := ctx.Deprecations().ValidateDeep(countVal, ctx.Path().Module(), expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
// Sensitive values are allowed in count but not for_each. This is a

View file

@ -519,7 +519,7 @@ If you do intend to export this data, annotate the output value as sensitive by
}
if n.Config.DeprecatedSet {
val = marks.RemoveDeprecationMarks(val)
val = marks.RemoveDeprecationMarksDeep(val)
if n.Addr.Module.IsRoot() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
@ -530,7 +530,7 @@ If you do intend to export this data, annotate the output value as sensitive by
}
} else if n.Config.Expr != nil {
var deprecationDiags tfdiags.Diagnostics
val, deprecationDiags = ctx.Deprecations().Validate(val, n.ModulePath(), n.Config.Expr.Range().Ptr())
val, deprecationDiags = ctx.Deprecations().ValidateDeep(val, n.ModulePath(), n.Config.Expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
}