Skip to content

Commit a44c8b8

Browse files
committed
terraform: state mv tests
1 parent 05cbb5c commit a44c8b8

4 files changed

Lines changed: 244 additions & 1 deletion

File tree

command/state_mv_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,87 @@ func TestStateMv_noState(t *testing.T) {
223223
}
224224
}
225225

226+
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
227+
state := &terraform.State{
228+
Modules: []*terraform.ModuleState{
229+
&terraform.ModuleState{
230+
Path: []string{"root"},
231+
Resources: map[string]*terraform.ResourceState{},
232+
},
233+
234+
&terraform.ModuleState{
235+
Path: []string{"root", "foo"},
236+
Resources: map[string]*terraform.ResourceState{},
237+
},
238+
239+
&terraform.ModuleState{
240+
Path: []string{"root", "foo", "child1"},
241+
Resources: map[string]*terraform.ResourceState{
242+
"test_instance.foo": &terraform.ResourceState{
243+
Type: "test_instance",
244+
Primary: &terraform.InstanceState{
245+
ID: "bar",
246+
Attributes: map[string]string{
247+
"foo": "value",
248+
"bar": "value",
249+
},
250+
},
251+
},
252+
},
253+
},
254+
255+
&terraform.ModuleState{
256+
Path: []string{"root", "foo", "child2"},
257+
Resources: map[string]*terraform.ResourceState{
258+
"test_instance.foo": &terraform.ResourceState{
259+
Type: "test_instance",
260+
Primary: &terraform.InstanceState{
261+
ID: "bar",
262+
Attributes: map[string]string{
263+
"foo": "value",
264+
"bar": "value",
265+
},
266+
},
267+
},
268+
},
269+
},
270+
},
271+
}
272+
273+
statePath := testStateFile(t, state)
274+
stateOutPath := statePath + ".out"
275+
276+
p := testProvider()
277+
ui := new(cli.MockUi)
278+
c := &StateMvCommand{
279+
Meta: Meta{
280+
ContextOpts: testCtxConfig(p),
281+
Ui: ui,
282+
},
283+
}
284+
285+
args := []string{
286+
"-state", statePath,
287+
"-state-out", stateOutPath,
288+
"module.foo",
289+
"module.bar",
290+
}
291+
if code := c.Run(args); code != 0 {
292+
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
293+
}
294+
295+
// Test it is correct
296+
testStateOutput(t, stateOutPath, testStateMvNestedModule_stateOut)
297+
testStateOutput(t, statePath, testStateMvNestedModule_stateOutSrc)
298+
299+
// Test we have backups
300+
backups := testStateBackups(t, filepath.Dir(statePath))
301+
if len(backups) != 1 {
302+
t.Fatalf("bad: %#v", backups)
303+
}
304+
testStateOutput(t, backups[0], testStateMvNestedModule_stateOutOriginal)
305+
}
306+
226307
const testStateMvOutputOriginal = `
227308
test_instance.baz:
228309
ID = foo
@@ -245,6 +326,41 @@ test_instance.baz:
245326
foo = value
246327
`
247328

