From 5d1e3f8d487568d74b53dc5c04adf48ac908d459 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 4f5acfe38117..6a80036e9ec8 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 829ad923bdee..33014b3dcac0 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 25addaefd5e5..1a8def111788 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: "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: "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