diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 65cebbfa13..8259135887 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -81,7 +81,7 @@ func interpolationFuncJoin() lang.Function { // interpolationFuncLookup implements the "lookup" function that allows // dynamic lookups of map types within a Terraform configuration. -func interpolationFuncLookup(vs map[string]string) lang.Function { +func interpolationFuncLookup(vs map[string]lang.Variable) lang.Function { return lang.Function{ ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, ReturnType: ast.TypeString, @@ -93,8 +93,13 @@ func interpolationFuncLookup(vs map[string]string) lang.Function { "lookup in '%s' failed to find '%s'", args[0].(string), args[1].(string)) } + if v.Type != ast.TypeString { + return "", fmt.Errorf( + "lookup in '%s' for '%s' has bad type %s", + args[0].(string), args[1].(string), v.Type) + } - return v, nil + return v.Value.(string), nil }, } } diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 757f8aa5bb..68fce1098c 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/hashicorp/terraform/config/lang" + "github.com/hashicorp/terraform/config/lang/ast" ) func TestInterpolateFuncConcat(t *testing.T) { @@ -108,7 +109,12 @@ func TestInterpolateFuncJoin(t *testing.T) { func TestInterpolateFuncLookup(t *testing.T) { testFunction(t, testFunctionConfig{ - Vars: map[string]string{"var.foo.bar": "baz"}, + Vars: map[string]lang.Variable{ + "var.foo.bar": lang.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + }, Cases: []testFunctionCase{ { `${lookup("foo", "bar")}`, @@ -170,7 +176,7 @@ func TestInterpolateFuncElement(t *testing.T) { type testFunctionConfig struct { Cases []testFunctionCase - Vars map[string]string + Vars map[string]lang.Variable } type testFunctionCase struct { diff --git a/config/lang/parse_test.go b/config/lang/parse_test.go index 05b37ac18a..cf7163271c 100644 --- a/config/lang/parse_test.go +++ b/config/lang/parse_test.go @@ -251,6 +251,12 @@ func TestParse(t *testing.T) { true, nil, }, + + { + "${var", + true, + nil, + }, } for _, tc := range cases { diff --git a/config/raw_config.go b/config/raw_config.go index 0e19391849..1fed79dfea 100644 --- a/config/raw_config.go +++ b/config/raw_config.go @@ -80,7 +80,7 @@ func (r *RawConfig) Config() map[string]interface{} { // Any prior calls to Interpolate are replaced with this one. // // If a variable key is missing, this will panic. -func (r *RawConfig) Interpolate(vs map[string]string) error { +func (r *RawConfig) Interpolate(vs map[string]lang.Variable) error { engine := langEngine(vs) return r.interpolate(func(root ast.Node) (string, error) { out, _, err := engine.Execute(root) @@ -203,12 +203,7 @@ type gobRawConfig struct { } // langEngine returns the lang.Engine to use for evaluating configurations. -func langEngine(vs map[string]string) *lang.Engine { - varMap := make(map[string]lang.Variable) - for k, v := range vs { - varMap[k] = lang.Variable{Value: v, Type: ast.TypeString} - } - +func langEngine(vs map[string]lang.Variable) *lang.Engine { funcMap := make(map[string]lang.Function) for k, v := range Funcs { funcMap[k] = v @@ -217,7 +212,7 @@ func langEngine(vs map[string]string) *lang.Engine { return &lang.Engine{ GlobalScope: &lang.Scope{ - VarMap: varMap, + VarMap: vs, FuncMap: funcMap, }, } diff --git a/config/raw_config_test.go b/config/raw_config_test.go index a17112552e..726c6c9550 100644 --- a/config/raw_config_test.go +++ b/config/raw_config_test.go @@ -4,6 +4,9 @@ import ( "encoding/gob" "reflect" "testing" + + "github.com/hashicorp/terraform/config/lang" + "github.com/hashicorp/terraform/config/lang/ast" ) func TestNewRawConfig(t *testing.T) { @@ -40,7 +43,12 @@ func TestRawConfig(t *testing.T) { t.Fatalf("bad: %#v", rc.Config()) } - vars := map[string]string{"var.bar": "baz"} + vars := map[string]lang.Variable{ + "var.bar": lang.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } @@ -68,7 +76,12 @@ func TestRawConfig_double(t *testing.T) { t.Fatalf("err: %s", err) } - vars := map[string]string{"var.bar": "baz"} + vars := map[string]lang.Variable{ + "var.bar": lang.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } @@ -82,7 +95,12 @@ func TestRawConfig_double(t *testing.T) { t.Fatalf("bad: %#v", actual) } - vars = map[string]string{"var.bar": "what"} + vars = map[string]lang.Variable{ + "var.bar": lang.Variable{ + Value: "what", + Type: ast.TypeString, + }, + } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } @@ -97,6 +115,16 @@ func TestRawConfig_double(t *testing.T) { } } +func TestRawConfig_syntax(t *testing.T) { + raw := map[string]interface{}{ + "foo": "${var", + } + + if _, err := NewRawConfig(raw); err == nil { + t.Fatal("should error") + } +} + func TestRawConfig_unknown(t *testing.T) { raw := map[string]interface{}{ "foo": "${var.bar}", @@ -107,7 +135,12 @@ func TestRawConfig_unknown(t *testing.T) { t.Fatalf("err: %s", err) } - vars := map[string]string{"var.bar": UnknownVariableValue} + vars := map[string]lang.Variable{ + "var.bar": lang.Variable{ + Value: UnknownVariableValue, + Type: ast.TypeString, + }, + } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } @@ -145,7 +178,12 @@ func TestRawConfigValue(t *testing.T) { t.Fatalf("err: %#v", rc.Value()) } - vars := map[string]string{"var.bar": "baz"} + vars := map[string]lang.Variable{ + "var.bar": lang.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } diff --git a/terraform/context.go b/terraform/context.go index 2f913eab37..36db6c466d 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -11,6 +11,8 @@ import ( "sync/atomic" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/lang" + "github.com/hashicorp/terraform/config/lang/ast" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/depgraph" "github.com/hashicorp/terraform/helper/multierror" @@ -1520,9 +1522,12 @@ func (c *walkContext) computeVars( } // Copy the default variables - vs := make(map[string]string) + vs := make(map[string]lang.Variable) for k, v := range c.defaultVariables { - vs[k] = v + vs[k] = lang.Variable{ + Value: v, + Type: ast.TypeString, + } } // Next, the actual computed variables @@ -1532,12 +1537,18 @@ func (c *walkContext) computeVars( switch v.Type { case config.CountValueIndex: if r != nil { - vs[n] = strconv.FormatInt(int64(r.CountIndex), 10) + vs[n] = lang.Variable{ + Value: int(r.CountIndex), + Type: ast.TypeInt, + } } } case *config.ModuleVariable: if c.Operation == walkValidate { - vs[n] = config.UnknownVariableValue + vs[n] = lang.Variable{ + Value: config.UnknownVariableValue, + Type: ast.TypeString, + } continue } @@ -1546,7 +1557,10 @@ func (c *walkContext) computeVars( return err } - vs[n] = value + vs[n] = lang.Variable{ + Value: value, + Type: ast.TypeString, + } case *config.PathVariable: switch v.Type { case config.PathValueCwd: @@ -1557,17 +1571,29 @@ func (c *walkContext) computeVars( v.FullKey(), err) } - vs[n] = wd + vs[n] = lang.Variable{ + Value: wd, + Type: ast.TypeString, + } case config.PathValueModule: if t := c.Context.module.Child(c.Path[1:]); t != nil { - vs[n] = t.Config().Dir + vs[n] = lang.Variable{ + Value: t.Config().Dir, + Type: ast.TypeString, + } } case config.PathValueRoot: - vs[n] = c.Context.module.Config().Dir + vs[n] = lang.Variable{ + Value: c.Context.module.Config().Dir, + Type: ast.TypeString, + } } case *config.ResourceVariable: if c.Operation == walkValidate { - vs[n] = config.UnknownVariableValue + vs[n] = lang.Variable{ + Value: config.UnknownVariableValue, + Type: ast.TypeString, + } continue } @@ -1582,16 +1608,25 @@ func (c *walkContext) computeVars( return err } - vs[n] = attr + vs[n] = lang.Variable{ + Value: attr, + Type: ast.TypeString, + } case *config.UserVariable: val, ok := c.Variables[v.Name] if ok { - vs[n] = val + vs[n] = lang.Variable{ + Value: val, + Type: ast.TypeString, + } continue } if _, ok := vs[n]; !ok && c.Operation == walkValidate { - vs[n] = config.UnknownVariableValue + vs[n] = lang.Variable{ + Value: config.UnknownVariableValue, + Type: ast.TypeString, + } continue } @@ -1599,7 +1634,10 @@ func (c *walkContext) computeVars( // those are map overrides. Include those. for k, val := range c.Variables { if strings.HasPrefix(k, v.Name+".") { - vs["var."+k] = val + vs["var."+k] = lang.Variable{ + Value: val, + Type: ast.TypeString, + } } } }