329+
const testStateMvNestedModule_stateOut = `
330+
module.bar:
331+
<no state>
332+
module.bar.child1:
333+
test_instance.foo:
334+
ID = bar
335+
bar = value
336+
foo = value
337+
module.bar.child2:
338+
test_instance.foo:
339+
ID = bar
340+
bar = value
341+
foo = value
342+
`
343+
344+
const testStateMvNestedModule_stateOutSrc = `
345+
<no state>
346+
`
347+
348+
const testStateMvNestedModule_stateOutOriginal = `
349+
<no state>
350+
module.foo:
351+
<no state>
352+
module.foo.child1:
353+
test_instance.foo:
354+
ID = bar
355+
bar = value
356+
foo = value
357+
module.foo.child2:
358+
test_instance.foo:
359+
ID = bar
360+
bar = value
361+
foo = value
362+
`
363+
248364
const testStateMvOutput_stateOut = `
249365
test_instance.bar:
250366
ID = bar

terraform/state.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,12 @@ func (m *ModuleState) IsRoot() bool {
808808
return reflect.DeepEqual(m.Path, rootModulePath)
809809
}
810810

811+
// IsDescendent returns true if other is a descendent of this module.
812+
func (m *ModuleState) IsDescendent(other *ModuleState) bool {
813+
i := len(m.Path)
814+
return len(other.Path) > i && reflect.DeepEqual(other.Path[:i], m.Path)
815+
}
816+
811817
// Orphans returns a list of keys of resources that are in the State
812818
// but aren't present in the configuration itself. Hence, these keys
813819
// represent the state of resources that are orphans.

terraform/state_add.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111
// module cannot be moved to a resource address, however a resource can be
1212
// moved to a module address (it retains the same name, under that resource).
1313
//
14+
// The item can also be a []*ModuleState, which is the case for nested
15+
// modules. In this case, Add will expect the zero-index to be the top-most
16+
// module to add and will only nest children from there. For semantics, this
17+
// is equivalent to module => module.
18+
//
1419
// The full semantics of Add:
1520
//
1621
// ┌───────────────────────┬───────────────────────┬───────────────────────┐
@@ -65,7 +70,26 @@ func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error
6570
}
6671

6772
func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
68-
src := raw.(*ModuleState).deepcopy()
73+
// raw can be either *ModuleState or []*ModuleState. The former means
74+
// we're moving just one module. The latter means we're moving a module
75+
// and children.
76+
root := raw
77+
var rest []*ModuleState
78+
if list, ok := raw.([]*ModuleState); ok {
79+
// We need at least one item
80+
if len(list) == 0 {
81+
return fmt.Errorf("module move with no value to: %s", addr)
82+
}
83+
84+
// The first item is always the root
85+
root = list[0]
86+
if len(list) > 1 {
87+
rest = list[1:]
88+
}
89+
}
90+
91+
// Get the actual module state
92+
src := root.(*ModuleState).deepcopy()
6993

7094
// If the target module exists, it is an error
7195
path := append([]string{"root"}, addr.Path...)
@@ -97,6 +121,22 @@ func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw i
97121
}
98122
}
99123

124+
// Add all the children if we have them
125+
for _, item := range rest {
126+
// If item isn't a descendent of our root, then ignore it
127+
if !src.IsDescendent(item) {
128+
continue
129+
}
130+
131+
// It is! Strip the leading prefix and attach that to our address
132+
extra := item.Path[len(src.Path)+1:]
133+
addrCopy := addr.Copy()
134+
addrCopy.Path = append(addrCopy.Path, extra)
135+
136+
// Add it
137+
s.Add(fromAddr.String(), addrCopy.String(), item)
138+
}
139+
100140
return nil
101141
}
102142

@@ -227,6 +267,8 @@ func detectValueAddLoc(raw interface{}) stateAddLoc {
227267
switch raw.(type) {
228268
case *ModuleState:
229269
return stateAddModule
270+
case []*ModuleState:
271+
return stateAddModule
230272
case *ResourceState:
231273
return stateAddResource
232274
case *InstanceState:

terraform/state_add_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,85 @@ func TestStateAdd(t *testing.T) {
194194
nil,
195195
},
196196

197+
"ModuleState with children => Module Addr (new)": {
198+
false,
199+
"module.foo",
200+
"module.bar",
201+
202+
[]*ModuleState{
203+
&ModuleState{
204+
Path: rootModulePath,
205+
Resources: map[string]*ResourceState{},
206+
},
207+
208+
&ModuleState{
209+
Path: []string{"root", "foo", "child1"},
210+
Resources: map[string]*ResourceState{
211+
"test_instance.foo": &ResourceState{
212+
Type: "test_instance",
213+
Primary: &InstanceState{
214+
ID: "foo",
215+
},
216+
},
217+
},
218+
},
219+
220+
&ModuleState{
221+
Path: []string{"root", "foo", "child2"},
222+
Resources: map[string]*ResourceState{
223+
"test_instance.foo": &ResourceState{
224+
Type: "test_instance",
225+
Primary: &InstanceState{
226+
ID: "foo",
227+
},
228+
},
229+
},
230+
},
231+
232+
// Should be ignored
233+
&ModuleState{
234+
Path: []string{"root", "bar", "child2"},
235+
Resources: map[string]*ResourceState{
236+
"test_instance.foo": &ResourceState{
237+
Type: "test_instance",
238+
Primary: &InstanceState{
239+
ID: "foo",
240+
},
241+
},
242+
},
243+
},
244+
},
245+
246+
&State{},
247+
&State{
248+
Modules: []*ModuleState{
249+
&ModuleState{
250+
Path: []string{"root", "bar", "child1"},
251+
Resources: map[string]*ResourceState{
252+
"test_instance.foo": &ResourceState{
253+
Type: "test_instance",
254+
Primary: &InstanceState{
255+
ID: "foo",
256+
},
257+
},
258+
},
259+
},
260+
261+
&ModuleState{
262+
Path: []string{"root", "bar", "child2"},
263+
Resources: map[string]*ResourceState{
264+
"test_instance.foo": &ResourceState{
265+
Type: "test_instance",
266+
Primary: &InstanceState{
267+
ID: "foo",
268+
},
269+
},
270+
},
271+
},
272+
},
273+
},
274+
},
275+
197276
"ResourceState => Resource Addr (new)": {
198277
false,
199278
"aws_instance.bar",

0 commit comments

Comments
 (0)