config: function calls work
This commit is contained in:
parent
cabc007ec4
commit
6a191d7395
|
@ -2,10 +2,15 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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.
|
||||
//
|
||||
|
@ -17,6 +22,10 @@ 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
|
||||
|
@ -25,6 +34,15 @@ 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 []InterpolatedVariable
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
// VariableInterpolation implements Interpolation for simple variable
|
||||
// interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}"
|
||||
type VariableInterpolation struct {
|
||||
|
@ -69,6 +87,33 @@ type UserMapVariable struct {
|
|||
// 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++ {
|
||||
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 {
|
||||
|
@ -99,6 +144,43 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
args[idx] = v
|
||||
}
|
||||
|
||||
return i.Func(vs, args...)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
result[k] = a
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *VariableInterpolation) FullString() string {
|
||||
return i.key
|
||||
}
|
||||
|
@ -166,6 +248,10 @@ func (v *UserVariable) FullKey() string {
|
|||
return v.key
|
||||
}
|
||||
|
||||
func (v *UserVariable) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *v)
|
||||
}
|
||||
|
||||
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
||||
name := key[len("var."):]
|
||||
idx := strings.Index(name, ".")
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package config
|
||||
|
||||
// Funcs is the mapping of built-in functions for configuration.
|
||||
var Funcs map[string]InterpolationFunc
|
||||
|
||||
func init() {
|
||||
Funcs = map[string]InterpolationFunc{
|
||||
"lookup": interpolationFuncLookup,
|
||||
}
|
||||
}
|
||||
|
||||
// interpolationFuncLookup implements the "lookup" function that allows
|
||||
// dynamic lookups of map types within a Terraform configuration.
|
||||
func interpolationFuncLookup(
|
||||
vs map[string]string, args ...string) (string, error) {
|
||||
return "", nil
|
||||
}
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -28,6 +29,25 @@ func TestNewInterpolation(t *testing.T) {
|
|||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"lookup(var.foo, var.bar)",
|
||||
&FunctionInterpolation{
|
||||
Func: nil, // Funcs["lookup"]
|
||||
Args: []InterpolatedVariable{
|
||||
&UserVariable{
|
||||
Name: "foo",
|
||||
key: "var.foo",
|
||||
},
|
||||
&UserVariable{
|
||||
Name: "bar",
|
||||
key: "var.bar",
|
||||
},
|
||||
},
|
||||
key: "lookup(var.foo, var.bar)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
@ -35,6 +55,13 @@ func TestNewInterpolation(t *testing.T) {
|
|||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("%d. Error: %s", i, err)
|
||||
}
|
||||
|
||||
// This is jank, but reflect.DeepEqual never has functions
|
||||
// being the same.
|
||||
if f, ok := actual.(*FunctionInterpolation); ok {
|
||||
f.Func = nil
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, tc.Result) {
|
||||
t.Fatalf("%d bad: %#v", i, actual)
|
||||
}
|
||||
|
@ -106,6 +133,55 @@ func TestNewUserVariable(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: []InterpolatedVariable{v1, v2},
|
||||
key: "foo",
|
||||
}
|
||||
if i.FullString() != "foo" {
|
||||
t.Fatalf("err: %#v", i)
|
||||
}
|
||||
|
||||
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 TestResourceVariable_impl(t *testing.T) {
|
||||
var _ InterpolatedVariable = new(ResourceVariable)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue