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.
This commit is contained in:
Alisdair McDiarmid 2022-02-02 16:08:07 -05:00
parent 0634c9437a
commit 7ded73f266
3 changed files with 96 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -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."
}
}
}