Skip to content

Commit 17e1c9d

Browse files
committed
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.
1 parent 67ee056 commit 17e1c9d

File tree

2 files changed

+78
-5
lines changed

2 files changed

+78
-5
lines changed

command/state_mv.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,7 @@ func (c *StateMvCommand) Run(args []string) int {
190190
return 1
191191
}
192192
diags = diags.Append(c.validateResourceMove(addrFrom, addrTo))
193-
if stateTo.Module(addrTo.Module) == nil {
194-
// moving something to a mew module, so we need to ensure it exists
195-
stateTo.EnsureModule(addrTo.Module)
196-
}
193+
197194
if stateTo.Resource(addrTo) != nil {
198195
diags = diags.Append(tfdiags.Sourceless(
199196
tfdiags.Error,
@@ -223,7 +220,7 @@ func (c *StateMvCommand) Run(args []string) int {
223220

224221
// Update the address before adding it to the state.
225222
rs.Addr = addrTo
226-
stateTo.Module(addrTo.Module).Resources[addrTo.Resource.String()] = rs
223+
stateTo.EnsureModule(addrTo.Module).Resources[addrTo.Resource.String()] = rs
227224
}
228225

229226
case addrs.AbsResourceInstance:

command/state_mv_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,62 @@ func TestStateMv_fromBackendToLocal(t *testing.T) {
11961196
testStateOutput(t, statePath, testStateMvOriginal_backend)
11971197
}
11981198

1199+
// This test covers moving the only resource in a module to a new address in
1200+
// that module, which triggers the maybePruneModule functionality. This caused
1201+
// a panic report: https://github.com/hashicorp/terraform/issues/25520
1202+
func TestStateMv_onlyResourceInModule(t *testing.T) {
1203+
state := states.BuildState(func(s *states.SyncState) {
1204+
s.SetResourceInstanceCurrent(
1205+
addrs.Resource{
1206+
Mode: addrs.ManagedResourceMode,
1207+
Type: "test_instance",
1208+
Name: "foo",
1209+
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey)),
1210+
&states.ResourceInstanceObjectSrc{
1211+
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
1212+
Status: states.ObjectReady,
1213+
},
1214+
addrs.AbsProviderConfig{
1215+
Provider: addrs.NewLegacyProvider("test"),
1216+
Module: addrs.RootModule,
1217+
},
1218+
)
1219+
})
1220+
1221+
statePath := testStateFile(t, state)
1222+
testStateOutput(t, statePath, testStateMvOnlyResourceInModule_original)
1223+
1224+
p := testProvider()
1225+
ui := new(cli.MockUi)
1226+
c := &StateMvCommand{
1227+
StateMeta{
1228+
Meta: Meta{
1229+
testingOverrides: metaOverridesForProvider(p),
1230+
Ui: ui,
1231+
},
1232+
},
1233+
}
1234+
1235+
args := []string{
1236+
"-state", statePath,
1237+
"module.foo.test_instance.foo",
1238+
"module.foo.test_instance.bar",
1239+
}
1240+
if code := c.Run(args); code != 0 {
1241+
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
1242+
}
1243+
1244+
// Test it is correct
1245+
testStateOutput(t, statePath, testStateMvOnlyResourceInModule_output)
1246+
1247+
// Test we have backups
1248+
backups := testStateBackups(t, filepath.Dir(statePath))
1249+
if len(backups) != 1 {
1250+
t.Fatalf("bad: %#v", backups)
1251+
}
1252+
testStateOutput(t, backups[0], testStateMvOnlyResourceInModule_original)
1253+
}
1254+
11991255
const testStateMvOutputOriginal = `
12001256
test_instance.baz:
12011257
ID = foo
@@ -1513,3 +1569,23 @@ test_instance.baz:
15131569
bar = value
15141570
foo = value
15151571
`
1572+
1573+
const testStateMvOnlyResourceInModule_original = `
1574+
<no state>
1575+
module.foo:
1576+
test_instance.foo.0:
1577+
ID = bar
1578+
provider = provider["registry.terraform.io/-/test"]
1579+
bar = value
1580+
foo = value
1581+
`
1582+
1583+
const testStateMvOnlyResourceInModule_output = `
1584+
<no state>
1585+
module.foo:
1586+
test_instance.bar.0:
1587+
ID = bar
1588+
provider = provider["registry.terraform.io/-/test"]
1589+
bar = value
1590+
foo = value
1591+
`

0 commit comments

Comments
 (0)