github.com/opentofu/opentofu@v1.7.1/internal/lang/globalref/reference.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 globalref
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/opentofu/opentofu/internal/addrs"
    13  	"github.com/opentofu/opentofu/internal/tfdiags"
    14  	"github.com/zclconf/go-cty/cty"
    15  )
    16  
    17  // Reference combines an addrs.Reference with the address of the module
    18  // instance or resource instance where it was found.
    19  //
    20  // Because of the design of the OpenTofu language, our main model of
    21  // references only captures the module-local part of the reference and assumes
    22  // that it's always clear from context which module a reference belongs to.
    23  // That's not true for globalref because our whole purpose is to work across
    24  // module boundaries, and so this package in particular has its own
    25  // representation of references.
    26  type Reference struct {
    27  	// ContainerAddr is always either addrs.ModuleInstance or
    28  	// addrs.AbsResourceInstance. The latter is required if LocalRef's
    29  	// subject is either an addrs.CountAddr or addrs.ForEachAddr, so
    30  	// we can know which resource's repetition expression it's
    31  	// referring to.
    32  	ContainerAddr addrs.Targetable
    33  
    34  	// LocalRef is a reference that would be resolved in the context
    35  	// of the module instance or resource instance given in ContainerAddr.
    36  	LocalRef *addrs.Reference
    37  }
    38  
    39  func absoluteRef(containerAddr addrs.Targetable, localRef *addrs.Reference) Reference {
    40  	ret := Reference{
    41  		ContainerAddr: containerAddr,
    42  		LocalRef:      localRef,
    43  	}
    44  	// For simplicity's sake, we always reduce the ContainerAddr to be
    45  	// just the module address unless it's a count.index, each.key, or
    46  	// each.value reference, because for anything else it's immaterial
    47  	// which resource it belongs to.
    48  	switch localRef.Subject.(type) {
    49  	case addrs.CountAttr, addrs.ForEachAttr:
    50  		// nothing to do
    51  	default:
    52  		ret.ContainerAddr = ret.ModuleAddr()
    53  	}
    54  	return ret
    55  }
    56  
    57  func absoluteRefs(containerAddr addrs.Targetable, refs []*addrs.Reference) []Reference {
    58  	if len(refs) == 0 {
    59  		return nil
    60  	}
    61  
    62  	ret := make([]Reference, len(refs))
    63  	for i, ref := range refs {
    64  		ret[i] = absoluteRef(containerAddr, ref)
    65  	}
    66  	return ret
    67  }
    68  
    69  // ModuleAddr returns the address of the module where the reference would
    70  // be resolved.
    71  //
    72  // This is either ContainerAddr directly if it's already just a module
    73  // instance, or the module instance part of it if it's a resource instance.
    74  func (r Reference) ModuleAddr() addrs.ModuleInstance {
    75  	switch addr := r.ContainerAddr.(type) {
    76  	case addrs.ModuleInstance:
    77  		return addr
    78  	case addrs.AbsResourceInstance:
    79  		return addr.Module
    80  	default:
    81  		// NOTE: We're intentionally using only a subset of possible
    82  		// addrs.Targetable implementations here, so anything else
    83  		// is invalid.
    84  		panic(fmt.Sprintf("reference has invalid container address type %T", addr))
    85  	}
    86  }
    87  
    88  // ResourceInstance returns the address of the resource where the reference
    89  // would be resolved, if there is one.
    90  //
    91  // Because not all references belong to resources, the extra boolean return
    92  // value indicates whether the returned address is valid.
    93  func (r Reference) ResourceInstance() (addrs.AbsResourceInstance, bool) {
    94  	switch container := r.ContainerAddr.(type) {
    95  	case addrs.ModuleInstance:
    96  		moduleInstance := container
    97  
    98  		switch ref := r.LocalRef.Subject.(type) {
    99  		case addrs.Resource:
   100  			return ref.Instance(addrs.NoKey).Absolute(moduleInstance), true
   101  		case addrs.ResourceInstance:
   102  			return ref.Absolute(moduleInstance), true
   103  		}
   104  
   105  		return addrs.AbsResourceInstance{}, false
   106  
   107  	case addrs.AbsResourceInstance:
   108  		return container, true
   109  	default:
   110  		// NOTE: We're intentionally using only a subset of possible
   111  		// addrs.Targetable implementations here, so anything else
   112  		// is invalid.
   113  		panic(fmt.Sprintf("reference has invalid container address type %T", container))
   114  	}
   115  }
   116  
   117  // DebugString returns an internal (but still somewhat OpenTofu-language-like)
   118  // compact string representation of the reciever, which isn't an address that
   119  // any of our usual address parsers could accept but still captures the
   120  // essence of what the reference represents.
   121  //
   122  // The DebugString result is not suitable for end-user-oriented messages.
   123  //
   124  // DebugString is also not suitable for use as a unique key for a reference,
   125  // because it's ambiguous (between a no-key resource instance and a resource)
   126  // and because it discards the source location information in the LocalRef.
   127  func (r Reference) DebugString() string {
   128  	// As the doc comment insinuates, we don't have any real syntax for
   129  	// "absolute references": references are always local, and targets are
   130  	// always absolute but only include modules and resources.
   131  	return r.ContainerAddr.String() + "::" + r.LocalRef.DisplayString()
   132  }
   133  
   134  // ResourceAttr converts the Reference value to a more specific ResourceAttr
   135  // value.
   136  //
   137  // Because not all references belong to resources, the extra boolean return
   138  // value indicates whether the returned address is valid.
   139  func (r Reference) ResourceAttr() (ResourceAttr, bool) {
   140  	res, ok := r.ResourceInstance()
   141  	if !ok {
   142  		return ResourceAttr{}, ok
   143  	}
   144  
   145  	traversal := r.LocalRef.Remaining
   146  
   147  	path := make(cty.Path, len(traversal))
   148  	for si, step := range traversal {
   149  		switch ts := step.(type) {
   150  		case hcl.TraverseRoot:
   151  			path[si] = cty.GetAttrStep{
   152  				Name: ts.Name,
   153  			}
   154  		case hcl.TraverseAttr:
   155  			path[si] = cty.GetAttrStep{
   156  				Name: ts.Name,
   157  			}
   158  		case hcl.TraverseIndex:
   159  			path[si] = cty.IndexStep{
   160  				Key: ts.Key,
   161  			}
   162  		default:
   163  			panic(fmt.Sprintf("unsupported traversal step %#v", step))
   164  		}
   165  	}
   166  
   167  	return ResourceAttr{
   168  		Resource: res,
   169  		Attr:     path,
   170  	}, true
   171  }
   172  
   173  // addrKey returns the referenceAddrKey value for the item that
   174  // this reference refers to, discarding any source location information.
   175  //
   176  // See the referenceAddrKey doc comment for more information on what this
   177  // is suitable for.
   178  func (r Reference) addrKey() referenceAddrKey {
   179  	// This is a pretty arbitrary bunch of stuff. We include the type here
   180  	// just to differentiate between no-key resource instances and resources.
   181  	return referenceAddrKey(fmt.Sprintf("%s(%T)%s", r.ContainerAddr.String(), r.LocalRef.Subject, r.LocalRef.DisplayString()))
   182  }
   183  
   184  // referenceAddrKey is a special string type which conventionally contains
   185  // a unique string representation of the object that a reference refers to,
   186  // although not of the reference itself because it ignores the information
   187  // that would differentiate two different references to the same object.
   188  //
   189  // The actual content of a referenceAddrKey is arbitrary, for internal use
   190  // only. and subject to change in future. We use a named type here only to
   191  // make it easier to see when we're intentionally using strings to uniquely
   192  // identify absolute reference addresses.
   193  type referenceAddrKey string
   194  
   195  // ResourceAttr represents a global resource and attribute reference.
   196  // This is a more specific form of the Reference type since it can only refer
   197  // to a specific AbsResource and one of its attributes.
   198  type ResourceAttr struct {
   199  	Resource addrs.AbsResourceInstance
   200  	Attr     cty.Path
   201  }
   202  
   203  func (r ResourceAttr) DebugString() string {
   204  	return r.Resource.String() + tfdiags.FormatCtyPath(r.Attr)
   205  }