variable and output deprecation

Variables can be deprecated through the `deprecated` attribute. If set the variable
will emit a diagnostic if a values is passed to it. This entails both root level and
module variables.

Outputs can be deprecated through the `deprecated` attribute as well. If set wherever
the value is used a diagnostic will be emitted. Root level outputs can not be deprecated.
The only acceptable usage of a deprecated output is another deprecated output (forwarding
the deprecation to the module user).

If modules not under your control have deprecation warnings you can add a `suppress_deprecations_warnigns`
attribute to the module call in question to silence any deeply nested warnings.
This commit is contained in:
Daniel Schmidt 2025-12-11 13:35:52 +01:00
parent 13247d19e2
commit 958a1ae1e7
33 changed files with 2392 additions and 39 deletions

View file

@ -0,0 +1,5 @@
kind: NEW FEATURES
body: You can set a `deprecated` attribute on variable and output blocks to indicate that they are deprecated. This will produce warnings when passing in a value for a deprecated variable or when referencing a deprecated output.
time: 2025-12-05T17:14:18.623477+01:00
custom:
Issue: "37795"

View file

@ -106,6 +106,11 @@ func (c AbsCheck) CheckRule(typ CheckRuleType, i int) CheckRule {
}
}
// ModuleInstance returns the module instance portion of the address.
func (c AbsCheck) ModuleInstance() ModuleInstance {
return c.Module
}
// ConfigCheckable returns the ConfigCheck address for this absolute reference.
func (c AbsCheck) ConfigCheckable() ConfigCheckable {
return ConfigCheck{

View file

@ -115,3 +115,8 @@ func (c CheckRuleType) Description() string {
return "Condition"
}
}
// ModuleInstance returns the module instance address containing this check rule.
func (c CheckRule) ModuleInstance() ModuleInstance {
return c.Container.ModuleInstance()
}

View file

@ -39,6 +39,7 @@ type Checkable interface {
CheckableKind() CheckableKind
String() string
ModuleInstance() ModuleInstance
}
var (

View file

@ -81,6 +81,11 @@ func (v AbsInputVariableInstance) CheckRule(typ CheckRuleType, i int) CheckRule
}
}
// ModuleInstance returns the module instance portion of the address.
func (v AbsInputVariableInstance) ModuleInstance() ModuleInstance {
return v.Module
}
func (v AbsInputVariableInstance) ConfigCheckable() ConfigCheckable {
return ConfigInputVariable{
Module: v.Module.Module(),

View file

@ -77,6 +77,11 @@ func (m ModuleInstance) OutputValue(name string) AbsOutputValue {
}
}
// ModuleInstance returns the module instance portion of the address.
func (v AbsOutputValue) ModuleInstance() ModuleInstance {
return v.Module
}
func (v AbsOutputValue) CheckRule(t CheckRuleType, i int) CheckRule {
return CheckRule{
Container: v,

View file

@ -280,6 +280,11 @@ func (m ModuleInstance) ResourceInstance(mode ResourceMode, typeName string, nam
}
}
// ModuleInstance returns the module instance portion of the address.
func (r AbsResourceInstance) ModuleInstance() ModuleInstance {
return r.Module
}
// ContainingResource returns the address of the resource that contains the
// receving resource instance. In other words, it discards the key portion
// of the address to produce an AbsResource value.

View file

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/getmodules/moduleaddrs"
@ -35,6 +36,8 @@ type ModuleCall struct {
DependsOn []hcl.Traversal
DeclRange hcl.Range
IgnoreNestedDeprecations bool
}
func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagnostics) {
@ -163,6 +166,30 @@ func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagno
mc.Providers = append(mc.Providers, providers...)
}
if attr, exists := content.Attributes["ignore_nested_deprecations"]; exists {
// We only allow static boolean values for this argument.
val, evalDiags := attr.Expr.Value(&hcl.EvalContext{})
if len(evalDiags.Errs()) > 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid value for ignore_nested_deprecations",
Detail: "The value for ignore_nested_deprecations must be a static boolean (true or false).",
Subject: attr.Expr.Range().Ptr(),
})
}
if val.Type() != cty.Bool {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid type for ignore_nested_deprecations",
Detail: fmt.Sprintf("The value for ignore_nested_deprecations must be a boolean (true or false), but the given value has type %s.", val.Type().FriendlyName()),
Subject: attr.Expr.Range().Ptr(),
})
}
mc.IgnoreNestedDeprecations = val.True()
}
var seenEscapeBlock *hcl.Block
for _, block := range content.Blocks {
switch block.Type {
@ -278,6 +305,9 @@ var moduleBlockSchema = &hcl.BodySchema{
{
Name: "providers",
},
{
Name: "ignore_nested_deprecations",
},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "_"}, // meta-argument escaping block

View file

@ -48,6 +48,10 @@ type Variable struct {
Nullable bool
NullableSet bool
Deprecated string
DeprecatedSet bool
DeprecatedRange hcl.Range
DeclRange hcl.Range
}
@ -186,6 +190,13 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
v.Default = val
}
if attr, exists := content.Attributes["deprecated"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Deprecated)
diags = append(diags, valDiags...)
v.DeprecatedSet = true
v.DeprecatedRange = attr.Range
}
for _, block := range content.Blocks {
switch block.Type {
@ -345,14 +356,17 @@ type Output struct {
DependsOn []hcl.Traversal
Sensitive bool
Ephemeral bool
Deprecated string
Preconditions []*CheckRule
DescriptionSet bool
SensitiveSet bool
EphemeralSet bool
DeprecatedSet bool
DeclRange hcl.Range
DeclRange hcl.Range
DeprecatedRange hcl.Range
}
func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) {
@ -402,6 +416,13 @@ func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostic
o.EphemeralSet = true
}
if attr, exists := content.Attributes["deprecated"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Deprecated)
diags = append(diags, valDiags...)
o.DeprecatedSet = true
o.DeprecatedRange = attr.Range
}
if attr, exists := content.Attributes["depends_on"]; exists {
deps, depsDiags := DecodeDependsOn(attr)
diags = append(diags, depsDiags...)
@ -441,6 +462,7 @@ func (o *Output) Addr() addrs.OutputValue {
type Local struct {
Name string
Expr hcl.Expression
Body hcl.Body // for better diagnostics
DeclRange hcl.Range
}
@ -466,6 +488,7 @@ func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) {
Name: name,
Expr: attr.Expr,
DeclRange: attr.Range,
Body: block.Body,
})
}
return locals, diags
@ -499,6 +522,9 @@ var variableBlockSchema = &hcl.BodySchema{
{
Name: "nullable",
},
{
Name: "deprecated",
},
},
Blocks: []hcl.BlockHeaderSchema{
{
@ -525,6 +551,9 @@ var outputBlockSchema = &hcl.BodySchema{
{
Name: "ephemeral",
},
{
Name: "deprecated",
},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "precondition"},

View file

@ -48,3 +48,56 @@ func TestVariableInvalidDefault(t *testing.T) {
}
}
}
func TestOutputDeprecation(t *testing.T) {
src := `
output "foo" {
value = "bar"
deprecated = "This output is deprecated"
}
`
hclF, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
if diags.HasErrors() {
t.Fatal(diags.Error())
}
b, diags := parseConfigFile(hclF.Body, nil, false, false)
if diags.HasErrors() {
t.Fatalf("unexpected error: %q", diags)
}
if !b.Outputs[0].DeprecatedSet {
t.Fatalf("expected output to be deprecated")
}
if b.Outputs[0].Deprecated != "This output is deprecated" {
t.Fatalf("expected output to have deprecation message")
}
}
func TestVariableDeprecation(t *testing.T) {
src := `
variable "foo" {
type = string
deprecated = "This variable is deprecated, use bar instead"
}
`
hclF, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
if diags.HasErrors() {
t.Fatal(diags.Error())
}
b, diags := parseConfigFile(hclF.Body, nil, false, false)
if diags.HasErrors() {
t.Fatalf("unexpected error: %q", diags)
}
if !b.Variables[0].DeprecatedSet {
t.Fatalf("expected variable to be deprecated")
}
if b.Variables[0].Deprecated != "This variable is deprecated, use bar instead" {
t.Fatalf("expected variable to have deprecation message")
}
}

