config: function calls work
This commit is contained in:
parent
cabc007ec4
commit
6a191d7395
|
@ -2,10 +2,15 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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
|
// Interpolation is something that can be contained in a "${}" in a
|
||||||
// configuration value.
|
// configuration value.
|
||||||
//
|
//
|
||||||
|
@ -17,6 +22,10 @@ type Interpolation interface {
|
||||||
Variables() map[string]InterpolatedVariable
|
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.
|
// An InterpolatedVariable is a variable reference within an interpolation.
|
||||||
//
|
//
|
||||||
// Implementations of this interface represents various sources where
|
// Implementations of this interface represents various sources where
|
||||||
|
@ -25,6 +34,15 @@ type InterpolatedVariable interface {
|
||||||
FullKey() string
|
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
|
// 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 {
|
||||||
|
@ -69,6 +87,33 @@ type UserMapVariable struct {
|
||||||
// interpolation could not be found or the interpolation itself
|
// interpolation could not be found or the interpolation itself
|
||||||
// is invalid.
|
// is invalid.
|
||||||
func NewInterpolation(v string) (Interpolation, error) {
|
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 {
|
if idx := strings.Index(v, "."); idx >= 0 {
|
||||||
v, err := NewInterpolatedVariable(v)
|
v, err := NewInterpolatedVariable(v)
|
||||||
if err != nil {
|
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 {
|
func (i *VariableInterpolation) FullString() string {
|
||||||
return i.key
|
return i.key
|
||||||
}
|
}
|
||||||
|
@ -166,6 +248,10 @@ func (v *UserVariable) FullKey() string {
|
||||||
return v.key
|
return v.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *UserVariable) GoString() string {
|
||||||
|
return fmt.Sprintf("*%#v", *v)
|
||||||
|
}
|
||||||
|
|
||||||
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
||||||
name := key[len("var."):]
|
name := key[len("var."):]
|
||||||
idx := strings.Index(name, ".")
|
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 (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,6 +29,25 @@ func TestNewInterpolation(t *testing.T) {
|
||||||
},
|
},
|
||||||
false,
|
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 {
|
for i, tc := range cases {
|
||||||
|
@ -35,6 +55,13 @@ func TestNewInterpolation(t *testing.T) {
|
||||||
if (err != nil) != tc.Error {
|
if (err != nil) != tc.Error {
|
||||||
t.Fatalf("%d. Error: %s", i, err)
|
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) {
|
if !reflect.DeepEqual(actual, tc.Result) {
|
||||||
t.Fatalf("%d bad: %#v", i, actual)
|
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) {
|
func TestResourceVariable_impl(t *testing.T) {
|
||||||
var _ InterpolatedVariable = new(ResourceVariable)
|
var _ InterpolatedVariable = new(ResourceVariable)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue