From 212ae6c4ba998d5a2c4f63bdde7777d03f91c86d Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Wed, 28 Jun 2023 09:47:24 +0200 Subject: [PATCH] Introduce separate testing scope for reference validation (#33339) --- internal/addrs/output_value.go | 19 ++- internal/addrs/parse_ref.go | 60 +++++++- internal/addrs/parse_ref_test.go | 141 +++++++++++++++++- internal/command/jsonconfig/expression.go | 7 +- internal/configs/checks.go | 2 +- internal/configs/resource.go | 2 +- internal/lang/data.go | 5 +- internal/lang/data_test.go | 13 +- internal/lang/eval.go | 36 ++++- internal/lang/eval_test.go | 11 +- .../globalref/analyzer_meta_references.go | 17 ++- .../analyzer_meta_references_shortcuts.go | 6 +- internal/lang/references.go | 13 +- internal/lang/scope.go | 12 ++ internal/terraform/eval_conditions.go | 4 +- internal/terraform/eval_for_each.go | 6 +- internal/terraform/evaluate.go | 65 ++++++++ internal/terraform/evaluate_test.go | 64 ++++++++ internal/terraform/evaluate_valid_test.go | 2 +- internal/terraform/node_check.go | 4 +- internal/terraform/node_local.go | 9 +- internal/terraform/node_module_expand.go | 4 +- internal/terraform/node_module_variable.go | 5 +- internal/terraform/node_output.go | 8 +- internal/terraform/node_resource_abstract.go | 22 +-- internal/terraform/node_resource_apply.go | 4 +- internal/terraform/node_resource_validate.go | 5 +- internal/terraform/test_context.go | 4 +- internal/terraform/transform_reference.go | 3 +- internal/terraform/validate_selfref.go | 2 +- 30 files changed, 477 insertions(+), 78 deletions(-) diff --git a/internal/addrs/output_value.go b/internal/addrs/output_value.go index 9b1da25c32..7dfdb1fdd7 100644 --- a/internal/addrs/output_value.go +++ b/internal/addrs/output_value.go @@ -16,10 +16,11 @@ import ( // that is defining it. // // This is related to but separate from ModuleCallOutput, which represents -// a module output from the perspective of its parent module. Since output -// values cannot be represented from the module where they are defined, -// OutputValue is not Referenceable, while ModuleCallOutput is. +// a module output from the perspective of its parent module. Outputs are +// referencable from the testing scope, in general terraform operation users +// will be referencing ModuleCallOutput. type OutputValue struct { + referenceable Name string } @@ -27,6 +28,16 @@ func (v OutputValue) String() string { return "output." + v.Name } +func (v OutputValue) Equal(o OutputValue) bool { + return v.Name == o.Name +} + +func (v OutputValue) UniqueKey() UniqueKey { + return v // An OutputValue is its own UniqueKey +} + +func (v OutputValue) uniqueKeySigil() {} + // Absolute converts the receiver into an absolute address within the given // module instance. func (v OutputValue) Absolute(m ModuleInstance) AbsOutputValue { @@ -82,7 +93,7 @@ func (v AbsOutputValue) String() string { } func (v AbsOutputValue) Equal(o AbsOutputValue) bool { - return v.OutputValue == o.OutputValue && v.Module.Equal(o.Module) + return v.OutputValue.Equal(o.OutputValue) && v.Module.Equal(o.Module) } func (v AbsOutputValue) ConfigOutputValue() ConfigOutputValue { diff --git a/internal/addrs/parse_ref.go b/internal/addrs/parse_ref.go index 3c757bf31b..d5cdbb78d1 100644 --- a/internal/addrs/parse_ref.go +++ b/internal/addrs/parse_ref.go @@ -9,8 +9,9 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/hashicorp/terraform/internal/tfdiags" "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/tfdiags" ) // Reference describes a reference to an address with source location @@ -82,6 +83,47 @@ func ParseRef(traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) { return ref, diags } +// ParseRefFromTestingScope adds check blocks and outputs into the available +// references returned by ParseRef. +// +// The testing files and functionality have a slightly expanded referencing +// scope and so should use this function to retrieve references. +func ParseRefFromTestingScope(traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) { + root := traversal.RootName() + + var diags tfdiags.Diagnostics + var reference *Reference + + switch root { + case "output": + name, rng, remain, outputDiags := parseSingleAttrRef(traversal) + reference = &Reference{ + Subject: OutputValue{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + } + diags = outputDiags + case "check": + name, rng, remain, checkDiags := parseSingleAttrRef(traversal) + reference = &Reference{ + Subject: Check{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + } + diags = checkDiags + } + + if reference != nil { + if len(reference.Remaining) == 0 { + reference.Remaining = nil + } + return reference, diags + } + + // If it's not an output or a check block, then just parse it as normal. + return ParseRef(traversal) +} + // ParseRefStr is a helper wrapper around ParseRef that takes a string // and parses it with the HCL native syntax traversal parser before // interpreting it. @@ -111,6 +153,22 @@ func ParseRefStr(str string) (*Reference, tfdiags.Diagnostics) { return ref, diags } +// ParseRefStrFromTestingScope matches ParseRefStr except it supports the +// references supported by ParseRefFromTestingScope. +func ParseRefStrFromTestingScope(str string) (*Reference, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) + diags = diags.Append(parseDiags) + if parseDiags.HasErrors() { + return nil, diags + } + + ref, targetDiags := ParseRefFromTestingScope(traversal) + diags = diags.Append(targetDiags) + return ref, diags +} + func parseRef(traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics diff --git a/internal/addrs/parse_ref_test.go b/internal/addrs/parse_ref_test.go index 91ad9e9fdb..2889ab33fa 100644 --- a/internal/addrs/parse_ref_test.go +++ b/internal/addrs/parse_ref_test.go @@ -9,10 +9,117 @@ import ( "github.com/go-test/deep" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/hashicorp/terraform/internal/tfdiags" "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/tfdiags" ) +func TestParseRefInTestingScope(t *testing.T) { + tests := []struct { + Input string + Want *Reference + WantErr string + }{ + { + `output.value`, + &Reference{ + Subject: OutputValue{ + Name: "value", + }, + SourceRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, + }, + }, + ``, + }, + { + `output`, + nil, + `The "output" object cannot be accessed directly. Instead, access one of its attributes.`, + }, + { + `output["foo"]`, + nil, + `The "output" object does not support this operation.`, + }, + + { + `check.health`, + &Reference{ + Subject: Check{ + Name: "health", + }, + SourceRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, + }, + }, + ``, + }, + { + `check`, + nil, + `The "check" object cannot be accessed directly. Instead, access one of its attributes.`, + }, + { + `check["foo"]`, + nil, + `The "check" object does not support this operation.`, + }, + + // Sanity check at least one of the others works to verify it does + // fall through to the core function. + { + `count.index`, + &Reference{ + Subject: CountAttr{ + Name: "index", + }, + SourceRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, + }, + }, + ``, + }, + } + for _, test := range tests { + t.Run(test.Input, func(t *testing.T) { + traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1}) + if travDiags.HasErrors() { + t.Fatal(travDiags.Error()) + } + + got, diags := ParseRefFromTestingScope(traversal) + + switch len(diags) { + case 0: + if test.WantErr != "" { + t.Fatalf("succeeded; want error: %s", test.WantErr) + } + case 1: + if test.WantErr == "" { + t.Fatalf("unexpected diagnostics: %s", diags.Err()) + } + if got, want := diags[0].Description().Detail, test.WantErr; got != want { + t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) + } + default: + t.Fatalf("too many diagnostics: %s", diags.Err()) + } + + if diags.HasErrors() { + return + } + + for _, problem := range deep.Equal(got, test.Want) { + t.Errorf(problem) + } + }) + } +} + func TestParseRef(t *testing.T) { tests := []struct { Input string @@ -719,6 +826,38 @@ func TestParseRef(t *testing.T) { nil, `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`, }, + + // Should interpret checks and outputs as resource types. + { + `output.value`, + &Reference{ + Subject: Resource{ + Mode: ManagedResourceMode, + Type: "output", + Name: "value", + }, + SourceRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, + }, + }, + ``, + }, + { + `check.health`, + &Reference{ + Subject: Resource{ + Mode: ManagedResourceMode, + Type: "check", + Name: "health", + }, + SourceRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, + }, + }, + ``, + }, } for _, test := range tests { diff --git a/internal/command/jsonconfig/expression.go b/internal/command/jsonconfig/expression.go index d6acca739d..ed29c7afa8 100644 --- a/internal/command/jsonconfig/expression.go +++ b/internal/command/jsonconfig/expression.go @@ -10,12 +10,13 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/lang/blocktoattr" - "github.com/zclconf/go-cty/cty" - ctyjson "github.com/zclconf/go-cty/cty/json" ) // expression represents any unparsed expression @@ -47,7 +48,7 @@ func marshalExpression(ex hcl.Expression) expression { ret.ConstantValue = valJSON } - refs, _ := lang.ReferencesInExpr(ex) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, ex) if len(refs) > 0 { var varString []string for _, ref := range refs { diff --git a/internal/configs/checks.go b/internal/configs/checks.go index 9bb4741b61..033ae83d7a 100644 --- a/internal/configs/checks.go +++ b/internal/configs/checks.go @@ -53,7 +53,7 @@ func (cr *CheckRule) validateSelfReferences(checkType string, addr addrs.Resourc if expr == nil { continue } - refs, _ := lang.References(expr.Variables()) + refs, _ := lang.References(addrs.ParseRef, expr.Variables()) for _, ref := range refs { var refAddr addrs.Resource diff --git a/internal/configs/resource.go b/internal/configs/resource.go index b88d93ac40..fe86a7ff2a 100644 --- a/internal/configs/resource.go +++ b/internal/configs/resource.go @@ -568,7 +568,7 @@ func decodeReplaceTriggeredBy(expr hcl.Expression) ([]hcl.Expression, hcl.Diagno exprs[i] = expr } - refs, refDiags := lang.ReferencesInExpr(expr) + refs, refDiags := lang.ReferencesInExpr(addrs.ParseRef, expr) for _, diag := range refDiags { severity := hcl.DiagError if diag.Severity() == tfdiags.Warning { diff --git a/internal/lang/data.go b/internal/lang/data.go index 2eb4f02bcb..cc8aeb1e46 100644 --- a/internal/lang/data.go +++ b/internal/lang/data.go @@ -4,9 +4,10 @@ package lang import ( + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // Data is an interface whose implementations can provide cty.Value @@ -33,4 +34,6 @@ type Data interface { GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) GetInputVariable(addrs.InputVariable, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) + GetOutput(addrs.OutputValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) + GetCheckBlock(addrs.Check, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) } diff --git a/internal/lang/data_test.go b/internal/lang/data_test.go index acaae54acb..c0dde96691 100644 --- a/internal/lang/data_test.go +++ b/internal/lang/data_test.go @@ -4,9 +4,10 @@ package lang import ( + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) type dataForTests struct { @@ -14,10 +15,12 @@ type dataForTests struct { ForEachAttrs map[string]cty.Value Resources map[string]cty.Value LocalValues map[string]cty.Value + OutputValues map[string]cty.Value Modules map[string]cty.Value PathAttrs map[string]cty.Value TerraformAttrs map[string]cty.Value InputVariables map[string]cty.Value + CheckBlocks map[string]cty.Value } var _ Data = &dataForTests{} @@ -63,3 +66,11 @@ func (d *dataForTests) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) func (d *dataForTests) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { return d.TerraformAttrs[addr.Name], nil } + +func (d *dataForTests) GetOutput(addr addrs.OutputValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return d.OutputValues[addr.Name], nil +} + +func (d *dataForTests) GetCheckBlock(addr addrs.Check, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return d.CheckBlocks[addr.Name], nil +} diff --git a/internal/lang/eval.go b/internal/lang/eval.go index 6928a49a0f..d81af25106 100644 --- a/internal/lang/eval.go +++ b/internal/lang/eval.go @@ -9,13 +9,14 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/ext/dynblock" "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/convert" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang/blocktoattr" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/convert" ) // ExpandBlock expands any "dynamic" blocks present in the given body. The @@ -28,7 +29,7 @@ func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body spec := schema.DecoderSpec() traversals := dynblock.ExpandVariablesHCLDec(body, spec) - refs, diags := References(traversals) + refs, diags := References(s.ParseRef, traversals) ctx, ctxDiags := s.EvalContext(refs) diags = diags.Append(ctxDiags) @@ -49,7 +50,7 @@ func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { spec := schema.DecoderSpec() - refs, diags := ReferencesInBlock(body, schema) + refs, diags := ReferencesInBlock(s.ParseRef, body, schema) ctx, ctxDiags := s.EvalContext(refs) diags = diags.Append(ctxDiags) @@ -96,7 +97,7 @@ func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschem }) } - refs, refDiags := References(hcldec.Variables(body, spec)) + refs, refDiags := References(s.ParseRef, hcldec.Variables(body, spec)) diags = diags.Append(refDiags) terraformAttrs := map[string]cty.Value{} @@ -161,7 +162,7 @@ func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschem // If the returned diagnostics contains errors then the result may be // incomplete, but will always be of the requested type. func (s *Scope) EvalExpr(expr hcl.Expression, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { - refs, diags := ReferencesInExpr(expr) + refs, diags := ReferencesInExpr(s.ParseRef, expr) ctx, ctxDiags := s.EvalContext(refs) diags = diags.Append(ctxDiags) @@ -281,10 +282,12 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl wholeModules := map[string]cty.Value{} inputVariables := map[string]cty.Value{} localValues := map[string]cty.Value{} + outputValues := map[string]cty.Value{} pathAttrs := map[string]cty.Value{} terraformAttrs := map[string]cty.Value{} countAttrs := map[string]cty.Value{} forEachAttrs := map[string]cty.Value{} + checkBlocks := map[string]cty.Value{} var self cty.Value for _, ref := range refs { @@ -405,6 +408,16 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl diags = diags.Append(valDiags) forEachAttrs[subj.Name] = val + case addrs.OutputValue: + val, valDiags := normalizeRefValue(s.Data.GetOutput(subj, rng)) + diags = diags.Append(valDiags) + outputValues[subj.Name] = val + + case addrs.Check: + val, valDiags := normalizeRefValue(s.Data.GetCheckBlock(subj, rng)) + diags = diags.Append(valDiags) + outputValues[subj.Name] = val + default: // Should never happen panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj)) @@ -429,6 +442,17 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl vals["terraform"] = cty.ObjectVal(terraformAttrs) vals["count"] = cty.ObjectVal(countAttrs) vals["each"] = cty.ObjectVal(forEachAttrs) + + // Checks and outputs are conditionally included in the available scope, so + // we'll only write out their values if we actually have something for them. + if len(checkBlocks) > 0 { + vals["check"] = cty.ObjectVal(checkBlocks) + } + + if len(outputValues) > 0 { + vals["output"] = cty.ObjectVal(outputValues) + } + if self != cty.NilVal { vals["self"] = self } diff --git a/internal/lang/eval_test.go b/internal/lang/eval_test.go index 07190dd656..2cfcd231f8 100644 --- a/internal/lang/eval_test.go +++ b/internal/lang/eval_test.go @@ -367,13 +367,14 @@ func TestScopeEvalContext(t *testing.T) { return } - refs, refsDiags := ReferencesInExpr(expr) + refs, refsDiags := ReferencesInExpr(addrs.ParseRef, expr) if refsDiags.HasErrors() { t.Fatal(refsDiags.Err()) } scope := &Scope{ - Data: data, + Data: data, + ParseRef: addrs.ParseRef, // "self" will just be an arbitrary one of the several resource // instances we have in our test dataset. @@ -680,7 +681,8 @@ func TestScopeExpandEvalBlock(t *testing.T) { body := file.Body scope := &Scope{ - Data: data, + Data: data, + ParseRef: addrs.ParseRef, } body, expandDiags := scope.ExpandBlock(body, schema) @@ -826,7 +828,8 @@ func TestScopeEvalSelfBlock(t *testing.T) { body := file.Body scope := &Scope{ - Data: data, + Data: data, + ParseRef: addrs.ParseRef, } gotVal, ctxDiags := scope.EvalSelfBlock(body, test.Self, schema, test.KeyData) diff --git a/internal/lang/globalref/analyzer_meta_references.go b/internal/lang/globalref/analyzer_meta_references.go index 1e5f26a1e6..4d450eb009 100644 --- a/internal/lang/globalref/analyzer_meta_references.go +++ b/internal/lang/globalref/analyzer_meta_references.go @@ -5,12 +5,13 @@ package globalref import ( "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/configs/configschema" - "github.com/hashicorp/terraform/internal/lang" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/gocty" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/lang" ) // MetaReferences inspects the configuration to find the references contained @@ -117,7 +118,7 @@ func (a *Analyzer) metaReferencesInputVariable(calleeAddr addrs.ModuleInstance, if attr == nil { return nil } - refs, _ := lang.ReferencesInExpr(attr.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, attr.Expr) return absoluteRefs(callerAddr, refs) } @@ -137,7 +138,7 @@ func (a *Analyzer) metaReferencesOutputValue(callerAddr addrs.ModuleInstance, ad // We don't check for errors here because we'll make a best effort to // analyze whatever partial result HCL is able to extract. - refs, _ := lang.ReferencesInExpr(oc.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, oc.Expr) return absoluteRefs(calleeAddr, refs) } @@ -154,7 +155,7 @@ func (a *Analyzer) metaReferencesLocalValue(moduleAddr addrs.ModuleInstance, add // We don't check for errors here because we'll make a best effort to // analyze whatever partial result HCL is able to extract. - refs, _ := lang.ReferencesInExpr(local.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, local.Expr) return absoluteRefs(moduleAddr, refs) } @@ -388,12 +389,12 @@ Steps: var refs []*addrs.Reference for _, expr := range exprs { - moreRefs, _ := lang.ReferencesInExpr(expr) + moreRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, expr) refs = append(refs, moreRefs...) } if schema != nil { for _, body := range bodies { - moreRefs, _ := lang.ReferencesInBlock(body, schema) + moreRefs, _ := lang.ReferencesInBlock(addrs.ParseRef, body, schema) refs = append(refs, moreRefs...) } } diff --git a/internal/lang/globalref/analyzer_meta_references_shortcuts.go b/internal/lang/globalref/analyzer_meta_references_shortcuts.go index 3518d1412f..8a76ae63eb 100644 --- a/internal/lang/globalref/analyzer_meta_references_shortcuts.go +++ b/internal/lang/globalref/analyzer_meta_references_shortcuts.go @@ -22,7 +22,7 @@ func (a *Analyzer) ReferencesFromOutputValue(addr addrs.AbsOutputValue) []Refere if oc == nil { return nil } - refs, _ := lang.ReferencesInExpr(oc.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, oc.Expr) return absoluteRefs(addr.Module, refs) } @@ -79,10 +79,10 @@ func (a *Analyzer) ReferencesFromResourceRepetition(addr addrs.AbsResource) []Re switch { case rc.ForEach != nil: - refs, _ := lang.ReferencesInExpr(rc.ForEach) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, rc.ForEach) return absoluteRefs(addr.Module, refs) case rc.Count != nil: - refs, _ := lang.ReferencesInExpr(rc.Count) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, rc.Count) return absoluteRefs(addr.Module, refs) default: return nil diff --git a/internal/lang/references.go b/internal/lang/references.go index a1d401aea8..25ddaa74f0 100644 --- a/internal/lang/references.go +++ b/internal/lang/references.go @@ -5,6 +5,7 @@ package lang import ( "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/blocktoattr" @@ -24,7 +25,7 @@ import ( // incomplete or invalid. Otherwise, the returned slice has one reference per // given traversal, though it is not guaranteed that the references will // appear in the same order as the given traversals. -func References(traversals []hcl.Traversal) ([]*addrs.Reference, tfdiags.Diagnostics) { +func References(parseRef ParseRef, traversals []hcl.Traversal) ([]*addrs.Reference, tfdiags.Diagnostics) { if len(traversals) == 0 { return nil, nil } @@ -33,7 +34,7 @@ func References(traversals []hcl.Traversal) ([]*addrs.Reference, tfdiags.Diagnos refs := make([]*addrs.Reference, 0, len(traversals)) for _, traversal := range traversals { - ref, refDiags := addrs.ParseRef(traversal) + ref, refDiags := parseRef(traversal) diags = diags.Append(refDiags) if ref == nil { continue @@ -50,7 +51,7 @@ func References(traversals []hcl.Traversal) ([]*addrs.Reference, tfdiags.Diagnos // // A block schema must be provided so that this function can determine where in // the body variables are expected. -func ReferencesInBlock(body hcl.Body, schema *configschema.Block) ([]*addrs.Reference, tfdiags.Diagnostics) { +func ReferencesInBlock(parseRef ParseRef, body hcl.Body, schema *configschema.Block) ([]*addrs.Reference, tfdiags.Diagnostics) { if body == nil { return nil, nil } @@ -69,16 +70,16 @@ func ReferencesInBlock(body hcl.Body, schema *configschema.Block) ([]*addrs.Refe // in a better position to test this due to having mock providers etc // available. traversals := blocktoattr.ExpandedVariables(body, schema) - return References(traversals) + return References(parseRef, traversals) } // ReferencesInExpr is a helper wrapper around References that first searches // the given expression for traversals, before converting those traversals // to references. -func ReferencesInExpr(expr hcl.Expression) ([]*addrs.Reference, tfdiags.Diagnostics) { +func ReferencesInExpr(parseRef ParseRef, expr hcl.Expression) ([]*addrs.Reference, tfdiags.Diagnostics) { if expr == nil { return nil, nil } traversals := expr.Variables() - return References(traversals) + return References(parseRef, traversals) } diff --git a/internal/lang/scope.go b/internal/lang/scope.go index 267d4c76bf..141ce54c8b 100644 --- a/internal/lang/scope.go +++ b/internal/lang/scope.go @@ -7,12 +7,16 @@ import ( "sync" "time" + "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty/function" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/experiments" + "github.com/hashicorp/terraform/internal/tfdiags" ) +type ParseRef func(traversal hcl.Traversal) (*addrs.Reference, tfdiags.Diagnostics) + // Scope is the main type in this package, allowing dynamic evaluation of // blocks and expressions based on some contextual information that informs // which variables and functions will be available. @@ -20,6 +24,14 @@ type Scope struct { // Data is used to resolve references in expressions. Data Data + // ParseRef is a function that the scope uses to extract references from + // a hcl.Traversal. This controls the type of references the scope currently + // supports. As an example, the testing scope can reference outputs directly + // while the main Terraform context scope can not. This means that this + // function for the testing scope will happily return outputs, while the + // main context scope would fail if a user attempts to reference an output. + ParseRef ParseRef + // SelfAddr is the address that the "self" object should be an alias of, // or nil if the "self" object should not be available at all. SelfAddr addrs.Referenceable diff --git a/internal/terraform/eval_conditions.go b/internal/terraform/eval_conditions.go index 6c86f741b6..7d617ceae2 100644 --- a/internal/terraform/eval_conditions.go +++ b/internal/terraform/eval_conditions.go @@ -73,9 +73,9 @@ type checkResult struct { func validateCheckRule(typ addrs.CheckRuleType, rule *configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData) (string, *hcl.EvalContext, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - refs, moreDiags := lang.ReferencesInExpr(rule.Condition) + refs, moreDiags := lang.ReferencesInExpr(addrs.ParseRef, rule.Condition) diags = diags.Append(moreDiags) - moreRefs, moreDiags := lang.ReferencesInExpr(rule.ErrorMessage) + moreRefs, moreDiags := lang.ReferencesInExpr(addrs.ParseRef, rule.ErrorMessage) diags = diags.Append(moreDiags) refs = append(refs, moreRefs...) diff --git a/internal/terraform/eval_for_each.go b/internal/terraform/eval_for_each.go index 5905f2b581..0e6875fab8 100644 --- a/internal/terraform/eval_for_each.go +++ b/internal/terraform/eval_for_each.go @@ -7,10 +7,12 @@ import ( "fmt" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // evaluateForEachExpression is our standard mechanism for interpreting an @@ -44,7 +46,7 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext, allowU return nullMap, diags } - refs, moreDiags := lang.ReferencesInExpr(expr) + refs, moreDiags := lang.ReferencesInExpr(addrs.ParseRef, expr) diags = diags.Append(moreDiags) scope := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey) var hclCtx *hcl.EvalContext diff --git a/internal/terraform/evaluate.go b/internal/terraform/evaluate.go index 57b368c9d0..eca75c422b 100644 --- a/internal/terraform/evaluate.go +++ b/internal/terraform/evaluate.go @@ -77,6 +77,7 @@ type Evaluator struct { func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable, source addrs.Referenceable) *lang.Scope { return &lang.Scope{ Data: data, + ParseRef: addrs.ParseRef, SelfAddr: self, SourceAddr: source, PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval, @@ -940,6 +941,70 @@ func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfd } } +func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + // First we'll make sure the requested value is declared in configuration, + // so we can produce a nice message if not. + moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath) + if moduleConfig == nil { + // should never happen, since we can't be evaluating in a module + // that wasn't mentioned in configuration. + panic(fmt.Sprintf("output value read from %s, which has no configuration", d.ModulePath)) + } + + config := moduleConfig.Module.Outputs[addr.Name] + if config == nil { + var suggestions []string + for k := range moduleConfig.Module.Outputs { + suggestions = append(suggestions, k) + } + suggestion := didyoumean.NameSuggestion(addr.Name, suggestions) + if suggestion != "" { + suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) + } + + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: `Reference to undeclared output value`, + Detail: fmt.Sprintf(`An output value with the name %q has not been declared.%s`, addr.Name, suggestion), + Subject: rng.ToHCL().Ptr(), + }) + return cty.DynamicVal, diags + } + + output := d.Evaluator.State.OutputValue(addr.Absolute(d.ModulePath)) + + val := output.Value + if val == cty.NilVal { + // Not evaluated yet? + val = cty.DynamicVal + } + + if output.Sensitive { + val = val.Mark(marks.Sensitive) + } + + return val, diags +} + +func (d *evaluationStateData) GetCheckBlock(addr addrs.Check, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + // For now, check blocks don't contain any meaningful data and can only + // be referenced from the testing scope within an expect_failures attribute. + // + // We've added them into the scope explicitly since they are referencable, + // but we'll actually just return an error message saying they can't be + // referenced in this context. + var diags tfdiags.Diagnostics + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Reference to \"check\" in invalid context", + Detail: "The \"check\" object can only be referenced from an \"expect_failures\" attribute within a Terraform testing \"run\" block.", + Subject: rng.ToHCL().Ptr(), + }) + return cty.NilVal, diags +} + // moduleDisplayAddr returns a string describing the given module instance // address that is appropriate for returning to users in situations where the // root module is possible. Specifically, it returns "the root module" if the diff --git a/internal/terraform/evaluate_test.go b/internal/terraform/evaluate_test.go index 7daf2bbcd3..a3d2ae669b 100644 --- a/internal/terraform/evaluate_test.go +++ b/internal/terraform/evaluate_test.go @@ -87,6 +87,70 @@ func TestEvaluatorGetPathAttr(t *testing.T) { }) } +func TestEvaluatorGetOutputValue(t *testing.T) { + evaluator := &Evaluator{ + Meta: &ContextMeta{ + Env: "foo", + }, + Config: &configs.Config{ + Module: &configs.Module{ + Outputs: map[string]*configs.Output{ + "some_output": { + Name: "some_output", + Sensitive: true, + }, + "some_other_output": { + Name: "some_other_output", + }, + }, + }, + }, + State: states.BuildState(func(state *states.SyncState) { + state.SetOutputValue(addrs.AbsOutputValue{ + Module: addrs.RootModuleInstance, + OutputValue: addrs.OutputValue{ + Name: "some_output", + }, + }, cty.StringVal("first"), true) + state.SetOutputValue(addrs.AbsOutputValue{ + Module: addrs.RootModuleInstance, + OutputValue: addrs.OutputValue{ + Name: "some_other_output", + }, + }, cty.StringVal("second"), false) + }).SyncWrapper(), + } + + data := &evaluationStateData{ + Evaluator: evaluator, + } + scope := evaluator.Scope(data, nil, nil) + + want := cty.StringVal("first").Mark(marks.Sensitive) + got, diags := scope.Data.GetOutput(addrs.OutputValue{ + Name: "some_output", + }, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + if !got.RawEquals(want) { + t.Errorf("wrong result %#v; want %#v", got, want) + } + + want = cty.StringVal("second") + got, diags = scope.Data.GetOutput(addrs.OutputValue{ + Name: "some_other_output", + }, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + if !got.RawEquals(want) { + t.Errorf("wrong result %#v; want %#v", got, want) + } +} + // This particularly tests that a sensitive attribute in config // results in a value that has a "sensitive" cty Mark func TestEvaluatorGetInputVariable(t *testing.T) { diff --git a/internal/terraform/evaluate_valid_test.go b/internal/terraform/evaluate_valid_test.go index 10048e8b8d..04028cf3c4 100644 --- a/internal/terraform/evaluate_valid_test.go +++ b/internal/terraform/evaluate_valid_test.go @@ -115,7 +115,7 @@ For example, to correlate with indices of a referring resource, use: t.Fatal(hclDiags.Error()) } - refs, diags := lang.References([]hcl.Traversal{traversal}) + refs, diags := lang.References(addrs.ParseRef, []hcl.Traversal{traversal}) if diags.HasErrors() { t.Fatal(diags.Err()) } diff --git a/internal/terraform/node_check.go b/internal/terraform/node_check.go index 87593beb4b..0ebc65a8c0 100644 --- a/internal/terraform/node_check.go +++ b/internal/terraform/node_check.go @@ -99,8 +99,8 @@ func (n *nodeExpandCheck) References() []*addrs.Reference { for _, assert := range n.config.Asserts { // Check blocks reference anything referenced by conditions or messages // in their check rules. - condition, _ := lang.ReferencesInExpr(assert.Condition) - message, _ := lang.ReferencesInExpr(assert.ErrorMessage) + condition, _ := lang.ReferencesInExpr(addrs.ParseRef, assert.Condition) + message, _ := lang.ReferencesInExpr(addrs.ParseRef, assert.ErrorMessage) refs = append(refs, condition...) refs = append(refs, message...) } diff --git a/internal/terraform/node_local.go b/internal/terraform/node_local.go index 47714566e8..18112d4fee 100644 --- a/internal/terraform/node_local.go +++ b/internal/terraform/node_local.go @@ -8,12 +8,13 @@ import ( "log" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/dag" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // nodeExpandLocal represents a named local value in a configuration module, @@ -61,7 +62,7 @@ func (n *nodeExpandLocal) ReferenceableAddrs() []addrs.Referenceable { // GraphNodeReferencer func (n *nodeExpandLocal) References() []*addrs.Reference { - refs, _ := lang.ReferencesInExpr(n.Config.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Config.Expr) return refs } @@ -124,7 +125,7 @@ func (n *NodeLocal) ReferenceableAddrs() []addrs.Referenceable { // GraphNodeReferencer func (n *NodeLocal) References() []*addrs.Reference { - refs, _ := lang.ReferencesInExpr(n.Config.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Config.Expr) return refs } @@ -138,7 +139,7 @@ func (n *NodeLocal) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Di // We ignore diags here because any problems we might find will be found // again in EvaluateExpr below. - refs, _ := lang.ReferencesInExpr(expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, expr) for _, ref := range refs { if ref.Subject == addr { diags = diags.Append(&hcl.Diagnostic{ diff --git a/internal/terraform/node_module_expand.go b/internal/terraform/node_module_expand.go index 99e2823323..6dbf480464 100644 --- a/internal/terraform/node_module_expand.go +++ b/internal/terraform/node_module_expand.go @@ -64,11 +64,11 @@ func (n *nodeExpandModule) References() []*addrs.Reference { // child module instances we might expand to during our evaluation. if n.ModuleCall.Count != nil { - countRefs, _ := lang.ReferencesInExpr(n.ModuleCall.Count) + countRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.ModuleCall.Count) refs = append(refs, countRefs...) } if n.ModuleCall.ForEach != nil { - forEachRefs, _ := lang.ReferencesInExpr(n.ModuleCall.ForEach) + forEachRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.ModuleCall.ForEach) refs = append(refs, forEachRefs...) } return refs diff --git a/internal/terraform/node_module_variable.go b/internal/terraform/node_module_variable.go index 9042d7b507..e1b5407452 100644 --- a/internal/terraform/node_module_variable.go +++ b/internal/terraform/node_module_variable.go @@ -8,13 +8,14 @@ import ( "log" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/dag" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // nodeExpandModuleVariable is the placeholder for an variable that has not yet had @@ -90,7 +91,7 @@ func (n *nodeExpandModuleVariable) References() []*addrs.Reference { // where our associated variable was declared, which is correct because // our value expression is assigned within a "module" block in the parent // module. - refs, _ := lang.ReferencesInExpr(n.Expr) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Expr) return refs } diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index 5e64a227f8..315cb738a7 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -282,16 +282,16 @@ func (n *NodeApplyableOutput) ReferenceableAddrs() []addrs.Referenceable { func referencesForOutput(c *configs.Output) []*addrs.Reference { var refs []*addrs.Reference - impRefs, _ := lang.ReferencesInExpr(c.Expr) - expRefs, _ := lang.References(c.DependsOn) + impRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, c.Expr) + expRefs, _ := lang.References(addrs.ParseRef, c.DependsOn) refs = append(refs, impRefs...) refs = append(refs, expRefs...) for _, check := range c.Preconditions { - condRefs, _ := lang.ReferencesInExpr(check.Condition) + condRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, check.Condition) refs = append(refs, condRefs...) - errRefs, _ := lang.ReferencesInExpr(check.ErrorMessage) + errRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, check.ErrorMessage) refs = append(refs, errRefs...) } diff --git a/internal/terraform/node_resource_abstract.go b/internal/terraform/node_resource_abstract.go index 1190f3b710..3607979ece 100644 --- a/internal/terraform/node_resource_abstract.go +++ b/internal/terraform/node_resource_abstract.go @@ -152,25 +152,25 @@ func (n *NodeAbstractResource) References() []*addrs.Reference { log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) } - refs, _ := lang.ReferencesInExpr(c.Count) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, c.Count) result = append(result, refs...) - refs, _ = lang.ReferencesInExpr(c.ForEach) + refs, _ = lang.ReferencesInExpr(addrs.ParseRef, c.ForEach) result = append(result, refs...) for _, expr := range c.TriggersReplacement { - refs, _ = lang.ReferencesInExpr(expr) + refs, _ = lang.ReferencesInExpr(addrs.ParseRef, expr) result = append(result, refs...) } // ReferencesInBlock() requires a schema if n.Schema != nil { - refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) + refs, _ = lang.ReferencesInBlock(addrs.ParseRef, c.Config, n.Schema) result = append(result, refs...) } if c.Managed != nil { if c.Managed.Connection != nil { - refs, _ = lang.ReferencesInBlock(c.Managed.Connection.Config, connectionBlockSupersetSchema) + refs, _ = lang.ReferencesInBlock(addrs.ParseRef, c.Managed.Connection.Config, connectionBlockSupersetSchema) result = append(result, refs...) } @@ -179,7 +179,7 @@ func (n *NodeAbstractResource) References() []*addrs.Reference { continue } if p.Connection != nil { - refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema) + refs, _ = lang.ReferencesInBlock(addrs.ParseRef, p.Connection.Config, connectionBlockSupersetSchema) result = append(result, refs...) } @@ -187,21 +187,21 @@ func (n *NodeAbstractResource) References() []*addrs.Reference { if schema == nil { log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name()) } - refs, _ = lang.ReferencesInBlock(p.Config, schema) + refs, _ = lang.ReferencesInBlock(addrs.ParseRef, p.Config, schema) result = append(result, refs...) } } for _, check := range c.Preconditions { - refs, _ := lang.ReferencesInExpr(check.Condition) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, check.Condition) result = append(result, refs...) - refs, _ = lang.ReferencesInExpr(check.ErrorMessage) + refs, _ = lang.ReferencesInExpr(addrs.ParseRef, check.ErrorMessage) result = append(result, refs...) } for _, check := range c.Postconditions { - refs, _ := lang.ReferencesInExpr(check.Condition) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, check.Condition) result = append(result, refs...) - refs, _ = lang.ReferencesInExpr(check.ErrorMessage) + refs, _ = lang.ReferencesInExpr(addrs.ParseRef, check.ErrorMessage) result = append(result, refs...) } diff --git a/internal/terraform/node_resource_apply.go b/internal/terraform/node_resource_apply.go index 17abf4ab02..3754196924 100644 --- a/internal/terraform/node_resource_apply.go +++ b/internal/terraform/node_resource_apply.go @@ -95,9 +95,9 @@ func (n *NodeApplyableResource) References() []*addrs.Reference { // Since this node type only updates resource-level metadata, we only // need to worry about the parts of the configuration that affect // our "each mode": the count and for_each meta-arguments. - refs, _ := lang.ReferencesInExpr(n.Config.Count) + refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Config.Count) result = append(result, refs...) - refs, _ = lang.ReferencesInExpr(n.Config.ForEach) + refs, _ = lang.ReferencesInExpr(addrs.ParseRef, n.Config.ForEach) result = append(result, refs...) return result diff --git a/internal/terraform/node_resource_validate.go b/internal/terraform/node_resource_validate.go index 372eb197f1..9afd88eb7c 100644 --- a/internal/terraform/node_resource_validate.go +++ b/internal/terraform/node_resource_validate.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" @@ -17,7 +19,6 @@ import ( "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/provisioners" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // NodeValidatableResource represents a resource that is used for validation @@ -469,7 +470,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag func (n *NodeValidatableResource) evaluateExpr(ctx EvalContext, expr hcl.Expression, wantTy cty.Type, self addrs.Referenceable, keyData instances.RepetitionData) (cty.Value, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - refs, refDiags := lang.ReferencesInExpr(expr) + refs, refDiags := lang.ReferencesInExpr(addrs.ParseRef, expr) diags = diags.Append(refDiags) scope := ctx.EvaluationScope(self, nil, keyData) diff --git a/internal/terraform/test_context.go b/internal/terraform/test_context.go index c592234b46..2311a4cae8 100644 --- a/internal/terraform/test_context.go +++ b/internal/terraform/test_context.go @@ -100,9 +100,9 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes // Now validate all the assertions within this run block. for _, rule := range run.Config.CheckRules { - refs, moreDiags := lang.ReferencesInExpr(rule.Condition) + refs, moreDiags := lang.ReferencesInExpr(addrs.ParseRefFromTestingScope, rule.Condition) run.Diagnostics = run.Diagnostics.Append(moreDiags) - moreRefs, moreDiags := lang.ReferencesInExpr(rule.ErrorMessage) + moreRefs, moreDiags := lang.ReferencesInExpr(addrs.ParseRefFromTestingScope, rule.ErrorMessage) run.Diagnostics = run.Diagnostics.Append(moreDiags) refs = append(refs, moreRefs...) diff --git a/internal/terraform/transform_reference.go b/internal/terraform/transform_reference.go index 7a5f76a25c..2240e281dd 100644 --- a/internal/terraform/transform_reference.go +++ b/internal/terraform/transform_reference.go @@ -9,6 +9,7 @@ import ( "sort" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/dag" @@ -555,6 +556,6 @@ func ReferencesFromConfig(body hcl.Body, schema *configschema.Block) []*addrs.Re if body == nil { return nil } - refs, _ := lang.ReferencesInBlock(body, schema) + refs, _ := lang.ReferencesInBlock(addrs.ParseRef, body, schema) return refs } diff --git a/internal/terraform/validate_selfref.go b/internal/terraform/validate_selfref.go index c2100c2b96..6f38d3e2a4 100644 --- a/internal/terraform/validate_selfref.go +++ b/internal/terraform/validate_selfref.go @@ -45,7 +45,7 @@ func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema * return diags } - refs, _ := lang.ReferencesInBlock(config, schema) + refs, _ := lang.ReferencesInBlock(addrs.ParseRef, config, schema) for _, ref := range refs { for _, addrStr := range addrStrs { if ref.Subject.String() == addrStr {