command: Fix state mv for only resource in module

When moving a resource block with multiple instances to a new address
within the same module, we need to ensure that the target module is
present as late as possible. Otherwise, deleting the resource from the
original address triggers pruning, and the module is removed just before
we try to add the resource to it, which causes a crash.

Includes regression test which panics without this code change.
This commit is contained in:
Alisdair McDiarmid 2020-07-08 17:09:58 -04:00
parent 67ee056c0b
commit 17e1c9dd05
2 changed files with 78 additions and 5 deletions

View File

@ -190,10 +190,7 @@ func (c *StateMvCommand) Run(args []string) int {
return 1
}
diags = diags.Append(c.validateResourceMove(addrFrom, addrTo))
if stateTo.Module(addrTo.Module) == nil {
// moving something to a mew module, so we need to ensure it exists
stateTo.EnsureModule(addrTo.Module)
}
if stateTo.Resource(addrTo) != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -223,7 +220,7 @@ func (c *StateMvCommand) Run(args []string) int {
// Update the address before adding it to the state.
rs.Addr = addrTo
stateTo.Module(addrTo.Module).Resources[addrTo.Resource.String()] = rs
stateTo.EnsureModule(addrTo.Module).Resources[addrTo.Resource.String()] = rs
}
case addrs.AbsResourceInstance:

View File

@ -1196,6 +1196,62 @@ func TestStateMv_fromBackendToLocal(t *testing.T) {
testStateOutput(t, statePath, testStateMvOriginal_backend)
}
// This test covers moving the only resource in a module to a new address in
// that module, which triggers the maybePruneModule functionality. This caused
// a panic report: https://github.com/hashicorp/terraform/issues/25520
func TestStateMv_onlyResourceInModule(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey)),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewLegacyProvider("test"),
Module: addrs.RootModule,
},
)
})
statePath := testStateFile(t, state)
testStateOutput(t, statePath, testStateMvOnlyResourceInModule_original)
p := testProvider()
ui := new(cli.MockUi)
c := &StateMvCommand{
StateMeta{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
},
}
args := []string{
"-state", statePath,
"module.foo.test_instance.foo",
"module.foo.test_instance.bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test it is correct
testStateOutput(t, statePath, testStateMvOnlyResourceInModule_output)
// Test we have backups
backups := testStateBackups(t, filepath.Dir(statePath))
if len(backups) != 1 {
t.Fatalf("bad: %#v", backups)
}
testStateOutput(t, backups[0], testStateMvOnlyResourceInModule_original)
}
const testStateMvOutputOriginal = `
test_instance.baz:
ID = foo
@ -1513,3 +1569,23 @@ test_instance.baz:
bar = value
foo = value
`
const testStateMvOnlyResourceInModule_original = `
<no state>
module.foo:
test_instance.foo.0:
ID = bar
provider = provider["registry.terraform.io/-/test"]
bar = value
foo = value
`
const testStateMvOnlyResourceInModule_output = `
<no state>
module.foo:
test_instance.bar.0:
ID = bar
provider = provider["registry.terraform.io/-/test"]
bar = value
foo = value
`