Merge pull request #7551 from hashicorp/b-computed-map-values

core: Allow use of computed values in maps
This commit is contained in:
James Nugent 2016-07-08 16:49:01 +01:00 committed by GitHub
commit 2944e2ace8
6 changed files with 140 additions and 4 deletions

View File

@ -54,6 +54,9 @@ type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node)
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
if loc == reflectwalk.WalkLoc {
w.sliceIndex = -1
}
return nil
}
@ -72,6 +75,7 @@ func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.SliceElem:
w.csKey = w.csKey[:len(w.csKey)-1]
w.sliceIndex = -1
}
return nil
@ -85,7 +89,13 @@ func (w *interpolationWalker) Map(m reflect.Value) error {
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.csKey = append(w.csKey, k)
w.key = append(w.key, k.String())
if w.sliceIndex != -1 {
w.key = append(w.key, fmt.Sprintf("%d.%s", w.sliceIndex, k.String()))
} else {
w.key = append(w.key, k.String())
}
w.lastValue = v
return nil
}
@ -164,6 +174,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
} else if replaceVal == UnknownVariableValue {
remove = true
}
if remove {
w.removeCurrent()
return nil

View File

@ -2325,3 +2325,43 @@ func TestContext2Plan_moduleMapLiteral(t *testing.T) {
t.Fatalf("err: %s", err)
}
}
func TestContext2Plan_computedValueInMap(t *testing.T) {
m := testModule(t, "plan-computed-value-in-map")
p := testProvider("aws")
p.DiffFn = func(info *InstanceInfo, state *InstanceState, c *ResourceConfig) (*InstanceDiff, error) {
switch info.Type {
case "aws_computed_source":
return &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"computed_read_only": &ResourceAttrDiff{
NewComputed: true,
},
},
}, nil
}
return testDiffFn(info, state, c)
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}
plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(testTerraformPlanComputedValueInMap)
if actual != expected {
t.Fatalf("bad:\n%s\n\nexpected\n\n%s", actual, expected)
}
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"reflect"
"strconv"
"strings"
"github.com/hashicorp/terraform/config"
@ -143,15 +144,60 @@ func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k)
}
for k, _ := range rc.Raw {
if _, ok := n.VariableValues[k]; !ok {
n.VariableValues[k] = config.UnknownVariableValue
for _, path := range rc.ComputedKeys {
log.Printf("[DEBUG] Setting Unknown Variable Value for computed key: %s", path)
err := n.setUnknownVariableValueForPath(path)
if err != nil {
return nil, err
}
}
return nil, nil
}
func (n *EvalVariableBlock) setUnknownVariableValueForPath(path string) error {
pathComponents := strings.Split(path, ".")
if len(pathComponents) < 1 {
return fmt.Errorf("No path comoponents in %s", path)
}
if len(pathComponents) == 1 {
// Special case the "top level" since we know the type
if _, ok := n.VariableValues[pathComponents[0]]; !ok {
n.VariableValues[pathComponents[0]] = config.UnknownVariableValue
}
return nil
}
// Otherwise find the correct point in the tree and then set to unknown
var current interface{} = n.VariableValues[pathComponents[0]]
for i := 1; i < len(pathComponents); i++ {
switch current.(type) {
case []interface{}, []map[string]interface{}:
tCurrent := current.([]interface{})
index, err := strconv.Atoi(pathComponents[i])
if err != nil {
return fmt.Errorf("Cannot convert %s to slice index in path %s",
pathComponents[i], path)
}
current = tCurrent[index]
case map[string]interface{}:
tCurrent := current.(map[string]interface{})
if val, hasVal := tCurrent[pathComponents[i]]; hasVal {
current = val
continue
}
tCurrent[pathComponents[i]] = config.UnknownVariableValue
break
}
}
return nil
}
// EvalCoerceMapVariable is an EvalNode implementation that recognizes a
// specific ambiguous HCL parsing situation and resolves it. In HCL parsing, a
// bare map literal is indistinguishable from a list of maps w/ one element.

View File

@ -1355,3 +1355,19 @@ aws_instance.foo:
ID = bar
ami = ami-abcd1234
`
const testTerraformPlanComputedValueInMap = `
DIFF:
CREATE: aws_computed_source.intermediates
computed_read_only: "" => "<computed>"
module.test_mod:
CREATE: aws_instance.inner2
looked_up: "" => "<computed>"
type: "" => "aws_instance"
STATE:
<no state>
`

View File

@ -0,0 +1,15 @@
resource "aws_computed_source" "intermediates" {}
module "test_mod" {
source = "./mod"
services {
"exists" = "true"
"elb" = "${aws_computed_source.intermediates.computed_read_only}"
}
services {
"otherexists" = " true"
"elb" = "${aws_computed_source.intermediates.computed_read_only}"
}
}

View File

@ -0,0 +1,8 @@
variable "services" {
type = "list"
}
resource "aws_instance" "inner2" {
looked_up = "${lookup(var.services[0], "elb")}"
}