config: basic interpolationWalker

This commit is contained in:
Mitchell Hashimoto 2014-07-21 11:24:44 -07:00
parent 582b0cf43e
commit 4c9e0f395c
3 changed files with 228 additions and 31 deletions

View File

@ -33,19 +33,6 @@ type VariableInterpolation struct {
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 {
@ -59,6 +46,72 @@ type ResourceVariable struct {
key string
}
// 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
}
// 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
}
// 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) {
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) {
if !strings.HasPrefix(v, "var.") {
return NewResourceVariable(v)
}
varKey := v[len("var."):]
if strings.Index(varKey, ".") == -1 {
return NewUserVariable(v)
} else {
return NewUserMapVariable(v)
}
}
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}
}
func NewResourceVariable(key string) (*ResourceVariable, error) {
parts := strings.SplitN(key, ".", 3)
field := parts[2]
@ -101,15 +154,6 @@ 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{
@ -122,15 +166,6 @@ 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, ".")

107
config/interpolate_walk.go Normal file
View File

@ -0,0 +1,107 @@
package config
import (
"reflect"
"regexp"
"github.com/mitchellh/reflectwalk"
)
// interpRegexp is a regexp that matches interpolations such as ${foo.bar}
var interpRegexp *regexp.Regexp = regexp.MustCompile(
`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
// interpolationWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// execute a callback for an interpolation.
type interpolationWalker struct {
// F must be one of interpolationWalkerFunc or
// interpolationReplaceWalkerFunc.
F interpolationWalkerFunc
Replace bool
key []string
loc reflectwalk.Location
cs []reflect.Value
csData interface{}
}
type interpolationWalkerFunc func(Interpolation) (string, error)
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
return nil
}
func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
w.loc = reflectwalk.None
switch loc {
case reflectwalk.Map:
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.MapValue:
w.key = w.key[:len(w.key)-1]
}
return nil
}
func (w *interpolationWalker) Map(m reflect.Value) error {
w.cs = append(w.cs, m)
return nil
}
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.key = append(w.key, k.String())
return nil
}
func (w *interpolationWalker) Primitive(v reflect.Value) error {
// We only care about strings
if v.Kind() == reflect.Interface {
v = v.Elem()
}
if v.Kind() != reflect.String {
return nil
}
// XXX: This can be a lot more efficient if we used a real
// parser. A regexp is a hammer though that will get this working.
matches := interpRegexp.FindAllStringSubmatch(v.String(), -1)
if len(matches) == 0 {
return nil
}
for _, match := range matches {
dollars := len(match[1])
// If there are even amounts of dollar signs, then it is escaped
if dollars%2 == 0 {
continue
}
// Interpolation found, instantiate it
key := match[2]
i, err := NewInterpolation(key)
if err != nil {
return err
}
replaceVal, err := w.F(i)
if err != nil {
return err
}
if w.Replace {
// TODO(mitchellh): replace
println(replaceVal)
}
return nil
}
return nil
}

View File

@ -0,0 +1,55 @@
package config
import (
"reflect"
"testing"
"github.com/mitchellh/reflectwalk"
)
func TestInterpolationWalker_detect(t *testing.T) {
cases := []struct {
Input interface{}
Result []Interpolation
}{
{
Input: map[string]interface{}{
"foo": "$${var.foo}",
},
Result: nil,
},
{
Input: map[string]interface{}{
"foo": "${var.foo}",
},
Result: []Interpolation{
&VariableInterpolation{
Variable: &UserVariable{
Name: "foo",
key: "var.foo",
},
key: "var.foo",
},
},
},
}
for i, tc := range cases {
var actual []Interpolation
detectFn := func(i Interpolation) (string, error) {
actual = append(actual, i)
return "", nil
}
w := &interpolationWalker{F: detectFn}
if err := reflectwalk.Walk(tc.Input, w); err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(actual, tc.Result) {
t.Fatalf("%d: bad:\n\n%#v", i, actual)
}
}
}