diff --git a/flatmap/expand.go b/flatmap/expand.go index b2072a62f..331357ff3 100644 --- a/flatmap/expand.go +++ b/flatmap/expand.go @@ -2,6 +2,7 @@ package flatmap import ( "fmt" + "sort" "strconv" "strings" ) @@ -42,9 +43,43 @@ func expandArray(m map[string]string, prefix string) []interface{} { panic(err) } + // The Schema "Set" type stores its values in an array format, but using + // numeric hash values instead of ordinal keys. Take the set of keys + // regardless of value, and expand them in numeric order. + // See GH-11042 for more details. + keySet := map[int]bool{} + for k := range m { + if !strings.HasPrefix(k, prefix+".") { + continue + } + + key := k[len(prefix)+1:] + idx := strings.Index(key, ".") + if idx != -1 { + key = key[:idx] + } + + // skip the count value + if key == "#" { + continue + } + + k, err := strconv.Atoi(key) + if err != nil { + panic(err) + } + keySet[int(k)] = true + } + + keysList := make([]int, 0, num) + for key := range keySet { + keysList = append(keysList, key) + } + sort.Ints(keysList) + result := make([]interface{}, num) - for i := 0; i < int(num); i++ { - result[i] = Expand(m, fmt.Sprintf("%s.%d", prefix, i)) + for i, key := range keysList { + result[i] = Expand(m, fmt.Sprintf("%s.%d", prefix, key)) } return result diff --git a/flatmap/expand_test.go b/flatmap/expand_test.go index b480e2ca9..b5da3197b 100644 --- a/flatmap/expand_test.go +++ b/flatmap/expand_test.go @@ -106,6 +106,17 @@ func TestExpand(t *testing.T) { "list2": []interface{}{"c"}, }, }, + + { + Map: map[string]string{ + "set.#": "3", + "set.1234": "a", + "set.1235": "b", + "set.1236": "c", + }, + Key: "set", + Output: []interface{}{"a", "b", "c"}, + }, } for _, tc := range cases { diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 9c6cdc40e..54f762fe7 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -838,6 +838,44 @@ func TestInterpolator_nestedMapsAndLists(t *testing.T) { interfaceToVariableSwallowError(mapOfList)) } +func TestInterpolator_sets(t *testing.T) { + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_network_interface.set": &ResourceState{ + Type: "aws_network_interface", + Dependencies: []string{}, + Primary: &InstanceState{ + ID: "null", + Attributes: map[string]string{ + "private_ips.#": "1", + "private_ips.3977356764": "10.42.16.179", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-multi-vars"), + StateLock: new(sync.RWMutex), + State: state, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + set := []interface{}{"10.42.16.179"} + + testInterpolate(t, i, scope, "aws_network_interface.set.private_ips", + interfaceToVariableSwallowError(set)) +} + func testInterpolate( t *testing.T, i *Interpolater, scope *InterpolationScope,