config: convert fucntions, put functions into Scope
This commit is contained in:
parent
4ba7de17a9
commit
1ccad4d729
|
@ -2,17 +2,12 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
// We really need to replace this with a real parser.
|
||||
var funcRegexp *regexp.Regexp = regexp.MustCompile(
|
||||
`(?i)([a-z0-9_]+)\(\s*(?:([.a-z0-9_]+)\s*,\s*)*([.a-z0-9_]+)\s*\)`)
|
||||
|
||||
// Interpolation is something that can be contained in a "${}" in a
|
||||
// configuration value.
|
||||
//
|
||||
|
@ -23,10 +18,6 @@ type Interpolation interface {
|
|||
Variables() map[string]InterpolatedVariable
|
||||
}
|
||||
|
||||
// InterpolationFunc is the function signature for implementing
|
||||
// callable functions in Terraform configurations.
|
||||
type InterpolationFunc func(map[string]string, ...string) (string, error)
|
||||
|
||||
// An InterpolatedVariable is a variable reference within an interpolation.
|
||||
//
|
||||
// Implementations of this interface represents various sources where
|
||||
|
@ -35,13 +26,6 @@ type InterpolatedVariable interface {
|
|||
FullKey() string
|
||||
}
|
||||
|
||||
// FunctionInterpolation is an Interpolation that executes a function
|
||||
// with some variable number of arguments to generate a value.
|
||||
type FunctionInterpolation struct {
|
||||
Func InterpolationFunc
|
||||
Args []Interpolation
|
||||
}
|
||||
|
||||
// LiteralInterpolation implements Interpolation for literals. Ex:
|
||||
// ${"foo"} will equal "foo".
|
||||
type LiteralInterpolation struct {
|
||||
|
@ -130,36 +114,6 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (i *FunctionInterpolation) Interpolate(
|
||||
vs map[string]string) (string, error) {
|
||||
args := make([]string, len(i.Args))
|
||||
for idx, a := range i.Args {
|
||||
v, err := a.Interpolate(vs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
args[idx] = v
|
||||
}
|
||||
|
||||
return i.Func(vs, args...)
|
||||
}
|
||||
|
||||
func (i *FunctionInterpolation) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *i)
|
||||
}
|
||||
|
||||
func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable {
|
||||
result := make(map[string]InterpolatedVariable)
|
||||
for _, a := range i.Args {
|
||||
for k, v := range a.Variables() {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *LiteralInterpolation) Interpolate(
|
||||
map[string]string) (string, error) {
|
||||
return i.Literal, nil
|
||||
|
|
|
@ -1,73 +1,60 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
// Funcs is the mapping of built-in functions for configuration.
|
||||
var Funcs map[string]InterpolationFunc
|
||||
var Funcs map[string]lang.Function
|
||||
|
||||
func init() {
|
||||
Funcs = map[string]InterpolationFunc{
|
||||
"concat": interpolationFuncConcat,
|
||||
"file": interpolationFuncFile,
|
||||
"join": interpolationFuncJoin,
|
||||
"lookup": interpolationFuncLookup,
|
||||
"element": interpolationFuncElement,
|
||||
Funcs = map[string]lang.Function{
|
||||
"file": interpolationFuncFile(),
|
||||
"join": interpolationFuncJoin(),
|
||||
//"lookup": interpolationFuncLookup(),
|
||||
"element": interpolationFuncElement(),
|
||||
}
|
||||
}
|
||||
|
||||
// interpolationFuncConcat implements the "concat" function that allows
|
||||
// strings to be joined together.
|
||||
func interpolationFuncConcat(
|
||||
vs map[string]string, args ...string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for _, a := range args {
|
||||
if _, err := buf.WriteString(a); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// interpolationFuncFile implements the "file" function that allows
|
||||
// loading contents from a file.
|
||||
func interpolationFuncFile(
|
||||
vs map[string]string, args ...string) (string, error) {
|
||||
if len(args) != 1 {
|
||||
return "", fmt.Errorf(
|
||||
"file expects 1 arguments, got %d", len(args))
|
||||
}
|
||||
func interpolationFuncFile() lang.Function {
|
||||
return lang.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
data, err := ioutil.ReadFile(args[0].(string))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
return string(data), nil
|
||||
},
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// interpolationFuncJoin implements the "join" function that allows
|
||||
// multi-variable values to be joined by some character.
|
||||
func interpolationFuncJoin(
|
||||
vs map[string]string, args ...string) (string, error) {
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("join expects 2 arguments")
|
||||
}
|
||||
func interpolationFuncJoin() lang.Function {
|
||||
return lang.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
var list []string
|
||||
for _, arg := range args[1:] {
|
||||
parts := strings.Split(arg.(string), InterpSplitDelim)
|
||||
list = append(list, parts...)
|
||||
}
|
||||
|
||||
var list []string
|
||||
for _, arg := range args[1:] {
|
||||
parts := strings.Split(arg, InterpSplitDelim)
|
||||
list = append(list, parts...)
|
||||
return strings.Join(list, args[0].(string)), nil
|
||||
},
|
||||
}
|
||||
|
||||
return strings.Join(list, args[0]), nil
|
||||
}
|
||||
|
||||
// interpolationFuncLookup implements the "lookup" function that allows
|
||||
|
@ -93,22 +80,21 @@ func interpolationFuncLookup(
|
|||
// interpolationFuncElement implements the "element" function that allows
|
||||
// a specific index to be looked up in a multi-variable value. Note that this will
|
||||
// wrap if the index is larger than the number of elements in the multi-variable value.
|
||||
func interpolationFuncElement(
|
||||
vs map[string]string, args ...string) (string, error) {
|
||||
if len(args) != 2 {
|
||||
return "", fmt.Errorf(
|
||||
"element expects 2 arguments, got %d", len(args))
|
||||
func interpolationFuncElement() lang.Function {
|
||||
return lang.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
list := strings.Split(args[0].(string), InterpSplitDelim)
|
||||
|
||||
index, err := strconv.Atoi(args[1].(string))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"invalid number for index, got %s", args[1])
|
||||
}
|
||||
|
||||
v := list[index%len(list)]
|
||||
return v, nil
|
||||
},
|
||||
}
|
||||
|
||||
list := strings.Split(args[0], InterpSplitDelim)
|
||||
|
||||
index, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"invalid number for index, got %s", args[1])
|
||||
}
|
||||
|
||||
v := list[index % len(list)]
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
|
@ -4,46 +4,12 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
)
|
||||
|
||||
func TestInterpolateFuncConcat(t *testing.T) {
|
||||
cases := []struct {
|
||||
Args []string
|
||||
Result string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
[]string{"foo", "bar", "baz"},
|
||||
"foobarbaz",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"foo", "bar"},
|
||||
"foobar",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"foo"},
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := interpolationFuncConcat(nil, tc.Args...)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("%d: err: %s", i, err)
|
||||
}
|
||||
|
||||
if actual != tc.Result {
|
||||
t.Fatalf("%d: bad: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpolateFuncFile(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "tf")
|
||||
if err != nil {
|
||||
|
@ -54,94 +20,67 @@ func TestInterpolateFuncFile(t *testing.T) {
|
|||
tf.Close()
|
||||
defer os.Remove(path)
|
||||
|
||||
cases := []struct {
|
||||
Args []string
|
||||
Result string
|
||||
Error bool
|
||||
}{
|
||||
testFunction(t, []testFunctionCase{
|
||||
{
|
||||
[]string{path},
|
||||
fmt.Sprintf(`${file("%s")}`, path),
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
// Invalid path
|
||||
{
|
||||
[]string{"/i/dont/exist"},
|
||||
"",
|
||||
`${file("/i/dont/exist")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Too many args
|
||||
{
|
||||
[]string{"foo", "bar"},
|
||||
"",
|
||||
`${file("foo", "bar")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := interpolationFuncFile(nil, tc.Args...)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("%d: err: %s", i, err)
|
||||
}
|
||||
|
||||
if actual != tc.Result {
|
||||
t.Fatalf("%d: bad: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncJoin(t *testing.T) {
|
||||
cases := []struct {
|
||||
Args []string
|
||||
Result string
|
||||
Error bool
|
||||
}{
|
||||
testFunction(t, []testFunctionCase{
|
||||
{
|
||||
[]string{","},
|
||||
"",
|
||||
`${join(",")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
[]string{",", "foo"},
|
||||
`${join(",", "foo")}`,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
[]string{",", "foo", "bar"},
|
||||
"foo,bar",
|
||||
false,
|
||||
},
|
||||
/*
|
||||
TODO
|
||||
{
|
||||
`${join(",", "foo", "bar")}`,
|
||||
"foo,bar",
|
||||
false,
|
||||
},
|
||||
*/
|
||||
|
||||
{
|
||||
[]string{
|
||||
".",
|
||||
fmt.Sprintf(`${join(".", "%s")}`,
|
||||
fmt.Sprintf(
|
||||
"foo%sbar%sbaz",
|
||||
InterpSplitDelim,
|
||||
InterpSplitDelim),
|
||||
},
|
||||
InterpSplitDelim)),
|
||||
"foo.bar.baz",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := interpolationFuncJoin(nil, tc.Args...)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("%d: err: %s", i, err)
|
||||
}
|
||||
|
||||
if actual != tc.Result {
|
||||
t.Fatalf("%d: bad: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
func TestInterpolateFuncLookup(t *testing.T) {
|
||||
testFunction(t, []testFunctionCase{
|
||||
cases := []struct {
|
||||
M map[string]string
|
||||
Args []string
|
||||
|
@ -189,48 +128,62 @@ func TestInterpolateFuncLookup(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestInterpolateFuncElement(t *testing.T) {
|
||||
cases := []struct {
|
||||
Args []string
|
||||
Result string
|
||||
Error bool
|
||||
}{
|
||||
testFunction(t, []testFunctionCase{
|
||||
{
|
||||
[]string{"foo" + InterpSplitDelim + "baz", "1"},
|
||||
fmt.Sprintf(`${element("%s", "1")}`,
|
||||
"foo"+InterpSplitDelim+"baz"),
|
||||
"baz",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"foo", "0"},
|
||||
`${element("foo", "0")}`,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
// Invalid index should wrap vs. out-of-bounds
|
||||
{
|
||||
[]string{"foo" + InterpSplitDelim + "baz", "2"},
|
||||
fmt.Sprintf(`${element("%s", "2")}`,
|
||||
"foo"+InterpSplitDelim+"baz"),
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
// Too many args
|
||||
{
|
||||
[]string{"foo" + InterpSplitDelim + "baz", "0", "1"},
|
||||
"",
|
||||
fmt.Sprintf(`${element("%s", "0", "2")}`,
|
||||
"foo"+InterpSplitDelim+"baz"),
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type testFunctionCase struct {
|
||||
Input string
|
||||
Result interface{}
|
||||
Error bool
|
||||
}
|
||||
|
||||
func testFunction(t *testing.T, cases []testFunctionCase) {
|
||||
for i, tc := range cases {
|
||||
actual, err := interpolationFuncElement(nil, tc.Args...)
|
||||
ast, err := lang.Parse(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: err: %s", i, err)
|
||||
}
|
||||
|
||||
engine := langEngine(nil)
|
||||
out, _, err := engine.Execute(ast)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("%d: err: %s", i, err)
|
||||
}
|
||||
|
||||
if actual != tc.Result {
|
||||
t.Fatalf("%d: bad: %#v", i, actual)
|
||||
if !reflect.DeepEqual(out, tc.Result) {
|
||||
t.Fatalf("%d: bad: %#v", i, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package config
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
|
@ -123,54 +122,6 @@ func TestNewUserVariable_map(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFunctionInterpolation_impl(t *testing.T) {
|
||||
var _ Interpolation = new(FunctionInterpolation)
|
||||
}
|
||||
|
||||
func TestFunctionInterpolation(t *testing.T) {
|
||||
v1, err := NewInterpolatedVariable("var.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
v2, err := NewInterpolatedVariable("var.bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
fn := func(vs map[string]string, args ...string) (string, error) {
|
||||
return strings.Join(args, " "), nil
|
||||
}
|
||||
|
||||
i := &FunctionInterpolation{
|
||||
Func: fn,
|
||||
Args: []Interpolation{
|
||||
&VariableInterpolation{Variable: v1},
|
||||
&VariableInterpolation{Variable: v2},
|
||||
},
|
||||
}
|
||||
|
||||
expected := map[string]InterpolatedVariable{
|
||||
"var.foo": v1,
|
||||
"var.bar": v2,
|
||||
}
|
||||
if !reflect.DeepEqual(i.Variables(), expected) {
|
||||
t.Fatalf("bad: %#v", i.Variables())
|
||||
}
|
||||
|
||||
actual, err := i.Interpolate(map[string]string{
|
||||
"var.foo": "bar",
|
||||
"var.bar": "baz",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if actual != "bar baz" {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiteralInterpolation_impl(t *testing.T) {
|
||||
var _ Interpolation = new(LiteralInterpolation)
|
||||
}
|
||||
|
|
|
@ -81,16 +81,7 @@ func (r *RawConfig) Config() map[string]interface{} {
|
|||
//
|
||||
// If a variable key is missing, this will panic.
|
||||
func (r *RawConfig) Interpolate(vs map[string]string) error {
|
||||
varMap := make(map[string]lang.Variable)
|
||||
for k, v := range vs {
|
||||
varMap[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||
}
|
||||
engine := &lang.Engine{
|
||||
GlobalScope: &lang.Scope{
|
||||
VarMap: varMap,
|
||||
},
|
||||
}
|
||||
|
||||
engine := langEngine(vs)
|
||||
return r.interpolate(func(root ast.Node) (string, error) {
|
||||
out, _, err := engine.Execute(root)
|
||||
if err != nil {
|
||||
|
@ -210,3 +201,17 @@ type gobRawConfig struct {
|
|||
Key string
|
||||
Raw map[string]interface{}
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
return &lang.Engine{
|
||||
GlobalScope: &lang.Scope{
|
||||
VarMap: varMap,
|
||||
FuncMap: Funcs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue