mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
add deprecation marks for resource attributes and blocks
This commit is contained in:
parent
2746850377
commit
a74b1c4fe3
27 changed files with 1877 additions and 89 deletions
5
.changes/v1.15/ENHANCEMENTS-20251223-110112.yaml
Normal file
5
.changes/v1.15/ENHANCEMENTS-20251223-110112.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
kind: ENHANCEMENTS
|
||||
body: improve detection of deprecated resource attributes
|
||||
time: 2025-12-23T11:01:12.544749+01:00
|
||||
custom:
|
||||
Issue: "38006"
|
||||
|
|
@ -77,27 +77,6 @@ func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnos
|
|||
}
|
||||
|
||||
if attrS, exists := b.Attributes[name]; exists {
|
||||
// Check for Deprecated status of this attribute.
|
||||
// We currently can't provide the user with any useful guidance because
|
||||
// the deprecation string is not part of the schema, but we can at
|
||||
// least warn them.
|
||||
//
|
||||
// This purposely does not attempt to recurse into nested attribute
|
||||
// types. Because nested attribute values are often not accessed via a
|
||||
// direct traversal to the leaf attributes, we cannot reliably detect
|
||||
// if a nested, deprecated attribute value is actually used from the
|
||||
// traversal alone. More precise detection of deprecated attributes
|
||||
// would require adding metadata like marks to the cty value itself, to
|
||||
// be caught during evaluation.
|
||||
if attrS.Deprecated {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated attribute`,
|
||||
Detail: fmt.Sprintf(`The attribute %q is deprecated. Refer to the provider documentation for details.`, name),
|
||||
Subject: next.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
// For attribute validation we will just apply the rest of the
|
||||
// traversal to an unknown value of the attribute type and pass
|
||||
// through HCL's own errors, since we don't want to replicate all
|
||||
|
|
|
|||
|
|
@ -224,10 +224,6 @@ func TestStaticValidateTraversal(t *testing.T) {
|
|||
`obj.nested_map["key"].optional`,
|
||||
``,
|
||||
},
|
||||
{
|
||||
`obj.deprecated`,
|
||||
`Deprecated attribute: The attribute "deprecated" is deprecated. Refer to the provider documentation for details.`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
|||
|
|
@ -43,21 +43,7 @@ func (d *Deprecations) Validate(value cty.Value, module addrs.Module, rng *hcl.R
|
|||
}
|
||||
|
||||
notDeprecatedValue := marks.RemoveDeprecationMarks(value)
|
||||
|
||||
// Check if we need to suppress deprecation warnings for this module call.
|
||||
if d.IsModuleCallDeprecationSuppressed(module) {
|
||||
return notDeprecatedValue, diags
|
||||
}
|
||||
|
||||
for _, depMark := range deprecationMarks {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Deprecated value used",
|
||||
Detail: depMark.Message,
|
||||
Subject: rng,
|
||||
})
|
||||
}
|
||||
|
||||
diags = diags.Append(d.diagnosticsForDeprecationMarks(deprecationMarks, module, rng))
|
||||
return notDeprecatedValue, diags
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +72,27 @@ func (d *Deprecations) ValidateAsConfig(value cty.Value, module addrs.Module) tf
|
|||
return diags
|
||||
}
|
||||
|
||||
func (d *Deprecations) DiagnosticsForValueMarks(valueMarks cty.ValueMarks, module addrs.Module, rng *hcl.Range) tfdiags.Diagnostics {
|
||||
return d.diagnosticsForDeprecationMarks(marks.FilterDeprecationMarks(valueMarks), module, rng)
|
||||
}
|
||||
|
||||
func (d *Deprecations) diagnosticsForDeprecationMarks(deprecationMarks []marks.DeprecationMark, module addrs.Module, rng *hcl.Range) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
// Check if we need to suppress deprecation warnings for this module call.
|
||||
if !d.IsModuleCallDeprecationSuppressed(module) {
|
||||
for _, depMark := range deprecationMarks {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Deprecated value used",
|
||||
Detail: depMark.Message,
|
||||
Subject: rng,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (d *Deprecations) IsModuleCallDeprecationSuppressed(addr addrs.Module) bool {
|
||||
for _, mod := range d.suppressedModules {
|
||||
if mod.TargetContains(addr) {
|
||||
|
|
|
|||
52
internal/deprecation/schema.go
Normal file
52
internal/deprecation/schema.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package deprecation
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// MarkDeprecatedValues inspects the given cty.Value according to the given
|
||||
// configschema.Block schema, and marks any deprecated attributes or blocks
|
||||
// found within the value with deprecation marks.
|
||||
// It works based on the given cty.Value's structure matching the given schema.
|
||||
func MarkDeprecatedValues(val cty.Value, schema *configschema.Block) cty.Value {
|
||||
if schema == nil {
|
||||
return val
|
||||
}
|
||||
newVal := val
|
||||
|
||||
// Check if the block is deprecated
|
||||
if schema.Deprecated {
|
||||
newVal = newVal.Mark(marks.NewDeprecation("deprecated resource block used"))
|
||||
}
|
||||
|
||||
if !newVal.IsKnown() {
|
||||
return newVal
|
||||
}
|
||||
|
||||
// Even if the block itself is not deprecated, its attributes might be
|
||||
// deprecated as well
|
||||
if val.Type().IsObjectType() || val.Type().IsMapType() || val.Type().IsCollectionType() {
|
||||
// We ignore the error, so errors are not allowed in the transform function
|
||||
newVal, _ = cty.Transform(newVal, func(p cty.Path, v cty.Value) (cty.Value, error) {
|
||||
|
||||
attr := schema.AttributeByPath(p)
|
||||
if attr != nil && attr.Deprecated {
|
||||
v = v.Mark(marks.NewDeprecation("deprecated resource attribute used"))
|
||||
}
|
||||
|
||||
block := schema.BlockByPath(p)
|
||||
if block != nil && block.Deprecated {
|
||||
v = v.Mark(marks.NewDeprecation("deprecated resource block used"))
|
||||
}
|
||||
|
||||
return v, nil
|
||||
})
|
||||
}
|
||||
|
||||
return newVal
|
||||
}
|
||||
1019
internal/deprecation/schema_test.go
Normal file
1019
internal/deprecation/schema_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -22,13 +22,13 @@ import (
|
|||
// It will either return a non-empty message string or it'll return diagnostics
|
||||
// with either errors or warnings that explain why the given expression isn't
|
||||
// acceptable.
|
||||
func EvalCheckErrorMessage(expr hcl.Expression, hclCtx *hcl.EvalContext, ruleAddr *addrs.CheckRule) (string, tfdiags.Diagnostics) {
|
||||
func EvalCheckErrorMessage(expr hcl.Expression, hclCtx *hcl.EvalContext, ruleAddr *addrs.CheckRule) (string, cty.ValueMarks, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
val, hclDiags := expr.Value(hclCtx)
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return "", diags
|
||||
return "", cty.NewValueMarks(), diags
|
||||
}
|
||||
|
||||
val, err := convert.Convert(val, cty.String)
|
||||
|
|
@ -41,10 +41,10 @@ func EvalCheckErrorMessage(expr hcl.Expression, hclCtx *hcl.EvalContext, ruleAdd
|
|||
Expression: expr,
|
||||
EvalContext: hclCtx,
|
||||
})
|
||||
return "", diags
|
||||
return "", cty.NewValueMarks(), diags
|
||||
}
|
||||
if !val.IsKnown() {
|
||||
return "", diags
|
||||
return "", cty.NewValueMarks(), diags
|
||||
}
|
||||
if val.IsNull() {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
|
|
@ -55,7 +55,7 @@ func EvalCheckErrorMessage(expr hcl.Expression, hclCtx *hcl.EvalContext, ruleAdd
|
|||
Expression: expr,
|
||||
EvalContext: hclCtx,
|
||||
})
|
||||
return "", diags
|
||||
return "", cty.NewValueMarks(), diags
|
||||
}
|
||||
|
||||
val, valMarks := val.Unmark()
|
||||
|
|
@ -70,7 +70,7 @@ You can correct this by removing references to sensitive values, or by carefully
|
|||
Expression: expr,
|
||||
EvalContext: hclCtx,
|
||||
})
|
||||
return "", diags
|
||||
return "", valMarks, diags
|
||||
}
|
||||
|
||||
if _, ephemeral := valMarks[marks.Ephemeral]; ephemeral {
|
||||
|
|
@ -90,11 +90,11 @@ You can correct this by removing references to ephemeral values, or by using the
|
|||
Subject: expr.Range().Ptr(),
|
||||
Extra: extra,
|
||||
})
|
||||
return "", diags
|
||||
return "", valMarks, diags
|
||||
}
|
||||
|
||||
// NOTE: We've discarded any other marks the string might have been carrying,
|
||||
// aside from the sensitive mark.
|
||||
|
||||
return strings.TrimSpace(val.AsString()), diags
|
||||
return strings.TrimSpace(val.AsString()), valMarks, diags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ func (ec *EvalContext) EvaluateRun(run *configs.TestRun, module *configs.Module,
|
|||
continue
|
||||
}
|
||||
|
||||
errorMessage, moreDiags := lang.EvalCheckErrorMessage(rule.ErrorMessage, hclCtx, nil)
|
||||
errorMessage, _, moreDiags := lang.EvalCheckErrorMessage(rule.ErrorMessage, hclCtx, nil)
|
||||
ruleDiags = ruleDiags.Append(moreDiags)
|
||||
|
||||
runVal, hclDiags := rule.Condition.Value(hclCtx)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
|
||||
"github.com/hashicorp/terraform/internal/provisioners"
|
||||
|
|
@ -2459,37 +2460,476 @@ resource "aws_instance" "test" {
|
|||
}
|
||||
|
||||
func TestContext2Validate_deprecatedAttr(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
for name, tc := range map[string]struct {
|
||||
attributeSchema map[string]*configschema.Attribute
|
||||
blockSchema map[string]*configschema.NestedBlock
|
||||
module map[string]string
|
||||
expectedValidationDiags func(*configs.Config) tfdiags.Diagnostics
|
||||
expectedPlanDiags func(*configs.Config) tfdiags.Diagnostics
|
||||
expectedApplyDiags func(*configs.Config) tfdiags.Diagnostics
|
||||
}{
|
||||
"in locals": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
locals {
|
||||
deprecated = aws_instance.test.foo
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 5, Column: 28, Byte: 108},
|
||||
End: hcl.Pos{Line: 5, Column: 49, Byte: 129},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
locals {
|
||||
deprecated = aws_instance.test.foo
|
||||
}
|
||||
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
"in count": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.Number, Required: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = 2
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
count = aws_instance.test.foo
|
||||
foo = 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(m *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 23, Byte: 152},
|
||||
End: hcl.Pos{Line: 6, Column: 44, Byte: 173},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m, nil)
|
||||
warn := diags.ErrWithWarnings().Error()
|
||||
if !strings.Contains(warn, `The attribute "foo" is deprecated`) {
|
||||
t.Fatalf("expected deprecated warning, got: %q\n", warn)
|
||||
"in for_each": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.Set(cty.String), Required: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = ["a", "b"]
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
for_each = aws_instance.test.foo
|
||||
foo = ["x"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(m *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(m.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 26, Byte: 164},
|
||||
End: hcl.Pos{Line: 6, Column: 47, Byte: 185},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in output": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
output "deprecated_output" {
|
||||
value = aws_instance.test.foo
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 5, Column: 23, Byte: 123},
|
||||
End: hcl.Pos{Line: 5, Column: 44, Byte: 144},
|
||||
},
|
||||
})
|
||||
},
|
||||
// During apply we take the planned value for the output. Since the plan
|
||||
// does not contain marks we can not detect this usage at apply time.
|
||||
expectedApplyDiags: func(c *configs.Config) tfdiags.Diagnostics { return tfdiags.Diagnostics{} },
|
||||
},
|
||||
|
||||
"in resource attribute": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
"bar": {Type: cty.String, Optional: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
bar = aws_instance.test.foo
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 5, Column: 21, Byte: 126},
|
||||
End: hcl.Pos{Line: 5, Column: 42, Byte: 147},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in precondition": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Required: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = "bar"
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
foo = "baz"
|
||||
lifecycle {
|
||||
precondition {
|
||||
condition = aws_instance.test.foo != ""
|
||||
error_message = "foo must not be empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedPlanDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 9, Column: 31, Byte: 245},
|
||||
End: hcl.Pos{Line: 9, Column: 58, Byte: 272},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in postcondition condition": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Required: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = "bar"
|
||||
lifecycle {
|
||||
postcondition {
|
||||
condition = self.foo != ""
|
||||
error_message = "foo must not be empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedPlanDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 31, Byte: 160},
|
||||
End: hcl.Pos{Line: 6, Column: 45, Byte: 174},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in dynamic block": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.Set(cty.String), Computed: true, Deprecated: true},
|
||||
},
|
||||
blockSchema: map[string]*configschema.NestedBlock{
|
||||
"bar": {
|
||||
Nesting: configschema.NestingList,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"baz": {
|
||||
Type: cty.String,
|
||||
Required: false,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = ["a", "b"]
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
dynamic "bar" {
|
||||
for_each = aws_instance.test.foo
|
||||
content {
|
||||
baz = bar.value
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedPlanDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 5, Column: 45, Byte: 135},
|
||||
End: hcl.Pos{Line: 5, Column: 45, Byte: 135},
|
||||
},
|
||||
}).Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 5, Column: 45, Byte: 135},
|
||||
End: hcl.Pos{Line: 5, Column: 45, Byte: 135},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in check assertion": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Required: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
foo = "bar"
|
||||
}
|
||||
check "test_check" {
|
||||
assert {
|
||||
condition = aws_instance.test.foo != ""
|
||||
error_message = "foo must not be empty"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedPlanDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 7, Column: 29, Byte: 170},
|
||||
End: hcl.Pos{Line: 7, Column: 56, Byte: 197},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in module input": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
module "child" {
|
||||
source = "./child"
|
||||
input = aws_instance.test.foo
|
||||
}
|
||||
`,
|
||||
"child/main.tf": `
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 23, Byte: 144},
|
||||
End: hcl.Pos{Line: 6, Column: 44, Byte: 165},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in provisioner and connection block": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
resource "aws_instance" "test2" {
|
||||
provisioner "shell" {
|
||||
test_string = aws_instance.test.foo
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = aws_instance.test.foo
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 36, Byte: 177},
|
||||
End: hcl.Pos{Line: 6, Column: 57, Byte: 198},
|
||||
},
|
||||
}).Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 9, Column: 26, Byte: 284},
|
||||
End: hcl.Pos{Line: 9, Column: 47, Byte: 305},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
"in action config": {
|
||||
attributeSchema: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true, Deprecated: true},
|
||||
},
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
resource "aws_instance" "test" {
|
||||
}
|
||||
action "aws_register" "example" {
|
||||
config {
|
||||
host = aws_instance.test.foo
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
expectedValidationDiags: func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: `Deprecated value used`,
|
||||
Detail: `deprecated resource attribute used`,
|
||||
Subject: &hcl.Range{
|
||||
Filename: filepath.Join(c.Module.SourceDir, "main.tf"),
|
||||
Start: hcl.Pos{Line: 6, Column: 24, Byte: 152},
|
||||
End: hcl.Pos{Line: 6, Column: 45, Byte: 173},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Default values
|
||||
if tc.expectedValidationDiags == nil {
|
||||
tc.expectedValidationDiags = func(c *configs.Config) tfdiags.Diagnostics {
|
||||
return tfdiags.Diagnostics{}
|
||||
}
|
||||
}
|
||||
// By default we want the same validations in plan as in validate
|
||||
if tc.expectedPlanDiags == nil {
|
||||
tc.expectedPlanDiags = tc.expectedValidationDiags
|
||||
}
|
||||
// And the same validations in apply as in plan
|
||||
if tc.expectedApplyDiags == nil {
|
||||
tc.expectedApplyDiags = tc.expectedPlanDiags
|
||||
}
|
||||
|
||||
pr := simpleMockProvisioner()
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: tc.attributeSchema,
|
||||
BlockTypes: tc.blockSchema,
|
||||
},
|
||||
},
|
||||
Actions: map[string]*providers.ActionSchema{
|
||||
"aws_register": {
|
||||
ConfigSchema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"host": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m := testModuleInline(t, tc.module)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
t.Run("validate", func(t *testing.T) {
|
||||
validateDiags := ctx.Validate(m, nil)
|
||||
tfdiags.AssertDiagnosticsMatch(t, validateDiags, tc.expectedValidationDiags(m))
|
||||
})
|
||||
|
||||
var plan *plans.Plan
|
||||
t.Run("plan", func(t *testing.T) {
|
||||
var planDiags tfdiags.Diagnostics
|
||||
plan, planDiags = ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
|
||||
tfdiags.AssertDiagnosticsMatch(t, planDiags, tc.expectedPlanDiags(m))
|
||||
})
|
||||
|
||||
t.Run("apply", func(t *testing.T) {
|
||||
_, applyDiags := ctx.Apply(plan, m, &ApplyOpts{})
|
||||
tfdiags.AssertDiagnosticsMatch(t, applyDiags, tc.expectedApplyDiags(m))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3846,3 +4286,55 @@ func TestContext2Validate_noListValidated(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
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},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,8 +103,9 @@ func validateCheckRule(addr addrs.CheckRule, rule *configs.CheckRule, ctx EvalCo
|
|||
hclCtx, moreDiags := scope.EvalContext(refs)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
errorMessage, moreDiags := lang.EvalCheckErrorMessage(rule.ErrorMessage, hclCtx, &addr)
|
||||
errorMessage, errorMessageValMarks, moreDiags := lang.EvalCheckErrorMessage(rule.ErrorMessage, hclCtx, &addr)
|
||||
diags = diags.Append(moreDiags)
|
||||
diags = diags.Append(ctx.Deprecations().DiagnosticsForValueMarks(errorMessageValMarks, ctx.Path().Module(), rule.ErrorMessage.Range().Ptr()))
|
||||
|
||||
return errorMessage, hclCtx, diags
|
||||
}
|
||||
|
|
@ -120,6 +121,10 @@ func evalCheckRule(addr addrs.CheckRule, rule *configs.CheckRule, ctx EvalContex
|
|||
resultVal, hclDiags := rule.Condition.Value(hclCtx)
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
resultVal, deprecationDiags = ctx.Deprecations().Validate(resultVal, ctx.Path().Module(), rule.Condition.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
log.Printf("[TRACE] evalCheckRule: %s: %s", addr.Type, diags.Err().Error())
|
||||
return checkResult{Status: checks.StatusError}, diags
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Val
|
|||
return nullCount, diags
|
||||
}
|
||||
|
||||
countVal, deprecationDiags := ctx.Deprecations().Validate(countVal, ctx.Path().Module(), expr.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
if marks.Has(countVal, marks.Ephemeral) {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -31,7 +32,11 @@ func TestEvaluateCountExpression(t *testing.T) {
|
|||
}
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := &MockEvalContext{}
|
||||
ctx := &MockEvalContext{
|
||||
Scope: evalContextModuleInstance{
|
||||
Addr: addrs.RootModuleInstance,
|
||||
},
|
||||
}
|
||||
ctx.installSimpleEval()
|
||||
countVal, diags := evaluateCountExpression(test.Expr, ctx, false)
|
||||
|
||||
|
|
@ -51,7 +56,11 @@ func TestEvaluateCountExpression(t *testing.T) {
|
|||
|
||||
func TestEvaluateCountExpression_ephemeral(t *testing.T) {
|
||||
expr := hcltest.MockExprLiteral(cty.NumberIntVal(8).Mark(marks.Ephemeral))
|
||||
ctx := &MockEvalContext{}
|
||||
ctx := &MockEvalContext{
|
||||
Scope: evalContextModuleInstance{
|
||||
Addr: addrs.RootModuleInstance,
|
||||
},
|
||||
}
|
||||
ctx.installSimpleEval()
|
||||
_, diags := evaluateCountExpression(expr, ctx, false)
|
||||
if !diags.HasErrors() {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ func (ev *forEachEvaluator) ResourceValue() (map[string]cty.Value, bool, tfdiags
|
|||
|
||||
// validate the for_each value for use in resource expansion
|
||||
diags = diags.Append(ev.validateResourceOrActionForEach(forEachVal, "resource"))
|
||||
forEachVal = marks.RemoveDeprecationMarks(forEachVal)
|
||||
if diags.HasErrors() {
|
||||
return res, false, diags
|
||||
}
|
||||
|
|
@ -328,6 +329,9 @@ func (ev *forEachEvaluator) validateResourceOrActionForEach(forEachVal cty.Value
|
|||
}
|
||||
|
||||
diags = diags.Append(ev.ensureNotEphemeral(forEachVal))
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
forEachVal, deprecationDiags = ev.ctx.Deprecations().Validate(forEachVal, ev.ctx.Path().Module(), ev.expr.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/deprecation"
|
||||
"github.com/hashicorp/terraform/internal/didyoumean"
|
||||
"github.com/hashicorp/terraform/internal/instances"
|
||||
"github.com/hashicorp/terraform/internal/lang"
|
||||
|
|
@ -627,9 +628,13 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
|
|||
// resource has (using d.Evaluator.Instances.ResourceInstanceKeys) and
|
||||
// then retrieving the value for each instance to assemble into the
|
||||
// result, using some per-resource-mode logic maintained elsewhere.
|
||||
return d.getEphemeralResource(addr, rng)
|
||||
val, ephemeralDiags := d.getEphemeralResource(addr, rng)
|
||||
diags = diags.Append(ephemeralDiags)
|
||||
return deprecation.MarkDeprecatedValues(val, schema.Body), diags
|
||||
case addrs.ListResourceMode:
|
||||
return d.getListResource(config, rng)
|
||||
val, listDiags := d.getListResource(config, rng)
|
||||
diags = diags.Append(listDiags)
|
||||
return deprecation.MarkDeprecatedValues(val, schema.Body), diags
|
||||
default:
|
||||
// continue with the rest of the function
|
||||
}
|
||||
|
|
@ -774,7 +779,24 @@ 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
|
||||
switch {
|
||||
case config.Count != nil:
|
||||
return cty.DynamicVal, diags
|
||||
case config.ForEach != nil:
|
||||
return cty.DynamicVal, diags
|
||||
default:
|
||||
|
||||
// We don't know the values of the single resource instance, but we know the general
|
||||
// shape these values will take.
|
||||
|
||||
content := map[string]cty.Value{}
|
||||
for attr, attrType := range ty.AttributeTypes() {
|
||||
content[attr] = cty.UnknownVal(attrType)
|
||||
}
|
||||
instance := cty.ObjectVal(content)
|
||||
instance = deprecation.MarkDeprecatedValues(instance, schema.Body)
|
||||
return instance, diags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -804,7 +826,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
|
|||
continue
|
||||
}
|
||||
|
||||
vals[int(intKey)] = instance
|
||||
vals[int(intKey)] = deprecation.MarkDeprecatedValues(instance, schema.Body)
|
||||
}
|
||||
|
||||
// Insert unknown values where there are any missing instances
|
||||
|
|
@ -826,7 +848,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
|
|||
// old key that is being dropped and not used for evaluation
|
||||
continue
|
||||
}
|
||||
vals[string(strKey)] = instance
|
||||
vals[string(strKey)] = deprecation.MarkDeprecatedValues(instance, schema.Body)
|
||||
}
|
||||
|
||||
if len(vals) > 0 {
|
||||
|
|
@ -846,7 +868,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
|
|||
val = cty.UnknownVal(ty)
|
||||
}
|
||||
|
||||
ret = val
|
||||
ret = deprecation.MarkDeprecatedValues(val, schema.Body)
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
|
|
|
|||
|
|
@ -71,7 +71,10 @@ func (n *NodeActionDeclarationInstance) Execute(ctx EvalContext, _ walkOperation
|
|||
valDiags := validateResourceForbiddenEphemeralValues(ctx, configVal, n.Schema.ConfigSchema)
|
||||
diags = diags.Append(valDiags.InConfigBody(n.Config.Config, n.Addr.String()))
|
||||
|
||||
if valDiags.HasErrors() {
|
||||
deprecationDiags := ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath())
|
||||
diags = diags.Append(deprecationDiags.InConfigBody(n.Config.Config, n.Addr.String()))
|
||||
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ func (n *NodeActionDeclarationPartialExpanded) Execute(ctx EvalContext, op walkO
|
|||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
deprecationDiags := ctx.Deprecations().ValidateAsConfig(configVal, n.ActionAddr().Module)
|
||||
diags = diags.Append(deprecationDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
ctx.Actions().AddPartialExpandedAction(n.addr, configVal, n.resolvedProvider)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -105,6 +105,12 @@ func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiag
|
|||
valDiags = validateResourceForbiddenEphemeralValues(ctx, configVal, schema.ConfigSchema)
|
||||
diags = diags.Append(valDiags.InConfigBody(config, n.Addr.String()))
|
||||
|
||||
deprecationDiags := ctx.Deprecations().ValidateAsConfig(configVal, n.Addr.Module)
|
||||
diags = diags.Append(deprecationDiags.InConfigBody(config, n.Addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
// Use unmarked value for validate request
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
log.Printf("[TRACE] Validating config for %q", n.Addr)
|
||||
|
|
|
|||
|
|
@ -234,5 +234,10 @@ func evaluateLocalValue(config *configs.Local, localAddr addrs.LocalValue, addrS
|
|||
if val == cty.NilVal {
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
val, deprecationDiags = ctx.Deprecations().Validate(val, ctx.Path().Module(), expr.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
return val, diags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,6 +306,10 @@ func (n *nodeModuleVariable) evalModuleVariable(ctx EvalContext, validateOnly bo
|
|||
finalVal, moreDiags := PrepareFinalInputVariableValue(n.Addr, rawVal, n.Config)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
finalVal, deprecationDiags = ctx.Deprecations().Validate(finalVal, n.ModulePath(), errSourceRange.ToHCL().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
return finalVal, diags.ErrWithWarnings()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -508,6 +508,10 @@ If you do intend to export this data, annotate the output value as sensitive by
|
|||
// "flagWarnOutputErrors", because they relate to features that were added
|
||||
// more recently than the historical change to treat invalid output values
|
||||
// as errors rather than warnings.
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
val, deprecationDiags = ctx.Deprecations().Validate(val, n.Addr.Module.Module(), n.Config.Expr.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
if !n.Config.Ephemeral && marks.Contains(val, marks.Ephemeral) {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
|
@ -586,6 +590,9 @@ func (n *nodeOutputInPartialModule) Execute(ctx EvalContext, op walkOperation) t
|
|||
if val == cty.NilVal {
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
var deprecationDiags tfdiags.Diagnostics
|
||||
val, deprecationDiags = ctx.Deprecations().Validate(val, n.Addr.Module.Module(), n.Config.Expr.Range().Ptr())
|
||||
diags = diags.Append(deprecationDiags)
|
||||
|
||||
// We'll also check that the depends_on argument is valid, since that's
|
||||
// a static concern anyway and so cannot vary between instances of the
|
||||
|
|
|
|||
|
|
@ -78,10 +78,16 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi
|
|||
}
|
||||
|
||||
configVal, _, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey)
|
||||
if evalDiags.HasErrors() {
|
||||
return diags.Append(evalDiags)
|
||||
}
|
||||
diags = diags.Append(evalDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
deprecationDiags := ctx.Deprecations().ValidateAsConfig(configVal, n.Addr.Module)
|
||||
diags = diags.Append(deprecationDiags.InConfigBody(configBody, n.Addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
// If our config value contains any marked values, ensure those are
|
||||
// stripped out before sending this to the provider
|
||||
|
|
|
|||
|
|
@ -866,6 +866,9 @@ 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
|
||||
}
|
||||
|
|
@ -1771,6 +1774,9 @@ func (n *NodeAbstractResourceInstance) providerMetas(ctx EvalContext) (cty.Value
|
|||
var configDiags tfdiags.Diagnostics
|
||||
metaConfigVal, _, configDiags = ctx.EvaluateBlock(m.Config, providerSchema.ProviderMeta.Body, nil, EvalDataForNoInstanceKey)
|
||||
diags = diags.Append(configDiags)
|
||||
diags = diags.Append(
|
||||
ctx.Deprecations().ValidateAsConfig(metaConfigVal, ctx.Path().Module()).InConfigBody(m.Config, n.Addr.String()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1847,6 +1853,9 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
|
|||
diags = diags.Append(
|
||||
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
|
||||
)
|
||||
diags = diags.Append(
|
||||
ctx.Deprecations().ValidateAsConfig(configVal, ctx.Path().Module()).InConfigBody(n.Config.Config, n.Addr.String()),
|
||||
)
|
||||
if diags.HasErrors() {
|
||||
return nil, nil, deferred, keyData, diags
|
||||
}
|
||||
|
|
@ -2184,6 +2193,13 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned
|
|||
return nil, keyData, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(
|
||||
ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()),
|
||||
)
|
||||
if diags.HasErrors() {
|
||||
return nil, keyData, diags
|
||||
}
|
||||
|
||||
newVal, readDeferred, readDiags := n.readDataSource(ctx, configVal)
|
||||
if check, nested := n.nestedInCheckBlock(); nested {
|
||||
addr := check.Addr().Absolute(n.Addr.Module)
|
||||
|
|
@ -2496,6 +2512,8 @@ func (n *NodeAbstractResourceInstance) evalProvisionerConfig(ctx EvalContext, bo
|
|||
config, _, configDiags := ctx.EvaluateBlock(body, schema, n.ResourceInstanceAddr().Resource, keyData)
|
||||
diags = diags.Append(configDiags)
|
||||
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(config, n.ModulePath()).InConfigBody(body, n.Addr.String()))
|
||||
|
||||
return config, diags
|
||||
}
|
||||
|
||||
|
|
@ -2512,6 +2530,7 @@ func (n *NodeAbstractResourceInstance) evalDestroyProvisionerConfig(ctx EvalCont
|
|||
evalScope := ctx.EvaluationScope(n.ResourceInstanceAddr().Resource, nil, keyData)
|
||||
config, evalDiags := evalScope.EvalSelfBlock(body, self, schema, keyData)
|
||||
diags = diags.Append(evalDiags)
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(config, n.ModulePath()).InConfigBody(body, n.Addr.String()))
|
||||
|
||||
return config, diags
|
||||
}
|
||||
|
|
@ -2561,6 +2580,10 @@ func (n *NodeAbstractResourceInstance) apply(
|
|||
if configDiags.HasErrors() {
|
||||
return state, diags
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
return state, diags
|
||||
}
|
||||
}
|
||||
|
||||
if !configVal.IsWhollyKnown() {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) (*provid
|
|||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(config.Config, inp.addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
unmarkedConfigVal, configMarks := configVal.UnmarkDeepWithPaths()
|
||||
|
||||
if !unmarkedConfigVal.IsWhollyKnown() {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/genconfig"
|
||||
"github.com/hashicorp/terraform/internal/instances"
|
||||
"github.com/hashicorp/terraform/internal/lang/ephemeral"
|
||||
|
|
@ -22,6 +23,7 @@ import (
|
|||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/plans/deferring"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/provisioners"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
|
@ -347,6 +349,15 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
|||
repData.EachValue = cty.DynamicVal
|
||||
}
|
||||
|
||||
// Even if we don't run the provisioners during plan we need to make sure they are valid
|
||||
if n.Config != nil && n.Config.Managed != nil {
|
||||
for _, p := range n.Config.Managed.Provisioners {
|
||||
diags = diags.Append(n.validateProvisioner(ctx, p, n.Config.Managed.Connection, repData))
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
}
|
||||
diags = diags.Append(n.replaceTriggered(ctx, repData))
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
|
|
@ -638,6 +649,12 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
|
|||
diags = diags.Append(configDiags)
|
||||
return nil, deferred, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()).InConfigBody(n.Config.Config, absAddr.String()))
|
||||
if diags.HasErrors() {
|
||||
return nil, deferred, diags
|
||||
}
|
||||
|
||||
configVal, _ = configVal.UnmarkDeep()
|
||||
|
||||
// Let's pretend we're reading the value as a data source so we
|
||||
|
|
@ -1009,6 +1026,75 @@ func (n *NodePlannableResourceInstance) generateResourceConfig(ctx EvalContext,
|
|||
return genconfig.ExtractLegacyConfigFromState(schema.Body, state), diags
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) validateProvisioner(ctx EvalContext, p *configs.Provisioner, baseConn *configs.Connection, repData instances.RepetitionData) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
provisioner, err := ctx.Provisioner(p.Type)
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return diags
|
||||
}
|
||||
|
||||
if provisioner == nil {
|
||||
return diags.Append(fmt.Errorf("provisioner %s not initialized", p.Type))
|
||||
}
|
||||
provisionerSchema, err := ctx.ProvisionerSchema(p.Type)
|
||||
if err != nil {
|
||||
return diags.Append(fmt.Errorf("failed to read schema for provisioner %s: %s", p.Type, err))
|
||||
}
|
||||
if provisionerSchema == nil {
|
||||
return diags.Append(fmt.Errorf("provisioner %s has no schema", p.Type))
|
||||
}
|
||||
|
||||
// Validate the provisioner's own config first
|
||||
configVal, _, configDiags := n.evaluateBlock(ctx, p.Config, provisionerSchema, repData)
|
||||
diags = diags.Append(configDiags)
|
||||
|
||||
if configVal == cty.NilVal {
|
||||
// Should never happen for a well-behaved EvaluateBlock implementation
|
||||
return diags.Append(fmt.Errorf("EvaluateBlock returned nil value"))
|
||||
}
|
||||
|
||||
// Use unmarked value for validate request
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
req := provisioners.ValidateProvisionerConfigRequest{
|
||||
Config: unmarkedConfigVal,
|
||||
}
|
||||
|
||||
resp := provisioner.ValidateProvisionerConfig(req)
|
||||
diags = diags.Append(resp.Diagnostics)
|
||||
|
||||
if p.Connection != nil {
|
||||
// We can't comprehensively validate the connection config since its
|
||||
// final structure is decided by the communicator and we can't instantiate
|
||||
// that until we have a complete instance state. However, we *can* catch
|
||||
// configuration keys that are not valid for *any* communicator, catching
|
||||
// typos early rather than waiting until we actually try to run one of
|
||||
// the resource's provisioners.
|
||||
|
||||
cfg := p.Connection.Config
|
||||
if baseConn != nil {
|
||||
// Merge the local config into the base connection config, if we
|
||||
// both specified.
|
||||
cfg = configs.MergeBodies(baseConn.Config, cfg)
|
||||
}
|
||||
|
||||
_, _, connDiags := n.evaluateBlock(ctx, cfg, connectionBlockSupersetSchema, repData)
|
||||
diags = diags.Append(connDiags)
|
||||
} else if baseConn != nil {
|
||||
// Just validate the baseConn directly.
|
||||
_, _, connDiags := n.evaluateBlock(ctx, baseConn.Config, connectionBlockSupersetSchema, repData)
|
||||
diags = diags.Append(connDiags)
|
||||
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block, keyData instances.RepetitionData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||
val, hclBody, diags := ctx.EvaluateBlock(body, schema, n.Addr.Resource, keyData)
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(val, n.ModulePath()).InConfigBody(body, n.Addr.String()))
|
||||
return val, hclBody, diags
|
||||
}
|
||||
|
||||
// mergeDeps returns the union of 2 sets of dependencies
|
||||
func mergeDeps(a, b []addrs.ConfigResource) []addrs.ConfigResource {
|
||||
switch {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
|
|||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(blockVal, n.ModulePath()).InConfigBody(config.Config, n.Addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
// Unmark before sending to provider
|
||||
unmarkedBlockVal, _ := blockVal.UnmarkDeepWithPaths()
|
||||
|
|
@ -63,6 +67,9 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
|
|||
if limitDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
var limitDeprecationDiags tfdiags.Diagnostics
|
||||
limitCty, limitDeprecationDiags = ctx.Deprecations().Validate(limitCty, ctx.Path().Module(), config.List.Limit.Range().Ptr())
|
||||
diags = diags.Append(limitDeprecationDiags)
|
||||
|
||||
includeRscCty, includeRsc, includeDiags := newIncludeRscEvaluator(false).EvaluateExpr(ctx, config.List.IncludeResource)
|
||||
diags = diags.Append(includeDiags)
|
||||
|
|
@ -70,6 +77,10 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
|
|||
return diags
|
||||
}
|
||||
|
||||
var includeDeprecationDiags tfdiags.Diagnostics
|
||||
includeRscCty, includeDeprecationDiags = ctx.Deprecations().Validate(includeRscCty, ctx.Path().Module(), config.List.IncludeResource.Range().Ptr())
|
||||
diags = diags.Append(includeDeprecationDiags)
|
||||
|
||||
rId := HookResourceIdentity{
|
||||
Addr: addr,
|
||||
ProviderAddr: n.ResolvedProvider.Provider,
|
||||
|
|
|
|||
|
|
@ -200,6 +200,11 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
|
|||
return &change, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ResourceAddr().Module).InConfigBody(n.config.Config, n.addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return &change, diags
|
||||
}
|
||||
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
log.Printf("[TRACE] Validating partially expanded config for %q", n.addr)
|
||||
validateResp := provider.ValidateResourceConfig(
|
||||
|
|
@ -354,6 +359,11 @@ func (n *nodePlannablePartialExpandedResource) dataResourceExecute(ctx EvalConte
|
|||
return &change, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(configVal, n.ResourceAddr().Module).InConfigBody(n.config.Config, n.addr.String()))
|
||||
if diags.HasErrors() {
|
||||
return &change, diags
|
||||
}
|
||||
|
||||
// Note: We're deliberately not doing anything special for nested-in-a-check
|
||||
// data sources. (*NodeAbstractResourceInstance).planDataSource has some
|
||||
// special handling for these, but it's founded on the assumption that we're
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ func (n *NodeValidatableResource) validateProvisioner(ctx EvalContext, p *config
|
|||
func (n *NodeValidatableResource) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||
keyData, selfAddr := n.stubRepetitionData(n.Config.Count != nil, n.Config.ForEach != nil)
|
||||
|
||||
return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
|
||||
val, hclBody, diags := ctx.EvaluateBlock(body, schema, selfAddr, keyData)
|
||||
diags = diags.Append(ctx.Deprecations().ValidateAsConfig(val, n.Addr.Module).InConfigBody(body, n.Addr.String()))
|
||||
return val, hclBody, diags
|
||||
}
|
||||
|
||||
// connectionBlockSupersetSchema is a schema representing the superset of all
|
||||
|
|
@ -355,6 +357,9 @@ 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 +439,9 @@ 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()),
|
||||
)
|
||||
|
||||
// Use unmarked value for validate request
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
|
|
@ -461,6 +469,9 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
|
|||
if valDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
diags = diags.Append(
|
||||
ctx.Deprecations().ValidateAsConfig(configVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()),
|
||||
)
|
||||
// Use unmarked value for validate request
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
req := providers.ValidateEphemeralResourceConfigRequest{
|
||||
|
|
@ -488,17 +499,28 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
|
|||
return diags
|
||||
}
|
||||
|
||||
diags = diags.Append(
|
||||
ctx.Deprecations().ValidateAsConfig(blockVal, n.ModulePath()).InConfigBody(n.Config.Config, n.Addr.String()),
|
||||
)
|
||||
|
||||
limit, _, limitDiags := newLimitEvaluator(true).EvaluateExpr(ctx, n.Config.List.Limit)
|
||||
diags = diags.Append(limitDiags)
|
||||
if limitDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
var limitDeprecationDiags tfdiags.Diagnostics
|
||||
limit, limitDeprecationDiags = ctx.Deprecations().Validate(limit, n.ModulePath(), n.Config.List.Limit.Range().Ptr())
|
||||
diags = diags.Append(limitDeprecationDiags)
|
||||
|
||||
includeResource, _, includeDiags := newIncludeRscEvaluator(true).EvaluateExpr(ctx, n.Config.List.IncludeResource)
|
||||
diags = diags.Append(includeDiags)
|
||||
if includeDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
var includeDeprecationDiags tfdiags.Diagnostics
|
||||
includeResource, includeDeprecationDiags = ctx.Deprecations().Validate(includeResource, n.ModulePath(), n.Config.List.IncludeResource.Range().Ptr())
|
||||
diags = diags.Append(includeDeprecationDiags)
|
||||
|
||||
// Use unmarked value for validate request
|
||||
unmarkedBlockVal, _ := blockVal.UnmarkDeep()
|
||||
|
|
|
|||
Loading…
Reference in a new issue