github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/addrs/move_endpoint_module.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/internal/tfdiags" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // anyKeyImpl is the InstanceKey representation indicating a wildcard, which 12 // matches all possible keys. This is only used internally for matching 13 // combinations of address types, where only portions of the path contain key 14 // information. 15 type anyKeyImpl rune 16 17 func (k anyKeyImpl) instanceKeySigil() { 18 } 19 20 func (k anyKeyImpl) String() string { 21 return fmt.Sprintf("[%s]", string(k)) 22 } 23 24 func (k anyKeyImpl) Value() cty.Value { 25 return cty.StringVal(string(k)) 26 } 27 28 // anyKey is the only valid value of anyKeyImpl 29 var anyKey = anyKeyImpl('*') 30 31 // MoveEndpointInModule annotates a MoveEndpoint with the address of the 32 // module where it was declared, which is the form we use for resolving 33 // whether move statements chain from or are nested within other move 34 // statements. 35 type MoveEndpointInModule struct { 36 // SourceRange is the location of the physical endpoint address 37 // in configuration, if this MoveEndpoint was decoded from a 38 // configuration expresson. 39 SourceRange tfdiags.SourceRange 40 41 // The internals are unexported here because, as with MoveEndpoint, 42 // we're somewhat abusing AbsMoveable here to represent an address 43 // relative to the module, rather than as an absolute address. 44 // Conceptually, the following two fields represent a matching pattern 45 // for AbsMoveables where the elements of "module" behave as 46 // ModuleInstanceStep values with a wildcard instance key, because 47 // a moved block in a module affects all instances of that module. 48 // Unlike MoveEndpoint, relSubject in this case can be any of the 49 // address types that implement AbsMoveable. 50 module Module 51 relSubject AbsMoveable 52 } 53 54 func (e *MoveEndpointInModule) ObjectKind() MoveEndpointKind { 55 return absMoveableEndpointKind(e.relSubject) 56 } 57 58 // String produces a string representation of the object matching pattern 59 // represented by the reciever. 60 // 61 // Since there is no direct syntax for representing such an object matching 62 // pattern, this function uses a splat-operator-like representation to stand 63 // in for the wildcard instance keys. 64 func (e *MoveEndpointInModule) String() string { 65 if e == nil { 66 return "" 67 } 68 var buf strings.Builder 69 for _, name := range e.module { 70 buf.WriteString("module.") 71 buf.WriteString(name) 72 buf.WriteString("[*].") 73 } 74 buf.WriteString(e.relSubject.String()) 75 76 // For consistency we'll also use the splat-like wildcard syntax to 77 // represent the final step being either a resource or module call 78 // rather than an instance, so we can more easily distinguish the two 79 // in the string representation. 80 switch e.relSubject.(type) { 81 case AbsModuleCall, AbsResource: 82 buf.WriteString("[*]") 83 } 84 85 return buf.String() 86 } 87 88 // Module returns the address of the module where the receiving address was 89 // declared. 90 func (e *MoveEndpointInModule) Module() Module { 91 return e.module 92 } 93 94 // InModuleInstance returns an AbsMovable address which concatenates the 95 // given module instance address with the receiver's relative object selection 96 // to produce one example of an instance that might be affected by this 97 // move statement. 98 // 99 // The result is meaningful only if the given module instance is an instance 100 // of the same module returned by the method Module. InModuleInstance doesn't 101 // fully verify that (aside from some cheap/easy checks), but it will produce 102 // meaningless garbage if not. 103 func (e *MoveEndpointInModule) InModuleInstance(modInst ModuleInstance) AbsMoveable { 104 if len(modInst) != len(e.module) { 105 // We don't check all of the steps to make sure that their names match, 106 // because it would be expensive to do that repeatedly for every 107 // instance of a module, but if the lengths don't match then that's 108 // _obviously_ wrong. 109 panic("given instance address does not match module address") 110 } 111 switch relSubject := e.relSubject.(type) { 112 case ModuleInstance: 113 ret := make(ModuleInstance, 0, len(modInst)+len(relSubject)) 114 ret = append(ret, modInst...) 115 ret = append(ret, relSubject...) 116 return ret 117 case AbsModuleCall: 118 retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) 119 retModAddr = append(retModAddr, modInst...) 120 retModAddr = append(retModAddr, relSubject.Module...) 121 return relSubject.Call.Absolute(retModAddr) 122 case AbsResourceInstance: 123 retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) 124 retModAddr = append(retModAddr, modInst...) 125 retModAddr = append(retModAddr, relSubject.Module...) 126 return relSubject.Resource.Absolute(retModAddr) 127 case AbsResource: 128 retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) 129 retModAddr = append(retModAddr, modInst...) 130 retModAddr = append(retModAddr, relSubject.Module...) 131 return relSubject.Resource.Absolute(retModAddr) 132 default: 133 panic(fmt.Sprintf("unexpected move subject type %T", relSubject)) 134 } 135 } 136 137 // ModuleCallTraversals returns both the address of the module where the 138 // receiver was declared and any other module calls it traverses through 139 // while selecting a particular object to move. 140 // 141 // This is a rather special-purpose function here mainly to support our 142 // validation rule that a module can only traverse down into child modules 143 // that belong to the same module package. 144 func (e *MoveEndpointInModule) ModuleCallTraversals() (Module, []ModuleCall) { 145 // We're returning []ModuleCall rather than Module here to make it clearer 146 // that this is a relative sequence of calls rather than an absolute 147 // module path. 148 149 var steps []ModuleInstanceStep 150 switch relSubject := e.relSubject.(type) { 151 case ModuleInstance: 152 // We want all of the steps except the last one here, because the 153 // last one is always selecting something declared in the same module 154 // even though our address structure doesn't capture that. 155 steps = []ModuleInstanceStep(relSubject[:len(relSubject)-1]) 156 case AbsModuleCall: 157 steps = []ModuleInstanceStep(relSubject.Module) 158 case AbsResourceInstance: 159 steps = []ModuleInstanceStep(relSubject.Module) 160 case AbsResource: 161 steps = []ModuleInstanceStep(relSubject.Module) 162 default: 163 panic(fmt.Sprintf("unexpected move subject type %T", relSubject)) 164 } 165 166 ret := make([]ModuleCall, len(steps)) 167 for i, step := range steps { 168 ret[i] = ModuleCall{Name: step.Name} 169 } 170 return e.module, ret 171 } 172 173 // synthModuleInstance constructs a module instance out of the module path and 174 // any module portion of the relSubject, substituting Module and Call segments 175 // with ModuleInstanceStep using the anyKey value. 176 // This is only used internally for comparison of these complete paths, but 177 // does not represent how the individual parts are handled elsewhere in the 178 // code. 179 func (e *MoveEndpointInModule) synthModuleInstance() ModuleInstance { 180 var inst ModuleInstance 181 182 for _, mod := range e.module { 183 inst = append(inst, ModuleInstanceStep{Name: mod, InstanceKey: anyKey}) 184 } 185 186 switch sub := e.relSubject.(type) { 187 case ModuleInstance: 188 inst = append(inst, sub...) 189 case AbsModuleCall: 190 inst = append(inst, sub.Module...) 191 inst = append(inst, ModuleInstanceStep{Name: sub.Call.Name, InstanceKey: anyKey}) 192 case AbsResource: 193 inst = append(inst, sub.Module...) 194 case AbsResourceInstance: 195 inst = append(inst, sub.Module...) 196 default: 197 panic(fmt.Sprintf("unhandled relative address type %T", sub)) 198 } 199 200 return inst 201 } 202 203 // SelectsModule returns true if the reciever directly selects either 204 // the given module or a resource nested directly inside that module. 205 // 206 // This is a good function to use to decide which modules in a state 207 // to consider when processing a particular move statement. For a 208 // module move the given module itself is what will move, while a 209 // resource move indicates that we should search each of the resources in 210 // the given module to see if they match. 211 func (e *MoveEndpointInModule) SelectsModule(addr ModuleInstance) bool { 212 synthInst := e.synthModuleInstance() 213 214 // In order to match the given module instance, our combined path must be 215 // equal in length. 216 if len(synthInst) != len(addr) { 217 return false 218 } 219 220 for i, step := range synthInst { 221 switch step.InstanceKey { 222 case anyKey: 223 // we can match any key as long as the name matches 224 if step.Name != addr[i].Name { 225 return false 226 } 227 default: 228 if step != addr[i] { 229 return false 230 } 231 } 232 } 233 return true 234 } 235 236 // moduleInstanceCanMatch indicates that modA can match modB taking into 237 // account steps with an anyKey InstanceKey as wildcards. The comparison of 238 // wildcard steps is done symmetrically, because varying portions of either 239 // instance's path could have been derived from configuration vs evaluation. 240 // The length of modA must be equal or shorter than the length of modB. 241 func moduleInstanceCanMatch(modA, modB ModuleInstance) bool { 242 for i, step := range modA { 243 switch { 244 case step.InstanceKey == anyKey || modB[i].InstanceKey == anyKey: 245 // we can match any key as long as the names match 246 if step.Name != modB[i].Name { 247 return false 248 } 249 default: 250 if step != modB[i] { 251 return false 252 } 253 } 254 } 255 return true 256 } 257 258 // CanChainFrom returns true if the reciever describes an address that could 259 // potentially select an object that the other given address could select. 260 // 261 // In other words, this decides whether the move chaining rule applies, if 262 // the reciever is the "to" from one statement and the other given address 263 // is the "from" of another statement. 264 func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool { 265 eMod := e.synthModuleInstance() 266 oMod := other.synthModuleInstance() 267 268 // if the complete paths are different lengths, these cannot refer to the 269 // same value. 270 if len(eMod) != len(oMod) { 271 return false 272 } 273 if !moduleInstanceCanMatch(oMod, eMod) { 274 return false 275 } 276 277 eSub := e.relSubject 278 oSub := other.relSubject 279 280 switch oSub := oSub.(type) { 281 case AbsModuleCall, ModuleInstance: 282 switch eSub.(type) { 283 case AbsModuleCall, ModuleInstance: 284 // we already know the complete module path including any final 285 // module call name is equal. 286 return true 287 } 288 289 case AbsResource: 290 switch eSub := eSub.(type) { 291 case AbsResource: 292 return eSub.Resource.Equal(oSub.Resource) 293 } 294 295 case AbsResourceInstance: 296 switch eSub := eSub.(type) { 297 case AbsResourceInstance: 298 return eSub.Resource.Equal(oSub.Resource) 299 } 300 } 301 302 return false 303 } 304 305 // NestedWithin returns true if the reciever describes an address that is 306 // contained within one of the objects that the given other address could 307 // select. 308 func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool { 309 eMod := e.synthModuleInstance() 310 oMod := other.synthModuleInstance() 311 312 // In order to be nested within the given endpoint, the module path must be 313 // shorter or equal. 314 if len(oMod) > len(eMod) { 315 return false 316 } 317 318 if !moduleInstanceCanMatch(oMod, eMod) { 319 return false 320 } 321 322 eSub := e.relSubject 323 oSub := other.relSubject 324 325 switch oSub := oSub.(type) { 326 case AbsModuleCall: 327 switch eSub.(type) { 328 case AbsModuleCall: 329 // we know the other endpoint selects our module, but if we are 330 // also a module call our path must be longer to be nested. 331 return len(eMod) > len(oMod) 332 } 333 334 return true 335 336 case ModuleInstance: 337 switch eSub.(type) { 338 case ModuleInstance, AbsModuleCall: 339 // a nested module must have a longer path 340 return len(eMod) > len(oMod) 341 } 342 343 return true 344 345 case AbsResource: 346 if len(eMod) != len(oMod) { 347 // these resources are from different modules 348 return false 349 } 350 351 // A resource can only contain a resource instance. 352 switch eSub := eSub.(type) { 353 case AbsResourceInstance: 354 return eSub.Resource.Resource.Equal(oSub.Resource) 355 } 356 } 357 358 return false 359 } 360 361 // matchModuleInstancePrefix is an internal helper to decide whether the given 362 // module instance address refers to either the module where the move endpoint 363 // was declared or some descendent of that module. 364 // 365 // If so, it will split the given address into two parts: the "prefix" part 366 // which corresponds with the module where the statement was declared, and 367 // the "relative" part which is the remainder that the relSubject of the 368 // statement might match against. 369 // 370 // The second return value is another example of our light abuse of 371 // ModuleInstance to represent _relative_ module references rather than 372 // absolute: it's a module instance address relative to the same return value. 373 // Because the exported idea of ModuleInstance represents only _absolute_ 374 // module instance addresses, we mustn't expose that value through any exported 375 // API. 376 func (e *MoveEndpointInModule) matchModuleInstancePrefix(instAddr ModuleInstance) (ModuleInstance, ModuleInstance, bool) { 377 if len(e.module) > len(instAddr) { 378 return nil, nil, false // to short to possibly match 379 } 380 for i := range e.module { 381 if e.module[i] != instAddr[i].Name { 382 return nil, nil, false 383 } 384 } 385 // If we get here then we have a match, so we'll slice up the input 386 // to produce the prefix and match segments. 387 return instAddr[:len(e.module)], instAddr[len(e.module):], true 388 } 389 390 // MoveDestination considers a an address representing a module 391 // instance in the context of source and destination move endpoints and then, 392 // if the module address matches the from endpoint, returns the corresponding 393 // new module address that the object should move to. 394 // 395 // MoveDestination will return false in its second return value if the receiver 396 // doesn't match fromMatch, indicating that the given move statement doesn't 397 // apply to this object. 398 // 399 // Both of the given endpoints must be from the same move statement and thus 400 // must have matching object types. If not, MoveDestination will panic. 401 func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (ModuleInstance, bool) { 402 // NOTE: This implementation assumes the invariant that fromMatch and 403 // toMatch both belong to the same configuration statement, and thus they 404 // will both have the same address type and the same declaration module. 405 406 // The root module instance is not itself moveable. 407 if m.IsRoot() { 408 return nil, false 409 } 410 411 // The two endpoints must either be module call or module instance 412 // addresses, or else this statement can never match. 413 if fromMatch.ObjectKind() != MoveEndpointModule { 414 return nil, false 415 } 416 417 // The rest of our work will be against the part of the reciever that's 418 // relative to the declaration module. mRel is a weird abuse of 419 // ModuleInstance that represents a relative module address, similar to 420 // what we do for MoveEndpointInModule.relSubject. 421 mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(m) 422 if !match { 423 return nil, false 424 } 425 426 // Our next goal is to split mRel into two parts: the match (if any) and 427 // the suffix. Our result will then replace the match with the replacement 428 // in toMatch while preserving the prefix and suffix. 429 var mSuffix, mNewMatch ModuleInstance 430 431 switch relSubject := fromMatch.relSubject.(type) { 432 case ModuleInstance: 433 if len(relSubject) > len(mRel) { 434 return nil, false // too short to possibly match 435 } 436 for i := range relSubject { 437 if relSubject[i] != mRel[i] { 438 return nil, false // this step doesn't match 439 } 440 } 441 // If we get to here then we've found a match. Since the statement 442 // addresses are already themselves ModuleInstance fragments we can 443 // just slice out the relevant parts. 444 mNewMatch = toMatch.relSubject.(ModuleInstance) 445 mSuffix = mRel[len(relSubject):] 446 case AbsModuleCall: 447 // The module instance part of relSubject must be a prefix of 448 // mRel, and mRel must be at least one step longer to account for 449 // the call step itself. 450 if len(relSubject.Module) > len(mRel)-1 { 451 return nil, false 452 } 453 for i := range relSubject.Module { 454 if relSubject.Module[i] != mRel[i] { 455 return nil, false // this step doesn't match 456 } 457 } 458 // The call name must also match the next step of mRel, after 459 // the relSubject.Module prefix. 460 callStep := mRel[len(relSubject.Module)] 461 if callStep.Name != relSubject.Call.Name { 462 return nil, false 463 } 464 // If we get to here then we've found a match. We need to construct 465 // a new mNewMatch that's an instance of the "new" relSubject with 466 // the same key as our call. 467 mNewMatch = toMatch.relSubject.(AbsModuleCall).Instance(callStep.InstanceKey) 468 mSuffix = mRel[len(relSubject.Module)+1:] 469 default: 470 panic("invalid address type for module-kind move endpoint") 471 } 472 473 ret := make(ModuleInstance, 0, len(mPrefix)+len(mNewMatch)+len(mSuffix)) 474 ret = append(ret, mPrefix...) 475 ret = append(ret, mNewMatch...) 476 ret = append(ret, mSuffix...) 477 return ret, true 478 } 479 480 // MoveDestination considers a an address representing a resource 481 // in the context of source and destination move endpoints and then, 482 // if the resource address matches the from endpoint, returns the corresponding 483 // new resource address that the object should move to. 484 // 485 // MoveDestination will return false in its second return value if the receiver 486 // doesn't match fromMatch, indicating that the given move statement doesn't 487 // apply to this object. 488 // 489 // Both of the given endpoints must be from the same move statement and thus 490 // must have matching object types. If not, MoveDestination will panic. 491 func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResource, bool) { 492 switch fromMatch.ObjectKind() { 493 case MoveEndpointModule: 494 // If we've moving a module then any resource inside that module 495 // moves too. 496 fromMod := r.Module 497 toMod, match := fromMod.MoveDestination(fromMatch, toMatch) 498 if !match { 499 return AbsResource{}, false 500 } 501 return r.Resource.Absolute(toMod), true 502 503 case MoveEndpointResource: 504 fromRelSubject, ok := fromMatch.relSubject.(AbsResource) 505 if !ok { 506 // The only other possible type for a resource move is 507 // AbsResourceInstance, and that can never match an AbsResource. 508 return AbsResource{}, false 509 } 510 511 // fromMatch can only possibly match the reciever if the resource 512 // portions are identical, regardless of the module paths. 513 if fromRelSubject.Resource != r.Resource { 514 return AbsResource{}, false 515 } 516 517 // The module path portion of relSubject must have a prefix that 518 // matches the module where our endpoints were declared. 519 mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module) 520 if !match { 521 return AbsResource{}, false 522 } 523 524 // The remaining steps of the module path must _exactly_ match 525 // the relative module path in the "fromMatch" address. 526 if len(mRel) != len(fromRelSubject.Module) { 527 return AbsResource{}, false // can't match if lengths are different 528 } 529 for i := range mRel { 530 if mRel[i] != fromRelSubject.Module[i] { 531 return AbsResource{}, false // all of the steps must match 532 } 533 } 534 535 // If we got here then we have a match, and so our result is the 536 // module instance where the statement was declared (mPrefix) followed 537 // by the "to" relative address in toMatch. 538 toRelSubject := toMatch.relSubject.(AbsResource) 539 var mNew ModuleInstance 540 if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 { 541 mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module)) 542 mNew = append(mNew, mPrefix...) 543 mNew = append(mNew, toRelSubject.Module...) 544 } 545 ret := toRelSubject.Resource.Absolute(mNew) 546 return ret, true 547 548 default: 549 panic("unexpected object kind") 550 } 551 } 552 553 // MoveDestination considers a an address representing a resource 554 // instance in the context of source and destination move endpoints and then, 555 // if the instance address matches the from endpoint, returns the corresponding 556 // new instance address that the object should move to. 557 // 558 // MoveDestination will return false in its second return value if the receiver 559 // doesn't match fromMatch, indicating that the given move statement doesn't 560 // apply to this object. 561 // 562 // Both of the given endpoints must be from the same move statement and thus 563 // must have matching object types. If not, MoveDestination will panic. 564 func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResourceInstance, bool) { 565 switch fromMatch.ObjectKind() { 566 case MoveEndpointModule: 567 // If we've moving a module then any resource inside that module 568 // moves too. 569 fromMod := r.Module 570 toMod, match := fromMod.MoveDestination(fromMatch, toMatch) 571 if !match { 572 return AbsResourceInstance{}, false 573 } 574 return r.Resource.Absolute(toMod), true 575 576 case MoveEndpointResource: 577 switch fromMatch.relSubject.(type) { 578 case AbsResource: 579 oldResource := r.ContainingResource() 580 newResource, match := oldResource.MoveDestination(fromMatch, toMatch) 581 if !match { 582 return AbsResourceInstance{}, false 583 } 584 return newResource.Instance(r.Resource.Key), true 585 case AbsResourceInstance: 586 fromRelSubject, ok := fromMatch.relSubject.(AbsResourceInstance) 587 if !ok { 588 // The only other possible type for a resource move is 589 // AbsResourceInstance, and that can never match an AbsResource. 590 return AbsResourceInstance{}, false 591 } 592 593 // fromMatch can only possibly match the reciever if the resource 594 // portions are identical, regardless of the module paths. 595 if fromRelSubject.Resource != r.Resource { 596 return AbsResourceInstance{}, false 597 } 598 599 // The module path portion of relSubject must have a prefix that 600 // matches the module where our endpoints were declared. 601 mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module) 602 if !match { 603 return AbsResourceInstance{}, false 604 } 605 606 // The remaining steps of the module path must _exactly_ match 607 // the relative module path in the "fromMatch" address. 608 if len(mRel) != len(fromRelSubject.Module) { 609 return AbsResourceInstance{}, false // can't match if lengths are different 610 } 611 for i := range mRel { 612 if mRel[i] != fromRelSubject.Module[i] { 613 return AbsResourceInstance{}, false // all of the steps must match 614 } 615 } 616 617 // If we got here then we have a match, and so our result is the 618 // module instance where the statement was declared (mPrefix) followed 619 // by the "to" relative address in toMatch. 620 toRelSubject := toMatch.relSubject.(AbsResourceInstance) 621 var mNew ModuleInstance 622 if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 { 623 mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module)) 624 mNew = append(mNew, mPrefix...) 625 mNew = append(mNew, toRelSubject.Module...) 626 } 627 ret := toRelSubject.Resource.Absolute(mNew) 628 return ret, true 629 default: 630 panic("invalid address type for resource-kind move endpoint") 631 } 632 default: 633 panic("unexpected object kind") 634 } 635 }