View file

@ -93,6 +93,17 @@ You can correct this by removing references to ephemeral values, or by using the
return "", diags
}
if depMarks := marks.FilterDeprecationMarks(valMarks); len(depMarks) > 0 {
for _, depMark := range depMarks {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: depMark.Message,
Subject: expr.Range().Ptr(),
})
}
}
// NOTE: We've discarded any other marks the string might have been carrying,
// aside from the sensitive mark.

View file

@ -7,6 +7,7 @@ import (
"bytes"
"errors"
"fmt"
"path/filepath"
"sort"
"strings"
"sync"
@ -18,6 +19,7 @@ import (
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/collections"
@ -4346,3 +4348,552 @@ func TestContext2Apply_noListValidated(t *testing.T) {
})
}
}
func TestContext2Apply_deprecatedOutputsAndVariables(t *testing.T) {
tests := map[string]struct {
modules map[string]string
buildState func(*states.SyncState)
vars InputValues
expectedDiagnostics func(m *configs.Config) tfdiags.Diagnostics
}{
"create resource using deprecated output": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = module.mod.old
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 27, Byte: 100},
},
})
},
},
"update resource to use deprecated output": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
output "new" {
value = "new-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = module.mod.old
}
`,
},
buildState: func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_resource.test"),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"value":"old-non-deprecated-value"}`),
Status: states.ObjectReady,
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 27, Byte: 100},
},
})
},
},
"no-op resource using deprecated output": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = module.mod.old
}
`,
},
buildState: func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_resource.test"),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"value":"deprecated-value"}`),
Status: states.ObjectReady,
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 27, Byte: 100},
},
})
},
},
"update resource to stop using deprecated output": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
output "new" {
value = "new-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = module.mod.old
}
`,
},
buildState: func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_resource.test"),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"value":"deprecated-value"}`),
Status: states.ObjectReady,
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 27, Byte: 100},
},
})
},
},
"create resource using deprecated variable": {
modules: map[string]string{
"mod/main.tf": `
variable "old_var" {
type = string
deprecated = "Please use new_var instead"
}
output "value" {
value = var.old_var
}
`,
"main.tf": `
module "mod" {
source = "./mod"
old_var = "test-value"
}
resource "test_resource" "test" {
value = module.mod.value
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "Please use new_var instead",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 4, Column: 15, Byte: 51},
End: hcl.Pos{Line: 4, Column: 27, Byte: 63},
},
})
},
},
"deprecated output in root output": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "old-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = "static"
}
output "test_output" {
value = module.mod.old
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
// Root outputs do not trigger deprecation warnings during apply
return tfdiags.Diagnostics{}
},
},
"deprecated variable with input value": {
modules: map[string]string{
"main.tf": `
variable "old_var" {
type = string
deprecated = "This variable is deprecated"
}
resource "test_resource" "test" {
value = var.old_var
}
`,
},
buildState: func(s *states.SyncState) {},
vars: InputValues{
"old_var": {
Value: cty.StringVal("test-value"),
},
},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "This variable is deprecated",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 4, Column: 5, Byte: 44},
End: hcl.Pos{Line: 4, Column: 47, Byte: 86},
},
})
},
},
"multiple deprecated outputs used": {
modules: map[string]string{
"mod/main.tf": `
output "old1" {
deprecated = "Stop using old1"
value = "value1"
}
output "old2" {
deprecated = "Stop using old2"
value = "value2"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test1" {
value = module.mod.old1
}
resource "test_resource" "test2" {
value = module.mod.old2
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Stop using old1",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 87},
End: hcl.Pos{Line: 7, Column: 28, Byte: 102},
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Stop using old2",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 11, Column: 13, Byte: 153},
End: hcl.Pos{Line: 11, Column: 28, Byte: 168},
},
})
},
},
"deprecated output in count": {
modules: map[string]string{
"mod/main.tf": `
output "count_val" {
deprecated = "Please stop using this output"
value = 2
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
count = module.mod.count_val
value = "test-${count.index}"
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 33, Byte: 106},
},
})
},
},
"deprecated output in conditional expression": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
output "new" {
value = "new-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
value = true ? module.mod.old : module.mod.new
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 86},
End: hcl.Pos{Line: 7, Column: 51, Byte: 124},
},
})
},
},
"deprecated output used in local value": {
modules: map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this output"
value = "deprecated-value"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
locals {
local_value = module.mod.old
}
resource "test_resource" "test" {
value = local.local_value
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 19, Byte: 67},
End: hcl.Pos{Line: 7, Column: 33, Byte: 81},
},
})
},
},
"deprecated variable in module with default value": {
modules: map[string]string{
"mod/main.tf": `
variable "old_var" {
type = string
deprecated = "Please use new_var instead"
default = "default-value"
}
output "value" {
value = var.old_var
}
`,
"main.tf": `
module "mod" {
source = "./mod"
old_var = "custom-value"
}
resource "test_resource" "test" {
value = module.mod.value
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "Please use new_var instead",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 4, Column: 15, Byte: 51},
End: hcl.Pos{Line: 4, Column: 29, Byte: 65},
},
})
},
},
"nested deprecated outputs": {
modules: map[string]string{
"inner/main.tf": `
output "old" {
deprecated = "Inner module: please stop using this"
value = "inner-value"
}
`,
"outer/main.tf": `
module "inner" {
source = "../inner"
}
output "old" {
deprecated = "Outer module: please stop using this"
value = module.inner.old
}
`,
"main.tf": `
module "outer" {
source = "./outer"
}
resource "test_resource" "test" {
value = module.outer.old
}
`,
},
buildState: func(s *states.SyncState) {},
expectedDiagnostics: func(m *configs.Config) tfdiags.Diagnostics {
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Outer module: please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 90},
End: hcl.Pos{Line: 7, Column: 29, Byte: 106},
},
})
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
m := testModuleInline(t, tc.modules)
state := states.BuildState(tc.buildState)
p := new(testing_provider.MockProvider)
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
Optional: true,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
vars := tc.vars
if vars == nil {
vars = InputValues{}
}
plan, planDiags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, vars))
if planDiags.HasErrors() {
t.Fatalf("plan errors: %s", planDiags.Err())
}
// Apply the plan
_, applyDiags := ctx.Apply(plan, m, nil)
if applyDiags.HasErrors() {
t.Fatalf("apply errors: %s", applyDiags.Err())
}
expectDiagnostics := tc.expectedDiagnostics(m)
tfdiags.AssertDiagnosticsMatch(t, applyDiags, expectDiagnostics)
})
}
}

