From 98c02ac11402c1bf602da46f55ee2fdc3d440f90 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Sat, 4 Jan 2020 08:29:25 -0500 Subject: [PATCH] remove stale dependencies on `state mv` Clear any Dependencies if there is an entry matching a `state mv` from address. While stale dependencies won't directly effect any current operations, clearing the list will allow them to be recreated in their entirety during refresh. This will help future releases that may rely solely on the pre-calculated dependencies for destruction ordering. --- command/command_test.go | 8 +++++ command/state_mv.go | 22 ++++++++++++ command/state_mv_test.go | 76 +++++++++++++++++++++------------------- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/command/command_test.go b/command/command_test.go index 4f5acfe38..6a80036e9 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -879,3 +879,11 @@ func normalizeJSON(t *testing.T, src []byte) string { } return buf.String() } + +func mustResourceAddr(s string) addrs.AbsResource { + addr, diags := addrs.ParseAbsResourceStr(s) + if diags.HasErrors() { + panic(diags.Err()) + } + return addr +} diff --git a/command/state_mv.go b/command/state_mv.go index 829ad923b..33014b3dc 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -314,6 +314,28 @@ func (c *StateMvCommand) Run(args []string) int { fmt.Sprintf("Cannot move %s: Terraform doesn't know how to move this object.", rawAddrFrom), )) } + + // Look for any dependencies that may be effected and + // remove them to ensure they are recreated in full. + for _, mod := range stateTo.Modules { + for _, res := range mod.Resources { + for _, ins := range res.Instances { + if ins.Current == nil { + continue + } + + for _, dep := range ins.Current.Dependencies { + // check both directions here, since we may be moving + // an instance which is in a resource, or a module + // which can contain a resource. + if dep.TargetContains(rawAddrFrom) || rawAddrFrom.TargetContains(dep) { + ins.Current.Dependencies = nil + break + } + } + } + } + } } if dryRun { diff --git a/command/state_mv_test.go b/command/state_mv_test.go index 25addaefd..c8b70f41a 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -36,8 +36,9 @@ func TestStateMv(t *testing.T) { Name: "baz", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ - AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), - Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")}, }, addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), ) @@ -96,8 +97,9 @@ func TestStateMv_resourceToInstance(t *testing.T) { Name: "baz", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ - AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), - Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")}, }, addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), ) @@ -445,8 +447,9 @@ func TestStateMv_backupExplicit(t *testing.T) { Name: "baz", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ - AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), - Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")}, }, addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), ) @@ -897,42 +900,40 @@ func TestStateMv_toNewModule(t *testing.T) { } testStateOutput(t, stateOutPath2, testStateMvModuleNewModule_stateOut) } + func TestStateMv_withinBackend(t *testing.T) { td := tempDir(t) copy.CopyDir(testFixturePath("backend-unchanged"), td) defer os.RemoveAll(td) defer testChdir(t, td)() - state := &terraform.State{ - Modules: []*terraform.ModuleState{ - &terraform.ModuleState{ - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test_instance.foo": &terraform.ResourceState{ - Type: "test_instance", - Primary: &terraform.InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "foo": "value", - "bar": "value", - }, - }, - }, - - "test_instance.baz": &terraform.ResourceState{ - Type: "test_instance", - Primary: &terraform.InstanceState{ - ID: "foo", - Attributes: map[string]string{ - "foo": "value", - "bar": "value", - }, - }, - }, - }, + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), + Status: states.ObjectReady, }, - }, - } + addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), + ) + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "baz", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")}, + }, + addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), + ) + }) // the local backend state file is "foo" statePath := "local-state.tfstate" @@ -944,7 +945,7 @@ func TestStateMv_withinBackend(t *testing.T) { } defer f.Close() - if err := terraform.WriteState(state, f); err != nil { + if err := writeStateForTesting(state, f); err != nil { t.Fatal(err) } @@ -1057,6 +1058,9 @@ test_instance.baz: provider = provider.test bar = value foo = value + + Dependencies: + test_instance.foo test_instance.foo: ID = bar provider = provider.test