From 7ded73f266f05ea118c3a0cf74a4ea65544446c1 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Wed, 2 Feb 2022 16:08:07 -0500 Subject: [PATCH] configs: Validate pre/postcondition self-refs Preconditions and postconditions for resources and data sources may not refer to the address of the containing resource or data source. This commit adds a parse-time validation for this rule. --- internal/configs/checks.go | 33 +++++++++++ internal/configs/resource.go | 8 +++ .../precondition-postcondition-selfref.tf | 55 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 internal/configs/testdata/error-files/precondition-postcondition-selfref.tf diff --git a/internal/configs/checks.go b/internal/configs/checks.go index 8339640cc..0980ec16c 100644 --- a/internal/configs/checks.go +++ b/internal/configs/checks.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/lang" ) // CheckRule represents a configuration-defined validation rule, precondition, @@ -32,6 +34,37 @@ type CheckRule struct { DeclRange hcl.Range } +// validateSelfReferences looks for references in the check rule matching the +// specified resource address, returning error diagnostics if such a reference +// is found. +func (cr *CheckRule) validateSelfReferences(checkType string, addr addrs.Resource) hcl.Diagnostics { + var diags hcl.Diagnostics + refs, _ := lang.References(cr.Condition.Variables()) + for _, ref := range refs { + var refAddr addrs.Resource + + switch rs := ref.Subject.(type) { + case addrs.Resource: + refAddr = rs + case addrs.ResourceInstance: + refAddr = rs.Resource + default: + continue + } + + if refAddr.Equal(addr) { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Invalid reference in %s", checkType), + Detail: fmt.Sprintf("Configuration for %s may not refer to itself.", addr.String()), + Subject: cr.Condition.Range().Ptr(), + }) + break + } + } + return diags +} + // decodeCheckRuleBlock decodes the contents of the given block as a check rule. // // Unlike most of our "decode..." functions, this one can be applied to blocks diff --git a/internal/configs/resource.go b/internal/configs/resource.go index 0928f701e..3d9e72533 100644 --- a/internal/configs/resource.go +++ b/internal/configs/resource.go @@ -245,6 +245,10 @@ func decodeResourceBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagno case "precondition", "postcondition": cr, moreDiags := decodeCheckRuleBlock(block, override) diags = append(diags, moreDiags...) + + moreDiags = cr.validateSelfReferences(block.Type, r.Addr()) + diags = append(diags, moreDiags...) + switch block.Type { case "precondition": r.Preconditions = append(r.Preconditions, cr) @@ -445,6 +449,10 @@ func decodeDataBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagnostic case "precondition", "postcondition": cr, moreDiags := decodeCheckRuleBlock(block, override) diags = append(diags, moreDiags...) + + moreDiags = cr.validateSelfReferences(block.Type, r.Addr()) + diags = append(diags, moreDiags...) + switch block.Type { case "precondition": r.Preconditions = append(r.Preconditions, cr) diff --git a/internal/configs/testdata/error-files/precondition-postcondition-selfref.tf b/internal/configs/testdata/error-files/precondition-postcondition-selfref.tf new file mode 100644 index 000000000..5f295c1c9 --- /dev/null +++ b/internal/configs/testdata/error-files/precondition-postcondition-selfref.tf @@ -0,0 +1,55 @@ +resource "test" "test" { + lifecycle { + precondition { + condition = test.test.foo # ERROR: Invalid reference in precondition + error_message = "Cannot refer to self." + } + postcondition { + condition = test.test.foo # ERROR: Invalid reference in postcondition + error_message = "Cannot refer to self." + } + } +} + +data "test" "test" { + lifecycle { + precondition { + condition = data.test.test.foo # ERROR: Invalid reference in precondition + error_message = "Cannot refer to self." + } + postcondition { + condition = data.test.test.foo # ERROR: Invalid reference in postcondition + error_message = "Cannot refer to self." + } + } +} + +resource "test" "test_counted" { + count = 1 + + lifecycle { + precondition { + condition = test.test_counted[0].foo # ERROR: Invalid reference in precondition + error_message = "Cannot refer to self." + } + postcondition { + condition = test.test_counted[0].foo # ERROR: Invalid reference in postcondition + error_message = "Cannot refer to self." + } + } +} + +data "test" "test_counted" { + count = 1 + + lifecycle { + precondition { + condition = data.test.test_counted[0].foo # ERROR: Invalid reference in precondition + error_message = "Cannot refer to self." + } + postcondition { + condition = data.test.test_counted[0].foo # ERROR: Invalid reference in postcondition + error_message = "Cannot refer to self." + } + } +}