View file

@ -7167,3 +7167,716 @@ func TestContext2Plan_resourceNamedList(t *testing.T) {
})
}
}
func TestContext2Plan_deprecated_output(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
output "old-and-unused" {
deprecated = "This should not show up in the errors, we are not using it"
value = "old"
}
output "new" {
value = "foo"
}
`,
"mod2/main.tf": `
variable "input" {
type = string
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
attr = module.mod.old # WARNING
}
resource "test_resource" "test2" {
attr = module.mod.new # OK
}
resource "test_resource" "test3" {
attr = module.mod.old # WARNING
}
output "test_output" {
value = module.mod.old # WARNING
}
output "test_output_conditional" {
value = false ? module.mod.old : module.mod.new # WARNING
}
module "mod2" {
source = "./mod2"
input = 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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
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: 6, Column: 12, Byte: 84},
End: hcl.Pos{Line: 6, Column: 26, Byte: 98},
},
},
).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: 12, Column: 12, Byte: 225},
End: hcl.Pos{Line: 12, Column: 26, Byte: 239},
},
},
).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: 15, Column: 10, Byte: 284},
End: hcl.Pos{Line: 15, Column: 24, Byte: 298},
},
},
).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: 18, Column: 10, Byte: 355},
End: hcl.Pos{Line: 18, Column: 49, Byte: 394},
},
},
).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: 22, Column: 10, Byte: 451},
End: hcl.Pos{Line: 22, Column: 24, Byte: 465},
},
},
))
}
func TestContext2Plan_deprecated_output_expansion(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/nested/main.tf": `
output "old" {
deprecated = "mod/nested: Please stop using this"
value = "old"
}
`,
"mod/main.tf": `
output "old" {
deprecated = "mod: Please stop using this"
value = "old"
}
module "modnested" {
source = "./nested"
}
output "new" {
deprecated = "mod: The dependency is deprecated, please stop using this"
value = module.modnested.old
}
`,
"mod2/main.tf": `
output "old" {
deprecated = "mod2: Please stop using this"
value = "old"
}
output "new" {
value = "new"
}
`,
"mod3/main.tf": `
output "old" {
deprecated = "mod2: Please stop using this"
value = "old"
}
output "new" {
value = "new"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
module "mod2" {
count = 2
source = "./mod2"
}
module "mod3" {
count = 2
source = "./mod3"
}
resource "test_resource" "foo" {
attr = module.mod.old # WARNING
}
resource "test_resource" "bar" {
attr = module.mod2[0].old # WARNING
}
resource "test_resource" "baz" {
attr = module.mod.new # WARNING
}
output "test_output_no_warning" {
value = module.mod3[0].new # OK
}
`,
})
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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
var expectedDiags tfdiags.Diagnostics
expectedDiags = expectedDiags.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "mod: Please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 17, Column: 10, Byte: 180},
End: hcl.Pos{Line: 17, Column: 24, Byte: 194},
},
},
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "mod2: Please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 21, Column: 10, Byte: 250},
End: hcl.Pos{Line: 21, Column: 28, Byte: 268},
},
},
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "mod: The dependency is deprecated, please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 25, Column: 10, Byte: 324},
End: hcl.Pos{Line: 25, Column: 24, Byte: 338},
},
},
)
tfdiags.AssertDiagnosticsMatch(t, diags, expectedDiags)
}
func TestContext2Plan_deprecated_output_expansion_with_splat(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" {
count = 2
source = "./mod"
}
output "test_output2" {
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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
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: 7, Column: 10, Byte: 80},
End: hcl.Pos{Line: 7, Column: 27, Byte: 97},
},
},
))
}
func TestContext2Plan_deprecated_output_used_in_check_block(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
output "old-and-unused" {
deprecated = "This should not show up in the errors, we are not using it"
value = "old"
}
output "new" {
value = "foo"
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
check "deprecated_check" {
assert {
condition = !strcontains(module.mod.old, "hello-world")
error_message = "Neither condition nor error_message should contain ${module.mod.old} deprecated value"
}
}
`,
})
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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
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: 7, Column: 21, Byte: 97},
End: hcl.Pos{Line: 7, Column: 64, Byte: 140},
},
},
).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: 8, Column: 21, Byte: 161},
End: hcl.Pos{Line: 8, Column: 108, Byte: 248},
},
},
))
}
func TestContext2Plan_deprecated_output_in_lifecycle_conditions(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 {
a = "a"
b = "b"
}
resource "test_resource" "test" {
attr = "not-the-problem"
lifecycle {
precondition {
condition = module.mod.old == "old"
error_message = "This is okay."
}
postcondition {
condition = local.a != local.b
error_message = "This should error with deprecated usage: ${module.mod.old}"
}
}
}
`,
})
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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
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: 16, Column: 29, Byte: 207},
End: hcl.Pos{Line: 16, Column: 52, Byte: 230},
},
},
).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: 22, Column: 29, Byte: 389},
End: hcl.Pos{Line: 22, Column: 89, Byte: 449},
},
},
))
}
func TestContext2Plan_deprecated_variable(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
variable "old-and-used" {
type = string
deprecated = "module variable deprecation 1"
default = "optional"
}
variable "old-and-unused" {
type = string
deprecated = "module variable deprecation 2"
default = "optional"
}
variable "new" {
type = string
default = "optional"
}
output "use-everything" {
value = {
used = var.old-and-used
unused = var.old-and-unused
new = var.new
}
}
`,
"main.tf": `
variable "root-old-and-used" {
type = string
deprecated = "root variable deprecation 1"
default = "optional"
}
variable "root-old-and-unused" {
type = string
deprecated = "root variable deprecation 2"
default = "optional"
}
variable "new" {
type = string
default = "new"
}
module "old-mod" {
source = "./mod"
old-and-used = "old"
}
module "new-mod" {
source = "./mod"
new = "new"
}
output "use-everything" {
value = {
used = var.root-old-and-used
unused = var.root-old-and-unused
new = var.new
}
}
`,
})
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,
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
vars := InputValues{
"root-old-and-used": {
Value: cty.StringVal("root-old-and-used"),
},
"root-old-and-unused": {
Value: cty.NullVal(cty.String),
},
"new": {
Value: cty.StringVal("new"),
},
}
// We can find module variable deprecation during validation
diags := ctx.Validate(m, &ValidateOpts{})
var expectedValidateDiags tfdiags.Diagnostics
expectedValidateDiags = expectedValidateDiags.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "module variable deprecation 1",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 21, Column: 20, Byte: 350},
End: hcl.Pos{Line: 21, Column: 25, Byte: 355},
},
},
)
tfdiags.AssertDiagnosticsMatch(t, diags, expectedValidateDiags)
_, diags = ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, vars))
var expectedPlanDiags tfdiags.Diagnostics
expectedPlanDiags = expectedPlanDiags.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "root variable deprecation 1",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 4, Column: 2, Byte: 48},
End: hcl.Pos{Line: 4, Column: 42, Byte: 90},
},
},
).Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: "module variable deprecation 1",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 21, Column: 20, Byte: 350},
End: hcl.Pos{Line: 21, Column: 25, Byte: 355},
},
},
)
tfdiags.AssertDiagnosticsMatch(t, diags, expectedPlanDiags)
}
func TestContext2Plan_ignoring_nested_expanded_module_deprecations(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
module "nested" {
count = 3
source = "./nested"
}
locals {
foo = module.nested.*.old # WARNING (if not muted)
}
`,
"mod/nested/main.tf": `
output "old" {
deprecated = "Please stop using this nested output"
value = "old"
}
module "deeper" {
count = 4
source = "./deeper"
}
locals {
bar = module.deeper[2].old # WARNING (if not muted)
}
`,
"mod/nested/deeper/main.tf": `
output "old" {
deprecated = "Please stop using this deeply nested output"
value = "old"
}
`,
"main.tf": `
module "silenced" {
count = 2
source = "./mod"
# We don't want deprecations within this module call
ignore_nested_deprecations = true
}
# We want to still have deprecation warnings within our control surface
locals {
using_silenced_old = module.silenced[0].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.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
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: 12, Column: 24, Byte: 259},
End: hcl.Pos{Line: 12, Column: 46, Byte: 281},
},
}))
}

