mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
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:
parent
13247d19e2
commit
958a1ae1e7
33 changed files with 2392 additions and 39 deletions
5
.changes/v1.15/NEW FEATURES-20251205-171418.yaml
Normal file
5
.changes/v1.15/NEW FEATURES-20251205-171418.yaml
Normal 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"
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ type Checkable interface {
|
|||
|
||||
CheckableKind() CheckableKind
|
||||
String() string
|
||||
ModuleInstance() ModuleInstance
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue