terraform/internal/addrs/move_endpoint_module_test.go

1746 lines
41 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)
}
},
)
}
}
func TestMoveEndpointChainAndNested(t *testing.T) {
tests := []struct {
Endpoint, Other AbsMoveable
EndpointMod, OtherMod Module
CanChainFrom, NestedWithin bool
}{
{
Endpoint: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
CanChainFrom: true,
NestedWithin: false,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
CanChainFrom: false,
NestedWithin: false,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"),
Other: AbsModuleCall{
Module: RootModuleInstance,
Call: ModuleCall{Name: "foo"},
},
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(),
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"),
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
Other: mustParseModuleInstanceStr("module.foo[2]"),
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
Other: mustParseModuleInstanceStr("module.foo[2]"),
CanChainFrom: true,
NestedWithin: false,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
Other: mustParseModuleInstanceStr("module.foo[2]"),
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"),
Other: mustParseModuleInstanceStr("module.foo[2]"),
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
CanChainFrom: false,
NestedWithin: false,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
CanChainFrom: false,
NestedWithin: false,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
CanChainFrom: true,
NestedWithin: false,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(),
CanChainFrom: false,
NestedWithin: true,
},
{
Endpoint: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
CanChainFrom: false,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
CanChainFrom: false,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
CanChainFrom: false,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
CanChainFrom: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
EndpointMod: Module{"foo"},
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
CanChainFrom: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
Other: mustParseAbsResourceInstanceStr("resource.baz"),
OtherMod: Module{"foo"},
CanChainFrom: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
EndpointMod: Module{"foo"},
Other: mustParseAbsResourceInstanceStr("resource.baz"),
OtherMod: Module{"foo"},
CanChainFrom: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
EndpointMod: Module{"foo"},
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
CanChainFrom: true,
},
{
Endpoint: mustParseModuleInstanceStr("module.foo[2].module.baz"),
Other: mustParseModuleInstanceStr("module.baz"),
OtherMod: Module{"foo"},
CanChainFrom: true,
},
{
Endpoint: AbsModuleCall{
Call: ModuleCall{Name: "bing"},
},
EndpointMod: Module{"foo", "baz"},
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.baz"),
Call: ModuleCall{Name: "bing"},
},
OtherMod: Module{"foo"},
CanChainFrom: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
EndpointMod: Module{"foo"},
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
OtherMod: Module{"foo"},
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
EndpointMod: Module{"foo"},
Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
OtherMod: Module{"foo"},
NestedWithin: true,
},
{
Endpoint: mustParseAbsResourceInstanceStr("ressurce.baz").ContainingResource(),
EndpointMod: Module{"foo"},
Other: mustParseModuleInstanceStr("module.foo[2]"),
NestedWithin: true,
},
{
Endpoint: AbsModuleCall{
Call: ModuleCall{Name: "bang"},
},
EndpointMod: Module{"foo", "baz", "bing"},
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.baz"),
Call: ModuleCall{Name: "bing"},
},
OtherMod: Module{"foo"},
NestedWithin: true,
},
{
Endpoint: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.bing"),
Call: ModuleCall{Name: "bang"},
},
EndpointMod: Module{"foo", "baz"},
Other: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo.module.baz"),
Call: ModuleCall{Name: "bing"},
},
NestedWithin: true,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other),
func(t *testing.T) {
endpoint := &MoveEndpointInModule{
relSubject: test.Endpoint,
module: test.EndpointMod,
}
other := &MoveEndpointInModule{
relSubject: test.Other,
module: test.OtherMod,
}
if endpoint.CanChainFrom(other) != test.CanChainFrom {
t.Errorf("expected %s CanChainFrom %s == %t", endpoint, other, test.CanChainFrom)
}
if endpoint.NestedWithin(other) != test.NestedWithin {
t.Errorf("expected %s NestedWithin %s == %t", endpoint, other, test.NestedWithin)
}
},
)
}
}
func TestSelectsModule(t *testing.T) {
tests := []struct {
Endpoint *MoveEndpointInModule
Addr ModuleInstance
Selects bool
}{
{
Endpoint: &MoveEndpointInModule{
relSubject: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
},
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1]"),
Selects: true,
},
{
Endpoint: &MoveEndpointInModule{
module: mustParseModuleInstanceStr("module.foo").Module(),
relSubject: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.bar[2]"),
Call: ModuleCall{Name: "baz"},
},
},
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[2].module.baz"),
Selects: true,
},
{
Endpoint: &MoveEndpointInModule{
module: mustParseModuleInstanceStr("module.foo").Module(),
relSubject: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.bar[2]"),
Call: ModuleCall{Name: "baz"},
},
},
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1].module.baz"),
Selects: false,
},
{
Endpoint: &MoveEndpointInModule{
relSubject: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.bar"),
Call: ModuleCall{Name: "baz"},
},
},
Addr: mustParseModuleInstanceStr("module.bar[1].module.baz"),
Selects: false,
},
{
Endpoint: &MoveEndpointInModule{
module: mustParseModuleInstanceStr("module.foo").Module(),
relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
},
Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`),
Selects: true,
},
{
Endpoint: &MoveEndpointInModule{
relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
},
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
Selects: true,
},
{
Endpoint: &MoveEndpointInModule{
relSubject: mustParseAbsResourceInstanceStr(`module.bar.module.baz["key"].resource.name`).ContainingResource(),
},
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
Selects: true,
},
{
Endpoint: &MoveEndpointInModule{
module: mustParseModuleInstanceStr("module.nope").Module(),
relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
},
Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`),
Selects: false,
},
{
Endpoint: &MoveEndpointInModule{
relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
},
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["nope"]`),
Selects: false,
},
{
Endpoint: &MoveEndpointInModule{
relSubject: mustParseAbsResourceInstanceStr(`module.nope.module.baz["key"].resource.name`).ContainingResource(),
},
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
Selects: false,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("[%02d]%s.SelectsModule(%s)", i, test.Endpoint, test.Addr),
func(t *testing.T) {
if test.Endpoint.SelectsModule(test.Addr) != test.Selects {
t.Errorf("expected %s SelectsModule %s == %t", test.Endpoint, test.Addr, test.Selects)
}
},
)
}
}
func TestSelectsResource(t *testing.T) {
matchingResource := Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "matching",
}
unmatchingResource := Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "unmatching",
}
childMod := Module{
"child",
}
childModMatchingInst := ModuleInstance{
ModuleInstanceStep{Name: "child", InstanceKey: StringKey("matching")},
}
childModUnmatchingInst := ModuleInstance{
ModuleInstanceStep{Name: "child", InstanceKey: StringKey("unmatching")},
}
tests := []struct {
Endpoint *MoveEndpointInModule
Addr AbsResource
Selects bool
}{
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: true, // exact match
},
{
Endpoint: &MoveEndpointInModule{
relSubject: unmatchingResource.Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: false, // wrong resource name
},
{
Endpoint: &MoveEndpointInModule{
relSubject: unmatchingResource.Instance(IntKey(1)).Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: false, // wrong resource name
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Instance(NoKey).Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: true, // matches one instance
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Instance(IntKey(0)).Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: true, // matches one instance
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Instance(StringKey("a")).Absolute(nil),
},
Addr: matchingResource.Absolute(nil),
Selects: true, // matches one instance
},
{
Endpoint: &MoveEndpointInModule{
module: childMod,
relSubject: matchingResource.Absolute(nil),
},
Addr: matchingResource.Absolute(childModMatchingInst),
Selects: true, // in one of the instances of the module where the statement was written
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Absolute(childModMatchingInst),
},
Addr: matchingResource.Absolute(childModMatchingInst),
Selects: true, // exact match
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Instance(IntKey(2)).Absolute(childModMatchingInst),
},
Addr: matchingResource.Absolute(childModMatchingInst),
Selects: true, // matches one instance
},
{
Endpoint: &MoveEndpointInModule{
relSubject: matchingResource.Absolute(childModMatchingInst),
},
Addr: matchingResource.Absolute(childModUnmatchingInst),
Selects: false, // the containing module instance doesn't match
},
{
Endpoint: &MoveEndpointInModule{
relSubject: AbsModuleCall{
Module: mustParseModuleInstanceStr("module.foo[2]"),
Call: ModuleCall{Name: "bar"},
},
},
Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
Selects: false, // a module call can't match a resource
},
{
Endpoint: &MoveEndpointInModule{
relSubject: mustParseModuleInstanceStr("module.foo[2]"),
},
Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
Selects: false, // a module instance can't match a resource
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("[%02d]%s SelectsResource(%s)", i, test.Endpoint, test.Addr),
func(t *testing.T) {
if got, want := test.Endpoint.SelectsResource(test.Addr), test.Selects; got != want {
t.Errorf("wrong result\nReceiver: %s\nArgument: %s\ngot: %t\nwant: %t", test.Endpoint, test.Addr, got, want)
}
},
)
}
}
func TestIsModuleMoveReIndex(t *testing.T) {
tests := []struct {
from, to AbsMoveable
expect bool
}{
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.bar`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.bar[0]`),
expect: true,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
to: mustParseModuleInstanceStr(`module.bar[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar["a"]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.foo`),
to: mustParseModuleInstanceStr(`module.bar`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.foo[0]`),
expect: false,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
to: mustParseModuleInstanceStr(`module.foo[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar["a"]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "foo"},
},
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.baz.module.baz`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.baz.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
to: mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`),
expect: true,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "baz"},
},
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "baz"},
},
expect: false,
},
{
from: AbsModuleCall{
Module: mustParseModuleInstanceStr(`module.bar[0]`),
Call: ModuleCall{Name: "baz"},
},
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: AbsModuleCall{
Module: mustParseModuleInstanceStr(`module.bar[0]`),
Call: ModuleCall{Name: "baz"},
},
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: mustParseModuleInstanceStr(`module.baz`),
expect: false,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to),
func(t *testing.T) {
from := &MoveEndpointInModule{
relSubject: test.from,
}
to := &MoveEndpointInModule{
relSubject: test.to,
}
if got := from.IsModuleReIndex(to); got != test.expect {
t.Errorf("expected %t, got %t", test.expect, got)
}
},
)
}
}
func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance {
r, diags := ParseAbsResourceInstanceStr(s)
if diags.HasErrors() {
panic(diags.ErrWithWarnings().Error())
}
return r
}