Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate self-references within resource count and foreach arguments #35047

Merged
merged 2 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion internal/terraform/node_resource_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
31 changes: 31 additions & 0 deletions internal/terraform/validate_selfref.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 15 additions & 1 deletion internal/terraform/validate_selfref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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())
}
}
})
}
}
Loading