diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go index 0615615f8..6b32251ff 100644 --- a/internal/addrs/move_endpoint_module.go +++ b/internal/addrs/move_endpoint_module.go @@ -354,8 +354,54 @@ func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInM } return newResource.Instance(r.Resource.Key), true case AbsResourceInstance: - // TODO: Implement - return AbsResourceInstance{}, false + fromRelSubject, ok := fromMatch.relSubject.(AbsResourceInstance) + if !ok { + // The only other possible type for a resource move is + // AbsResourceInstance, and that can never match an AbsResource. + return AbsResourceInstance{}, false + } + + // fromMatch can only possibly match the reciever if the resource + // portions are identical, regardless of the module paths. + if fromRelSubject.Resource != r.Resource { + return AbsResourceInstance{}, false + } + + // The module path portion of relSubject must have a prefix that + // matches the module where our endpoints were declared. + if len(fromMatch.module) > len(r.Module) { + return AbsResourceInstance{}, false // too short to possibly match + } + for i := range fromMatch.module { + if fromMatch.module[i] != r.Module[i].Name { + return AbsResourceInstance{}, false // this step doesn't match + } + } + + // The remaining steps of the module path must _exactly_ match + // the relative module path in the "fromMatch" address. + mPrefix, mRel := r.Module[:len(fromMatch.module)], r.Module[len(fromMatch.module):] + if len(mRel) != len(fromRelSubject.Module) { + return AbsResourceInstance{}, false // can't match if lengths are different + } + for i := range mRel { + if mRel[i] != fromRelSubject.Module[i] { + return AbsResourceInstance{}, false // all of the steps must match + } + } + + // If we got here then we have a match, and so our result is the + // module instance where the statement was declared (mPrefix) followed + // by the "to" relative address in toMatch. + toRelSubject := toMatch.relSubject.(AbsResourceInstance) + var mNew ModuleInstance + if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 { + mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module)) + mNew = append(mNew, mPrefix...) + mNew = append(mNew, toRelSubject.Module...) + } + ret := toRelSubject.Resource.Absolute(mNew) + return ret, true default: panic("invalid address type for resource-kind move endpoint") } diff --git a/internal/addrs/move_endpoint_module_test.go b/internal/addrs/move_endpoint_module_test.go index 3e738bd19..1e52eea23 100644 --- a/internal/addrs/move_endpoint_module_test.go +++ b/internal/addrs/move_endpoint_module_test.go @@ -312,6 +312,121 @@ func TestAbsResourceInstanceMoveDestination(t *testing.T) { WantMatch bool WantResult string }{ + { + ``, + `test_object.beep`, + `test_object.boop`, + `test_object.beep`, + true, + `test_object.boop`, + }, + { + ``, + `test_object.beep`, + `test_object.beep[2]`, + `test_object.beep`, + true, + `test_object.beep[2]`, + }, + { + ``, + `test_object.beep`, + `module.foo.test_object.beep`, + `test_object.beep`, + true, + `module.foo.test_object.beep`, + }, + { + ``, + `test_object.beep[2]`, + `module.foo.test_object.beep["a"]`, + `test_object.beep[2]`, + true, + `module.foo.test_object.beep["a"]`, + }, + { + ``, + `test_object.beep`, + `module.foo[0].test_object.beep`, + `test_object.beep`, + true, + `module.foo[0].test_object.beep`, + }, + { + ``, + `module.foo.test_object.beep`, + `test_object.beep`, + `module.foo.test_object.beep`, + true, + `test_object.beep`, + }, + { + ``, + `module.foo[0].test_object.beep`, + `test_object.beep`, + `module.foo[0].test_object.beep`, + true, + `test_object.beep`, + }, + { + `foo`, + `test_object.beep`, + `test_object.boop`, + `module.foo[0].test_object.beep`, + true, + `module.foo[0].test_object.boop`, + }, + { + `foo`, + `test_object.beep`, + `test_object.beep[1]`, + `module.foo[0].test_object.beep`, + true, + `module.foo[0].test_object.beep[1]`, + }, + { + ``, + `test_object.beep`, + `test_object.boop`, + `test_object.boop`, + false, // the reciever is already the "to" address + ``, + }, + { + ``, + `test_object.beep[1]`, + `test_object.beep[2]`, + `test_object.beep[5]`, + false, // the receiver has a non-matching instance key + ``, + }, + { + `foo`, + `test_object.beep`, + `test_object.boop`, + `test_object.beep`, + false, // the receiver is not inside an instance of module "foo" + ``, + }, + { + `foo.bar`, + `test_object.beep`, + `test_object.boop`, + `test_object.beep`, + false, // the receiver is not inside an instance of module "foo.bar" + ``, + }, + { + ``, + `module.foo[0].test_object.beep`, + `test_object.beep`, + `module.foo[1].test_object.beep`, + false, // receiver is in a different instance of module.foo + ``, + }, + + // Moving a module also moves all of the resources declared within it. + // The following tests all cover variations of that rule. { ``, `module.foo`, @@ -618,7 +733,6 @@ func TestAbsResourceMoveDestination(t *testing.T) { true, `module.foo[0].test_object.beep`, }, - { ``, `module.foo.test_object.beep`, @@ -643,7 +757,6 @@ func TestAbsResourceMoveDestination(t *testing.T) { true, `module.foo[0].test_object.boop`, }, - { ``, `test_object.beep`,