View file

@ -3846,3 +3846,808 @@ func TestContext2Validate_noListValidated(t *testing.T) {
})
}
}
func TestContext2Validate_deprecated_output_used_in_for_each(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = toset(["old"])
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
action "test_action" "test" {
for_each = module.mod.old # WARNING
}
resource "test_resource" "test" {
for_each = module.mod.old # WARNING
attr = "not-deprecated"
}
`,
})
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{},
},
},
})
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: 6, Column: 16, Byte: 84},
End: hcl.Pos{Line: 6, Column: 30, Byte: 98},
},
}).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: 9, Column: 16, Byte: 160},
End: hcl.Pos{Line: 9, Column: 30, Byte: 174},
},
}))
}
func TestContext2Validate_deprecated_output_used_in_count(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = 42
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
action "test_action" "test" {
count = module.mod.old # WARNING
}
resource "test_resource" "test" {
count = module.mod.old # WARNING
attr = "not-deprecated"
}
`,
})
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{},
},
},
})
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: 6, Column: 11, Byte: 79},
End: hcl.Pos{Line: 6, Column: 25, Byte: 93},
},
}).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: 9, Column: 16, Byte: 155},
End: hcl.Pos{Line: 9, Column: 30, Byte: 169},
},
}))
}
func TestContext2Validate_deprecated_output_used_in_resource_config(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
output "old-and-unused" {
deprecated = "This should not show up in the errors, we are not using it"
value = "old"
}
output "new" {
value = "foo"
}
`,
"mod2/main.tf": `
variable "input" {
type = string
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
resource "test_resource" "test" {
attr = module.mod.old # WARNING
}
resource "test_resource" "test2" {
attr = module.mod.new # OK
}
resource "test_resource" "test3" {
attr = module.mod.old # WARNING
}
output "test_output" {
value = module.mod.old # WARNING
}
output "test_output_conditional" {
value = false ? module.mod.old : module.mod.new # WARNING
}
module "mod2" {
source = "./mod2"
input = 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,
},
},
},
},
})
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: 6, Column: 12, Byte: 84},
End: hcl.Pos{Line: 6, Column: 26, Byte: 98},
},
}).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: 12, Column: 12, Byte: 225},
End: hcl.Pos{Line: 12, Column: 26, Byte: 239},
},
}).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: 15, Column: 10, Byte: 284},
End: hcl.Pos{Line: 15, Column: 24, Byte: 298},
},
}).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: 18, Column: 10, Byte: 355},
End: hcl.Pos{Line: 18, Column: 49, Byte: 394},
},
}).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: 22, Column: 10, Byte: 451},
End: hcl.Pos{Line: 22, Column: 24, Byte: 465},
},
}))
}
func TestContext2Validate_deprecated_output_only_triggers_once(t *testing.T) {
m := testModuleInline(t, map[string]string{
"nested/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
`,
"mod/main.tf": `
module "nested" {
source = "../nested"
}
# This is an acceptable use of a deprecated value, so no warning
output "redeprecated" {
deprecated = "This should not be in use, dependency is deprecated"
value = module.nested.old
}
`,
"mod2/main.tf": `
module "nested" {
source = "../nested"
}
# This is an unacceptable use of a deprecated value, so warning
# but the value of this is not deprecated, we want the warning to exit where it is used
# not multiple times for the same value
output "undeprecated_use_of_deprecated_value" {
value = module.nested.old
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
module "mod2" {
source = "./mod2"
}
resource "test_resource" "test" {
attr = module.mod.redeprecated # WARNING
}
resource "test_resource" "test2" {
attr = module.mod2.undeprecated_use_of_deprecated_value # OK - error was already thrown
}
output "test_output_deprecated_use" {
value = module.mod.redeprecated # 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,
},
},
},
},
})
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: "This should not be in use, dependency is deprecated",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 9, Column: 12, Byte: 124},
End: hcl.Pos{Line: 9, Column: 35, Byte: 147},
},
},
).Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "This should not be in use, dependency is deprecated",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 15, Column: 10, Byte: 336},
End: hcl.Pos{Line: 15, Column: 33, Byte: 359},
},
},
).Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "mod2", "main.tf"),
Start: hcl.Pos{Line: 10, Column: 11, Byte: 295},
End: hcl.Pos{Line: 10, Column: 28, Byte: 312},
},
},
),
)
}
func TestContext2Validate_deprecated_output_used_in_local(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = toset(["old"])
}
`,
"main.tf": `
module "mod" {
source = "./mod"
}
locals {
value = module.mod.old # WARNING
}
resource "test_resource" "test" {
for_each = local.value # OK - Warning happened above
attr = "not-deprecated"
}
`,
})
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,
},
},
},
},
})
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: 7, Column: 11, Byte: 59},
End: hcl.Pos{Line: 7, Column: 25, Byte: 73},
},
}))
}
func TestContext2Validate_deprecated_output_used_in_action(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"
}
action "test_action" "test" {
config {
attr = 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: 8, Column: 12, Byte: 92},
End: hcl.Pos{Line: 8, Column: 26, Byte: 106},
},
}))
}
func TestContext2Validate_deprecated_output_used_in_other_module(t *testing.T) {
m := testModuleInline(t, map[string]string{
"source/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
`,
"sink/main.tf": `
variable "input" {
type = string
}
`,
"main.tf": `
module "source" {
source = "./source"
}
module "sink" {
source = "./sink"
input = module.source.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: 8, Column: 13, Byte: 100},
End: hcl.Pos{Line: 8, Column: 30, Byte: 117},
},
}))
}
func TestContext2Validate_deprecated_resource(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
resource "test_resource" "test" { # WARNING
attr = "value"
}
output "a" {
value = test_resource.test.attr # WARNING
}
`,
})
p := new(testing_provider.MockProvider)
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Deprecated: true,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.String,
Computed: 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: `Usage of deprecated resource "test_resource"`,
Detail: `The resource "test_resource" has been marked as deprecated by its provider. Please check the provider documentation for more information.`,
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 2, Column: 1, Byte: 1},
End: hcl.Pos{Line: 2, Column: 32, Byte: 32},
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: `Resource "test_resource" is deprecated`,
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 13, Byte: 92},
End: hcl.Pos{Line: 7, Column: 36, Byte: 115},
},
}))
}
func TestContext2Validate_deprecated_root_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"
}
output "test_output" {
deprecated = "Deprecating the root output is not ok" # ERROR
value = module.mod.old
}
`,
})
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,
},
},
},
},
})
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.DiagError,
Summary: "Root module output deprecated",
Detail: "Root module outputs cannot be deprecated, as there is no higher-level module to inform of the deprecation.",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
Start: hcl.Pos{Line: 7, Column: 5, Byte: 67},
End: hcl.Pos{Line: 7, Column: 57, Byte: 119},
},
}))
}
func TestContext2Validate_ignoring_nested_module_deprecations(t *testing.T) {
m := testModuleInline(t, map[string]string{
"mod/main.tf": `
output "old" {
deprecated = "Please stop using this"
value = "old"
}
module "nested" {
source = "./nested"
}
locals {
foo = module.nested.old # WARNING (if not muted)
}
`,
"mod/nested/main.tf": `
output "old" {
deprecated = "Please stop using this nested output"
value = "old"
}
module "deeper" {
source = "./deeper"
}
locals {
bar = module.deeper.old # WARNING (if not muted)
}
`,
"mod/nested/deeper/main.tf": `
output "old" {
deprecated = "Please stop using this deeply nested output"
value = "old"
}
`,
"main.tf": `
module "normal" {
source = "./mod"
}
module "silenced" {
source = "./mod"
# We don't want deprecations within this module call
ignore_nested_deprecations = true
}
# We want to still have deprecation warnings within our control surface
locals {
using_normal_old = module.normal.old # WARNING
using_silenced_old = module.silenced.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: 15, Column: 22, Byte: 285},
End: hcl.Pos{Line: 15, Column: 39, Byte: 302},
},
}).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: 16, Column: 24, Byte: 336},
End: hcl.Pos{Line: 16, Column: 43, Byte: 355},
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this nested output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "mod", "main.tf"),
Start: hcl.Pos{Line: 12, Column: 9, Byte: 141},
End: hcl.Pos{Line: 12, Column: 26, Byte: 158},
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this deeply nested output",
Subject: &hcl.Range{
Filename: filepath.Join(m.Module.SourceDir, "mod", "nested", "main.tf"),
Start: hcl.Pos{Line: 12, Column: 9, Byte: 155},
End: hcl.Pos{Line: 12, Column: 26, Byte: 172},
},
}))
}

