From 5294d1f35d148bfd249d29468dfaa5734b2d1e9f Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Tue, 23 Apr 2024 09:06:03 +0200 Subject: [PATCH] Validate self-references within resource count and foreach arguments (#35047) * Validate self-references within resource count and foreach arguments * add nil check --- internal/terraform/node_resource_plan.go | 14 +++++++++- internal/terraform/validate_selfref.go | 31 +++++++++++++++++++++ internal/terraform/validate_selfref_test.go | 16 ++++++++++- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/internal/terraform/node_resource_plan.go b/internal/terraform/node_resource_plan.go index 875e4b72297a..5bbe21077066 100644 --- a/internal/terraform/node_resource_plan.go +++ b/internal/terraform/node_resource_plan.go @@ -96,13 +96,25 @@ func (n *nodeExpandPlannableResource) ModifyCreateBeforeDestroy(v bool) error { } func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + // First, make sure the count and the foreach don't refer to the same + // resource. The config maybe nil if we are generating configuration, or + // deleting a resource. + if n.Config != nil { + diags = diags.Append(validateSelfRefInExpr(n.Addr.Resource, n.Config.Count)) + diags = diags.Append(validateSelfRefInExpr(n.Addr.Resource, n.Config.ForEach)) + if diags.HasErrors() { + return nil, diags + } + } + // Expand the current module. expander := ctx.InstanceExpander() moduleInstances := expander.ExpandModule(n.Addr.Module) // Expand the imports for this resource. // TODO: Add support for unknown instances in import blocks. - var diags tfdiags.Diagnostics imports, importDiags := n.expandResourceImports(ctx) diags = diags.Append(importDiags) diff --git a/internal/terraform/validate_selfref.go b/internal/terraform/validate_selfref.go index bd55d58e865f..679038a24fae 100644 --- a/internal/terraform/validate_selfref.go +++ b/internal/terraform/validate_selfref.go @@ -58,6 +58,37 @@ func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema p return diags } +// validateSelfRefInExpr checks to ensure that a specific expression does not +// reference the same block that it is contained within. +func validateSelfRefInExpr(addr addrs.Referenceable, expr hcl.Expression) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + addrStrs := make([]string, 0, 1) + addrStrs = append(addrStrs, addr.String()) + switch tAddr := addr.(type) { + case addrs.ResourceInstance: + // A resource instance may not refer to its containing resource either. + addrStrs = append(addrStrs, tAddr.ContainingResource().String()) + } + + refs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, expr) + for _, ref := range refs { + + for _, addrStr := range addrStrs { + if ref.Subject.String() == addrStr { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Self-referential block", + Detail: fmt.Sprintf("Configuration for %s may not refer to itself.", addrStr), + Subject: ref.SourceRange.ToHCL().Ptr(), + }) + } + } + } + + return diags +} + // Legacy provisioner configurations may refer to single instances using the // resource address. We need to filter these out from the reported references // to prevent cycles. diff --git a/internal/terraform/validate_selfref_test.go b/internal/terraform/validate_selfref_test.go index ad6534fc8642..8228871a1cd8 100644 --- a/internal/terraform/validate_selfref_test.go +++ b/internal/terraform/validate_selfref_test.go @@ -12,8 +12,9 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcltest" - "github.com/hashicorp/terraform/internal/addrs" "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/addrs" ) func TestValidateSelfRef(t *testing.T) { @@ -98,6 +99,8 @@ func TestValidateSelfRef(t *testing.T) { }, } + // First test the expression within the context of the configuration + // body. diags := validateSelfRef(test.Addr, body, ps) if diags.HasErrors() != test.Err { if test.Err { @@ -106,6 +109,17 @@ func TestValidateSelfRef(t *testing.T) { t.Errorf("unexpected error\n\n%s", diags.Err()) } } + + // We can use the same expressions to test the expression + // validation. + diags = validateSelfRefInExpr(test.Addr, test.Expr) + if diags.HasErrors() != test.Err { + if test.Err { + t.Errorf("unexpected success; want error") + } else { + t.Errorf("unexpected error\n\n%s", diags.Err()) + } + } }) } }