config: if any var is computed, the entire interpolation is computed
This commit is contained in:
parent
2feaebdca5
commit
5e25dc54a7
|
@ -152,12 +152,12 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
||||||
if w.loc == reflectwalk.SliceElem {
|
if w.loc == reflectwalk.SliceElem {
|
||||||
parts := strings.Split(replaceVal, InterpSplitDelim)
|
parts := strings.Split(replaceVal, InterpSplitDelim)
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
if strings.Contains(p, UnknownVariableValue) {
|
if p == UnknownVariableValue {
|
||||||
remove = true
|
remove = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if strings.Contains(replaceVal, UnknownVariableValue) {
|
} else if replaceVal == UnknownVariableValue {
|
||||||
remove = true
|
remove = true
|
||||||
}
|
}
|
||||||
if remove {
|
if remove {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/lang"
|
|
||||||
"github.com/hashicorp/terraform/config/lang/ast"
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
@ -125,7 +124,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
"foo": "hello, ${var.foo}",
|
"foo": "hello, ${var.foo}",
|
||||||
},
|
},
|
||||||
Output: map[string]interface{}{
|
Output: map[string]interface{}{
|
||||||
"foo": "hello, bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
Value: "bar",
|
Value: "bar",
|
||||||
},
|
},
|
||||||
|
@ -171,38 +170,11 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
Output: map[string]interface{}{},
|
Output: map[string]interface{}{},
|
||||||
Value: UnknownVariableValue + InterpSplitDelim + "baz",
|
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 {
|
for i, tc := range cases {
|
||||||
config := &lang.EvalConfig{
|
fn := func(ast.Node) (string, error) {
|
||||||
GlobalScope: &ast.BasicScope{
|
return tc.Value, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &interpolationWalker{F: fn, Replace: true}
|
w := &interpolationWalker{F: fn, Replace: true}
|
||||||
|
|
|
@ -83,6 +83,29 @@ func (r *RawConfig) Config() map[string]interface{} {
|
||||||
func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
||||||
config := langEvalConfig(vs)
|
config := langEvalConfig(vs)
|
||||||
return r.interpolate(func(root ast.Node) (string, error) {
|
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)
|
out, _, err := lang.Eval(root, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -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) {
|
func TestRawConfigValue(t *testing.T) {
|
||||||
raw := map[string]interface{}{
|
raw := map[string]interface{}{
|
||||||
"foo": "${var.bar}",
|
"foo": "${var.bar}",
|
||||||
|
|
Loading…
Reference in New Issue