View file

@ -159,6 +159,11 @@ func evalCheckRule(addr addrs.CheckRule, rule *configs.CheckRule, ctx EvalContex
})
return checkResult{Status: checks.StatusError}, diags
}
// We don't care about the returned value here, only the diagnostics
_, deprecationDiags := ctx.Deprecations().Validate(resultVal, addr.ModuleInstance().Module(), rule.Condition.Range().Ptr())
diags = diags.Append(deprecationDiags)
var err error
resultVal, err = convert.Convert(resultVal, cty.Bool)
if err != nil {

View file

@ -10,6 +10,7 @@ import (
"github.com/zclconf/go-cty/cty/gocty"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -28,8 +29,8 @@ import (
// true instead permits unknown values, indicating them by returning the
// placeholder value -1. Callers can assume that a return value of -1 without
// any error diagnostics represents a valid unknown value.
func evaluateCountExpression(expr hcl.Expression, ctx EvalContext, allowUnknown bool) (int, tfdiags.Diagnostics) {
countVal, diags := evaluateCountExpressionValue(expr, ctx)
func evaluateCountExpression(expr hcl.Expression, ctx EvalContext, moduleAddr addrs.Module, allowUnknown bool) (int, tfdiags.Diagnostics) {
countVal, diags := evaluateCountExpressionValue(expr, ctx, moduleAddr)
if !allowUnknown && !countVal.IsKnown() {
// Currently this is a rather bad outcome from a UX standpoint, since we have
// no real mechanism to deal with this situation and all we can do is produce
@ -74,7 +75,7 @@ func evaluateCountExpression(expr hcl.Expression, ctx EvalContext, allowUnknown
// evaluateCountExpressionValue is like evaluateCountExpression
// except that it returns a cty.Value which must be a cty.Number and can be
// unknown.
func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) {
func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext, moduleAddr addrs.Module) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
nullCount := cty.NullVal(cty.Number)
if expr == nil {
@ -102,6 +103,9 @@ func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Val
})
}
countVal, deprecationDiags := ctx.Deprecations().Validate(countVal, moduleAddr, expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
// Sensitive values are allowed in count but not for_each. This is a
// somewhat-dubious decision because the number of instances planned
// will disclose exactly what the value was, but in practice it's rare

View file

@ -11,6 +11,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcltest"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
@ -33,7 +34,7 @@ func TestEvaluateCountExpression(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
countVal, diags := evaluateCountExpression(test.Expr, ctx, false)
countVal, diags := evaluateCountExpression(test.Expr, ctx, addrs.RootModule, false)
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
@ -53,7 +54,7 @@ func TestEvaluateCountExpression_ephemeral(t *testing.T) {
expr := hcltest.MockExprLiteral(cty.NumberIntVal(8).Mark(marks.Ephemeral))
ctx := &MockEvalContext{}
ctx.installSimpleEval()
_, diags := evaluateCountExpression(expr, ctx, false)
_, diags := evaluateCountExpression(expr, ctx, addrs.RootModule, false)
if !diags.HasErrors() {
t.Fatalf("unexpected success; want error")
}
@ -82,7 +83,7 @@ func TestEvaluateCountExpression_allowUnknown(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
countVal, diags := evaluateCountExpression(test.Expr, ctx, true)
countVal, diags := evaluateCountExpression(test.Expr, ctx, addrs.RootModule, true)
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))

View file

@ -19,13 +19,13 @@ import (
// evaluateForEachExpression differs from evaluateForEachExpressionValue by
// returning an error if the count value is not known, and converting the
// cty.Value to a map[string]cty.Value for compatibility with other calls.
func evaluateForEachExpression(expr hcl.Expression, ctx EvalContext, allowUnknown bool) (forEach map[string]cty.Value, known bool, diags tfdiags.Diagnostics) {
return newForEachEvaluator(expr, ctx, allowUnknown).ResourceValue()
func evaluateForEachExpression(expr hcl.Expression, ctx EvalContext, module addrs.Module, allowUnknown bool) (forEach map[string]cty.Value, known bool, diags tfdiags.Diagnostics) {
return newForEachEvaluator(expr, ctx, module, allowUnknown).ResourceValue()
}
// forEachEvaluator is the standard mechanism for interpreting an expression
// given for a "for_each" argument on a resource, module, or import.
func newForEachEvaluator(expr hcl.Expression, ctx EvalContext, allowUnknown bool) *forEachEvaluator {
func newForEachEvaluator(expr hcl.Expression, ctx EvalContext, module addrs.Module, allowUnknown bool) *forEachEvaluator {
if ctx == nil {
panic("nil EvalContext")
}
@ -33,6 +33,7 @@ func newForEachEvaluator(expr hcl.Expression, ctx EvalContext, allowUnknown bool
return &forEachEvaluator{
ctx: ctx,
expr: expr,
moduleAddr: module,
allowUnknown: allowUnknown,
}
}
@ -49,6 +50,8 @@ type forEachEvaluator struct {
ctx EvalContext
expr hcl.Expression
moduleAddr addrs.Module
// TEMP: If allowUnknown is set then we skip the usual restriction that
// unknown values are not allowed in for_each. A caller that sets this
// must therefore be ready to deal with the result being unknown.
@ -327,6 +330,12 @@ func (ev *forEachEvaluator) validateResourceOrActionForEach(forEachVal cty.Value
})
}
// We don't care about the returned value here, only the diagnostics
module := addrs.RootModule // TODO: FIXME
_, deprecationDiags := ev.ctx.Deprecations().Validate(forEachVal, module, ev.expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
diags = diags.Append(ev.ensureNotEphemeral(forEachVal))
if diags.HasErrors() {

View file

@ -11,6 +11,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcltest"
"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"
@ -82,7 +83,7 @@ func TestEvaluateForEachExpression_valid(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
forEachMap, _, diags := evaluateForEachExpression(test.Expr, ctx, false)
forEachMap, _, diags := evaluateForEachExpression(test.Expr, ctx, addrs.RootModule, false)
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
@ -201,7 +202,7 @@ func TestEvaluateForEachExpression_errors(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
_, _, diags := evaluateForEachExpression(test.Expr, ctx, false)
_, _, diags := evaluateForEachExpression(test.Expr, ctx, addrs.RootModule, false)
if len(diags) != 1 {
t.Fatalf("got %d diagnostics; want 1", len(diags))
@ -261,7 +262,7 @@ func TestEvaluateForEachExpression_allowUnknown(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
_, known, diags := evaluateForEachExpression(test.Expr, ctx, true)
_, known, diags := evaluateForEachExpression(test.Expr, ctx, addrs.RootModule, true)
// With allowUnknown set, all of these expressions should be treated
// as valid for_each values.
@ -284,7 +285,7 @@ func TestEvaluateForEachExpressionKnown(t *testing.T) {
t.Run(name, func(t *testing.T) {
ctx := &MockEvalContext{}
ctx.installSimpleEval()
diags := newForEachEvaluator(expr, ctx, false).ValidateResourceValue()
diags := newForEachEvaluator(expr, ctx, addrs.RootModule, false).ValidateResourceValue()
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))

View file

@ -411,9 +411,19 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
// structural type of similar kind, so that it can be considered as
// valid during both the validate and plan walks.
if d.Operation == walkValidate {
// In case of non-expanded module calls we return a known object with unknonwn values
// In case of an expanded module call we return unknown list/map
// This means deprecation can only for non-expanded modules be detected during validate
// since we don't want false positives. The plan walk will give definitive warnings.
atys := make(map[string]cty.Type, len(outputConfigs))
for name := range outputConfigs {
as := make(map[string]cty.Value, len(outputConfigs))
for name, c := range outputConfigs {
atys[name] = cty.DynamicPseudoType // output values are dynamically-typed
val := cty.UnknownVal(cty.DynamicPseudoType)
if c.DeprecatedSet {
val = val.Mark(marks.NewDeprecation(c.Deprecated))
}
as[name] = val
}
instTy := cty.Object(atys)
@ -423,7 +433,8 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
case callConfig.ForEach != nil:
return cty.UnknownVal(cty.Map(instTy)), diags
default:
return cty.UnknownVal(instTy), diags
val := cty.ObjectVal(as)
return val, diags
}
}
@ -469,6 +480,10 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
if cfg.Sensitive {
outputVal = outputVal.Mark(marks.Sensitive)
}
if cfg.DeprecatedSet {
outputVal = outputVal.Mark(marks.NewDeprecation(cfg.Deprecated))
}
attrs[name] = outputVal
}
@ -774,7 +789,11 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
// We should only end up here during the validate walk (or
// console/eval), since later walks should have at least partial
// states populated for all resources in the configuration.
return cty.DynamicVal, diags
ret := cty.DynamicVal
if schema.Body.Deprecated {
ret = ret.Mark(marks.NewDeprecation(fmt.Sprintf("Resource %q is deprecated", addr.Type)))
}
return ret, diags
}
}
@ -849,6 +868,10 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
ret = val
}
if schema.Body.Deprecated {
ret = ret.Mark(marks.NewDeprecation(fmt.Sprintf("Resource %q is deprecated", addr.Type)))
}
return ret, diags
}
@ -1128,6 +1151,9 @@ func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.Sour
if config.Ephemeral {
value = value.Mark(marks.Ephemeral)
}
if config.DeprecatedSet {
value = value.Mark(marks.NewDeprecation(config.Deprecated))
}
return value, diags
}

View file

@ -115,7 +115,7 @@ func (n *nodeExpandActionDeclaration) recordActionData(ctx EvalContext, addr add
switch {
case n.Config.Count != nil:
count, countDiags := evaluateCountExpression(n.Config.Count, ctx, false)
count, countDiags := evaluateCountExpression(n.Config.Count, ctx, n.ModulePath(), false)
diags = diags.Append(countDiags)
if countDiags.HasErrors() {
return diags
@ -130,7 +130,7 @@ func (n *nodeExpandActionDeclaration) recordActionData(ctx EvalContext, addr add
}
case n.Config.ForEach != nil:
forEach, known, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, false)
forEach, known, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), false)
diags = diags.Append(forEachDiags)
if forEachDiags.HasErrors() {
return diags

View file

@ -57,7 +57,7 @@ func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiag
// Basic type-checking of the count argument. More complete validation
// of this will happen when we DynamicExpand during the plan walk.
_, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx)
_, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx, n.ModulePath())
diags = diags.Append(countDiags)
case n.Config.ForEach != nil:
@ -67,7 +67,7 @@ func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiag
}
// Evaluate the for_each expression here so we can expose the diagnostics
forEachDiags := newForEachEvaluator(n.Config.ForEach, ctx, false).ValidateActionValue()
forEachDiags := newForEachEvaluator(n.Config.ForEach, ctx, n.ModulePath(), false).ValidateActionValue()
diags = diags.Append(forEachDiags)
}
@ -102,6 +102,8 @@ func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiag
}
}
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()))
valDiags = validateResourceForbiddenEphemeralValues(ctx, configVal, schema.ConfigSchema)
diags = diags.Append(valDiags.InConfigBody(config, n.Addr.String()))

View file

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/lang/langrefs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -140,7 +141,10 @@ func (n *NodeLocal) References() []*addrs.Reference {
func (n *NodeLocal) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
namedVals := ctx.NamedValues()
val, diags := evaluateLocalValue(n.Config, n.Addr.LocalValue, n.Addr.String(), ctx)
namedVals.SetLocalValue(n.Addr, val)
valWithoutDeprecations, deprecationDiags := ctx.Deprecations().Validate(val, n.ModulePath(), n.Config.Expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
namedVals.SetLocalValue(n.Addr, valWithoutDeprecations)
return diags
}
@ -236,3 +240,22 @@ func evaluateLocalValue(config *configs.Local, localAddr addrs.LocalValue, addrS
}
return val, diags
}
func validateExprUsingDeprecatedValues(val cty.Value, expr hcl.Expression) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
_, pvms := val.UnmarkDeepWithPaths()
for _, pvm := range pvms {
for m := range pvm.Marks {
if depMark, ok := m.(marks.DeprecationMark); ok {
diags = diags.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: depMark.Message,
Subject: expr.Range().Ptr(),
})
}
}
}
return diags
}

