From e6d969a2eb0860838078babdb86caabaf36d72b9 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Wed, 14 Jan 2026 15:18:18 +0100 Subject: [PATCH] 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 --- internal/deprecation/deprecation.go | 31 ++++++++++++++++-------- internal/lang/marks/marks.go | 21 ++++++++++++++++ internal/terraform/context_plan2_test.go | 11 +++++++++ internal/terraform/eval_conditions.go | 4 +-- internal/terraform/eval_count.go | 2 +- internal/terraform/node_output.go | 4 +-- 6 files changed, 58 insertions(+), 15 deletions(-) diff --git a/internal/deprecation/deprecation.go b/internal/deprecation/deprecation.go index 76c6f52f36..1507b9a07c 100644 --- a/internal/deprecation/deprecation.go +++ b/internal/deprecation/deprecation.go @@ -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 diff --git a/internal/lang/marks/marks.go b/internal/lang/marks/marks.go index 6b078fc5a1..6421463f60 100644 --- a/internal/lang/marks/marks.go +++ b/internal/lang/marks/marks.go @@ -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) diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index e62cecdab6..d7f5eaae5c 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -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}, + }, + }, )) } diff --git a/internal/terraform/eval_conditions.go b/internal/terraform/eval_conditions.go index 293df00403..3a5810734f 100644 --- a/internal/terraform/eval_conditions.go +++ b/internal/terraform/eval_conditions.go @@ -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 diff --git a/internal/terraform/eval_count.go b/internal/terraform/eval_count.go index 0eb12ddffd..836995d731 100644 --- a/internal/terraform/eval_count.go +++ b/internal/terraform/eval_count.go @@ -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 diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index fa68dc2e0d..ad08b187d8 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -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) }