github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/globalref/reference.go (about)

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