View file

@ -112,6 +112,10 @@ func (n *nodeExpandModule) Execute(globalCtx EvalContext, op walkOperation) (dia
expander := globalCtx.InstanceExpander()
_, call := n.Addr.Call()
if n.ModuleCall.IgnoreNestedDeprecations {
globalCtx.Deprecations().SuppressModuleCallDeprecation(n.Addr)
}
// Allowing unknown values in count and for_each is a top-level plan option.
//
// If this is false then the codepaths that handle unknown values below
@ -127,7 +131,7 @@ func (n *nodeExpandModule) Execute(globalCtx EvalContext, op walkOperation) (dia
switch {
case n.ModuleCall.Count != nil:
count, ctDiags := evaluateCountExpression(n.ModuleCall.Count, moduleCtx, allowUnknown)
count, ctDiags := evaluateCountExpression(n.ModuleCall.Count, moduleCtx, n.Addr, allowUnknown)
diags = diags.Append(ctDiags)
if diags.HasErrors() {
return diags
@ -140,7 +144,7 @@ func (n *nodeExpandModule) Execute(globalCtx EvalContext, op walkOperation) (dia
}
case n.ModuleCall.ForEach != nil:
forEach, known, feDiags := evaluateForEachExpression(n.ModuleCall.ForEach, moduleCtx, allowUnknown)
forEach, known, feDiags := evaluateForEachExpression(n.ModuleCall.ForEach, moduleCtx, module.Module(), allowUnknown)
diags = diags.Append(feDiags)
if diags.HasErrors() {
return diags
@ -261,6 +265,10 @@ func (n *nodeValidateModule) Execute(globalCtx EvalContext, op walkOperation) (d
_, call := n.Addr.Call()
expander := globalCtx.InstanceExpander()
if n.ModuleCall.IgnoreNestedDeprecations {
globalCtx.Deprecations().SuppressModuleCallDeprecation(n.Addr)
}
// Modules all evaluate to single instances during validation, only to
// create a proper context within which to evaluate. All parent modules
// will be a single instance, but still get our address in the expected
@ -273,11 +281,11 @@ func (n *nodeValidateModule) Execute(globalCtx EvalContext, op walkOperation) (d
// a full expansion, presuming these errors will be caught in later steps
switch {
case n.ModuleCall.Count != nil:
_, countDiags := evaluateCountExpressionValue(n.ModuleCall.Count, moduleCtx)
_, countDiags := evaluateCountExpressionValue(n.ModuleCall.Count, moduleCtx, n.ModulePath())
diags = diags.Append(countDiags)
case n.ModuleCall.ForEach != nil:
forEachDiags := newForEachEvaluator(n.ModuleCall.ForEach, moduleCtx, false).ValidateResourceValue()
forEachDiags := newForEachEvaluator(n.ModuleCall.ForEach, moduleCtx, module.Module(), false).ValidateResourceValue()
diags = diags.Append(forEachDiags)
}

View file

@ -230,6 +230,7 @@ func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags t
if diags.HasErrors() {
return diags
}
diags = diags.Append(validateExprUsingDeprecatedValues(val, n.Expr))
// Set values for arguments of a child module call, for later retrieval
// during expression evaluation.
@ -306,6 +307,15 @@ func (n *nodeModuleVariable) evalModuleVariable(ctx EvalContext, validateOnly bo
finalVal, moreDiags := PrepareFinalInputVariableValue(n.Addr, rawVal, n.Config)
diags = diags.Append(moreDiags)
if n.Config.DeprecatedSet && !givenVal.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: n.Config.Deprecated,
Subject: n.Expr.Range().Ptr(),
})
}
return finalVal, diags.ErrWithWarnings()
}

View file

@ -518,6 +518,21 @@ If you do intend to export this data, annotate the output value as sensitive by
return diags
}
if n.Config.DeprecatedSet {
val = marks.RemoveDeprecationMarks(val)
if n.Addr.Module.IsRoot() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Root module output deprecated",
Detail: "Root module outputs cannot be deprecated, as there is no higher-level module to inform of the deprecation.",
Subject: n.Config.DeprecatedRange.Ptr(),
})
}
} else {
_, deprecationDiags := ctx.Deprecations().Validate(val, n.ModulePath(), n.Config.Expr.Range().Ptr())
diags = diags.Append(deprecationDiags)
}
n.setValue(ctx.NamedValues(), state, changes, ctx.Deferrals(), val)
// If we were able to evaluate a new value, we can update that in the

