diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 9593080d6d96..b0443e9542d8 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/hil/ast" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/flatmap" ) const ( @@ -213,6 +214,7 @@ func (i *Interpolater) valueResourceVar( result map[string]ast.Variable) error { // If we're computing all dynamic fields, then module vars count // and we mark it as computed. + if i.Operation == walkValidate { result[n] = ast.Variable{ Value: config.UnknownVariableValue, @@ -343,6 +345,7 @@ func (i *Interpolater) valueUserVar( func (i *Interpolater) computeResourceVariable( scope *InterpolationScope, v *config.ResourceVariable) (*ast.Variable, error) { + id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) @@ -589,21 +592,19 @@ func (i *Interpolater) interpolateComplexTypeAttribute( return unknownVariable(), nil } - var keys []string + keys := make([]string, 0) listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$") for id, _ := range attributes { if listElementKey.MatchString(id) { keys = append(keys, id) } } + sort.Strings(keys) var members []string for _, key := range keys { members = append(members, attributes[key]) } - // This behaviour still seems very broken to me... it retains BC but is - // probably going to cause problems in future - sort.Strings(members) return hil.InterfaceToVariable(members) } @@ -620,19 +621,16 @@ func (i *Interpolater) interpolateComplexTypeAttribute( return unknownVariable(), nil } - var keys []string + resourceFlatMap := make(map[string]string) mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$") - for id, _ := range attributes { - if submatches := mapElementKey.FindAllStringSubmatch(id, -1); len(submatches) > 0 { - keys = append(keys, submatches[0][1]) + for id, val := range attributes { + if mapElementKey.MatchString(id) { + resourceFlatMap[id] = val } } - members := make(map[string]interface{}) - for _, key := range keys { - members[key] = attributes[resourceID+"."+key] - } - return hil.InterfaceToVariable(members) + expanded := flatmap.Expand(resourceFlatMap, resourceID) + return hil.InterfaceToVariable(expanded) } return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID) diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 7d25aacf6b05..c6f70511da0b 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -141,6 +141,95 @@ func TestInterpolater_pathRoot(t *testing.T) { }) } +func TestInterpolater_resourceVariableMap(t *testing.T) { + lock := new(sync.RWMutex) + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "amap.%": "3", + "amap.key1": "value1", + "amap.key2": "value2", + "amap.key3": "value3", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-resource-variable"), + State: state, + StateLock: lock, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + expected := map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + + testInterpolate(t, i, scope, "aws_instance.web.amap", + interfaceToVariableSwallowError(expected)) +} + +func TestInterpolater_resourceVariableComplexMap(t *testing.T) { + lock := new(sync.RWMutex) + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "amap.%": "2", + "amap.key1.#": "2", + "amap.key1.0": "hello", + "amap.key1.1": "world", + "amap.key2.#": "1", + "amap.key2.0": "foo", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-resource-variable"), + State: state, + StateLock: lock, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + expected := map[string]interface{}{ + "key1": []interface{}{"hello", "world"}, + "key2": []interface{}{"foo"}, + } + + testInterpolate(t, i, scope, "aws_instance.web.amap", + interfaceToVariableSwallowError(expected)) +} + func TestInterpolater_resourceVariable(t *testing.T) { lock := new(sync.RWMutex) state := &State{ @@ -278,10 +367,10 @@ func TestInterpolator_resourceMultiAttributes(t *testing.T) { lock := new(sync.RWMutex) state := &State{ Modules: []*ModuleState{ - &ModuleState{ + { Path: rootModulePath, Resources: map[string]*ResourceState{ - "aws_route53_zone.yada": &ResourceState{ + "aws_route53_zone.yada": { Type: "aws_route53_zone", Dependencies: []string{}, Primary: &InstanceState{ @@ -354,8 +443,8 @@ func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { "ns-601.awsdns-11.net", "ns-000.awsdns-38.org", "ns-444.awsdns-18.co.uk", - "ns-666.awsdns-11.net", "ns-999.awsdns-62.com", + "ns-666.awsdns-11.net", } // More than 1 element