terraform/internal/addrs/move_endpoint_module_test.go

1077 lines
22 KiB
Go

package addrs
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestModuleInstanceMoveDestination(t *testing.T) {
tests := []struct {
DeclModule string
StmtFrom, StmtTo string
Receiver string
WantMatch bool
WantResult string
}{
{
``,
`module.foo`,
`module.bar`,
`module.foo`,
true,
`module.bar`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo[1]`,
true,
`module.bar[1]`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo["a"]`,
true,
`module.bar["a"]`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo`,
true,
`module.bar.module.foo`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar`,
true,
`module.bar`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[1]`,
true,
`module.foo[2]`,
},
{
``,
`module.foo[1]`,
`module.foo`,
`module.foo[1]`,
true,
`module.foo`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo`,
true,
`module.foo[1]`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar`,
true,
`module.foo[1].module.bar`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar[0]`,
true,
`module.foo[1].module.bar[0]`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo[0]`,
true,
`module.bar.module.foo[0]`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar[0]`,
true,
`module.bar[0]`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo.module.bar`,
true,
`module.foo.module.baz`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo[1].module.bar`,
true,
`module.foo[1].module.baz`,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.foo[1].module.bar`,
true,
`module.foo[1].module.bar[1]`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo`,
false, // the receiver has a non-matching instance key (NoKey)
``,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[2]`,
false, // the receiver is already the "to" address
``,
},
{
``,
`module.foo`,
`module.bar`,
``,
false, // the root module can never be moved
``,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
`foo.bar`,
`module.bar`,
`module.bar[1]`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
`foo.bar`,
`module.a`,
`module.b`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.c`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.c`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.a1.module.b2`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.b1.module.a2`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.a1.module.b2[0]`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`foo_instance.bar`,
`foo_instance.baz`,
`module.foo`,
false, // a resource address can never match a module instance
``,
},
}
for _, test := range tests {
t.Run(
fmt.Sprintf(
"%s: %s to %s with %s",
test.DeclModule,
test.StmtFrom, test.StmtTo,
test.Receiver,
),
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 := RootModuleInstance
if test.Receiver != "" {
var diags tfdiags.Diagnostics
receiverAddr, diags = ParseModuleInstanceStr(test.Receiver)
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\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Errorf("unexpected non-match\nreceiver: %s\nfrom: %s\nto: %s", test.Receiver, fromEP, toEP)
}
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
}
},
)
}
}
func TestAbsResourceInstanceMoveDestination(t *testing.T) {
tests := []struct {
DeclModule string
StmtFrom, StmtTo string
Receiver string
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`,
`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.Receiver,
),
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.Receiver)
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\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: (no match)\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
}
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
Receiver string
WantMatch bool
WantResult string
}{
{
``,
`test_object.beep`,
`test_object.boop`,
`test_object.beep`,
true,
`test_object.boop`,
},
{
``,
`test_object.beep`,
`module.foo.test_object.beep`,
`test_object.beep`,
true,
`module.foo.test_object.beep`,
},
{
``,
`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`,
},
{
``,
`test_object.beep`,
`test_object.boop`,
`test_object.boop`,
false, // the reciever is already the "to" address
``,
},
{
`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`,
`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 i, test := range tests {
t.Run(
fmt.Sprintf(
"[%02d] %s: %s to %s with %s",
i,
test.DeclModule,
test.StmtFrom, test.StmtTo,
test.Receiver,
),
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.Receiver)
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\nreceiver: %s (%T)\nfrom: %s\nto: %s\nresult: %s", test.Receiver, receiverAddr, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: no match\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
}
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
}
},
)
}
}