View file

@ -446,7 +446,7 @@ func (n *NodeAbstractResource) recordResourceData(ctx EvalContext, addr addrs.Ab
switch {
case n.Config != nil && n.Config.Count != nil:
count, countDiags := evaluateCountExpression(n.Config.Count, ctx, allowUnknown)
count, countDiags := evaluateCountExpression(n.Config.Count, ctx, n.ModulePath(), allowUnknown)
diags = diags.Append(countDiags)
if countDiags.HasErrors() {
return diags
@ -460,7 +460,7 @@ func (n *NodeAbstractResource) recordResourceData(ctx EvalContext, addr addrs.Ab
}
case n.Config != nil && n.Config.ForEach != nil:
forEach, known, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, allowUnknown)
forEach, known, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), allowUnknown)
diags = diags.Append(forEachDiags)
if forEachDiags.HasErrors() {
return diags

View file

@ -842,7 +842,7 @@ func (n *NodeAbstractResourceInstance) plan(
}
// Evaluate the configuration
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), false)
keyData = EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)
@ -869,6 +869,7 @@ func (n *NodeAbstractResourceInstance) plan(
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, origConfigVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(origConfigVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()))
if diags.HasErrors() {
return nil, nil, deferred, keyData, diags
}
@ -1842,7 +1843,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
objTy := schema.Body.ImpliedType()
priorVal := cty.NullVal(objTy)
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, n.ModulePath(), false)
keyData = EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)
checkDiags := evalCheckRules(
@ -2169,7 +2170,7 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned
return nil, keyData, diags
}
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, n.ModulePath(), false)
keyData = EvalDataForInstanceKey(n.Addr.Resource.Key, forEach)
checkDiags := evalCheckRules(
@ -2503,7 +2504,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
func (n *NodeAbstractResourceInstance) evalProvisionerConfig(ctx EvalContext, body hcl.Body, self cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
forEach, _, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, false)
forEach, _, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), false)
diags = diags.Append(forEachDiags)
keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)

