From df9ff1eabb77eb41b73bd22029b8c0a082c5b7e8 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 15 Nov 2024 11:53:18 +0100 Subject: [PATCH 1/8] Add new deprecated attribute to output blocks --- internal/configs/named_values.go | 11 +++++++++++ internal/configs/named_values_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/internal/configs/named_values.go b/internal/configs/named_values.go index c9c92e45e074..35e371facd29 100644 --- a/internal/configs/named_values.go +++ b/internal/configs/named_values.go @@ -345,12 +345,14 @@ 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 } @@ -402,6 +404,12 @@ 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 + } + if attr, exists := content.Attributes["depends_on"]; exists { deps, depsDiags := DecodeDependsOn(attr) diags = append(diags, depsDiags...) @@ -525,6 +533,9 @@ var outputBlockSchema = &hcl.BodySchema{ { Name: "ephemeral", }, + { + Name: "deprecated", + }, }, Blocks: []hcl.BlockHeaderSchema{ {Type: "precondition"}, diff --git a/internal/configs/named_values_test.go b/internal/configs/named_values_test.go index 0626157c031e..5190986d5e4e 100644 --- a/internal/configs/named_values_test.go +++ b/internal/configs/named_values_test.go @@ -48,3 +48,30 @@ 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") + } +} From a57dcb294275ac3a48d798e6790b1b5431066364 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 15 Nov 2024 11:58:27 +0100 Subject: [PATCH 2/8] Track output dependants in an extra graph transformer --- internal/terraform/graph_builder_apply.go | 2 ++ internal/terraform/graph_builder_plan.go | 2 ++ internal/terraform/node_output.go | 5 ++++ internal/terraform/transform_output.go | 1 + .../terraform/transform_output_references.go | 30 +++++++++++++++++++ 5 files changed, 40 insertions(+) create mode 100644 internal/terraform/transform_output_references.go diff --git a/internal/terraform/graph_builder_apply.go b/internal/terraform/graph_builder_apply.go index ee871eb0e57f..3b2d44a44b2d 100644 --- a/internal/terraform/graph_builder_apply.go +++ b/internal/terraform/graph_builder_apply.go @@ -193,6 +193,8 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { &ReferenceTransformer{}, &AttachDependenciesTransformer{}, + &OutputReferencesTransformer{}, + // Nested data blocks should be loaded after every other resource has // done its thing. &checkStartTransformer{Config: b.Config, Operation: b.Operation}, diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index 22c267afaadc..7e7677868c05 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -241,6 +241,8 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &ReferenceTransformer{}, + &OutputReferencesTransformer{}, + &AttachDependenciesTransformer{}, // Make sure data sources are aware of any depends_on from the diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index b92f1a30d747..f504c7666606 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -46,6 +46,8 @@ type nodeExpandOutput struct { Overrides *mocking.Overrides Dependencies []addrs.ConfigResource + + Dependants []*addrs.Reference } var ( @@ -136,6 +138,7 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagn Planning: n.Planning, Override: n.getOverrideValue(absAddr.Module), Dependencies: n.Dependencies, + Dependants: n.Dependants, } } @@ -283,6 +286,8 @@ type NodeApplyableOutput struct { // Dependencies is the full set of resources that are referenced by this // output. Dependencies []addrs.ConfigResource + + Dependants []*addrs.Reference } var ( diff --git a/internal/terraform/transform_output.go b/internal/terraform/transform_output.go index 3bcfa5558f55..f0b6f19f12d4 100644 --- a/internal/terraform/transform_output.go +++ b/internal/terraform/transform_output.go @@ -67,6 +67,7 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error { RefreshOnly: t.RefreshOnly, Planning: t.Planning, Overrides: t.Overrides, + Dependants: []*addrs.Reference{}, } log.Printf("[TRACE] OutputTransformer: adding %s as %T", o.Name, node) diff --git a/internal/terraform/transform_output_references.go b/internal/terraform/transform_output_references.go new file mode 100644 index 000000000000..91d0f18a64dd --- /dev/null +++ b/internal/terraform/transform_output_references.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package terraform + +// OutputReferencesTransformer is a GraphTransformer that adds meta-information +// about references to output values in the configuration. This is used to +// determine when deprecated outputs are used. +type OutputReferencesTransformer struct{} + +func (t *OutputReferencesTransformer) Transform(g *Graph) error { + // Build a reference map so we can efficiently look up the references + vs := g.Vertices() + m := NewReferenceMap(vs) + + // Find the things that reference things and connect them + for _, v := range vs { + if dependant, ok := v.(GraphNodeReferencer); ok { + parents := m.References(v) + + for _, parent := range parents { + if output, ok := parent.(*nodeExpandOutput); ok { + output.Dependants = append(output.Dependants, dependant.References()...) + } + } + } + } + + return nil +} From 74eff88fd8d5411b5ef569473b3b182c96f195ab Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 15 Nov 2024 12:01:35 +0100 Subject: [PATCH 3/8] Add diagnostic for deprecated outputs --- internal/terraform/context_validate_test.go | 119 ++++++++++++++++++++ internal/terraform/node_output.go | 11 ++ 2 files changed, 130 insertions(+) diff --git a/internal/terraform/context_validate_test.go b/internal/terraform/context_validate_test.go index 6452202437fb..d144c15cd6e6 100644 --- a/internal/terraform/context_validate_test.go +++ b/internal/terraform/context_validate_test.go @@ -3094,3 +3094,122 @@ module "child" { diags := ctx.Validate(m, &ValidateOpts{}) assertNoDiagnostics(t, diags) } + +func TestContext2Validate_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 +} + +resource "test_resource" "test2" { + attr = module.mod.new +} + +resource "test_resource" "test3" { + attr = module.mod.old +} + +output "test_output" { + value = module.mod.old +} + +module "mod2" { + source = "./mod2" + + input = 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{}) + var expectedDiags tfdiags.Diagnostics + expectedDiags = expectedDiags.Append( + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 7, Column: 12, Byte: 85}, + End: hcl.Pos{Line: 7, Column: 26, Byte: 99}, + }, + }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 15, Column: 12, Byte: 213}, + End: hcl.Pos{Line: 15, Column: 26, Byte: 227}, + }, + }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 19, Column: 10, Byte: 263}, + End: hcl.Pos{Line: 19, Column: 24, Byte: 277}, + }, + }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 25, Column: 10, Byte: 326}, + End: hcl.Pos{Line: 25, Column: 24, Byte: 340}, + }, + }, + ) + + assertDiagnosticsMatch(t, diags, expectedDiags) +} diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index f504c7666606..aa599900a49b 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -384,6 +384,17 @@ func (n *NodeApplyableOutput) References() []*addrs.Reference { // GraphNodeExecutable func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { + if op == walkValidate && n.Config.DeprecatedSet && len(n.Dependants) > 0 { + for _, d := range n.Dependants { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: n.Config.Deprecated, + Subject: d.SourceRange.ToHCL().Ptr(), + }) + } + } + state := ctx.State() if state == nil { return From 1529ebab47d6cbe50e45222f76cc3fe1d2dda136 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 29 Nov 2024 15:20:35 +0100 Subject: [PATCH 4/8] WIP --- internal/plans/changes.go | 5 +- internal/stacks/stackplan/planned_change.go | 2 +- internal/terraform/context_plan.go | 2 +- internal/terraform/context_validate_test.go | 156 ++++++++++++++++--- internal/terraform/eval_context.go | 4 + internal/terraform/eval_context_builtin.go | 26 ++++ internal/terraform/eval_context_mock.go | 9 ++ internal/terraform/graph_walk_context.go | 4 + internal/terraform/node_output.go | 12 +- internal/terraform/node_resource_validate.go | 11 ++ 10 files changed, 202 insertions(+), 29 deletions(-) diff --git a/internal/plans/changes.go b/internal/plans/changes.go index 662a28a04dc7..680b48822ed3 100644 --- a/internal/plans/changes.go +++ b/internal/plans/changes.go @@ -617,16 +617,17 @@ func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { unmarkedAfter, marksesAfter := c.After.UnmarkDeepWithPaths() sensitiveAttrsBefore, unsupportedMarksesBefore := marks.PathsWithMark(marksesBefore, marks.Sensitive) sensitiveAttrsAfter, unsupportedMarksesAfter := marks.PathsWithMark(marksesAfter, marks.Sensitive) + if len(unsupportedMarksesBefore) != 0 { return nil, fmt.Errorf( - "prior value %s: can't serialize value marked with %#v (this is a bug in Terraform)", + "prior value %s: cannot serialize value marked with %#v (this is a bug in Terraform)", tfdiags.FormatCtyPath(unsupportedMarksesBefore[0].Path), unsupportedMarksesBefore[0].Marks, ) } if len(unsupportedMarksesAfter) != 0 { return nil, fmt.Errorf( - "new value %s: can't serialize value marked with %#v (this is a bug in Terraform)", + "new value %s: cannot serialize value marked with %#v (this is a bug in Terraform)", tfdiags.FormatCtyPath(unsupportedMarksesAfter[0].Path), unsupportedMarksesAfter[0].Marks, ) diff --git a/internal/stacks/stackplan/planned_change.go b/internal/stacks/stackplan/planned_change.go index ca60f82c5856..0f58d95f916a 100644 --- a/internal/stacks/stackplan/planned_change.go +++ b/internal/stacks/stackplan/planned_change.go @@ -501,7 +501,7 @@ func DynamicValueToTerraform1(val cty.Value, ty cty.Type) (*stacks.DynamicValue, sensitivePaths, withOtherMarks := marks.PathsWithMark(markPaths, marks.Sensitive) if len(withOtherMarks) != 0 { return nil, withOtherMarks[0].Path.NewErrorf( - "can't serialize value marked with %#v (this is a bug in Terraform)", + "cannot serialize value marked with %#v (this is a bug in Terraform)", withOtherMarks[0].Marks, ) } diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index 3eae86d47d60..5cdd2401d5f0 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -349,7 +349,7 @@ The -target option is not for routine use, and is provided only for exceptional panic("nil plan but no errors") } - if plan != nil { + if plan != nil && plan.Changes != nil { relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan) diags = diags.Append(rDiags) plan.RelevantAttributes = relevantAttrs diff --git a/internal/terraform/context_validate_test.go b/internal/terraform/context_validate_test.go index d144c15cd6e6..c85d613ad8e8 100644 --- a/internal/terraform/context_validate_test.go +++ b/internal/terraform/context_validate_test.go @@ -3122,22 +3122,26 @@ module "mod" { source = "./mod" } -resource "test_resource" "test" { - attr = module.mod.old -} +// resource "test_resource" "test" { +// attr = module.mod.old +// } -resource "test_resource" "test2" { - attr = module.mod.new -} +// resource "test_resource" "test2" { +// attr = module.mod.new +// } -resource "test_resource" "test3" { - attr = module.mod.old -} +// resource "test_resource" "test3" { +// attr = module.mod.old +// } output "test_output" { value = module.mod.old } +output "test_output_conditional" { + value = false ? module.mod.old : module.mod.new +} + module "mod2" { source = "./mod2" @@ -3169,44 +3173,154 @@ module "mod2" { diags := ctx.Validate(m, &ValidateOpts{}) var expectedDiags tfdiags.Diagnostics expectedDiags = expectedDiags.Append( + // &hcl.Diagnostic{ + // Severity: hcl.DiagWarning, + // Summary: "Usage of deprecated output", + // Detail: "Please stop using this", + // Subject: &hcl.Range{ + // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + // Start: hcl.Pos{Line: 7, Column: 12, Byte: 85}, + // End: hcl.Pos{Line: 7, Column: 26, Byte: 99}, + // }, + // }, + // &hcl.Diagnostic{ + // Severity: hcl.DiagWarning, + // Summary: "Usage of deprecated output", + // Detail: "Please stop using this", + // Subject: &hcl.Range{ + // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + // Start: hcl.Pos{Line: 15, Column: 12, Byte: 213}, + // End: hcl.Pos{Line: 15, Column: 26, Byte: 227}, + // }, + // }, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", Detail: "Please stop using this", Subject: &hcl.Range{ Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 7, Column: 12, Byte: 85}, - End: hcl.Pos{Line: 7, Column: 26, Byte: 99}, + Start: hcl.Pos{Line: 19, Column: 10, Byte: 263}, + End: hcl.Pos{Line: 19, Column: 24, Byte: 277}, }, }, + // &hcl.Diagnostic{ + // Severity: hcl.DiagWarning, + // Summary: "Usage of deprecated output", + // Detail: "Please stop using this", + // Subject: &hcl.Range{ + // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + // Start: hcl.Pos{Line: 25, Column: 10, Byte: 326}, + // End: hcl.Pos{Line: 25, Column: 24, Byte: 340}, + // }, + // }, + ) + + assertDiagnosticsMatch(t, diags, expectedDiags) +} + +func TestContext2Validate_deprecated_output_conflicts(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "mod/mod/main.tf": ` +output "old" { + deprecated = "mod/mod: Please stop using this" + value = "old" +} +`, + "mod/main.tf": ` +output "old" { + deprecated = "mod: Please stop using this" + value = "old" +} + +module "mod" { + source = "./mod" +} + +output "new" { + value = module.mod.old +} +`, + "mod2/main.tf": ` +output "old" { + deprecated = "mod2: Please stop using this" + value = "old" +} + +output "new" { + value = "new" +} +`, + "main.tf": ` +module "mod" { + source = "./mod" +} + +output "test_output" { + value = module.mod.old +} + +module "mod2" { + count = 2 + source = "./mod2" +} + +output "test_output2" { + value = module.mod2[*].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{}) + var expectedDiags tfdiags.Diagnostics + expectedDiags = expectedDiags.Append( &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", - Detail: "Please stop using this", + Detail: "mod: Please stop using this", Subject: &hcl.Range{ Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 15, Column: 12, Byte: 213}, - End: hcl.Pos{Line: 15, Column: 26, Byte: 227}, + Start: hcl.Pos{Line: 7, Column: 10, Byte: 72}, + End: hcl.Pos{Line: 7, Column: 24, Byte: 86}, }, }, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", - Detail: "Please stop using this", + Detail: "mod2: Please stop using this", Subject: &hcl.Range{ Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 19, Column: 10, Byte: 263}, - End: hcl.Pos{Line: 19, Column: 24, Byte: 277}, + Start: hcl.Pos{Line: 15, Column: 10, Byte: 161}, + End: hcl.Pos{Line: 15, Column: 24, Byte: 176}, }, }, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", - Detail: "Please stop using this", + Detail: "mod/mod: Please stop using this", Subject: &hcl.Range{ - Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 25, Column: 10, Byte: 326}, - End: hcl.Pos{Line: 25, Column: 24, Byte: 340}, + Filename: filepath.Join(m.Module.SourceDir, "mod", "main.tf"), + Start: hcl.Pos{Line: 12, Column: 10, Byte: 150}, + End: hcl.Pos{Line: 12, Column: 24, Byte: 164}, }, }, ) diff --git a/internal/terraform/eval_context.go b/internal/terraform/eval_context.go index bde1626ca3db..7ae9d3513ebb 100644 --- a/internal/terraform/eval_context.go +++ b/internal/terraform/eval_context.go @@ -216,6 +216,10 @@ type EvalContext interface { // Forget if set to true will cause the plan to forget all resources. This is // only allowed in the context of a destroy plan. Forget() bool + + // ReferencableDeprecationMessage returns the deprecation message for the referencable + ReferencableDeprecationMessage(addrs.Module, addrs.Referenceable) (string, bool) + MarkReferencableAsDeprecated(addrs.ConfigOutputValue, string) } func evalContextForModuleInstance(baseCtx EvalContext, addr addrs.ModuleInstance) EvalContext { diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index c9950f608d1b..876c0ed8d7e6 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -91,6 +91,7 @@ type BuiltinEvalContext struct { InstanceExpanderValue *instances.Expander MoveResultsValue refactoring.MoveResults OverrideValues *mocking.Overrides + DeprecatedReferencables map[string]string } // BuiltinEvalContext implements EvalContext @@ -620,3 +621,28 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities WriteOnlyAttributesAllowed: true, } } + +func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x addrs.Referenceable) (string, bool) { + // TODO: We want to make this available in node_resource_validation, therefore we want to talk about config objects, the referencable should somehow contain the config object, how do we get it out? + if foo, ok := x.(addrs.ModuleCallInstanceOutput); ok { + cov := addrs.ConfigOutputValue{ + Module: m, + OutputValue: addrs.OutputValue{ + Name: foo.Name, + }, + } + + fmt.Printf("\n\t ctx.DeprecatedReferencables --> %#v \n", ctx.DeprecatedReferencables) + fmt.Printf("\n\t cov.String() --> %#v \n", cov.String()) + msg, ok := ctx.DeprecatedReferencables[cov.String()] + return msg, ok + } + + return "", false +} + +func (ctx *BuiltinEvalContext) MarkReferencableAsDeprecated(x addrs.ConfigOutputValue, msg string) { + fmt.Printf("\n\t marking x --> %#v \n", x) + fmt.Printf("\n\t msg --> %#v \n", msg) + ctx.DeprecatedReferencables[x.String()] = msg +} diff --git a/internal/terraform/eval_context_mock.go b/internal/terraform/eval_context_mock.go index bd2edf2031b7..2d45856f1db7 100644 --- a/internal/terraform/eval_context_mock.go +++ b/internal/terraform/eval_context_mock.go @@ -431,3 +431,12 @@ func (ctx *MockEvalContext) ClientCapabilities() providers.ClientCapabilities { WriteOnlyAttributesAllowed: true, } } + +func (ctx *MockEvalContext) ReferencableDeprecationMessage(addrs.Module, addrs.Referenceable) (string, bool) { + // TODO: implement + return "", false +} + +func (ctx *MockEvalContext) MarkReferencableAsDeprecated(addrs.ConfigOutputValue, string) { + // TODO: implement +} diff --git a/internal/terraform/graph_walk_context.go b/internal/terraform/graph_walk_context.go index 5f18184f3bda..dc5873aab8fc 100644 --- a/internal/terraform/graph_walk_context.go +++ b/internal/terraform/graph_walk_context.go @@ -69,6 +69,8 @@ type ContextGraphWalker struct { provisionerCache map[string]provisioners.Interface provisionerSchemas map[string]*configschema.Block provisionerLock sync.Mutex + + DeprecatedReferencables map[string]string } var _ GraphWalker = (*ContextGraphWalker)(nil) @@ -139,6 +141,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext { Evaluator: evaluator, OverrideValues: w.Overrides, forget: w.Forget, + DeprecatedReferencables: w.DeprecatedReferencables, } return ctx @@ -151,6 +154,7 @@ func (w *ContextGraphWalker) init() { w.providerSchemas = make(map[string]providers.ProviderSchema) w.provisionerCache = make(map[string]provisioners.Interface) w.provisionerSchemas = make(map[string]*configschema.Block) + w.DeprecatedReferencables = make(map[string]string) } func (w *ContextGraphWalker) Execute(ctx EvalContext, n GraphNodeExecutable) tfdiags.Diagnostics { diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index aa599900a49b..1d608b31a928 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -384,13 +384,17 @@ func (n *NodeApplyableOutput) References() []*addrs.Reference { // GraphNodeExecutable func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { - if op == walkValidate && n.Config.DeprecatedSet && len(n.Dependants) > 0 { - for _, d := range n.Dependants { + if n.Config.DeprecatedSet { + ctx.MarkReferencableAsDeprecated(n.Config.Addr().InModule(n.Addr.Module.Module()), n.Config.Deprecated) + } + + for _, ref := range n.References() { + if msg, ok := ctx.ReferencableDeprecationMessage(n.Addr.Module.Module(), ref.Subject); ok { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", - Detail: n.Config.Deprecated, - Subject: d.SourceRange.ToHCL().Ptr(), + Detail: msg, + Subject: ref.SourceRange.ToHCL().Ptr(), }) } } diff --git a/internal/terraform/node_resource_validate.go b/internal/terraform/node_resource_validate.go index b102d61d8903..e8bc859e3a88 100644 --- a/internal/terraform/node_resource_validate.go +++ b/internal/terraform/node_resource_validate.go @@ -57,6 +57,17 @@ func (n *NodeValidatableResource) Execute(ctx EvalContext, op walkOperation) (di diags = diags.Append(n.validateCheckRules(ctx, n.Config)) + for _, ref := range n.References() { + if msg, ok := ctx.ReferencableDeprecationMessage(n.ModulePath(), ref.Subject); ok { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: msg, + Subject: ref.SourceRange.ToHCL().Ptr(), + }) + } + } + if managed := n.Config.Managed; managed != nil { // Validate all the provisioners for _, p := range managed.Provisioners { From 6a511936c33a85ab3989579e92261e680df61348 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 10 Jan 2025 17:55:53 +0100 Subject: [PATCH 5/8] WIP2 --- internal/terraform/context_validate_test.go | 174 ++++++++++++++------ internal/terraform/eval_context_builtin.go | 20 ++- internal/terraform/node_module_variable.go | 15 +- 3 files changed, 152 insertions(+), 57 deletions(-) diff --git a/internal/terraform/context_validate_test.go b/internal/terraform/context_validate_test.go index c85d613ad8e8..61de7baccf22 100644 --- a/internal/terraform/context_validate_test.go +++ b/internal/terraform/context_validate_test.go @@ -3122,17 +3122,17 @@ module "mod" { source = "./mod" } -// resource "test_resource" "test" { -// attr = module.mod.old -// } +resource "test_resource" "test" { + attr = module.mod.old +} -// resource "test_resource" "test2" { -// attr = module.mod.new -// } +resource "test_resource" "test2" { + attr = module.mod.new +} -// resource "test_resource" "test3" { -// attr = module.mod.old -// } +resource "test_resource" "test3" { + attr = module.mod.old +} output "test_output" { value = module.mod.old @@ -3173,26 +3173,26 @@ module "mod2" { diags := ctx.Validate(m, &ValidateOpts{}) var expectedDiags tfdiags.Diagnostics expectedDiags = expectedDiags.Append( - // &hcl.Diagnostic{ - // Severity: hcl.DiagWarning, - // Summary: "Usage of deprecated output", - // Detail: "Please stop using this", - // Subject: &hcl.Range{ - // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - // Start: hcl.Pos{Line: 7, Column: 12, Byte: 85}, - // End: hcl.Pos{Line: 7, Column: 26, Byte: 99}, - // }, - // }, - // &hcl.Diagnostic{ - // Severity: hcl.DiagWarning, - // Summary: "Usage of deprecated output", - // Detail: "Please stop using this", - // Subject: &hcl.Range{ - // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - // Start: hcl.Pos{Line: 15, Column: 12, Byte: 213}, - // End: hcl.Pos{Line: 15, Column: 26, Byte: 227}, - // }, - // }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 7, Column: 12, Byte: 85}, + End: hcl.Pos{Line: 7, Column: 26, Byte: 99}, + }, + }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 15, Column: 12, Byte: 213}, + End: hcl.Pos{Line: 15, Column: 26, Byte: 227}, + }, + }, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", @@ -3203,26 +3203,36 @@ module "mod2" { End: hcl.Pos{Line: 19, Column: 24, Byte: 277}, }, }, - // &hcl.Diagnostic{ - // Severity: hcl.DiagWarning, - // Summary: "Usage of deprecated output", - // Detail: "Please stop using this", - // Subject: &hcl.Range{ - // Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - // Start: hcl.Pos{Line: 25, Column: 10, Byte: 326}, - // End: hcl.Pos{Line: 25, Column: 24, Byte: 340}, - // }, - // }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 23, Column: 18, Byte: 333}, + End: hcl.Pos{Line: 23, Column: 32, Byte: 347}, + }, + }, + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 29, Column: 10, Byte: 413}, + End: hcl.Pos{Line: 29, Column: 24, Byte: 427}, + }, + }, ) assertDiagnosticsMatch(t, diags, expectedDiags) } -func TestContext2Validate_deprecated_output_conflicts(t *testing.T) { +func TestContext2Validate_deprecated_output_expansion(t *testing.T) { m := testModuleInline(t, map[string]string{ - "mod/mod/main.tf": ` + "mod/nested/main.tf": ` output "old" { - deprecated = "mod/mod: Please stop using this" + deprecated = "mod/nested: Please stop using this" value = "old" } `, @@ -3232,12 +3242,12 @@ output "old" { value = "old" } -module "mod" { - source = "./mod" +module "modnested" { + source = "./nested" } output "new" { - value = module.mod.old + value = module.modnested.old # This should trigger a warning } `, "mod2/main.tf": ` @@ -3256,7 +3266,7 @@ module "mod" { } output "test_output" { - value = module.mod.old + value = module.mod.old # This should trigger a warning } module "mod2" { @@ -3265,7 +3275,7 @@ module "mod2" { } output "test_output2" { - value = module.mod2[*].old + value = module.mod2[0].old # This should trigger a warning } `, }) @@ -3309,18 +3319,78 @@ output "test_output2" { Detail: "mod2: Please stop using this", Subject: &hcl.Range{ Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 15, Column: 10, Byte: 161}, - End: hcl.Pos{Line: 15, Column: 24, Byte: 176}, + Start: hcl.Pos{Line: 16, Column: 10, Byte: 204}, + End: hcl.Pos{Line: 16, Column: 28, Byte: 222}, }, }, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Usage of deprecated output", - Detail: "mod/mod: Please stop using this", + Detail: "mod/nested: Please stop using this", Subject: &hcl.Range{ Filename: filepath.Join(m.Module.SourceDir, "mod", "main.tf"), - Start: hcl.Pos{Line: 12, Column: 10, Byte: 150}, - End: hcl.Pos{Line: 12, Column: 24, Byte: 164}, + Start: hcl.Pos{Line: 12, Column: 13, Byte: 159}, + End: hcl.Pos{Line: 12, Column: 33, Byte: 179}, + }, + }, + ) + + assertDiagnosticsMatch(t, diags, expectedDiags) +} + +func TestContext2Validate_deprecated_output_expansion_with_splat(t *testing.T) { + t.Skip("Not supported yet, might need HCL adjustments (splat operation returns only partial traversal)") + + 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 # This should trigger a warning, but isn't supported yet +} +`, + }) + + 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{}) + var expectedDiags tfdiags.Diagnostics + expectedDiags = expectedDiags.Append( + &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: "Please stop using this", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 8, Column: 10, Byte: 204}, + End: hcl.Pos{Line: 8, Column: 28, Byte: 222}, }, }, ) diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index 876c0ed8d7e6..d3f806b935e3 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -624,15 +624,27 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x addrs.Referenceable) (string, bool) { // TODO: We want to make this available in node_resource_validation, therefore we want to talk about config objects, the referencable should somehow contain the config object, how do we get it out? - if foo, ok := x.(addrs.ModuleCallInstanceOutput); ok { + fmt.Printf("\n\t XXX T --> %#v \n", x) + if mcio, ok := x.(addrs.ModuleCallInstanceOutput); ok { cov := addrs.ConfigOutputValue{ - Module: m, + Module: m.Child(mcio.Call.Call.Name), OutputValue: addrs.OutputValue{ - Name: foo.Name, + Name: mcio.Name, }, } fmt.Printf("\n\t ctx.DeprecatedReferencables --> %#v \n", ctx.DeprecatedReferencables) + fmt.Printf("\n\t cov.String() --> %#v \n", cov.String()) + msg, ok := ctx.DeprecatedReferencables[cov.String()] + return msg, ok + } else if mc, ok := x.(addrs.ModuleCall); ok { + cov := addrs.ConfigOutputValue{ + Module: m.Child(mc.Name), + OutputValue: addrs.OutputValue{ + Name: mc.Name, // <-- this is wrong, we need to get the output name from somewhere + }, + } + fmt.Printf("\n\t cov.String() --> %#v \n", cov.String()) msg, ok := ctx.DeprecatedReferencables[cov.String()] return msg, ok @@ -642,7 +654,7 @@ func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x } func (ctx *BuiltinEvalContext) MarkReferencableAsDeprecated(x addrs.ConfigOutputValue, msg string) { - fmt.Printf("\n\t marking x --> %#v \n", x) + fmt.Printf("\n\t marking x --> %q \n", x.String()) fmt.Printf("\n\t msg --> %#v \n", msg) ctx.DeprecatedReferencables[x.String()] = msg } diff --git a/internal/terraform/node_module_variable.go b/internal/terraform/node_module_variable.go index 1fc34ad5f9d2..ad58448f5bef 100644 --- a/internal/terraform/node_module_variable.go +++ b/internal/terraform/node_module_variable.go @@ -50,8 +50,10 @@ func (n *nodeExpandModuleVariable) temporaryValue() bool { return true } +// TODO: can nodeExpandModuleVariable implement a validate? func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagnostics) { var g Graph + var diags tfdiags.Diagnostics // If this variable has preconditions, we need to report these checks now. // @@ -64,6 +66,17 @@ func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, tfdia } } + for _, ref := range n.References() { + if msg, ok := ctx.ReferencableDeprecationMessage(n.ModulePath(), ref.Subject); ok { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Usage of deprecated output", + Detail: msg, + Subject: ref.SourceRange.ToHCL().Ptr(), + }) + } + } + expander := ctx.InstanceExpander() forEachModuleInstance(expander, n.Module, false, func(module addrs.ModuleInstance) { addr := n.Addr.Absolute(module) @@ -97,7 +110,7 @@ func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, tfdia ctx.Checks().ReportCheckableObjects(n.Addr.InModule(n.Module), checkableAddrs) } - return &g, nil + return &g, diags } func (n *nodeExpandModuleVariable) Name() string { From 41c81bafebfb9034b234dbfde413d1f255a76f0d Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 16 Jan 2025 15:42:24 +0100 Subject: [PATCH 6/8] Cleanup --- internal/terraform/eval_context_builtin.go | 6 ---- internal/terraform/graph_builder_apply.go | 2 -- internal/terraform/graph_builder_plan.go | 2 -- internal/terraform/node_output.go | 5 ---- internal/terraform/transform_output.go | 1 - .../terraform/transform_output_references.go | 30 ------------------- 6 files changed, 46 deletions(-) delete mode 100644 internal/terraform/transform_output_references.go diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index d3f806b935e3..b4e13953d831 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -624,7 +624,6 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x addrs.Referenceable) (string, bool) { // TODO: We want to make this available in node_resource_validation, therefore we want to talk about config objects, the referencable should somehow contain the config object, how do we get it out? - fmt.Printf("\n\t XXX T --> %#v \n", x) if mcio, ok := x.(addrs.ModuleCallInstanceOutput); ok { cov := addrs.ConfigOutputValue{ Module: m.Child(mcio.Call.Call.Name), @@ -633,8 +632,6 @@ func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x }, } - fmt.Printf("\n\t ctx.DeprecatedReferencables --> %#v \n", ctx.DeprecatedReferencables) - fmt.Printf("\n\t cov.String() --> %#v \n", cov.String()) msg, ok := ctx.DeprecatedReferencables[cov.String()] return msg, ok } else if mc, ok := x.(addrs.ModuleCall); ok { @@ -645,7 +642,6 @@ func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x }, } - fmt.Printf("\n\t cov.String() --> %#v \n", cov.String()) msg, ok := ctx.DeprecatedReferencables[cov.String()] return msg, ok } @@ -654,7 +650,5 @@ func (ctx *BuiltinEvalContext) ReferencableDeprecationMessage(m addrs.Module, x } func (ctx *BuiltinEvalContext) MarkReferencableAsDeprecated(x addrs.ConfigOutputValue, msg string) { - fmt.Printf("\n\t marking x --> %q \n", x.String()) - fmt.Printf("\n\t msg --> %#v \n", msg) ctx.DeprecatedReferencables[x.String()] = msg } diff --git a/internal/terraform/graph_builder_apply.go b/internal/terraform/graph_builder_apply.go index 3b2d44a44b2d..ee871eb0e57f 100644 --- a/internal/terraform/graph_builder_apply.go +++ b/internal/terraform/graph_builder_apply.go @@ -193,8 +193,6 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { &ReferenceTransformer{}, &AttachDependenciesTransformer{}, - &OutputReferencesTransformer{}, - // Nested data blocks should be loaded after every other resource has // done its thing. &checkStartTransformer{Config: b.Config, Operation: b.Operation}, diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index 7e7677868c05..22c267afaadc 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -241,8 +241,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &ReferenceTransformer{}, - &OutputReferencesTransformer{}, - &AttachDependenciesTransformer{}, // Make sure data sources are aware of any depends_on from the diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index 1d608b31a928..933a7228a156 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -46,8 +46,6 @@ type nodeExpandOutput struct { Overrides *mocking.Overrides Dependencies []addrs.ConfigResource - - Dependants []*addrs.Reference } var ( @@ -138,7 +136,6 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagn Planning: n.Planning, Override: n.getOverrideValue(absAddr.Module), Dependencies: n.Dependencies, - Dependants: n.Dependants, } } @@ -286,8 +283,6 @@ type NodeApplyableOutput struct { // Dependencies is the full set of resources that are referenced by this // output. Dependencies []addrs.ConfigResource - - Dependants []*addrs.Reference } var ( diff --git a/internal/terraform/transform_output.go b/internal/terraform/transform_output.go index f0b6f19f12d4..3bcfa5558f55 100644 --- a/internal/terraform/transform_output.go +++ b/internal/terraform/transform_output.go @@ -67,7 +67,6 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error { RefreshOnly: t.RefreshOnly, Planning: t.Planning, Overrides: t.Overrides, - Dependants: []*addrs.Reference{}, } log.Printf("[TRACE] OutputTransformer: adding %s as %T", o.Name, node) diff --git a/internal/terraform/transform_output_references.go b/internal/terraform/transform_output_references.go deleted file mode 100644 index 91d0f18a64dd..000000000000 --- a/internal/terraform/transform_output_references.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package terraform - -// OutputReferencesTransformer is a GraphTransformer that adds meta-information -// about references to output values in the configuration. This is used to -// determine when deprecated outputs are used. -type OutputReferencesTransformer struct{} - -func (t *OutputReferencesTransformer) Transform(g *Graph) error { - // Build a reference map so we can efficiently look up the references - vs := g.Vertices() - m := NewReferenceMap(vs) - - // Find the things that reference things and connect them - for _, v := range vs { - if dependant, ok := v.(GraphNodeReferencer); ok { - parents := m.References(v) - - for _, parent := range parents { - if output, ok := parent.(*nodeExpandOutput); ok { - output.Dependants = append(output.Dependants, dependant.References()...) - } - } - } - } - - return nil -} From f64dd2db257f896c34af497a0baafcd56e43f52d Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 22 Jan 2025 17:41:19 +0100 Subject: [PATCH 7/8] Expand testcase --- internal/terraform/context_validate_test.go | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/terraform/context_validate_test.go b/internal/terraform/context_validate_test.go index 61de7baccf22..3f611cdc8f3b 100644 --- a/internal/terraform/context_validate_test.go +++ b/internal/terraform/context_validate_test.go @@ -3259,6 +3259,16 @@ output "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" { @@ -3277,6 +3287,15 @@ module "mod2" { output "test_output2" { value = module.mod2[0].old # This should trigger a warning } + +module "mod3" { + count = 2 + source = "./mod3" +} + +output "test_output_no_warning" { + value = module.mod3[0].new +} `, }) @@ -3328,7 +3347,8 @@ output "test_output2" { Summary: "Usage of deprecated output", Detail: "mod/nested: Please stop using this", Subject: &hcl.Range{ - Filename: filepath.Join(m.Module.SourceDir, "mod", "main.tf"), + // TODO: investigate what is going on with the file path + Filename: filepath.Join("/private", m.Module.SourceDir, "mod", "main.tf"), Start: hcl.Pos{Line: 12, Column: 13, Byte: 159}, End: hcl.Pos{Line: 12, Column: 33, Byte: 179}, }, From e3feb25922405a185378cc4c49ad923688252d21 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 23 Jan 2025 14:46:03 +0100 Subject: [PATCH 8/8] deprecation mark --- internal/lang/marks/marks.go | 5 +++++ internal/terraform/evaluate.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/internal/lang/marks/marks.go b/internal/lang/marks/marks.go index 7ba2752d2286..af401919cdb4 100644 --- a/internal/lang/marks/marks.go +++ b/internal/lang/marks/marks.go @@ -16,6 +16,11 @@ func (m valueMark) GoString() string { return "marks." + string(m) } +type DeprecationMark struct { + AttrPath cty.Path + Message string +} + // Has returns true if and only if the cty.Value has the given mark. func Has(val cty.Value, mark valueMark) bool { return val.HasMark(mark) diff --git a/internal/terraform/evaluate.go b/internal/terraform/evaluate.go index d1e22e12e845..c48348a5dff2 100644 --- a/internal/terraform/evaluate.go +++ b/internal/terraform/evaluate.go @@ -958,6 +958,12 @@ func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.Sour if output.Sensitive { val = val.Mark(marks.Sensitive) } + if config.DeprecatedSet { + val = val.Mark(marks.DeprecationMark{ + Message: config.Deprecated, + // AttrPath: , + }) + } return val, diags }