diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index 143b96131a..720a8b285f 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -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 diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 9ad2d1f194..09ed1bd82b 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -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) + } +} diff --git a/terraform/eval_variable.go b/terraform/eval_variable.go index ce6064706c..47bd2ea2b6 100644 --- a/terraform/eval_variable.go +++ b/terraform/eval_variable.go @@ -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. diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index c608d3d73e..333e72767d 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -1355,3 +1355,19 @@ aws_instance.foo: ID = bar ami = ami-abcd1234 ` + +const testTerraformPlanComputedValueInMap = ` +DIFF: + +CREATE: aws_computed_source.intermediates + computed_read_only: "" => "" + +module.test_mod: + CREATE: aws_instance.inner2 + looked_up: "" => "" + type: "" => "aws_instance" + +STATE: + + +` diff --git a/terraform/test-fixtures/plan-computed-value-in-map/main.tf b/terraform/test-fixtures/plan-computed-value-in-map/main.tf new file mode 100644 index 0000000000..b820b1705a --- /dev/null +++ b/terraform/test-fixtures/plan-computed-value-in-map/main.tf @@ -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}" + } +} diff --git a/terraform/test-fixtures/plan-computed-value-in-map/mod/main.tf b/terraform/test-fixtures/plan-computed-value-in-map/mod/main.tf new file mode 100644 index 0000000000..82ee1e4944 --- /dev/null +++ b/terraform/test-fixtures/plan-computed-value-in-map/mod/main.tf @@ -0,0 +1,8 @@ +variable "services" { + type = "list" +} + +resource "aws_instance" "inner2" { + looked_up = "${lookup(var.services[0], "elb")}" +} +