diff --git a/command/state_mv.go b/command/state_mv.go index 10e9a86f7..812441699 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -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: diff --git a/command/state_mv_test.go b/command/state_mv_test.go index f970185b1..38cfe34a8 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -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 = ` + +module.foo: + test_instance.foo.0: + ID = bar + provider = provider["registry.terraform.io/-/test"] + bar = value + foo = value +` + +const testStateMvOnlyResourceInModule_output = ` + +module.foo: + test_instance.bar.0: + ID = bar + provider = provider["registry.terraform.io/-/test"] + bar = value + foo = value +`