addrs: Module move support for AbsResource and AbsResourceInstance
This is a subset of the MoveDestination behavior for AbsResource and AbsResourceInstance which deals with source and destination addresses that refer to module calls or module instances. They both work by delegating to ModuleInstance.MoveDestination and then applying the same resource or resource instance address to the newly-chosen module instance address, thus ensuring that when we move a module we also move all of the resources inside that module in the same way. This doesn't yet include support for moving between specific resource or resource instance addresses; that'll follow later. This commit should have enough logic to support moving between module names and module instance keys, including any module calls or resources nested within.
This commit is contained in:
parent
4d733b4d2d
commit
994ee23c06
|
@ -255,7 +255,24 @@ func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule
|
|||
// Both of the given endpoints must be from the same move statement and thus
|
||||
// must have matching object types. If not, MoveDestination will panic.
|
||||
func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResource, bool) {
|
||||
return AbsResource{}, false
|
||||
switch fromMatch.ObjectKind() {
|
||||
case MoveEndpointModule:
|
||||
// If we've moving a module then any resource inside that module
|
||||
// moves too.
|
||||
fromMod := r.Module
|
||||
toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
|
||||
if !match {
|
||||
return AbsResource{}, false
|
||||
}
|
||||
return r.Resource.Absolute(toMod), true
|
||||
|
||||
case MoveEndpointResource:
|
||||
// TODO: Implement
|
||||
return AbsResource{}, false
|
||||
|
||||
default:
|
||||
panic("unexpected object kind")
|
||||
}
|
||||
}
|
||||
|
||||
// MoveDestination considers a an address representing a resource
|
||||
|
@ -270,5 +287,33 @@ func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (
|
|||
// Both of the given endpoints must be from the same move statement and thus
|
||||
// must have matching object types. If not, MoveDestination will panic.
|
||||
func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResourceInstance, bool) {
|
||||
return AbsResourceInstance{}, false
|
||||
switch fromMatch.ObjectKind() {
|
||||
case MoveEndpointModule:
|
||||
// If we've moving a module then any resource inside that module
|
||||
// moves too.
|
||||
fromMod := r.Module
|
||||
toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
|
||||
if !match {
|
||||
return AbsResourceInstance{}, false
|
||||
}
|
||||
return r.Resource.Absolute(toMod), true
|
||||
|
||||
case MoveEndpointResource:
|
||||
switch fromMatch.relSubject.(type) {
|
||||
case AbsResource:
|
||||
oldResource := r.ContainingResource()
|
||||
newResource, match := oldResource.MoveDestination(fromMatch, toMatch)
|
||||
if !match {
|
||||
return AbsResourceInstance{}, false
|
||||
}
|
||||
return newResource.Instance(r.Resource.Key), true
|
||||
case AbsResourceInstance:
|
||||
// TODO: Implement
|
||||
return AbsResourceInstance{}, false
|
||||
default:
|
||||
panic("invalid address type for resource-kind move endpoint")
|
||||
}
|
||||
default:
|
||||
panic("unexpected object kind")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,3 +303,575 @@ func TestModuleInstanceMoveDestination(t *testing.T) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsResourceInstanceMoveDestination(t *testing.T) {
|
||||
tests := []struct {
|
||||
DeclModule string
|
||||
StmtFrom, StmtTo string
|
||||
Reciever string
|
||||
WantMatch bool
|
||||
WantResult string
|
||||
}{
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.bar[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo["a"].test_object.beep`,
|
||||
true,
|
||||
`module.bar["a"].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar.module.foo`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.bar.module.foo.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo.module.bar`,
|
||||
`module.bar`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.foo[2].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.foo.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.module.bar[0].test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar.module.foo`,
|
||||
`module.foo[0].test_object.beep`,
|
||||
true,
|
||||
`module.bar.module.foo[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo.module.bar`,
|
||||
`module.bar`,
|
||||
`module.foo.module.bar[0].test_object.beep`,
|
||||
true,
|
||||
`module.bar[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.baz`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo.module.baz.test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.baz`,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.baz.test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo.test_object.beep`,
|
||||
false, // the receiver module has a non-matching instance key (NoKey)
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo[2].test_object.beep`,
|
||||
false, // the receiver is already at the "to" address
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo.bar`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo.bar`,
|
||||
`module.a`,
|
||||
`module.b`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.c.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2[0]`,
|
||||
`module.b1.module.b2[1]`,
|
||||
`module.c.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.a1.module.b2.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.b1.module.a2.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2[0]`,
|
||||
`module.b1.module.b2[1]`,
|
||||
`module.a1.module.b2[0].test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`foo_instance.bar`,
|
||||
`foo_instance.baz`,
|
||||
`module.foo.test_object.beep`,
|
||||
false, // the resource address is unrelated to the move statements
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(
|
||||
fmt.Sprintf(
|
||||
"%s: %s to %s with %s",
|
||||
test.DeclModule,
|
||||
test.StmtFrom, test.StmtTo,
|
||||
test.Reciever,
|
||||
),
|
||||
func(t *testing.T) {
|
||||
|
||||
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
|
||||
t.Helper()
|
||||
|
||||
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
|
||||
if hclDiags.HasErrors() {
|
||||
// We're not trying to test the HCL parser here, so any
|
||||
// failures at this point are likely to be bugs in the
|
||||
// test case itself.
|
||||
t.Fatalf("syntax error: %s", hclDiags.Error())
|
||||
}
|
||||
|
||||
moveEp, diags := ParseMoveEndpoint(traversal)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
||||
}
|
||||
return moveEp
|
||||
}
|
||||
|
||||
fromEPLocal := parseStmtEP(t, test.StmtFrom)
|
||||
toEPLocal := parseStmtEP(t, test.StmtTo)
|
||||
|
||||
declModule := RootModule
|
||||
if test.DeclModule != "" {
|
||||
declModule = strings.Split(test.DeclModule, ".")
|
||||
}
|
||||
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
|
||||
if fromEP == nil || toEP == nil {
|
||||
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
|
||||
}
|
||||
|
||||
receiverAddr, diags := ParseAbsResourceInstanceStr(test.Reciever)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
|
||||
}
|
||||
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
|
||||
if !test.WantMatch {
|
||||
if gotMatch {
|
||||
t.Errorf("unexpected match\nreciever: %s\nfrom: %s\nto: %s\nresult: %s", test.Reciever, fromEP, toEP, gotAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !gotMatch {
|
||||
t.Errorf("unexpected non-match\nreciever: %s\nfrom: %s\nto: %s", test.Reciever, fromEP, toEP)
|
||||
}
|
||||
|
||||
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsResourceMoveDestination(t *testing.T) {
|
||||
tests := []struct {
|
||||
DeclModule string
|
||||
StmtFrom, StmtTo string
|
||||
Reciever string
|
||||
WantMatch bool
|
||||
WantResult string
|
||||
}{
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.bar[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar`,
|
||||
`module.foo["a"].test_object.beep`,
|
||||
true,
|
||||
`module.bar["a"].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar.module.foo`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.bar.module.foo.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo.module.bar`,
|
||||
`module.bar`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.foo[2].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo`,
|
||||
`module.foo[1].test_object.beep`,
|
||||
true,
|
||||
`module.foo.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.foo[1]`,
|
||||
`module.foo.module.bar[0].test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo`,
|
||||
`module.bar.module.foo`,
|
||||
`module.foo[0].test_object.beep`,
|
||||
true,
|
||||
`module.bar.module.foo[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo.module.bar`,
|
||||
`module.bar`,
|
||||
`module.foo.module.bar[0].test_object.beep`,
|
||||
true,
|
||||
`module.bar[0].test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.baz`,
|
||||
`module.foo.module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo.module.baz.test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.baz`,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.baz.test_object.beep`,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.foo[1].module.bar.test_object.beep`,
|
||||
true,
|
||||
`module.foo[1].module.bar[1].test_object.beep`,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo.test_object.beep`,
|
||||
false, // the receiver module has a non-matching instance key (NoKey)
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.foo[1]`,
|
||||
`module.foo[2]`,
|
||||
`module.foo[2].test_object.beep`,
|
||||
false, // the receiver is already at the "to" address
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo.bar`,
|
||||
`module.bar`,
|
||||
`module.bar[1]`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
`foo.bar`,
|
||||
`module.a`,
|
||||
`module.b`,
|
||||
`module.boz.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.c.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2[0]`,
|
||||
`module.b1.module.b2[1]`,
|
||||
`module.c.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.a1.module.b2.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2`,
|
||||
`module.b1.module.b2`,
|
||||
`module.b1.module.a2.test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`module.a1.module.a2[0]`,
|
||||
`module.b1.module.b2[1]`,
|
||||
`module.a1.module.b2[0].test_object.beep`,
|
||||
false, // the receiver module is outside the declaration module
|
||||
``,
|
||||
},
|
||||
{
|
||||
``,
|
||||
`foo_instance.bar`,
|
||||
`foo_instance.baz`,
|
||||
`module.foo.test_object.beep`,
|
||||
false, // the resource address is unrelated to the move statements
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(
|
||||
fmt.Sprintf(
|
||||
"%s: %s to %s with %s",
|
||||
test.DeclModule,
|
||||
test.StmtFrom, test.StmtTo,
|
||||
test.Reciever,
|
||||
),
|
||||
func(t *testing.T) {
|
||||
|
||||
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
|
||||
t.Helper()
|
||||
|
||||
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
|
||||
if hclDiags.HasErrors() {
|
||||
// We're not trying to test the HCL parser here, so any
|
||||
// failures at this point are likely to be bugs in the
|
||||
// test case itself.
|
||||
t.Fatalf("syntax error: %s", hclDiags.Error())
|
||||
}
|
||||
|
||||
moveEp, diags := ParseMoveEndpoint(traversal)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
||||
}
|
||||
return moveEp
|
||||
}
|
||||
|
||||
fromEPLocal := parseStmtEP(t, test.StmtFrom)
|
||||
toEPLocal := parseStmtEP(t, test.StmtTo)
|
||||
|
||||
declModule := RootModule
|
||||
if test.DeclModule != "" {
|
||||
declModule = strings.Split(test.DeclModule, ".")
|
||||
}
|
||||
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
|
||||
if fromEP == nil || toEP == nil {
|
||||
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
|
||||
}
|
||||
|
||||
// We only have an AbsResourceInstance parser, not an
|
||||
// AbsResourceParser, and so we'll just cheat and parse this
|
||||
// as a resource instance but fail if it includes an instance
|
||||
// key.
|
||||
receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Reciever)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
|
||||
}
|
||||
if receiverInstanceAddr.Resource.Key != NoKey {
|
||||
t.Fatalf("invalid reciever address: must be a resource, not a resource instance")
|
||||
}
|
||||
receiverAddr := receiverInstanceAddr.ContainingResource()
|
||||
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
|
||||
if !test.WantMatch {
|
||||
if gotMatch {
|
||||
t.Errorf("unexpected match\nreciever: %s\nfrom: %s\nto: %s\nresult: %s", test.Reciever, fromEP, toEP, gotAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !gotMatch {
|
||||
t.Errorf("unexpected non-match\nreciever: %s\nfrom: %s\nto: %s", test.Reciever, fromEP, toEP)
|
||||
}
|
||||
|
||||
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue