add deprecation singleton
Some checks failed
Quick Checks / Unit Tests (push) Has been cancelled
Quick Checks / Race Tests (push) Has been cancelled
Quick Checks / End-to-end Tests (push) Has been cancelled
Quick Checks / Code Consistency Checks (push) Has been cancelled

This struct tracks and validates deprecations in the context of a
graph walk. We need a struct to keep track of the module calls that
opt-out of deprecation warnings.
This commit is contained in:
Daniel Schmidt 2025-12-11 13:31:14 +01:00
parent 5c898c6d9a
commit af549ab8c9
No known key found for this signature in database
GPG key ID: 377C3A4D62FBBBE2
6 changed files with 130 additions and 3 deletions

View file

@ -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
}

View file

@ -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(),
}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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()
}

View file

@ -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