Merge pull request #12219 from hashicorp/jbardin/state-mv-sort
fix sorting of module resources during state mv
This commit is contained in:
commit
82914b5e44
|
@ -370,6 +370,184 @@ func TestStateMv_stateOutNew_count(t *testing.T) {
|
||||||
testStateOutput(t, backups[0], testStateMvCount_stateOutOriginal)
|
testStateOutput(t, backups[0], testStateMvCount_stateOutOriginal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modules with more than 10 resources were sorted lexically, causing the
|
||||||
|
// indexes in the new location to change.
|
||||||
|
func TestStateMv_stateOutNew_largeCount(t *testing.T) {
|
||||||
|
state := &terraform.State{
|
||||||
|
Modules: []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.ResourceState{
|
||||||
|
"test_instance.foo.0": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo0",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.1": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo1",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.2": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo2",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.3": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo3",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.4": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo4",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.5": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo5",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.6": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo6",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.7": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo7",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.8": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo8",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.9": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo9",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.10": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo10",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &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,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.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, testStateMvLargeCount_stateOut)
|
||||||
|
testStateOutput(t, statePath, testStateMvLargeCount_stateOutSrc)
|
||||||
|
|
||||||
|
// Test we have backups
|
||||||
|
backups := testStateBackups(t, filepath.Dir(statePath))
|
||||||
|
if len(backups) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", backups)
|
||||||
|
}
|
||||||
|
testStateOutput(t, backups[0], testStateMvLargeCount_stateOutOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := &terraform.State{
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
|
@ -506,6 +684,111 @@ test_instance.foo.1:
|
||||||
foo = value
|
foo = value
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testStateMvLargeCount_stateOut = `
|
||||||
|
test_instance.bar.0:
|
||||||
|
ID = foo0
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.1:
|
||||||
|
ID = foo1
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.2:
|
||||||
|
ID = foo2
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.3:
|
||||||
|
ID = foo3
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.4:
|
||||||
|
ID = foo4
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.5:
|
||||||
|
ID = foo5
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.6:
|
||||||
|
ID = foo6
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.7:
|
||||||
|
ID = foo7
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.8:
|
||||||
|
ID = foo8
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.9:
|
||||||
|
ID = foo9
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.bar.10:
|
||||||
|
ID = foo10
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvLargeCount_stateOutSrc = `
|
||||||
|
test_instance.bar:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvLargeCount_stateOutOriginal = `
|
||||||
|
test_instance.bar:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.0:
|
||||||
|
ID = foo0
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.1:
|
||||||
|
ID = foo1
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.2:
|
||||||
|
ID = foo2
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.3:
|
||||||
|
ID = foo3
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.4:
|
||||||
|
ID = foo4
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.5:
|
||||||
|
ID = foo5
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.6:
|
||||||
|
ID = foo6
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.7:
|
||||||
|
ID = foo7
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.8:
|
||||||
|
ID = foo8
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.9:
|
||||||
|
ID = foo9
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo.10:
|
||||||
|
ID = foo10
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
const testStateMvNestedModule_stateOut = `
|
const testStateMvNestedModule_stateOut = `
|
||||||
<no state>
|
<no state>
|
||||||
module.bar:
|
module.bar:
|
||||||
|
|
|
@ -2756,12 +2756,6 @@ aws_instance.foo.0:
|
||||||
ID = i-abc0
|
ID = i-abc0
|
||||||
aws_instance.foo.1:
|
aws_instance.foo.1:
|
||||||
ID = i-abc1
|
ID = i-abc1
|
||||||
aws_instance.foo.10:
|
|
||||||
ID = i-abc10
|
|
||||||
aws_instance.foo.11:
|
|
||||||
ID = i-abc11
|
|
||||||
aws_instance.foo.12:
|
|
||||||
ID = i-abc12
|
|
||||||
aws_instance.foo.2:
|
aws_instance.foo.2:
|
||||||
ID = i-abc2
|
ID = i-abc2
|
||||||
aws_instance.foo.3:
|
aws_instance.foo.3:
|
||||||
|
@ -2778,6 +2772,12 @@ aws_instance.foo.8:
|
||||||
ID = i-abc8
|
ID = i-abc8
|
||||||
aws_instance.foo.9:
|
aws_instance.foo.9:
|
||||||
ID = i-abc9
|
ID = i-abc9
|
||||||
|
aws_instance.foo.10:
|
||||||
|
ID = i-abc10
|
||||||
|
aws_instance.foo.11:
|
||||||
|
ID = i-abc11
|
||||||
|
aws_instance.foo.12:
|
||||||
|
ID = i-abc12
|
||||||
`)
|
`)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
||||||
|
|
|
@ -1164,7 +1164,8 @@ func (m *ModuleState) String() string {
|
||||||
for name, _ := range m.Resources {
|
for name, _ := range m.Resources {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
|
||||||
|
sort.Sort(resourceNameSort(names))
|
||||||
|
|
||||||
for _, k := range names {
|
for _, k := range names {
|
||||||
rs := m.Resources[k]
|
rs := m.Resources[k]
|
||||||
|
@ -1204,6 +1205,7 @@ func (m *ModuleState) String() string {
|
||||||
|
|
||||||
attrKeys = append(attrKeys, ak)
|
attrKeys = append(attrKeys, ak)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(attrKeys)
|
sort.Strings(attrKeys)
|
||||||
|
|
||||||
for _, ak := range attrKeys {
|
for _, ak := range attrKeys {
|
||||||
|
@ -1234,6 +1236,7 @@ func (m *ModuleState) String() string {
|
||||||
for k, _ := range m.Outputs {
|
for k, _ := range m.Outputs {
|
||||||
ks = append(ks, k)
|
ks = append(ks, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(ks)
|
sort.Strings(ks)
|
||||||
|
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
|
@ -2032,6 +2035,48 @@ func WriteState(d *State, dst io.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resourceNameSort implements the sort.Interface to sort name parts lexically for
|
||||||
|
// strings and numerically for integer indexes.
|
||||||
|
type resourceNameSort []string
|
||||||
|
|
||||||
|
func (r resourceNameSort) Len() int { return len(r) }
|
||||||
|
func (r resourceNameSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||||
|
|
||||||
|
func (r resourceNameSort) Less(i, j int) bool {
|
||||||
|
iParts := strings.Split(r[i], ".")
|
||||||
|
jParts := strings.Split(r[j], ".")
|
||||||
|
|
||||||
|
end := len(iParts)
|
||||||
|
if len(jParts) < end {
|
||||||
|
end = len(jParts)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < end; idx++ {
|
||||||
|
if iParts[idx] == jParts[idx] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort on the first non-matching part
|
||||||
|
iInt, iIntErr := strconv.Atoi(iParts[idx])
|
||||||
|
jInt, jIntErr := strconv.Atoi(jParts[idx])
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case iIntErr == nil && jIntErr == nil:
|
||||||
|
// sort numerically if both parts are integers
|
||||||
|
return iInt < jInt
|
||||||
|
case iIntErr == nil:
|
||||||
|
// numbers sort before strings
|
||||||
|
return true
|
||||||
|
case jIntErr == nil:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return iParts[idx] < jParts[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// moduleStateSort implements sort.Interface to sort module states
|
// moduleStateSort implements sort.Interface to sort module states
|
||||||
type moduleStateSort []*ModuleState
|
type moduleStateSort []*ModuleState
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
|
||||||
as[i] = a
|
as[i] = a
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we werent given any filters, then we list all
|
// If we weren't given any filters, then we list all
|
||||||
if len(fs) == 0 {
|
if len(fs) == 0 {
|
||||||
as = append(as, &ResourceAddress{Index: -1})
|
as = append(as, &ResourceAddress{Index: -1})
|
||||||
}
|
}
|
||||||
|
@ -250,6 +250,13 @@ func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
func (s StateFilterResultSlice) Less(i, j int) bool {
|
func (s StateFilterResultSlice) Less(i, j int) bool {
|
||||||
a, b := s[i], s[j]
|
a, b := s[i], s[j]
|
||||||
|
|
||||||
|
// if these address contain an index, we want to sort by index rather than name
|
||||||
|
addrA, errA := ParseResourceAddress(a.Address)
|
||||||
|
addrB, errB := ParseResourceAddress(b.Address)
|
||||||
|
if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
|
||||||
|
return addrA.Index < addrB.Index
|
||||||
|
}
|
||||||
|
|
||||||
// If the addresses are different it is just lexographic sorting
|
// If the addresses are different it is just lexographic sorting
|
||||||
if a.Address != b.Address {
|
if a.Address != b.Address {
|
||||||
return a.Address < b.Address
|
return a.Address < b.Address
|
||||||
|
|
Loading…
Reference in New Issue