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  }