config: basic interpolationWalker
This commit is contained in:
parent
582b0cf43e
commit
4c9e0f395c
|
@ -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, ".")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue