From 2f392d904ed2673d3748f0f61ae2cff6e18c8deb Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Wed, 14 Jan 2026 14:15:13 +0100 Subject: [PATCH] deprecation.Validate should only check top-level marks --- internal/deprecation/deprecation.go | 6 ++ internal/lang/marks/marks.go | 2 +- internal/terraform/context_validate_test.go | 70 +++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/internal/deprecation/deprecation.go b/internal/deprecation/deprecation.go index 4e63617193..76c6f52f36 100644 --- a/internal/deprecation/deprecation.go +++ b/internal/deprecation/deprecation.go @@ -35,6 +35,9 @@ func (d *Deprecations) SuppressModuleCallDeprecation(addr addrs.Module) { d.suppressedModules.Add(addr) } +// 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) @@ -67,6 +70,9 @@ func (d *Deprecations) Validate(value cty.Value, module addrs.Module, rng *hcl.R return notDeprecatedValue, diags } +// ValidateAsConfig checks the given value for deprecation marks and returns diagnostics +// for each deprecation found, unless deprecation warnings are suppressed for the given module. +// It checks for deeply nested deprecation marks as well. func (d *Deprecations) ValidateAsConfig(value cty.Value, module addrs.Module) tfdiags.Diagnostics { var diags tfdiags.Diagnostics _, pvms := value.UnmarkDeepWithPaths() diff --git a/internal/lang/marks/marks.go b/internal/lang/marks/marks.go index 60a578f847..6b078fc5a1 100644 --- a/internal/lang/marks/marks.go +++ b/internal/lang/marks/marks.go @@ -64,7 +64,7 @@ func FilterDeprecationMarks(marks cty.ValueMarks) []DeprecationMark { // GetDeprecationMarks returns all deprecation marks present on the given // cty.Value. func GetDeprecationMarks(val cty.Value) []DeprecationMark { - _, marks := val.UnmarkDeep() + _, marks := val.Unmark() return FilterDeprecationMarks(marks) } diff --git a/internal/terraform/context_validate_test.go b/internal/terraform/context_validate_test.go index dc4d0725cd..e77a1d3621 100644 --- a/internal/terraform/context_validate_test.go +++ b/internal/terraform/context_validate_test.go @@ -4594,3 +4594,73 @@ locals { }, })) } + +func TestContext2Validate_using_module_with_deprecated_output(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "mod/main.tf": ` +output "old" { + deprecated = "Please stop using this" + value = "old" +} + +`, + "main.tf": ` +module "mod" { + source = "./mod" +} + +locals { + m = module.mod # OK - deprecated value is not used +} + +output "test_output" { + value = module.mod.old # WARNING +} +`, + }) + + p := new(testing_provider.MockProvider) + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "attr": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + Actions: map[string]*providers.ActionSchema{ + "test_action": { + ConfigSchema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "attr": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + diags := ctx.Validate(m, &ValidateOpts{}) + + tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.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: 11, Column: 13, Byte: 143}, + End: hcl.Pos{Line: 11, Column: 27, Byte: 157}, + }, + })) +}