blob: 410c2fb3e71611cbe53442406fd0266375f2b7e3 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package refactoring
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestImpliedMoveStatements(t *testing.T) {
resourceAddr := func(name string) addrs.AbsResource {
return addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: name,
}.Absolute(addrs.RootModuleInstance)
}
nestedResourceAddr := func(mod, name string) addrs.AbsResource {
return addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: name,
}.Absolute(addrs.RootModuleInstance.Child(mod, addrs.NoKey))
}
instObjState := func() *states.ResourceInstanceObjectSrc {
return &states.ResourceInstanceObjectSrc{}
}
providerAddr := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("hashicorp/foo"),
}
rootCfg, _ := loadRefactoringFixture(t, "testdata/move-statement-implied")
prevRunState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
resourceAddr("formerly_count").Instance(addrs.IntKey(0)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("formerly_count").Instance(addrs.IntKey(1)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("now_count").Instance(addrs.NoKey),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("formerly_count_explicit").Instance(addrs.IntKey(0)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("formerly_count_explicit").Instance(addrs.IntKey(1)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("now_count_explicit").Instance(addrs.NoKey),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("now_for_each_formerly_count").Instance(addrs.IntKey(0)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("now_for_each_formerly_no_count").Instance(addrs.NoKey),
instObjState(),
providerAddr,
)
// This "ambiguous" resource is representing a rare but possible
// situation where we end up having a mixture of different index
// types in the state at the same time. The main way to get into
// this state would be to remove "count = 1" and then have the
// provider fail to destroy the zero-key instance even though we
// already created the no-key instance. Users can also get here
// by using "terraform state mv" in weird ways.
s.SetResourceInstanceCurrent(
resourceAddr("ambiguous").Instance(addrs.NoKey),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
resourceAddr("ambiguous").Instance(addrs.IntKey(0)),
instObjState(),
providerAddr,
)
// Add two resource nested in a module to ensure we find these
// recursively.
s.SetResourceInstanceCurrent(
nestedResourceAddr("child", "formerly_count").Instance(addrs.IntKey(0)),
instObjState(),
providerAddr,
)
s.SetResourceInstanceCurrent(
nestedResourceAddr("child", "now_count").Instance(addrs.NoKey),
instObjState(),
providerAddr,
)
})
explicitStmts := FindMoveStatements(rootCfg)
got := ImpliedMoveStatements(rootCfg, prevRunState, explicitStmts)
want := []MoveStatement{
{
From: addrs.ImpliedMoveStatementEndpoint(resourceAddr("formerly_count").Instance(addrs.IntKey(0)), tfdiags.SourceRange{}),
To: addrs.ImpliedMoveStatementEndpoint(resourceAddr("formerly_count").Instance(addrs.NoKey), tfdiags.SourceRange{}),
Implied: true,
DeclRange: tfdiags.SourceRange{
Filename: "testdata/move-statement-implied/move-statement-implied.tf",
Start: tfdiags.SourcePos{Line: 5, Column: 1, Byte: 180},
End: tfdiags.SourcePos{Line: 5, Column: 32, Byte: 211},
},
},
// Found implied moves in a nested module, ignoring the explicit moves
{
From: addrs.ImpliedMoveStatementEndpoint(nestedResourceAddr("child", "formerly_count").Instance(addrs.IntKey(0)), tfdiags.SourceRange{}),
To: addrs.ImpliedMoveStatementEndpoint(nestedResourceAddr("child", "formerly_count").Instance(addrs.NoKey), tfdiags.SourceRange{}),
Implied: true,
DeclRange: tfdiags.SourceRange{
Filename: "testdata/move-statement-implied/child/move-statement-implied.tf",
Start: tfdiags.SourcePos{Line: 5, Column: 1, Byte: 180},
End: tfdiags.SourcePos{Line: 5, Column: 32, Byte: 211},
},
},
{
From: addrs.ImpliedMoveStatementEndpoint(resourceAddr("now_count").Instance(addrs.NoKey), tfdiags.SourceRange{}),
To: addrs.ImpliedMoveStatementEndpoint(resourceAddr("now_count").Instance(addrs.IntKey(0)), tfdiags.SourceRange{}),
Implied: true,
DeclRange: tfdiags.SourceRange{
Filename: "testdata/move-statement-implied/move-statement-implied.tf",
Start: tfdiags.SourcePos{Line: 10, Column: 11, Byte: 282},
End: tfdiags.SourcePos{Line: 10, Column: 12, Byte: 283},
},
},
// Found implied moves in a nested module, ignoring the explicit moves
{
From: addrs.ImpliedMoveStatementEndpoint(nestedResourceAddr("child", "now_count").Instance(addrs.NoKey), tfdiags.SourceRange{}),
To: addrs.ImpliedMoveStatementEndpoint(nestedResourceAddr("child", "now_count").Instance(addrs.IntKey(0)), tfdiags.SourceRange{}),
Implied: true,
DeclRange: tfdiags.SourceRange{
Filename: "testdata/move-statement-implied/child/move-statement-implied.tf",
Start: tfdiags.SourcePos{Line: 10, Column: 11, Byte: 282},
End: tfdiags.SourcePos{Line: 10, Column: 12, Byte: 283},
},
},
// We generate foo.ambiguous[0] to foo.ambiguous here, even though
// there's already a foo.ambiguous in the state, because it's the
// responsibility of the later ApplyMoves step to deal with the
// situation where an object wants to move into an address already
// occupied by another object.
{
From: addrs.ImpliedMoveStatementEndpoint(resourceAddr("ambiguous").Instance(addrs.IntKey(0)), tfdiags.SourceRange{}),
To: addrs.ImpliedMoveStatementEndpoint(resourceAddr("ambiguous").Instance(addrs.NoKey), tfdiags.SourceRange{}),
Implied: true,
DeclRange: tfdiags.SourceRange{
Filename: "testdata/move-statement-implied/move-statement-implied.tf",
Start: tfdiags.SourcePos{Line: 46, Column: 1, Byte: 806},
End: tfdiags.SourcePos{Line: 46, Column: 27, Byte: 832},
},
},
}
sort.Slice(got, func(i, j int) bool {
// This is just an arbitrary sort to make the result consistent
// regardless of what order the ImpliedMoveStatements function
// visits the entries in the state/config.
return got[i].DeclRange.Start.Line < got[j].DeclRange.Start.Line
})
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}