View file

@ -213,7 +213,7 @@ func (n *nodeExpandPlannableResource) expandResourceImports(ctx EvalContext, all
continue
}
forEachData, known, forEachDiags := newForEachEvaluator(imp.Config.ForEach, ctx, allowUnknown).ImportValues()
forEachData, known, forEachDiags := newForEachEvaluator(imp.Config.ForEach, ctx, n.ModulePath(), allowUnknown).ImportValues()
diags = diags.Append(forEachDiags)
if forEachDiags.HasErrors() {
return knownImports, unknownImports, diags

View file

@ -515,7 +515,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
// values, which could result in a post-condition check relying on that
// value being inaccurate. Unless we decide to store the value of the
// for-each expression in state, this is unavoidable.
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), false)
repeatData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)
checkDiags := evalCheckRules(
@ -645,7 +645,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
return nil, deferred, diags
}
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, n.ModulePath(), false)
keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)
configVal, _, configDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
if configDiags.HasErrors() {

View file

@ -32,7 +32,7 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
keyData := EvalDataForInstanceKey(addr.Resource.Key, nil)
if config.ForEach != nil {
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, false)
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, n.ModulePath(), false)
keyData = EvalDataForInstanceKey(addr.Resource.Key, forEach)
}

View file

@ -68,6 +68,16 @@ func (n *NodeValidatableResource) Execute(ctx EvalContext, op walkOperation) (di
}
}
}
if n.Schema != nil && n.Schema.Body != nil && n.Schema.Body.Deprecated {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("Usage of deprecated resource %q", n.Addr.Resource.Type),
Detail: fmt.Sprintf("The resource %q has been marked as deprecated by its provider. Please check the provider documentation for more information.", n.Addr.Resource.Type),
Subject: &n.Config.DeclRange,
})
}
return diags
}
@ -302,7 +312,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
// Basic type-checking of the count argument. More complete validation
// of this will happen when we DynamicExpand during the plan walk.
_, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx)
_, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx, n.ModulePath())
diags = diags.Append(countDiags)
case n.Config.ForEach != nil:
@ -312,7 +322,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
}
// Evaluate the for_each expression here so we can expose the diagnostics
forEachDiags := newForEachEvaluator(n.Config.ForEach, ctx, false).ValidateResourceValue()
forEachDiags := newForEachEvaluator(n.Config.ForEach, ctx, n.ModulePath(), false).ValidateResourceValue()
diags = diags.Append(forEachDiags)
}
@ -355,6 +365,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()))
if n.Config.Managed != nil { // can be nil only in tests with poorly-configured mocks
for _, traversal := range n.Config.Managed.IgnoreChanges {
@ -434,6 +445,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()))
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
@ -469,6 +481,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
}
resp := provider.ValidateEphemeralResourceConfig(req)
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()))
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.ListResourceMode:
schema := providerSchema.SchemaForListResourceType(n.Config.Type)
@ -487,18 +500,21 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
if valDiags.HasErrors() {
return diags
}
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(blockVal, n.ModulePath()))
limit, _, limitDiags := newLimitEvaluator(true).EvaluateExpr(ctx, n.Config.List.Limit)
diags = diags.Append(limitDiags)
if limitDiags.HasErrors() {
return diags
}
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(limit, n.ModulePath()))
includeResource, _, includeDiags := newIncludeRscEvaluator(true).EvaluateExpr(ctx, n.Config.List.IncludeResource)
diags = diags.Append(includeDiags)
if includeDiags.HasErrors() {
return diags
}
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(includeResource, n.ModulePath()))
// Use unmarked value for validate request
unmarkedBlockVal, _ := blockVal.UnmarkDeep()
@ -630,7 +646,7 @@ func (n *NodeValidatableResource) validateImportTargets(ctx EvalContext) tfdiags
return diags
}
forEachData, _, forEachDiags := newForEachEvaluator(imp.Config.ForEach, ctx, true).ImportValues()
forEachData, _, forEachDiags := newForEachEvaluator(imp.Config.ForEach, ctx, n.ModulePath(), true).ImportValues()
diags = diags.Append(forEachDiags)
if forEachDiags.HasErrors() {
return diags

View file

@ -92,6 +92,15 @@ func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Di
}
}
if n.Config.DeprecatedSet && !givenVal.Value.IsNull() && givenVal.Value.IsKnown() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated variable got a value",
Detail: n.Config.Deprecated,
Subject: &n.Config.DeprecatedRange,
})
}
if n.Planning {
if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(addrs.RootModule)) {
ctx.Checks().ReportCheckableObjects(