core: Fix interpolation of complex structures

This commit makes two changes: map interpolation can now read flatmapped
structures, such as those present in remote state outputs, and lists are
sorted by the index instead of the value.
This commit is contained in:
James Nugent 2016-06-11 12:27:39 +01:00
parent dbf725bd68
commit f51c9d5efd
2 changed files with 103 additions and 16 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/hil/ast" "github.com/hashicorp/hil/ast"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/flatmap"
) )
const ( const (
@ -213,6 +214,7 @@ func (i *Interpolater) valueResourceVar(
result map[string]ast.Variable) error { result map[string]ast.Variable) error {
// If we're computing all dynamic fields, then module vars count // If we're computing all dynamic fields, then module vars count
// and we mark it as computed. // and we mark it as computed.
if i.Operation == walkValidate { if i.Operation == walkValidate {
result[n] = ast.Variable{ result[n] = ast.Variable{
Value: config.UnknownVariableValue, Value: config.UnknownVariableValue,
@ -343,6 +345,7 @@ func (i *Interpolater) valueUserVar(
func (i *Interpolater) computeResourceVariable( func (i *Interpolater) computeResourceVariable(
scope *InterpolationScope, scope *InterpolationScope,
v *config.ResourceVariable) (*ast.Variable, error) { v *config.ResourceVariable) (*ast.Variable, error) {
id := v.ResourceId() id := v.ResourceId()
if v.Multi { if v.Multi {
id = fmt.Sprintf("%s.%d", id, v.Index) id = fmt.Sprintf("%s.%d", id, v.Index)
@ -589,21 +592,19 @@ func (i *Interpolater) interpolateComplexTypeAttribute(
return unknownVariable(), nil return unknownVariable(), nil
} }
var keys []string keys := make([]string, 0)
listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$") listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
for id, _ := range attributes { for id, _ := range attributes {
if listElementKey.MatchString(id) { if listElementKey.MatchString(id) {
keys = append(keys, id) keys = append(keys, id)
} }
} }
sort.Strings(keys)
var members []string var members []string
for _, key := range keys { for _, key := range keys {
members = append(members, attributes[key]) 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) return hil.InterfaceToVariable(members)
} }
@ -620,19 +621,16 @@ func (i *Interpolater) interpolateComplexTypeAttribute(
return unknownVariable(), nil return unknownVariable(), nil
} }
var keys []string resourceFlatMap := make(map[string]string)
mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$") mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$")
for id, _ := range attributes { for id, val := range attributes {
if submatches := mapElementKey.FindAllStringSubmatch(id, -1); len(submatches) > 0 { if mapElementKey.MatchString(id) {
keys = append(keys, submatches[0][1]) resourceFlatMap[id] = val
} }
} }
members := make(map[string]interface{}) expanded := flatmap.Expand(resourceFlatMap, resourceID)
for _, key := range keys { return hil.InterfaceToVariable(expanded)
members[key] = attributes[resourceID+"."+key]
}
return hil.InterfaceToVariable(members)
} }
return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID) return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID)

View File

@ -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) { func TestInterpolater_resourceVariable(t *testing.T) {
lock := new(sync.RWMutex) lock := new(sync.RWMutex)
state := &State{ state := &State{
@ -278,10 +367,10 @@ func TestInterpolator_resourceMultiAttributes(t *testing.T) {
lock := new(sync.RWMutex) lock := new(sync.RWMutex)
state := &State{ state := &State{
Modules: []*ModuleState{ Modules: []*ModuleState{
&ModuleState{ {
Path: rootModulePath, Path: rootModulePath,
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_route53_zone.yada": &ResourceState{ "aws_route53_zone.yada": {
Type: "aws_route53_zone", Type: "aws_route53_zone",
Dependencies: []string{}, Dependencies: []string{},
Primary: &InstanceState{ Primary: &InstanceState{
@ -354,8 +443,8 @@ func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
"ns-601.awsdns-11.net", "ns-601.awsdns-11.net",
"ns-000.awsdns-38.org", "ns-000.awsdns-38.org",
"ns-444.awsdns-18.co.uk", "ns-444.awsdns-18.co.uk",
"ns-666.awsdns-11.net",
"ns-999.awsdns-62.com", "ns-999.awsdns-62.com",
"ns-666.awsdns-11.net",
} }
// More than 1 element // More than 1 element