config: parser fixes and application

This commit is contained in:
Mitchell Hashimoto 2014-07-22 15:59:53 -07:00
parent bffdcbaede
commit 4f57437144
7 changed files with 149 additions and 195 deletions

View File

@ -1,84 +1,90 @@
// This is the yacc input for creating the parser for interpolation // This is the yacc input for creating the parser for interpolation
// expressions in Go. // expressions in Go.
// To build it: // To build it:
// //
// go tool yacc -p "expr" expr.y (produces y.go) // go tool yacc -p "expr" expr.y (produces y.go)
// //
%{ %{
package config package config
import ( import (
"fmt" "fmt"
) )
%} %}
%union { %union {
expr Interpolation expr Interpolation
str string str string
variable InterpolatedVariable variable InterpolatedVariable
args []Interpolation args []Interpolation
} }
%type <args> args %type <args> args
%type <expr> expr %type <expr> expr
%type <str> string %type <str> string
%type <variable> variable %type <variable> variable
%token <str> STRING IDENTIFIER %token <str> STRING IDENTIFIER
%token <str> COMMA LEFTPAREN RIGHTPAREN %token <str> COMMA LEFTPAREN RIGHTPAREN
%% %%
top: top:
expr expr
{ {
fmt.Printf("%#v", $1) exprResult = $1
} }
expr: expr:
string string
{ {
$$ = &LiteralInterpolation{Literal: $1} $$ = &LiteralInterpolation{Literal: $1}
} }
| variable | variable
{ {
$$ = &VariableInterpolation{Variable: $1} $$ = &VariableInterpolation{Variable: $1}
} }
| IDENTIFIER LEFTPAREN args RIGHTPAREN | IDENTIFIER LEFTPAREN args RIGHTPAREN
{ {
$$ = &FunctionInterpolation{Func: $1, Args: $3} f, ok := Funcs[$1]
} if !ok {
exprErrors = append(exprErrors, fmt.Errorf(
args: "Unknown function: %s", $1))
{ }
$$ = nil
} $$ = &FunctionInterpolation{Func: f, Args: $3}
| expr COMMA expr }
{
$$ = append($$, $1, $3) args:
} {
| expr $$ = nil
{ }
$$ = append($$, $1) | expr COMMA expr
} {
$$ = append($$, $1, $3)
string: }
STRING | expr
{ {
$$ = $1 $$ = append($$, $1)
} }
variable: string:
IDENTIFIER STRING
{ {
var err error $$ = $1
$$, err = NewInterpolatedVariable($1) }
if err != nil {
panic(err) variable:
} IDENTIFIER
} {
var err error
%% $$, err = NewInterpolatedVariable($1)
if err != nil {
panic(err)
}
}
%%

View File

