config: introduce Interpolation, not hooked up completely yet
This commit is contained in:
parent
b772f8078d
commit
582b0cf43e
|
@ -0,0 +1,157 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interpolation is something that can be contained in a "${}" in a
|
||||||
|
// configuration value.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InterpolatedVariable is a variable reference within an interpolation.
|
||||||
|
//
|
||||||
|
// Implementations of this interface represents various sources where
|
||||||
|
// variables can come from: user variables, resources, etc.
|
||||||
|
type InterpolatedVariable interface {
|
||||||
|
FullKey() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableInterpolation implements Interpolation for simple variable
|
||||||
|
// interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}"
|
||||||
|
type VariableInterpolation struct {
|
||||||
|
Variable InterpolatedVariable
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) FullString() string {
|
||||||
|
return i.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) Interpolate(
|
||||||
|
vs map[string]string) (string, error) {
|
||||||
|
return vs[i.key], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable {
|
||||||
|
return map[string]InterpolatedVariable{i.key: i.Variable}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ResourceVariable is a variable that is referencing the field
|
||||||
|
// of a resource, such as "${aws_instance.foo.ami}"
|
||||||
|
type ResourceVariable struct {
|
||||||
|
Type string // Resource type, i.e. "aws_instance"
|
||||||
|
Name string // Resource name
|
||||||
|
Field string // Resource field
|
||||||
|
|
||||||
|
Multi bool // True if multi-variable: aws_instance.foo.*.id
|
||||||
|
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||||
|
parts := strings.SplitN(key, ".", 3)
|
||||||
|
field := parts[2]
|
||||||
|
multi := false
|
||||||
|
var index int
|
||||||
|
|
||||||
|
if idx := strings.Index(field, "."); idx != -1 {
|
||||||
|
indexStr := field[:idx]
|
||||||
|
multi = indexStr == "*"
|
||||||
|
index = -1
|
||||||
|
|
||||||
|
if !multi {
|
||||||
|
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
|
||||||
|
if err == nil {
|
||||||
|
multi = true
|
||||||
|
index = int(indexInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if multi {
|
||||||
|
field = field[idx+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ResourceVariable{
|
||||||
|
Type: parts[0],
|
||||||
|
Name: parts[1],
|
||||||
|
Field: field,
|
||||||
|
Multi: multi,
|
||||||
|
Index: index,
|
||||||
|
key: key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ResourceVariable) ResourceId() string {
|
||||||
|
return fmt.Sprintf("%s.%s", v.Type, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ResourceVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UserVariable is a variable that is referencing a user variable
|
||||||
|
// that is inputted from outside the configuration. This looks like
|
||||||
|
// "${var.foo}"
|
||||||
|
type UserVariable struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserVariable(key string) (*UserVariable, error) {
|
||||||
|
name := key[len("var."):]
|
||||||
|
return &UserVariable{
|
||||||
|
key: key,
|
||||||
|
Name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UserMapVariable is a variable that is referencing a user
|
||||||
|
// variable that is a map. This looks like "${var.amis.us-east-1}"
|
||||||
|
type UserMapVariable struct {
|
||||||
|
Name string
|
||||||
|
Elem string
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
||||||
|
name := key[len("var."):]
|
||||||
|
idx := strings.Index(name, ".")
|
||||||
|
if idx == -1 {
|
||||||
|
return nil, fmt.Errorf("not a user map variable: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := name[idx+1:]
|
||||||
|
name = name[:idx]
|
||||||
|
return &UserMapVariable{
|
||||||
|
Name: name,
|
||||||
|
Elem: elem,
|
||||||
|
|
||||||
|
key: key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserMapVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserMapVariable) GoString() string {
|
||||||
|
return fmt.Sprintf("%#v", *v)
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewResourceVariable(t *testing.T) {
|
||||||
|
v, err := NewResourceVariable("foo.bar.baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Type != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v.Name != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v.Field != "baz" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v.Multi {
|
||||||
|
t.Fatal("should not be multi")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.FullKey() != "foo.bar.baz" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewUserVariable(t *testing.T) {
|
||||||
|
v, err := NewUserVariable("var.bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Name != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", v.Name)
|
||||||
|
}
|
||||||
|
if v.FullKey() != "var.bar" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceVariable_impl(t *testing.T) {
|
||||||
|
var _ InterpolatedVariable = new(ResourceVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceVariable_Multi(t *testing.T) {
|
||||||
|
v, err := NewResourceVariable("foo.bar.*.baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Type != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v.Name != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v.Field != "baz" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if !v.Multi {
|
||||||
|
t.Fatal("should be multi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceVariable_MultiIndex(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Index int
|
||||||
|
Field string
|
||||||
|
}{
|
||||||
|
{"foo.bar.*.baz", -1, "baz"},
|
||||||
|
{"foo.bar.0.baz", 0, "baz"},
|
||||||
|
{"foo.bar.5.baz", 5, "baz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
v, err := NewResourceVariable(tc.Input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !v.Multi {
|
||||||
|
t.Fatalf("should be multi: %s", tc.Input)
|
||||||
|
}
|
||||||
|
if v.Index != tc.Index {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", v.Index, tc.Input)
|
||||||
|
}
|
||||||
|
if v.Field != tc.Field {
|
||||||
|
t.Fatalf("bad: %s\n\n%s", v.Field, tc.Input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserVariable_impl(t *testing.T) {
|
||||||
|
var _ InterpolatedVariable = new(UserVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserMapVariable_impl(t *testing.T) {
|
||||||
|
var _ InterpolatedVariable = new(UserMapVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableInterpolation_impl(t *testing.T) {
|
||||||
|
var _ Interpolation = new(VariableInterpolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableInterpolation(t *testing.T) {
|
||||||
|
uv, err := NewUserVariable("var.foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &VariableInterpolation{Variable: uv, key: "var.foo"}
|
||||||
|
if i.FullString() != "var.foo" {
|
||||||
|
t.Fatalf("err: %#v", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]InterpolatedVariable{"var.foo": uv}
|
||||||
|
if !reflect.DeepEqual(i.Variables(), expected) {
|
||||||
|
t.Fatalf("bad: %#v", i.Variables())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := i.Interpolate(map[string]string{
|
||||||
|
"var.foo": "bar",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
|
@ -17,126 +16,6 @@ func init() {
|
||||||
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
|
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An InterpolatedVariable is a variable that is embedded within a string
|
|
||||||
// in the configuration, such as "hello ${world}" (world in this case is
|
|
||||||
// an interpolated variable).
|
|
||||||
//
|
|
||||||
// These variables can come from a variety of sources, represented by
|
|
||||||
// implementations of this interface.
|
|
||||||
type InterpolatedVariable interface {
|
|
||||||
FullKey() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ResourceVariable is a variable that is referencing the field
|
|
||||||
// of a resource, such as "${aws_instance.foo.ami}"
|
|
||||||
type ResourceVariable struct {
|
|
||||||
Type string // Resource type, i.e. "aws_instance"
|
|
||||||
Name string // Resource name
|
|
||||||
Field string // Resource field
|
|
||||||
|
|
||||||
Multi bool // True if multi-variable: aws_instance.foo.*.id
|
|
||||||
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
|
|
||||||
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ResourceVariable) ResourceId() string {
|
|
||||||
return fmt.Sprintf("%s.%s", v.Type, v.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ResourceVariable) FullKey() string {
|
|
||||||
return v.key
|
|
||||||
}
|
|
||||||
|
|
||||||
// A UserVariable is a variable that is referencing a user variable
|
|
||||||
// that is inputted from outside the configuration. This looks like
|
|
||||||
// "${var.foo}"
|
|
||||||
type UserVariable struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *UserVariable) FullKey() string {
|
|
||||||
return v.key
|
|
||||||
}
|
|
||||||
|
|
||||||
// A UserMapVariable is a variable that is referencing a user
|
|
||||||
// variable that is a map. This looks like "${var.amis.us-east-1}"
|
|
||||||
type UserMapVariable struct {
|
|
||||||
Name string
|
|
||||||
Elem string
|
|
||||||
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
|
||||||
name := key[len("var."):]
|
|
||||||
idx := strings.Index(name, ".")
|
|
||||||
if idx == -1 {
|
|
||||||
return nil, fmt.Errorf("not a user map variable: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
elem := name[idx+1:]
|
|
||||||
name = name[:idx]
|
|
||||||
return &UserMapVariable{
|
|
||||||
Name: name,
|
|
||||||
Elem: elem,
|
|
||||||
|
|
||||||
key: key,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *UserMapVariable) FullKey() string {
|
|
||||||
return v.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *UserMapVariable) GoString() string {
|
|
||||||
return fmt.Sprintf("%#v", *v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
|
||||||
parts := strings.SplitN(key, ".", 3)
|
|
||||||
field := parts[2]
|
|
||||||
multi := false
|
|
||||||
var index int
|
|
||||||
|
|
||||||
if idx := strings.Index(field, "."); idx != -1 {
|
|
||||||
indexStr := field[:idx]
|
|
||||||
multi = indexStr == "*"
|
|
||||||
index = -1
|
|
||||||
|
|
||||||
if !multi {
|
|
||||||
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
|
|
||||||
if err == nil {
|
|
||||||
multi = true
|
|
||||||
index = int(indexInt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if multi {
|
|
||||||
field = field[idx+1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ResourceVariable{
|
|
||||||
Type: parts[0],
|
|
||||||
Name: parts[1],
|
|
||||||
Field: field,
|
|
||||||
Multi: multi,
|
|
||||||
Index: index,
|
|
||||||
key: key,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserVariable(key string) (*UserVariable, error) {
|
|
||||||
name := key[len("var."):]
|
|
||||||
return &UserVariable{
|
|
||||||
key: key,
|
|
||||||
Name: name,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceVariables takes a configuration and a mapping of variables
|
// ReplaceVariables takes a configuration and a mapping of variables
|
||||||
// and performs the structure walking necessary to properly replace
|
// and performs the structure walking necessary to properly replace
|
||||||
// all the variables.
|
// all the variables.
|
||||||
|
@ -229,6 +108,7 @@ func (w *variableDetectWalker) Primitive(v reflect.Value) error {
|
||||||
// will _panic_. The variableDetectWalker will tell you all variables
|
// will _panic_. The variableDetectWalker will tell you all variables
|
||||||
// you need.
|
// you need.
|
||||||
type variableReplaceWalker struct {
|
type variableReplaceWalker struct {
|
||||||
|
Variables map[string]InterpolatedVariable
|
||||||
Values map[string]string
|
Values map[string]string
|
||||||
UnknownKeys []string
|
UnknownKeys []string
|
||||||
|
|
||||||
|
|
|
@ -36,44 +36,6 @@ func BenchmarkVariableReplaceWalker(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResourceVariable(t *testing.T) {
|
|
||||||
v, err := NewResourceVariable("foo.bar.baz")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Type != "foo" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if v.Name != "bar" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if v.Field != "baz" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if v.Multi {
|
|
||||||
t.Fatal("should not be multi")
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.FullKey() != "foo.bar.baz" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewUserVariable(t *testing.T) {
|
|
||||||
v, err := NewUserVariable("var.bar")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Name != "bar" {
|
|
||||||
t.Fatalf("bad: %#v", v.Name)
|
|
||||||
}
|
|
||||||
if v.FullKey() != "var.bar" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceVariables(t *testing.T) {
|
func TestReplaceVariables(t *testing.T) {
|
||||||
input := "foo-${var.bar}"
|
input := "foo-${var.bar}"
|
||||||
expected := "foo-bar"
|
expected := "foo-bar"
|
||||||
|
@ -93,66 +55,6 @@ func TestReplaceVariables(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceVariable_impl(t *testing.T) {
|
|
||||||
var _ InterpolatedVariable = new(ResourceVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceVariable_Multi(t *testing.T) {
|
|
||||||
v, err := NewResourceVariable("foo.bar.*.baz")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Type != "foo" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if v.Name != "bar" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if v.Field != "baz" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
if !v.Multi {
|
|
||||||
t.Fatal("should be multi")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceVariable_MultiIndex(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
Input string
|
|
||||||
Index int
|
|
||||||
Field string
|
|
||||||
}{
|
|
||||||
{"foo.bar.*.baz", -1, "baz"},
|
|
||||||
{"foo.bar.0.baz", 0, "baz"},
|
|
||||||
{"foo.bar.5.baz", 5, "baz"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
v, err := NewResourceVariable(tc.Input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if !v.Multi {
|
|
||||||
t.Fatalf("should be multi: %s", tc.Input)
|
|
||||||
}
|
|
||||||
if v.Index != tc.Index {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", v.Index, tc.Input)
|
|
||||||
}
|
|
||||||
if v.Field != tc.Field {
|
|
||||||
t.Fatalf("bad: %s\n\n%s", v.Field, tc.Input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserVariable_impl(t *testing.T) {
|
|
||||||
var _ InterpolatedVariable = new(UserVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserMapVariable_impl(t *testing.T) {
|
|
||||||
var _ InterpolatedVariable = new(UserMapVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker(t *testing.T) {
|
func TestVariableDetectWalker(t *testing.T) {
|
||||||
w := new(variableDetectWalker)
|
w := new(variableDetectWalker)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue