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.
This commit is contained in:
James Bardin 2020-01-04 08:29:25 -05:00
parent dc178789b2
commit 98c02ac114
3 changed files with 70 additions and 36 deletions

View File

@ -879,3 +879,11 @@ func normalizeJSON(t *testing.T, src []byte) string {
} }
return buf.String() return buf.String()
} }
func mustResourceAddr(s string) addrs.AbsResource {
addr, diags := addrs.ParseAbsResourceStr(s)
if diags.HasErrors() {
panic(diags.Err())
}
return addr
}

View File

@ -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), 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 { if dryRun {

View File

@ -38,6 +38,7 @@ func TestStateMv(t *testing.T) {
&states.ResourceInstanceObjectSrc{ &states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady, Status: states.ObjectReady,
Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")},
}, },
addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
) )
@ -98,6 +99,7 @@ func TestStateMv_resourceToInstance(t *testing.T) {
&states.ResourceInstanceObjectSrc{ &states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady, Status: states.ObjectReady,
Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")},
}, },
addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
) )
@ -447,6 +449,7 @@ func TestStateMv_backupExplicit(t *testing.T) {
&states.ResourceInstanceObjectSrc{ &states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady, Status: states.ObjectReady,
Dependencies: []addrs.AbsResource{mustResourceAddr("test_instance.foo")},
}, },
addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance), addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
) )
@ -897,42 +900,40 @@ func TestStateMv_toNewModule(t *testing.T) {
} }
testStateOutput(t, stateOutPath2, testStateMvModuleNewModule_stateOut) testStateOutput(t, stateOutPath2, testStateMvModuleNewModule_stateOut)
} }
func TestStateMv_withinBackend(t *testing.T) { func TestStateMv_withinBackend(t *testing.T) {
td := tempDir(t) td := tempDir(t)
copy.CopyDir(testFixturePath("backend-unchanged"), td) copy.CopyDir(testFixturePath("backend-unchanged"), td)
defer os.RemoveAll(td) defer os.RemoveAll(td)
defer testChdir(t, td)() defer testChdir(t, td)()
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Primary: &terraform.InstanceState{ Name: "foo",
ID: "bar", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Attributes: map[string]string{ &states.ResourceInstanceObjectSrc{
"foo": "value", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
"bar": "value", Status: states.ObjectReady,
}, },
}, addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
}, )
s.SetResourceInstanceCurrent(
"test_instance.baz": &terraform.ResourceState{ addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance", Type: "test_instance",
Primary: &terraform.InstanceState{ Name: "baz",
ID: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Attributes: map[string]string{ &states.ResourceInstanceObjectSrc{
"foo": "value", AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"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" // the local backend state file is "foo"
statePath := "local-state.tfstate" statePath := "local-state.tfstate"
@ -944,7 +945,7 @@ func TestStateMv_withinBackend(t *testing.T) {
} }
defer f.Close() defer f.Close()
if err := terraform.WriteState(state, f); err != nil { if err := writeStateForTesting(state, f); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1057,6 +1058,9 @@ test_instance.baz:
provider = provider.test provider = provider.test
bar = value bar = value
foo = value foo = value
Dependencies:
test_instance.foo
test_instance.foo: test_instance.foo:
ID = bar ID = bar
provider = provider.test provider = provider.test