Files
opentf/internal/tofu/validate_selfref.go
James Humphries 19af81d28e Implement resource identity support (#3671)
Signed-off-by: James Humphries <james@james-humphries.co.uk>
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Co-authored-by: Martin Atkins <mart@degeneration.co.uk>
2026-03-31 16:57:21 +01:00

99 lines
3.0 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// validateSelfRef checks to ensure that expressions within a particular
// referenceable block do not reference that same block.
func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema providers.ProviderSchema) 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())
}
var schema *configschema.Block
switch tAddr := addr.(type) {
case addrs.Resource:
sc, _ := providerSchema.SchemaForResourceAddr(tAddr)
if sc == nil {
diags = diags.Append(fmt.Errorf("no schema available for %s to validate for self-references; this is a bug in OpenTofu and should be reported", addr))
return diags
}
schema = sc.Block
case addrs.ResourceInstance:
sc, _ := providerSchema.SchemaForResourceAddr(tAddr.ContainingResource())
if sc == nil {
diags = diags.Append(fmt.Errorf("no schema available for %s to validate for self-references; this is a bug in OpenTofu and should be reported", addr))
return diags
}
schema = sc.Block
}
if schema == nil {
diags = diags.Append(fmt.Errorf("no schema available for %s to validate for self-references; this is a bug in OpenTofu and should be reported", addr))
return diags
}
refs, _ := lang.ReferencesInBlock(addrs.ParseRef, config, schema)
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.
func filterSelfRefs(self addrs.Resource, refs []*addrs.Reference) []*addrs.Reference {
for i := 0; i < len(refs); i++ {
ref := refs[i]
var subject addrs.Resource
switch subj := ref.Subject.(type) {
case addrs.Resource:
subject = subj
case addrs.ResourceInstance:
subject = subj.ContainingResource()
default:
continue
}
if self.Equal(subject) {
tail := len(refs) - 1
refs[i], refs[tail] = refs[tail], refs[i]
refs = refs[:tail]
}
}
return refs
}