github.com/opentofu/opentofu@v1.7.1/internal/tofu/validate_selfref.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "fmt" 10 11 "github.com/hashicorp/hcl/v2" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/configs/configschema" 15 "github.com/opentofu/opentofu/internal/lang" 16 "github.com/opentofu/opentofu/internal/providers" 17 "github.com/opentofu/opentofu/internal/tfdiags" 18 ) 19 20 // validateSelfRef checks to ensure that expressions within a particular 21 // referencable block do not reference that same block. 22 func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema providers.ProviderSchema) tfdiags.Diagnostics { 23 var diags tfdiags.Diagnostics 24 25 addrStrs := make([]string, 0, 1) 26 addrStrs = append(addrStrs, addr.String()) 27 switch tAddr := addr.(type) { 28 case addrs.ResourceInstance: 29 // A resource instance may not refer to its containing resource either. 30 addrStrs = append(addrStrs, tAddr.ContainingResource().String()) 31 } 32 33 var schema *configschema.Block 34 switch tAddr := addr.(type) { 35 case addrs.Resource: 36 schema, _ = providerSchema.SchemaForResourceAddr(tAddr) 37 case addrs.ResourceInstance: 38 schema, _ = providerSchema.SchemaForResourceAddr(tAddr.ContainingResource()) 39 } 40 41 if schema == nil { 42 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)) 43 return diags 44 } 45 46 refs, _ := lang.ReferencesInBlock(addrs.ParseRef, config, schema) 47 for _, ref := range refs { 48 for _, addrStr := range addrStrs { 49 if ref.Subject.String() == addrStr { 50 diags = diags.Append(&hcl.Diagnostic{ 51 Severity: hcl.DiagError, 52 Summary: "Self-referential block", 53 Detail: fmt.Sprintf("Configuration for %s may not refer to itself.", addrStr), 54 Subject: ref.SourceRange.ToHCL().Ptr(), 55 }) 56 } 57 } 58 } 59 60 return diags 61 } 62 63 // Legacy provisioner configurations may refer to single instances using the 64 // resource address. We need to filter these out from the reported references 65 // to prevent cycles. 66 func filterSelfRefs(self addrs.Resource, refs []*addrs.Reference) []*addrs.Reference { 67 for i := 0; i < len(refs); i++ { 68 ref := refs[i] 69 70 var subject addrs.Resource 71 switch subj := ref.Subject.(type) { 72 case addrs.Resource: 73 subject = subj 74 case addrs.ResourceInstance: 75 subject = subj.ContainingResource() 76 default: 77 continue 78 } 79 80 if self.Equal(subject) { 81 tail := len(refs) - 1 82 83 refs[i], refs[tail] = refs[tail], refs[i] 84 refs = refs[:tail] 85 } 86 } 87 return refs 88 }