config: if any var is computed, the entire interpolation is computed

This commit is contained in:
Mitchell Hashimoto 2015-02-27 22:47:43 -08:00
parent 2feaebdca5
commit 5e25dc54a7
4 changed files with 61 additions and 33 deletions

View File

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

View File

@ -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}

View File

@ -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

View File

@ -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}",