From a5cb36b34c2094c92fc965606be0d53929f74a3d Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 5 Dec 2019 16:06:25 -0500 Subject: [PATCH] Allow moving instances to new resources If a state mv target happens to be a resource that doesn't exist, allow the creation of the new resource inferring the EachMode from the target address. --- command/state_mv.go | 33 ++++++++++++------- command/state_mv_test.go | 68 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/command/state_mv.go b/command/state_mv.go index 00b48306f..829ad923b 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -278,7 +278,9 @@ func (c *StateMvCommand) Run(args []string) int { c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1])) if !dryRun { fromResourceAddr := addrFrom.ContainingResource() - fromProviderAddr := ssFrom.Resource(fromResourceAddr).ProviderConfig + fromResource := ssFrom.Resource(fromResourceAddr) + fromProviderAddr := fromResource.ProviderConfig + fromEachMode := fromResource.EachMode ssFrom.ForgetResourceInstanceAll(addrFrom) ssFrom.RemoveResourceIfEmpty(fromResourceAddr) @@ -287,22 +289,17 @@ func (c *StateMvCommand) Run(args []string) int { // If we're moving to an address without an index then that // suggests the user's intent is to establish both the // resource and the instance at the same time (since the - // address covers both), but if there's an index in the - // target then the resource must already exist. + // address covers both). If there's an index in the + // target then allow creating the new instance here, + // inferring the mode from how the new address was parsed. if addrTo.Resource.Key != addrs.NoKey { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - msgInvalidTarget, - fmt.Sprintf("Cannot move to %s: %s does not exist in the current state.", addrTo, addrTo.ContainingResource()), - )) - c.showDiagnostics(diags) - return 1 + fromEachMode = eachModeForInstanceKey(addrTo.Resource.Key) } resourceAddr := addrTo.ContainingResource() stateTo.SyncWrapper().SetResourceMeta( resourceAddr, - states.NoEach, + fromEachMode, fromProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource ) rs = stateTo.Resource(resourceAddr) @@ -358,6 +355,20 @@ func (c *StateMvCommand) Run(args []string) int { return 0 } +func eachModeForInstanceKey(key addrs.InstanceKey) states.EachMode { + switch key.(type) { + case addrs.IntKey: + return states.EachList + case addrs.StringKey: + return states.EachMap + default: + if key == addrs.NoKey { + return states.NoEach + } + panic(fmt.Sprintf("don't know an each mode for instance key %#v", key)) + } +} + // sourceObjectAddrs takes a single source object address and expands it to // potentially multiple objects that need to be handled within it. // diff --git a/command/state_mv_test.go b/command/state_mv_test.go index 504c4fca1..f777d8807 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -237,6 +237,74 @@ test_instance.foo.0: `) } +func TestStateMv_instanceToNewResource(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), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance), + ) + }) + statePath := testStateFile(t, state) + + p := testProvider() + ui := new(cli.MockUi) + c := &StateMvCommand{ + StateMeta{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + }, + } + + args := []string{ + "-state", statePath, + "test_instance.foo[0]", + "test_instance.bar[\"new\"]", + } + 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, ` +test_instance.bar["new"]: + ID = bar + provider = provider.test + bar = value + foo = value +`) + + // now move the instance to a new resource in a new module + args = []string{ + "-state", statePath, + "test_instance.bar[\"new\"]", + "module.test.test_instance.baz[\"new\"]", + } + 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, ` + +module.test: + test_instance.baz["new"]: + ID = bar + provider = provider.test + bar = value + foo = value +`) +} + func TestStateMv_differentResourceTypes(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(