diff --git a/internal/deprecation/deprecation.go b/internal/deprecation/deprecation.go new file mode 100644 index 0000000000..ac4e8e65f2 --- /dev/null +++ b/internal/deprecation/deprecation.go @@ -0,0 +1,98 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package deprecation + +import ( + "sync" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/lang/marks" + "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/zclconf/go-cty/cty" +) + +// Deprecations keeps track of meta-information related to deprecation, e.g. which module calls +// suppress deprecation warnings. +type Deprecations struct { + // Must hold this lock when accessing all fields after this one. + mu sync.Mutex + + suppressedModules addrs.Set[addrs.Module] +} + +func NewDeprecations() *Deprecations { + return &Deprecations{ + suppressedModules: addrs.MakeSet[addrs.Module](), + } +} + +func (d *Deprecations) SuppressModuleCallDeprecation(addr addrs.Module) { + d.mu.Lock() + defer d.mu.Unlock() + + d.suppressedModules.Add(addr) +} + +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 + } + + notDeprecatedValue := marks.RemoveDeprecationMarks(value) + + // Check if we need to suppress deprecation warnings for this module call. + if d.IsModuleCallDeprecationSuppressed(module) { + return notDeprecatedValue, diags + } + + for _, depMark := range deprecationMarks { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Deprecated value used", + Detail: depMark.Message, + Subject: rng, + }) + } + + return notDeprecatedValue, diags +} + +func (d *Deprecations) ValidateAsConfig(value cty.Value, module addrs.Module) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + _, pvms := value.UnmarkDeepWithPaths() + + if len(pvms) == 0 || d.IsModuleCallDeprecationSuppressed(module) { + return diags + } + + for _, pvm := range pvms { + for m := range pvm.Marks { + if depMark, ok := m.(marks.DeprecationMark); ok { + diags = diags.Append( + tfdiags.AttributeValue( + tfdiags.Warning, + "Deprecated value used", + depMark.Message, + pvm.Path, + ), + ) + } + } + } + return diags +} + +func (d *Deprecations) IsModuleCallDeprecationSuppressed(addr addrs.Module) bool { + d.mu.Lock() + defer d.mu.Unlock() + for _, mod := range d.suppressedModules { + if mod.TargetContains(addr) { + return true + } + } + return false +} diff --git a/internal/terraform/context_walk.go b/internal/terraform/context_walk.go index 55d0bd9571..42e5fc2b09 100644 --- a/internal/terraform/context_walk.go +++ b/internal/terraform/context_walk.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/deprecation" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/moduletest/mocking" @@ -201,5 +202,6 @@ func (c *Context) graphWalker(graph *Graph, operation walkOperation, opts *graph functionResults: opts.FunctionResults, Forget: opts.Forget, Actions: actions.NewActions(), + Deprecations: deprecation.NewDeprecations(), } } diff --git a/internal/terraform/eval_context.go b/internal/terraform/eval_context.go index eb4d338411..f8b02e724f 100644 --- a/internal/terraform/eval_context.go +++ b/internal/terraform/eval_context.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/deprecation" "github.com/hashicorp/terraform/internal/experiments" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" @@ -222,6 +223,10 @@ type EvalContext interface { // declarations and their instances that are available in this // EvalContext. Actions() *actions.Actions + + // Deprecations returns the deprecations object that tracks meta-information + // about deprecation, e.g. which module calls suppress deprecation warnings. + Deprecations() *deprecation.Deprecations } func evalContextForModuleInstance(baseCtx EvalContext, addr addrs.ModuleInstance) EvalContext { diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index 2fc9fdeecd..5f66c0fdb2 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/deprecation" "github.com/hashicorp/terraform/internal/experiments" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" @@ -93,6 +94,7 @@ type BuiltinEvalContext struct { MoveResultsValue refactoring.MoveResults OverrideValues *mocking.Overrides ActionsValue *actions.Actions + DeprecationsValue *deprecation.Deprecations } // BuiltinEvalContext implements EvalContext @@ -664,3 +666,7 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities func (ctx *BuiltinEvalContext) Actions() *actions.Actions { return ctx.ActionsValue } + +func (ctx *BuiltinEvalContext) Deprecations() *deprecation.Deprecations { + return ctx.DeprecationsValue +} diff --git a/internal/terraform/eval_context_mock.go b/internal/terraform/eval_context_mock.go index df29bf8c0c..20dffe8ef6 100644 --- a/internal/terraform/eval_context_mock.go +++ b/internal/terraform/eval_context_mock.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/deprecation" "github.com/hashicorp/terraform/internal/experiments" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" @@ -170,6 +171,9 @@ type MockEvalContext struct { ActionsCalled bool ActionsState *actions.Actions + + DeprecationCalled bool + DeprecationState *deprecation.Deprecations } // MockEvalContext implements EvalContext @@ -451,3 +455,11 @@ func (c *MockEvalContext) Actions() *actions.Actions { c.ActionsCalled = true return c.ActionsState } + +func (c *MockEvalContext) Deprecations() *deprecation.Deprecations { + c.DeprecationCalled = true + if c.DeprecationState != nil { + return c.DeprecationState + } + return deprecation.NewDeprecations() +} diff --git a/internal/terraform/graph_walk_context.go b/internal/terraform/graph_walk_context.go index 7dbe5e19d1..a8f5dbcbb6 100644 --- a/internal/terraform/graph_walk_context.go +++ b/internal/terraform/graph_walk_context.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/internal/collections" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/deprecation" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/moduletest/mocking" @@ -53,9 +54,11 @@ type ContextGraphWalker struct { PlanTimestamp time.Time Overrides *mocking.Overrides // Forget if set to true will cause the plan to forget all resources. This is - // only allowd in the context of a destroy plan. - Forget bool - Actions *actions.Actions + // only allowed in the context of a destroy plan. + Forget bool + + Actions *actions.Actions + Deprecations *deprecation.Deprecations // This is an output. Do not set this, nor read it while a graph walk // is in progress. @@ -144,6 +147,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext { OverrideValues: w.Overrides, forget: w.Forget, ActionsValue: w.Actions, + DeprecationsValue: w.Deprecations, } return ctx