github.com/pulumi/terraform@v1.4.0/pkg/addrs/move_endpoint.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/pulumi/terraform/pkg/tfdiags" 8 ) 9 10 // MoveEndpoint is to AbsMoveable and ConfigMoveable what Target is to 11 // Targetable: a wrapping struct that captures the result of decoding an HCL 12 // traversal representing a relative path from the current module to 13 // a moveable object. 14 // 15 // Its name reflects that its primary purpose is for the "from" and "to" 16 // addresses in a "moved" statement in the configuration, but it's also 17 // valid to use MoveEndpoint for other similar mechanisms that give 18 // Terraform hints about historical configuration changes that might 19 // prompt creating a different plan than Terraform would by default. 20 // 21 // To obtain a full address from a MoveEndpoint you must use 22 // either the package function UnifyMoveEndpoints (to get an AbsMoveable) or 23 // the method ConfigMoveable (to get a ConfigMoveable). 24 type MoveEndpoint struct { 25 // SourceRange is the location of the physical endpoint address 26 // in configuration, if this MoveEndpoint was decoded from a 27 // configuration expresson. 28 SourceRange tfdiags.SourceRange 29 30 // Internally we (ab)use AbsMoveable as the representation of our 31 // relative address, even though everywhere else in Terraform 32 // AbsMoveable always represents a fully-absolute address. 33 // In practice, due to the implementation of ParseMoveEndpoint, 34 // this is always either a ModuleInstance or an AbsResourceInstance, 35 // and we only consider the possibility of interpreting it as 36 // a AbsModuleCall or an AbsResource in UnifyMoveEndpoints. 37 // This is intentionally unexported to encapsulate this unusual 38 // meaning of AbsMoveable. 39 relSubject AbsMoveable 40 } 41 42 func (e *MoveEndpoint) ObjectKind() MoveEndpointKind { 43 return absMoveableEndpointKind(e.relSubject) 44 } 45 46 func (e *MoveEndpoint) String() string { 47 // Our internal pseudo-AbsMoveable representing the relative 48 // address (either ModuleInstance or AbsResourceInstance) is 49 // a good enough proxy for the relative move endpoint address 50 // serialization. 51 return e.relSubject.String() 52 } 53 54 func (e *MoveEndpoint) Equal(other *MoveEndpoint) bool { 55 switch { 56 case (e == nil) != (other == nil): 57 return false 58 case e == nil: 59 return true 60 default: 61 // Since we only use ModuleInstance and AbsResourceInstance in our 62 // string representation, we have no ambiguity between address types 63 // and can safely just compare the string representations to 64 // compare the relSubject values. 65 return e.String() == other.String() && e.SourceRange == other.SourceRange 66 } 67 } 68 69 // MightUnifyWith returns true if it is possible that a later call to 70 // UnifyMoveEndpoints might succeed if given the reciever and the other 71 // given endpoint. 72 // 73 // This is intended for early static validation of obviously-wrong situations, 74 // although there are still various semantic errors that this cannot catch. 75 func (e *MoveEndpoint) MightUnifyWith(other *MoveEndpoint) bool { 76 // For our purposes here we'll just do a unify without a base module 77 // address, because the rules for whether unify can succeed depend 78 // only on the relative part of the addresses, not on which module 79 // they were declared in. 80 from, to := UnifyMoveEndpoints(RootModule, e, other) 81 return from != nil && to != nil 82 } 83 84 // ConfigMovable transforms the reciever into a ConfigMovable by resolving it 85 // relative to the given base module, which should be the module where 86 // the MoveEndpoint expression was found. 87 // 88 // The result is useful for finding the target object in the configuration, 89 // but it's not sufficient for fully interpreting a move statement because 90 // it lacks the specific module and resource instance keys. 91 func (e *MoveEndpoint) ConfigMoveable(baseModule Module) ConfigMoveable { 92 addr := e.relSubject 93 switch addr := addr.(type) { 94 case ModuleInstance: 95 ret := make(Module, 0, len(baseModule)+len(addr)) 96 ret = append(ret, baseModule...) 97 ret = append(ret, addr.Module()...) 98 return ret 99 case AbsResourceInstance: 100 moduleAddr := make(Module, 0, len(baseModule)+len(addr.Module)) 101 moduleAddr = append(moduleAddr, baseModule...) 102 moduleAddr = append(moduleAddr, addr.Module.Module()...) 103 return ConfigResource{ 104 Module: moduleAddr, 105 Resource: addr.Resource.Resource, 106 } 107 default: 108 // The above should be exhaustive for all of the types 109 // that ParseMoveEndpoint produces as our intermediate 110 // address representation. 111 panic(fmt.Sprintf("unsupported address type %T", addr)) 112 } 113 114 } 115 116 // ParseMoveEndpoint attempts to interpret the given traversal as a 117 // "move endpoint" address, which is a relative path from the module containing 118 // the traversal to a movable object in either the same module or in some 119 // child module. 120 // 121 // This deals only with the syntactic element of a move endpoint expression 122 // in configuration. Before the result will be useful you'll need to combine 123 // it with the address of the module where it was declared in order to get 124 // an absolute address relative to the root module. 125 func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnostics) { 126 path, remain, diags := parseModuleInstancePrefix(traversal) 127 if diags.HasErrors() { 128 return nil, diags 129 } 130 131 rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange()) 132 133 if len(remain) == 0 { 134 return &MoveEndpoint{ 135 relSubject: path, 136 SourceRange: rng, 137 }, diags 138 } 139 140 riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain) 141 diags = diags.Append(moreDiags) 142 if diags.HasErrors() { 143 return nil, diags 144 } 145 146 return &MoveEndpoint{ 147 relSubject: riAddr, 148 SourceRange: rng, 149 }, diags 150 } 151 152 // UnifyMoveEndpoints takes a pair of MoveEndpoint objects representing the 153 // "from" and "to" addresses in a moved block, and returns a pair of 154 // MoveEndpointInModule addresses guaranteed to be of the same dynamic type 155 // that represent what the two MoveEndpoint addresses refer to. 156 // 157 // moduleAddr must be the address of the module where the move was declared. 158 // 159 // This function deals both with the conversion from relative to absolute 160 // addresses and with resolving the ambiguity between no-key instance 161 // addresses and whole-object addresses, returning the least specific 162 // address type possible. 163 // 164 // Not all combinations of addresses are unifyable: the two addresses must 165 // either both include resources or both just be modules. If the two 166 // given addresses are incompatible then UnifyMoveEndpoints returns (nil, nil), 167 // in which case the caller should typically report an error to the user 168 // stating the unification constraints. 169 func UnifyMoveEndpoints(moduleAddr Module, relFrom, relTo *MoveEndpoint) (modFrom, modTo *MoveEndpointInModule) { 170 171 // First we'll make a decision about which address type we're 172 // ultimately trying to unify to. For our internal purposes 173 // here we're going to borrow TargetableAddrType just as a 174 // convenient way to talk about our address types, even though 175 // targetable address types are not 100% aligned with moveable 176 // address types. 177 fromType := relFrom.internalAddrType() 178 toType := relTo.internalAddrType() 179 var wantType TargetableAddrType 180 181 // Our goal here is to choose the whole-resource or whole-module-call 182 // addresses if both agree on it, but to use specific instance addresses 183 // otherwise. This is a somewhat-arbitrary way to resolve syntactic 184 // ambiguity between the two situations which allows both for renaming 185 // whole resources and for switching from a single-instance object to 186 // a multi-instance object. 187 switch { 188 case fromType == AbsResourceInstanceAddrType || toType == AbsResourceInstanceAddrType: 189 wantType = AbsResourceInstanceAddrType 190 case fromType == AbsResourceAddrType || toType == AbsResourceAddrType: 191 wantType = AbsResourceAddrType 192 case fromType == ModuleInstanceAddrType || toType == ModuleInstanceAddrType: 193 wantType = ModuleInstanceAddrType 194 case fromType == ModuleAddrType || toType == ModuleAddrType: 195 // NOTE: We're fudging a little here and using 196 // ModuleAddrType to represent AbsModuleCall rather 197 // than Module. 198 wantType = ModuleAddrType 199 default: 200 panic("unhandled move address types") 201 } 202 203 modFrom = relFrom.prepareMoveEndpointInModule(moduleAddr, wantType) 204 modTo = relTo.prepareMoveEndpointInModule(moduleAddr, wantType) 205 if modFrom == nil || modTo == nil { 206 // if either of them failed then they both failed, to make the 207 // caller's life a little easier. 208 return nil, nil 209 } 210 return modFrom, modTo 211 } 212 213 func (e *MoveEndpoint) prepareMoveEndpointInModule(moduleAddr Module, wantType TargetableAddrType) *MoveEndpointInModule { 214 // relAddr can only be either AbsResourceInstance or ModuleInstance, the 215 // internal intermediate representation produced by ParseMoveEndpoint. 216 relAddr := e.relSubject 217 218 switch relAddr := relAddr.(type) { 219 case ModuleInstance: 220 switch wantType { 221 case ModuleInstanceAddrType: 222 // Since our internal representation is already a module instance, 223 // we can just rewrap this one. 224 return &MoveEndpointInModule{ 225 SourceRange: e.SourceRange, 226 module: moduleAddr, 227 relSubject: relAddr, 228 } 229 case ModuleAddrType: 230 // NOTE: We're fudging a little here and using 231 // ModuleAddrType to represent AbsModuleCall rather 232 // than Module. 233 callerAddr, callAddr := relAddr.Call() 234 absCallAddr := AbsModuleCall{ 235 Module: callerAddr, 236 Call: callAddr, 237 } 238 return &MoveEndpointInModule{ 239 SourceRange: e.SourceRange, 240 module: moduleAddr, 241 relSubject: absCallAddr, 242 } 243 default: 244 return nil // can't make any other types from a ModuleInstance 245 } 246 case AbsResourceInstance: 247 switch wantType { 248 case AbsResourceInstanceAddrType: 249 return &MoveEndpointInModule{ 250 SourceRange: e.SourceRange, 251 module: moduleAddr, 252 relSubject: relAddr, 253 } 254 case AbsResourceAddrType: 255 return &MoveEndpointInModule{ 256 SourceRange: e.SourceRange, 257 module: moduleAddr, 258 relSubject: relAddr.ContainingResource(), 259 } 260 default: 261 return nil // can't make any other types from an AbsResourceInstance 262 } 263 default: 264 panic(fmt.Sprintf("unhandled address type %T", relAddr)) 265 } 266 } 267 268 // internalAddrType helps facilitate our slight abuse of TargetableAddrType 269 // as a way to talk about our different possible result address types in 270 // UnifyMoveEndpoints. 271 // 272 // It's not really correct to use TargetableAddrType in this way, because 273 // it's for Targetable rather than for AbsMoveable, but as long as the two 274 // remain aligned enough it saves introducing yet another enumeration with 275 // similar members that would be for internal use only anyway. 276 func (e *MoveEndpoint) internalAddrType() TargetableAddrType { 277 switch addr := e.relSubject.(type) { 278 case ModuleInstance: 279 if !addr.IsRoot() && addr[len(addr)-1].InstanceKey == NoKey { 280 // NOTE: We're fudging a little here and using 281 // ModuleAddrType to represent AbsModuleCall rather 282 // than Module. 283 return ModuleAddrType 284 } 285 return ModuleInstanceAddrType 286 case AbsResourceInstance: 287 if addr.Resource.Key == NoKey { 288 return AbsResourceAddrType 289 } 290 return AbsResourceInstanceAddrType 291 default: 292 // The above should cover all of the address types produced 293 // by ParseMoveEndpoint. 294 panic(fmt.Sprintf("unsupported address type %T", addr)) 295 } 296 }