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.
This commit is contained in:
James Bardin 2019-12-05 16:06:25 -05:00
parent ba9cb786c3
commit a5cb36b34c
2 changed files with 90 additions and 11 deletions

View File

@ -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])) c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1]))
if !dryRun { if !dryRun {
fromResourceAddr := addrFrom.ContainingResource() fromResourceAddr := addrFrom.ContainingResource()
fromProviderAddr := ssFrom.Resource(fromResourceAddr).ProviderConfig fromResource := ssFrom.Resource(fromResourceAddr)
fromProviderAddr := fromResource.ProviderConfig
fromEachMode := fromResource.EachMode
ssFrom.ForgetResourceInstanceAll(addrFrom) ssFrom.ForgetResourceInstanceAll(addrFrom)
ssFrom.RemoveResourceIfEmpty(fromResourceAddr) 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 // If we're moving to an address without an index then that
// suggests the user's intent is to establish both the // suggests the user's intent is to establish both the
// resource and the instance at the same time (since the // resource and the instance at the same time (since the
// address covers both), but if there's an index in the // address covers both). If there's an index in the
// target then the resource must already exist. // target then allow creating the new instance here,
// inferring the mode from how the new address was parsed.
if addrTo.Resource.Key != addrs.NoKey { if addrTo.Resource.Key != addrs.NoKey {
diags = diags.Append(tfdiags.Sourceless( fromEachMode = eachModeForInstanceKey(addrTo.Resource.Key)
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
} }
resourceAddr := addrTo.ContainingResource() resourceAddr := addrTo.ContainingResource()
stateTo.SyncWrapper().SetResourceMeta( stateTo.SyncWrapper().SetResourceMeta(
resourceAddr, resourceAddr,
states.NoEach, fromEachMode,
fromProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource fromProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource
) )
rs = stateTo.Resource(resourceAddr) rs = stateTo.Resource(resourceAddr)
@ -358,6 +355,20 @@ func (c *StateMvCommand) Run(args []string) int {
return 0 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 // sourceObjectAddrs takes a single source object address and expands it to
// potentially multiple objects that need to be handled within it. // potentially multiple objects that need to be handled within it.
// //

View File

@ -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, `
<no state>
module.test:
test_instance.baz["new"]:
ID = bar
provider = provider.test
bar = value
foo = value
`)
}
func TestStateMv_differentResourceTypes(t *testing.T) { func TestStateMv_differentResourceTypes(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) { state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent( s.SetResourceInstanceCurrent(