mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 17:01:04 -06:00
terraform: state mv tests
This commit is contained in:
parent
05cbb5c0ea
commit
a44c8b8760
@ -223,6 +223,87 @@ func TestStateMv_noState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
||||
state := &terraform.State{
|
||||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*terraform.ResourceState{},
|
||||
},
|
||||
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "foo"},
|
||||
Resources: map[string]*terraform.ResourceState{},
|
||||
},
|
||||
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "foo", "child1"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "foo", "child2"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
statePath := testStateFile(t, state)
|
||||
stateOutPath := statePath + ".out"
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &StateMvCommand{
|
||||
Meta: Meta{
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
"-state-out", stateOutPath,
|
||||
"module.foo",
|
||||
"module.bar",
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// Test it is correct
|
||||
testStateOutput(t, stateOutPath, testStateMvNestedModule_stateOut)
|
||||
testStateOutput(t, statePath, testStateMvNestedModule_stateOutSrc)
|
||||
|
||||
// Test we have backups
|
||||
backups := testStateBackups(t, filepath.Dir(statePath))
|
||||
if len(backups) != 1 {
|
||||
t.Fatalf("bad: %#v", backups)
|
||||
}
|
||||
testStateOutput(t, backups[0], testStateMvNestedModule_stateOutOriginal)
|
||||
}
|
||||
|
||||
const testStateMvOutputOriginal = `
|
||||
test_instance.baz:
|
||||
ID = foo
|
||||
@ -245,6 +326,41 @@ test_instance.baz:
|
||||
foo = value
|
||||
`
|
||||
|
||||
const testStateMvNestedModule_stateOut = `
|
||||
module.bar:
|
||||
<no state>
|
||||
module.bar.child1:
|
||||
test_instance.foo:
|
||||
ID = bar
|
||||
bar = value
|
||||
foo = value
|
||||
module.bar.child2:
|
||||
test_instance.foo:
|
||||
ID = bar
|
||||
bar = value
|
||||
foo = value
|
||||
`
|
||||
|
||||
const testStateMvNestedModule_stateOutSrc = `
|
||||
<no state>
|
||||
`
|
||||
|
||||
const testStateMvNestedModule_stateOutOriginal = `
|
||||
<no state>
|
||||
module.foo:
|
||||
<no state>
|
||||
module.foo.child1:
|
||||
test_instance.foo:
|
||||
ID = bar
|
||||
bar = value
|
||||
foo = value
|
||||
module.foo.child2:
|
||||
test_instance.foo:
|
||||
ID = bar
|
||||
bar = value
|
||||
foo = value
|
||||
`
|
||||
|
||||
const testStateMvOutput_stateOut = `
|
||||
test_instance.bar:
|
||||
ID = bar
|
||||
|
@ -808,6 +808,12 @@ func (m *ModuleState) IsRoot() bool {
|
||||
return reflect.DeepEqual(m.Path, rootModulePath)
|
||||
}
|
||||
|
||||
// IsDescendent returns true if other is a descendent of this module.
|
||||
func (m *ModuleState) IsDescendent(other *ModuleState) bool {
|
||||
i := len(m.Path)
|
||||
return len(other.Path) > i && reflect.DeepEqual(other.Path[:i], m.Path)
|
||||
}
|
||||
|
||||
// Orphans returns a list of keys of resources that are in the State
|
||||
// but aren't present in the configuration itself. Hence, these keys
|
||||
// represent the state of resources that are orphans.
|
||||
|
@ -11,6 +11,11 @@ import (
|
||||
// module cannot be moved to a resource address, however a resource can be
|
||||
// moved to a module address (it retains the same name, under that resource).
|
||||
//
|
||||
// The item can also be a []*ModuleState, which is the case for nested
|
||||
// modules. In this case, Add will expect the zero-index to be the top-most
|
||||
// module to add and will only nest children from there. For semantics, this
|
||||
// is equivalent to module => module.
|
||||
//
|
||||
// The full semantics of Add:
|
||||
//
|
||||
// ┌───────────────────────┬───────────────────────┬───────────────────────┐
|
||||
@ -65,7 +70,26 @@ func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error
|
||||
}
|
||||
|
||||
func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
||||
src := raw.(*ModuleState).deepcopy()
|
||||
// raw can be either *ModuleState or []*ModuleState. The former means
|
||||
// we're moving just one module. The latter means we're moving a module
|
||||
// and children.
|
||||
root := raw
|
||||
var rest []*ModuleState
|
||||
if list, ok := raw.([]*ModuleState); ok {
|
||||
// We need at least one item
|
||||
if len(list) == 0 {
|
||||
return fmt.Errorf("module move with no value to: %s", addr)
|
||||
}
|
||||
|
||||
// The first item is always the root
|
||||
root = list[0]
|
||||
if len(list) > 1 {
|
||||
rest = list[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Get the actual module state
|
||||
src := root.(*ModuleState).deepcopy()
|
||||
|
||||
// If the target module exists, it is an error
|
||||
path := append([]string{"root"}, addr.Path...)
|
||||
@ -97,6 +121,22 @@ func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw i
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the children if we have them
|
||||
for _, item := range rest {
|
||||
// If item isn't a descendent of our root, then ignore it
|
||||
if !src.IsDescendent(item) {
|
||||
continue
|
||||
}
|
||||
|
||||
// It is! Strip the leading prefix and attach that to our address
|
||||
extra := item.Path[len(src.Path)+1:]
|
||||
addrCopy := addr.Copy()
|
||||
addrCopy.Path = append(addrCopy.Path, extra)
|
||||
|
||||
// Add it
|
||||
s.Add(fromAddr.String(), addrCopy.String(), item)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -227,6 +267,8 @@ func detectValueAddLoc(raw interface{}) stateAddLoc {
|
||||
switch raw.(type) {
|
||||
case *ModuleState:
|
||||
return stateAddModule
|
||||
case []*ModuleState:
|
||||
return stateAddModule
|
||||
case *ResourceState:
|
||||
return stateAddResource
|
||||
case *InstanceState:
|
||||
|
@ -194,6 +194,85 @@ func TestStateAdd(t *testing.T) {
|
||||
nil,
|
||||
},
|
||||
|
||||
"ModuleState with children => Module Addr (new)": {
|
||||
false,
|
||||
"module.foo",
|
||||
"module.bar",
|
||||
|
||||
[]*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{},
|
||||
},
|
||||
|
||||
&ModuleState{
|
||||
Path: []string{"root", "foo", "child1"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.foo": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&ModuleState{
|
||||
Path: []string{"root", "foo", "child2"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.foo": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Should be ignored
|
||||
&ModuleState{
|
||||
Path: []string{"root", "bar", "child2"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.foo": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&State{},
|
||||
&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root", "bar", "child1"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.foo": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&ModuleState{
|
||||
Path: []string{"root", "bar", "child2"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.foo": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"ResourceState => Resource Addr (new)": {
|
||||
false,
|
||||
"aws_instance.bar",
|
||||
|
Loading…
Reference in New Issue
Block a user