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 }