diff --git a/config/expr.y b/config/expr.y index 58e5fac67..727e0ef59 100644 --- a/config/expr.y +++ b/config/expr.y @@ -1,84 +1,90 @@ -// This is the yacc input for creating the parser for interpolation -// expressions in Go. - -// To build it: -// -// go tool yacc -p "expr" expr.y (produces y.go) -// - -%{ -package config - -import ( - "fmt" -) - -%} - -%union { - expr Interpolation - str string - variable InterpolatedVariable - args []Interpolation -} - -%type args -%type expr -%type string -%type variable - -%token STRING IDENTIFIER -%token COMMA LEFTPAREN RIGHTPAREN - -%% - -top: - expr - { - fmt.Printf("%#v", $1) - } - -expr: - string - { - $$ = &LiteralInterpolation{Literal: $1} - } -| variable - { - $$ = &VariableInterpolation{Variable: $1} - } -| IDENTIFIER LEFTPAREN args RIGHTPAREN - { - $$ = &FunctionInterpolation{Func: $1, Args: $3} - } - -args: - { - $$ = nil - } -| expr COMMA expr - { - $$ = append($$, $1, $3) - } -| expr - { - $$ = append($$, $1) - } - -string: - STRING - { - $$ = $1 - } - -variable: - IDENTIFIER - { - var err error - $$, err = NewInterpolatedVariable($1) - if err != nil { - panic(err) - } - } - -%% +// This is the yacc input for creating the parser for interpolation +// expressions in Go. + +// To build it: +// +// go tool yacc -p "expr" expr.y (produces y.go) +// + +%{ +package config + +import ( + "fmt" +) + +%} + +%union { + expr Interpolation + str string + variable InterpolatedVariable + args []Interpolation +} + +%type args +%type expr +%type string +%type variable + +%token STRING IDENTIFIER +%token COMMA LEFTPAREN RIGHTPAREN + +%% + +top: + expr + { + exprResult = $1 + } + +expr: + string + { + $$ = &LiteralInterpolation{Literal: $1} + } +| variable + { + $$ = &VariableInterpolation{Variable: $1} + } +| IDENTIFIER LEFTPAREN args RIGHTPAREN + { + f, ok := Funcs[$1] + if !ok { + exprErrors = append(exprErrors, fmt.Errorf( + "Unknown function: %s", $1)) + } + + $$ = &FunctionInterpolation{Func: f, Args: $3} + } + +args: + { + $$ = nil + } +| expr COMMA expr + { + $$ = append($$, $1, $3) + } +| expr + { + $$ = append($$, $1) + } + +string: + STRING + { + $$ = $1 + } + +variable: + IDENTIFIER + { + var err error + $$, err = NewInterpolatedVariable($1) + if err != nil { + panic(err) + } + } + +%% diff --git a/config/expr_lex.go b/config/expr_lex.go index 5e8404431..b54a14af8 100644 --- a/config/expr_lex.go +++ b/config/expr_lex.go @@ -58,6 +58,7 @@ func (x *exprLex) lexId(yylval *exprSymType) int { // If this isn't a character we want in an ID, return out. // One day we should make this a regexp. if c != '_' && + c != '-' && c != '.' && c != '*' && !unicode.IsLetter(c) && diff --git a/config/expr_parse.go b/config/expr_parse.go new file mode 100644 index 000000000..0ffb0ba14 --- /dev/null +++ b/config/expr_parse.go @@ -0,0 +1,34 @@ +package config + +import ( + "sync" + + "github.com/hashicorp/terraform/helper/multierror" +) + +// exprErrors are the errors built up from parsing. These should not +// be accessed directly. +var exprErrors []error +var exprLock sync.Mutex +var exprResult Interpolation + +// ExprParse parses the given expression and returns an executable +// Interpolation. +func ExprParse(v string) (Interpolation, error) { + exprLock.Lock() + defer exprLock.Unlock() + exprErrors = nil + exprResult = nil + + // Parse + exprParse(&exprLex{input: v}) + + // Build up the errors + var err error + if len(exprErrors) > 0 { + err = &multierror.Error{Errors: exprErrors} + exprResult = nil + } + + return exprResult, err +} diff --git a/config/interpolate.go b/config/interpolate.go index 64cb90df0..5250627b4 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -17,7 +17,6 @@ var funcRegexp *regexp.Regexp = regexp.MustCompile( // Interpolations might be simple variable references, or it might be // function calls, or even nested function calls. type Interpolation interface { - FullString() string Interpolate(map[string]string) (string, error) Variables() map[string]InterpolatedVariable } @@ -37,26 +36,20 @@ type InterpolatedVariable interface { // FunctionInterpolation is an Interpolation that executes a function // with some variable number of arguments to generate a value. type FunctionInterpolation struct { - Func string + Func InterpolationFunc Args []Interpolation - - key string } // LiteralInterpolation implements Interpolation for literals. Ex: // ${"foo"} will equal "foo". type LiteralInterpolation struct { Literal string - - key string } // VariableInterpolation implements Interpolation for simple variable // interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}" type VariableInterpolation struct { Variable InterpolatedVariable - - key string } // A ResourceVariable is a variable that is referencing the field @@ -82,61 +75,6 @@ type UserVariable struct { key string } -// NewInterpolation takes some string and returns the valid -// Interpolation associated with it, or error if a valid -// interpolation could not be found or the interpolation itself -// is invalid. -func NewInterpolation(v string) (Interpolation, error) { - match := funcRegexp.FindStringSubmatch(v) - if match != nil { - fn, ok := Funcs[match[1]] - if !ok { - return nil, fmt.Errorf( - "%s: Unknown function '%s'", - v, match[1]) - } - - args := make([]InterpolatedVariable, 0, len(match)-2) - for i := 2; i < len(match); i++ { - // This can be empty if we have a single argument - // due to the format of the regexp. - if match[i] == "" { - continue - } - - v, err := NewInterpolatedVariable(match[i]) - if err != nil { - return nil, err - } - - args = append(args, v) - } - - return &FunctionInterpolation{ - Func: fn, - Args: args, - - key: v, - }, nil - } - - if idx := strings.Index(v, "."); idx >= 0 { - v, err := NewInterpolatedVariable(v) - if err != nil { - return nil, err - } - - return &VariableInterpolation{ - Variable: v, - key: v.FullKey(), - }, nil - } - - return nil, fmt.Errorf( - "Interpolation '%s' is not a valid interpolation. " + - "Please check your syntax and try again.") -} - func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { if !strings.HasPrefix(v, "var.") { return NewResourceVariable(v) @@ -145,21 +83,13 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { return NewUserVariable(v) } -func (i *FunctionInterpolation) FullString() string { - return i.key -} - func (i *FunctionInterpolation) Interpolate( vs map[string]string) (string, error) { args := make([]string, len(i.Args)) for idx, a := range i.Args { - k := a.FullKey() - v, ok := vs[k] - if !ok { - return "", fmt.Errorf( - "%s: variable argument value unknown: %s", - i.FullString(), - k) + v, err := a.Interpolate(vs) + if err != nil { + return "", err } args[idx] = v @@ -171,21 +101,14 @@ func (i *FunctionInterpolation) Interpolate( func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable { result := make(map[string]InterpolatedVariable) for _, a := range i.Args { - k := a.FullKey() - if _, ok := result[k]; ok { - continue + for k, v := range a.Variables() { + result[k] = v } - - result[k] = a } return result } -func (i *LiteralInterpolation) FullString() string { - return i.key -} - func (i *LiteralInterpolation) Interpolate( map[string]string) (string, error) { return i.Literal, nil @@ -195,24 +118,20 @@ func (i *LiteralInterpolation) Variables() map[string]InterpolatedVariable { return nil } -func (i *VariableInterpolation) FullString() string { - return i.key -} - func (i *VariableInterpolation) Interpolate( vs map[string]string) (string, error) { - v, ok := vs[i.key] + v, ok := vs[i.Variable.FullKey()] if !ok { return "", fmt.Errorf( "%s: value for variable not found", - i.key) + i.Variable.FullKey()) } return v, nil } func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable { - return map[string]InterpolatedVariable{i.key: i.Variable} + return map[string]InterpolatedVariable{i.Variable.FullKey(): i.Variable} } func NewResourceVariable(key string) (*ResourceVariable, error) { diff --git a/config/interpolate_test.go b/config/interpolate_test.go index 20d72f18a..77188081e 100644 --- a/config/interpolate_test.go +++ b/config/interpolate_test.go @@ -6,6 +6,7 @@ import ( "testing" ) +/* func TestNewInterpolation(t *testing.T) { cases := []struct { Input string @@ -67,6 +68,7 @@ func TestNewInterpolation(t *testing.T) { } } } +*/ func TestNewInterpolatedVariable(t *testing.T) { cases := []struct { @@ -171,11 +173,10 @@ func TestFunctionInterpolation(t *testing.T) { i := &FunctionInterpolation{ Func: fn, - Args: []InterpolatedVariable{v1, v2}, - key: "foo", - } - if i.FullString() != "foo" { - t.Fatalf("err: %#v", i) + Args: []Interpolation{ + &VariableInterpolation{Variable: v1}, + &VariableInterpolation{Variable: v2}, + }, } expected := map[string]InterpolatedVariable{ @@ -206,10 +207,6 @@ func TestLiteralInterpolation_impl(t *testing.T) { func TestLiteralInterpolation(t *testing.T) { i := &LiteralInterpolation{ Literal: "bar", - key: "foo", - } - if i.FullString() != "foo" { - t.Fatalf("err: %#v", i) } if i.Variables() != nil { @@ -292,10 +289,7 @@ func TestVariableInterpolation(t *testing.T) { t.Fatalf("err: %s", err) } - i := &VariableInterpolation{Variable: uv, key: "var.foo"} - if i.FullString() != "var.foo" { - t.Fatalf("err: %#v", i) - } + i := &VariableInterpolation{Variable: uv} expected := map[string]InterpolatedVariable{"var.foo": uv} if !reflect.DeepEqual(i.Variables(), expected) { @@ -320,7 +314,7 @@ func TestVariableInterpolation_missing(t *testing.T) { t.Fatalf("err: %s", err) } - i := &VariableInterpolation{Variable: uv, key: "var.foo"} + i := &VariableInterpolation{Variable: uv} _, err = i.Interpolate(map[string]string{ "var.bar": "bar", }) diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index fd8a70833..83fa6c222 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -95,7 +95,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error { // Interpolation found, instantiate it key := match[2] - i, err := NewInterpolation(key) + i, err := ExprParse(key) if err != nil { return err } diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index a731f8615..c27770e59 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -29,7 +29,6 @@ func TestInterpolationWalker_detect(t *testing.T) { Name: "foo", key: "var.foo", }, - key: "var.foo", }, }, }, @@ -41,13 +40,14 @@ func TestInterpolationWalker_detect(t *testing.T) { Result: []Interpolation{ &FunctionInterpolation{ Func: nil, - Args: []InterpolatedVariable{ - &UserVariable{ - Name: "foo", - key: "var.foo", + Args: []Interpolation{ + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "foo", + key: "var.foo", + }, }, }, - key: "lookup(var.foo)", }, }, },