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 }