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