diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 252fb76373..152e60971d 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -620,6 +620,33 @@ func (i *Interpolater) computeResourceMultiVariable( return &variable, err } +type indexKeys []string + +// we need to separate the index integer from the ID, and sort numerically +func (i indexKeys) Less(j, k int) bool { + jDot := strings.LastIndex(i[j], ".") + kDot := strings.LastIndex(i[j], ".") + + // These should all be properly formatted, but check the indexes and return + // a safe value just in case. + if jDot < 0 || kDot < 0 { + return i[j] < i[k] + } + + jIdx, _ := strconv.Atoi(i[j][jDot+1:]) + kIdx, _ := strconv.Atoi(i[k][kDot+1:]) + + return jIdx < kIdx +} + +func (i indexKeys) Swap(j, k int) { + i[j], i[k] = i[k], i[j] +} + +func (i indexKeys) Len() int { + return len(i) +} + func (i *Interpolater) interpolateComplexTypeAttribute( resourceID string, attributes map[string]string) (ast.Variable, error) { @@ -648,7 +675,9 @@ func (i *Interpolater) interpolateComplexTypeAttribute( keys = append(keys, id) } } - sort.Strings(keys) + + // sort the keys by their index number, rather than lexicographically by the key + sort.Sort(indexKeys(keys)) var members []string for _, key := range keys { diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 9613fc776d..de24692115 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "sort" "sync" "testing" @@ -674,6 +675,68 @@ func TestInterpolater_selfVarWithoutResource(t *testing.T) { } } +// Verify sorting by key index number +func TestInterpolator_indexKeySort(t *testing.T) { + keys := []string{"a.1", "a.2", "a.10", "a.20", "a.3"} + sorted := []string{"a.1", "a.2", "a.3", "a.10", "a.20"} + + sort.Sort(indexKeys(keys)) + for i := range keys { + if keys[i] != sorted[i] { + t.Fatalf("indexes out of order\nexpected: %q\ngot: %q", sorted, keys) + } + } +} + +func TestInterpolator_interpolatedListOrder(t *testing.T) { + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_route53_zone.list": &ResourceState{ + Type: "aws_route53_zone", + Dependencies: []string{}, + Primary: &InstanceState{ + ID: "null", + Attributes: map[string]string{ + "foo.#": "12", + "foo.0": "a", + "foo.1": "b", + "foo.2": "c", + "foo.3": "d", + "foo.4": "e", + "foo.5": "f", + "foo.6": "g", + "foo.7": "h", + "foo.8": "i", + "foo.9": "j", + "foo.10": "k", + "foo.11": "l", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-multi-vars"), + StateLock: new(sync.RWMutex), + State: state, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"} + + testInterpolate(t, i, scope, "aws_route53_zone.list.foo", + interfaceToVariableSwallowError(list)) +} + func getInterpolaterFixture(t *testing.T) *Interpolater { lock := new(sync.RWMutex) state := &State{