@ -58,6 +58,7 @@ func (x *exprLex) lexId(yylval *exprSymType) int {
// If this isn't a character we want in an ID, return out. // If this isn't a character we want in an ID, return out.
// One day we should make this a regexp. // One day we should make this a regexp.
if c != '_' && if c != '_' &&
c != '-' &&
c != '.' && c != '.' &&
c != '*' && c != '*' &&
!unicode.IsLetter(c) && !unicode.IsLetter(c) &&

34
config/expr_parse.go Normal file
View File

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

View File

@ -17,7 +17,6 @@ var funcRegexp *regexp.Regexp = regexp.MustCompile(
// Interpolations might be simple variable references, or it might be // Interpolations might be simple variable references, or it might be
// function calls, or even nested function calls. // function calls, or even nested function calls.
type Interpolation interface { type Interpolation interface {
FullString() string
Interpolate(map[string]string) (string, error) Interpolate(map[string]string) (string, error)
Variables() map[string]InterpolatedVariable Variables() map[string]InterpolatedVariable
} }
@ -37,26 +36,20 @@ type InterpolatedVariable interface {
// FunctionInterpolation is an Interpolation that executes a function // FunctionInterpolation is an Interpolation that executes a function
// with some variable number of arguments to generate a value. // with some variable number of arguments to generate a value.
type FunctionInterpolation struct { type FunctionInterpolation struct {
Func string Func InterpolationFunc
Args []Interpolation Args []Interpolation
key string
} }
// LiteralInterpolation implements Interpolation for literals. Ex: // LiteralInterpolation implements Interpolation for literals. Ex:
// ${"foo"} will equal "foo". // ${"foo"} will equal "foo".
type LiteralInterpolation struct { type LiteralInterpolation struct {
Literal string Literal string
key string
} }
// VariableInterpolation implements Interpolation for simple variable // VariableInterpolation implements Interpolation for simple variable
// interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}" // interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}"
type VariableInterpolation struct { type VariableInterpolation struct {
Variable InterpolatedVariable Variable InterpolatedVariable
key string
} }
// A ResourceVariable is a variable that is referencing the field // A ResourceVariable is a variable that is referencing the field
@ -82,61 +75,6 @@ type UserVariable struct {
key string 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) { func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
if !strings.HasPrefix(v, "var.") { if !strings.HasPrefix(v, "var.") {
return NewResourceVariable(v) return NewResourceVariable(v)
@ -145,21 +83,13 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
return NewUserVariable(v) return NewUserVariable(v)
} }
func (i *FunctionInterpolation) FullString() string {
return i.key
}
func (i *FunctionInterpolation) Interpolate( func (i *FunctionInterpolation) Interpolate(
vs map[string]string) (string, error) { vs map[string]string) (string, error) {
args := make([]string, len(i.Args)) args := make([]string, len(i.Args))
for idx, a := range i.Args { for idx, a := range i.Args {
k := a.FullKey() v, err := a.Interpolate(vs)
v, ok := vs[k] if err != nil {
if !ok { return "", err
return "", fmt.Errorf(
"%s: variable argument value unknown: %s",
i.FullString(),
k)
} }
args[idx] = v args[idx] = v
@ -171,21 +101,14 @@ func (i *FunctionInterpolation) Interpolate(
func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable { func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable {
result := make(map[string]InterpolatedVariable) result := make(map[string]InterpolatedVariable)
for _, a := range i.Args { for _, a := range i.Args {
k := a.FullKey() for k, v := range a.Variables() {
if _, ok := result[k]; ok { result[k] = v
continue
} }
result[k] = a
} }
return result return result
} }
func (i *LiteralInterpolation) FullString() string {
return i.key
}
func (i *LiteralInterpolation) Interpolate( func (i *LiteralInterpolation) Interpolate(
map[string]string) (string, error) { map[string]string) (string, error) {
return i.Literal, nil return i.Literal, nil
@ -195,24 +118,20 @@ func (i *LiteralInterpolation) Variables() map[string]InterpolatedVariable {
return nil return nil
} }
func (i *VariableInterpolation) FullString() string {
return i.key
}
func (i *VariableInterpolation) Interpolate( func (i *VariableInterpolation) Interpolate(
vs map[string]string) (string, error) { vs map[string]string) (string, error) {
v, ok := vs[i.key] v, ok := vs[i.Variable.FullKey()]
if !ok { if !ok {
return "", fmt.Errorf( return "", fmt.Errorf(
"%s: value for variable not found", "%s: value for variable not found",
i.key) i.Variable.FullKey())
} }
return v, nil return v, nil
} }
func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable { 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) { func NewResourceVariable(key string) (*ResourceVariable, error) {

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
) )
/*
func TestNewInterpolation(t *testing.T) { func TestNewInterpolation(t *testing.T) {
cases := []struct { cases := []struct {
Input string Input string
@ -67,6 +68,7 @@ func TestNewInterpolation(t *testing.T) {
} }
} }
} }
*/
func TestNewInterpolatedVariable(t *testing.T) { func TestNewInterpolatedVariable(t *testing.T) {
cases := []struct { cases := []struct {
@ -171,11 +173,10 @@ func TestFunctionInterpolation(t *testing.T) {
i := &FunctionInterpolation{ i := &FunctionInterpolation{
Func: fn, Func: fn,
Args: []InterpolatedVariable{v1, v2}, Args: []Interpolation{
key: "foo", &VariableInterpolation{Variable: v1},
} &VariableInterpolation{Variable: v2},
if i.FullString() != "foo" { },
t.Fatalf("err: %#v", i)
} }
expected := map[string]InterpolatedVariable{ expected := map[string]InterpolatedVariable{
@ -206,10 +207,6 @@ func TestLiteralInterpolation_impl(t *testing.T) {
func TestLiteralInterpolation(t *testing.T) { func TestLiteralInterpolation(t *testing.T) {
i := &LiteralInterpolation{ i := &LiteralInterpolation{
Literal: "bar", Literal: "bar",
key: "foo",
}
if i.FullString() != "foo" {
t.Fatalf("err: %#v", i)
} }
if i.Variables() != nil { if i.Variables() != nil {
@ -292,10 +289,7 @@ func TestVariableInterpolation(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
i := &VariableInterpolation{Variable: uv, key: "var.foo"} i := &VariableInterpolation{Variable: uv}
if i.FullString() != "var.foo" {
t.Fatalf("err: %#v", i)
}
expected := map[string]InterpolatedVariable{"var.foo": uv} expected := map[string]InterpolatedVariable{"var.foo": uv}
if !reflect.DeepEqual(i.Variables(), expected) { if !reflect.DeepEqual(i.Variables(), expected) {
@ -320,7 +314,7 @@ func TestVariableInterpolation_missing(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
i := &VariableInterpolation{Variable: uv, key: "var.foo"} i := &VariableInterpolation{Variable: uv}
_, err = i.Interpolate(map[string]string{ _, err = i.Interpolate(map[string]string{
"var.bar": "bar", "var.bar": "bar",
}) })

View File

@ -95,7 +95,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
// Interpolation found, instantiate it // Interpolation found, instantiate it
key := match[2] key := match[2]
i, err := NewInterpolation(key) i, err := ExprParse(key)
if err != nil { if err != nil {
return err return err
} }

View File

@ -29,7 +29,6 @@ func TestInterpolationWalker_detect(t *testing.T) {
Name: "foo", Name: "foo",
key: "var.foo", key: "var.foo",
}, },
key: "var.foo",
}, },
}, },
}, },
@ -41,13 +40,14 @@ func TestInterpolationWalker_detect(t *testing.T) {
Result: []Interpolation{ Result: []Interpolation{
&FunctionInterpolation{ &FunctionInterpolation{
Func: nil, Func: nil,
Args: []InterpolatedVariable{ Args: []Interpolation{
&UserVariable{ &VariableInterpolation{
Name: "foo", Variable: &UserVariable{
key: "var.foo", Name: "foo",
key: "var.foo",
},
}, },
}, },
key: "lookup(var.foo)",
}, },
}, },
}, },