diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index 3cbb3e26f..17329e5a8 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -152,12 +152,12 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error { if w.loc == reflectwalk.SliceElem { parts := strings.Split(replaceVal, InterpSplitDelim) for _, p := range parts { - if strings.Contains(p, UnknownVariableValue) { + if p == UnknownVariableValue { remove = true break } } - } else if strings.Contains(replaceVal, UnknownVariableValue) { + } else if replaceVal == UnknownVariableValue { remove = true } if remove { diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index ce0671e0a..9b2c34133 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "github.com/hashicorp/terraform/config/lang" "github.com/hashicorp/terraform/config/lang/ast" "github.com/mitchellh/reflectwalk" ) @@ -125,7 +124,7 @@ func TestInterpolationWalker_replace(t *testing.T) { "foo": "hello, ${var.foo}", }, Output: map[string]interface{}{ - "foo": "hello, bar", + "foo": "bar", }, Value: "bar", }, @@ -171,38 +170,11 @@ func TestInterpolationWalker_replace(t *testing.T) { Output: map[string]interface{}{}, Value: UnknownVariableValue + InterpSplitDelim + "baz", }, - - { - Input: map[string]interface{}{ - "foo": []interface{}{ - "${var.foo}/32", - "bing", - }, - }, - Output: map[string]interface{}{}, - Value: UnknownVariableValue, - }, } for i, tc := range cases { - config := &lang.EvalConfig{ - GlobalScope: &ast.BasicScope{ - VarMap: map[string]ast.Variable{ - "var.foo": ast.Variable{ - Value: tc.Value, - Type: ast.TypeString, - }, - }, - }, - } - - fn := func(root ast.Node) (string, error) { - value, _, err := lang.Eval(root, config) - if err != nil { - return "", err - } - - return value.(string), nil + fn := func(ast.Node) (string, error) { + return tc.Value, nil } w := &interpolationWalker{F: fn, Replace: true} diff --git a/config/raw_config.go b/config/raw_config.go index f8fcc2ca7..181a95f61 100644 --- a/config/raw_config.go +++ b/config/raw_config.go @@ -83,6 +83,29 @@ func (r *RawConfig) Config() map[string]interface{} { func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error { config := langEvalConfig(vs) return r.interpolate(func(root ast.Node) (string, error) { + // We detect the variables again and check if the value of any + // of the variables is the computed value. If it is, then we + // treat this entire value as computed. + // + // We have to do this here before the `lang.Eval` because + // if any of the variables it depends on are computed, then + // the interpolation can fail at runtime for other reasons. Example: + // `${count.index+1}`: in a world where `count.index` is computed, + // this would fail a type check since the computed placeholder is + // a string, but realistically the whole value is just computed. + vars, err := DetectVariables(root) + if err != nil { + return "", err + } + for _, v := range vars { + varVal, ok := vs[v.FullKey()] + if ok && varVal.Value == UnknownVariableValue { + return UnknownVariableValue, nil + } + } + + // None of the variables we need are computed, meaning we should + // be able to properly evaluate. out, _, err := lang.Eval(root, config) if err != nil { return "", err diff --git a/config/raw_config_test.go b/config/raw_config_test.go index 324f98ee6..1b5de3e16 100644 --- a/config/raw_config_test.go +++ b/config/raw_config_test.go @@ -238,6 +238,39 @@ func TestRawConfig_unknown(t *testing.T) { } } +func TestRawConfig_unknownPartial(t *testing.T) { + raw := map[string]interface{}{ + "foo": "${var.bar}/32", + } + + rc, err := NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + vars := map[string]ast.Variable{ + "var.bar": ast.Variable{ + Value: UnknownVariableValue, + Type: ast.TypeString, + }, + } + if err := rc.Interpolate(vars); err != nil { + t.Fatalf("err: %s", err) + } + + actual := rc.Config() + expected := map[string]interface{}{} + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } + + expectedKeys := []string{"foo"} + if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) { + t.Fatalf("bad: %#v", rc.UnknownKeys()) + } +} + func TestRawConfigValue(t *testing.T) { raw := map[string]interface{}{ "foo": "${var.bar}",