config: variables must be typed

This commit is contained in:
Mitchell Hashimoto 2015-01-14 10:40:43 -08:00
parent dd456871e9
commit 8ae14f06b3
6 changed files with 118 additions and 30 deletions

View File

@ -81,7 +81,7 @@ func interpolationFuncJoin() lang.Function {
// interpolationFuncLookup implements the "lookup" function that allows // interpolationFuncLookup implements the "lookup" function that allows
// dynamic lookups of map types within a Terraform configuration. // 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{ return lang.Function{
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
ReturnType: ast.TypeString, ReturnType: ast.TypeString,
@ -93,8 +93,13 @@ func interpolationFuncLookup(vs map[string]string) lang.Function {
"lookup in '%s' failed to find '%s'", "lookup in '%s' failed to find '%s'",
args[0].(string), args[1].(string)) 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
}, },
} }
} }

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/config/lang" "github.com/hashicorp/terraform/config/lang"
"github.com/hashicorp/terraform/config/lang/ast"
) )
func TestInterpolateFuncConcat(t *testing.T) { func TestInterpolateFuncConcat(t *testing.T) {
@ -108,7 +109,12 @@ func TestInterpolateFuncJoin(t *testing.T) {
func TestInterpolateFuncLookup(t *testing.T) { func TestInterpolateFuncLookup(t *testing.T) {
testFunction(t, testFunctionConfig{ 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{ Cases: []testFunctionCase{
{ {
`${lookup("foo", "bar")}`, `${lookup("foo", "bar")}`,
@ -170,7 +176,7 @@ func TestInterpolateFuncElement(t *testing.T) {
type testFunctionConfig struct { type testFunctionConfig struct {
Cases []testFunctionCase Cases []testFunctionCase
Vars map[string]string Vars map[string]lang.Variable
} }
type testFunctionCase struct { type testFunctionCase struct {

View File

@ -251,6 +251,12 @@ func TestParse(t *testing.T) {
true, true,
nil, nil,
}, },
{
"${var",
true,
nil,
},
} }
for _, tc := range cases { for _, tc := range cases {

View File

@ -80,7 +80,7 @@ func (r *RawConfig) Config() map[string]interface{} {
// Any prior calls to Interpolate are replaced with this one. // Any prior calls to Interpolate are replaced with this one.
// //
// If a variable key is missing, this will panic. // 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) engine := langEngine(vs)
return r.interpolate(func(root ast.Node) (string, error) { return r.interpolate(func(root ast.Node) (string, error) {
out, _, err := engine.Execute(root) out, _, err := engine.Execute(root)
@ -203,12 +203,7 @@ type gobRawConfig struct {
} }
// langEngine returns the lang.Engine to use for evaluating configurations. // langEngine returns the lang.Engine to use for evaluating configurations.
func langEngine(vs map[string]string) *lang.Engine { func langEngine(vs map[string]lang.Variable) *lang.Engine {
varMap := make(map[string]lang.Variable)
for k, v := range vs {
varMap[k] = lang.Variable{Value: v, Type: ast.TypeString}
}
funcMap := make(map[string]lang.Function) funcMap := make(map[string]lang.Function)
for k, v := range Funcs { for k, v := range Funcs {
funcMap[k] = v funcMap[k] = v
@ -217,7 +212,7 @@ func langEngine(vs map[string]string) *lang.Engine {
return &lang.Engine{ return &lang.Engine{
GlobalScope: &lang.Scope{ GlobalScope: &lang.Scope{
VarMap: varMap, VarMap: vs,
FuncMap: funcMap, FuncMap: funcMap,
}, },
} }

View File

@ -4,6 +4,9 @@ import (
"encoding/gob" "encoding/gob"
"reflect" "reflect"
"testing" "testing"
"github.com/hashicorp/terraform/config/lang"
"github.com/hashicorp/terraform/config/lang/ast"
) )
func TestNewRawConfig(t *testing.T) { func TestNewRawConfig(t *testing.T) {
@ -40,7 +43,12 @@ func TestRawConfig(t *testing.T) {
t.Fatalf("bad: %#v", rc.Config()) 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 { if err := rc.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -68,7 +76,12 @@ func TestRawConfig_double(t *testing.T) {
t.Fatalf("err: %s", err) 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 { if err := rc.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -82,7 +95,12 @@ func TestRawConfig_double(t *testing.T) {
t.Fatalf("bad: %#v", actual) 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 { if err := rc.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err) 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) { func TestRawConfig_unknown(t *testing.T) {
raw := map[string]interface{}{ raw := map[string]interface{}{
"foo": "${var.bar}", "foo": "${var.bar}",
@ -107,7 +135,12 @@ func TestRawConfig_unknown(t *testing.T) {
t.Fatalf("err: %s", err) 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 { if err := rc.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -145,7 +178,12 @@ func TestRawConfigValue(t *testing.T) {
t.Fatalf("err: %#v", rc.Value()) 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 { if err := rc.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }

View File

@ -11,6 +11,8 @@ import (
"sync/atomic" "sync/atomic"
"github.com/hashicorp/terraform/config" "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/config/module"
"github.com/hashicorp/terraform/depgraph" "github.com/hashicorp/terraform/depgraph"
"github.com/hashicorp/terraform/helper/multierror" "github.com/hashicorp/terraform/helper/multierror"
@ -1520,9 +1522,12 @@ func (c *walkContext) computeVars(
} }
// Copy the default variables // Copy the default variables
vs := make(map[string]string) vs := make(map[string]lang.Variable)
for k, v := range c.defaultVariables { for k, v := range c.defaultVariables {
vs[k] = v vs[k] = lang.Variable{
Value: v,
Type: ast.TypeString,
}
} }
// Next, the actual computed variables // Next, the actual computed variables
@ -1532,12 +1537,18 @@ func (c *walkContext) computeVars(
switch v.Type { switch v.Type {
case config.CountValueIndex: case config.CountValueIndex:
if r != nil { 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: case *config.ModuleVariable:
if c.Operation == walkValidate { if c.Operation == walkValidate {
vs[n] = config.UnknownVariableValue vs[n] = lang.Variable{
Value: config.UnknownVariableValue,
Type: ast.TypeString,
}
continue continue
} }
@ -1546,7 +1557,10 @@ func (c *walkContext) computeVars(
return err return err
} }
vs[n] = value vs[n] = lang.Variable{
Value: value,
Type: ast.TypeString,
}
case *config.PathVariable: case *config.PathVariable:
switch v.Type { switch v.Type {
case config.PathValueCwd: case config.PathValueCwd:
@ -1557,17 +1571,29 @@ func (c *walkContext) computeVars(
v.FullKey(), err) v.FullKey(), err)
} }
vs[n] = wd vs[n] = lang.Variable{
Value: wd,
Type: ast.TypeString,
}
case config.PathValueModule: case config.PathValueModule:
if t := c.Context.module.Child(c.Path[1:]); t != nil { 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: 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: case *config.ResourceVariable:
if c.Operation == walkValidate { if c.Operation == walkValidate {
vs[n] = config.UnknownVariableValue vs[n] = lang.Variable{
Value: config.UnknownVariableValue,
Type: ast.TypeString,
}
continue continue
} }
@ -1582,16 +1608,25 @@ func (c *walkContext) computeVars(
return err return err
} }
vs[n] = attr vs[n] = lang.Variable{
Value: attr,
Type: ast.TypeString,
}
case *config.UserVariable: case *config.UserVariable:
val, ok := c.Variables[v.Name] val, ok := c.Variables[v.Name]
if ok { if ok {
vs[n] = val vs[n] = lang.Variable{
Value: val,
Type: ast.TypeString,
}
continue continue
} }
if _, ok := vs[n]; !ok && c.Operation == walkValidate { if _, ok := vs[n]; !ok && c.Operation == walkValidate {
vs[n] = config.UnknownVariableValue vs[n] = lang.Variable{
Value: config.UnknownVariableValue,
Type: ast.TypeString,
}
continue continue
} }
@ -1599,7 +1634,10 @@ func (c *walkContext) computeVars(
// those are map overrides. Include those. // those are map overrides. Include those.
for k, val := range c.Variables { for k, val := range c.Variables {
if strings.HasPrefix(k, v.Name+".") { if strings.HasPrefix(k, v.Name+".") {
vs["var."+k] = val vs["var."+k] = lang.Variable{
Value: val,
Type: ast.TypeString,
}
} }
} }
} }