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/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)

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) {
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