config: basic interpolationWalker
This commit is contained in:
parent
582b0cf43e
commit
4c9e0f395c
|
@ -33,19 +33,6 @@ type VariableInterpolation struct {
|
||||||
key string
|
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
|
// A ResourceVariable is a variable that is referencing the field
|
||||||
// of a resource, such as "${aws_instance.foo.ami}"
|
// of a resource, such as "${aws_instance.foo.ami}"
|
||||||
type ResourceVariable struct {
|
type ResourceVariable struct {
|
||||||
|
@ -59,6 +46,72 @@ type ResourceVariable struct {
|
||||||
key string
|
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) {
|
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||||
parts := strings.SplitN(key, ".", 3)
|
parts := strings.SplitN(key, ".", 3)
|
||||||
field := parts[2]
|
field := parts[2]
|
||||||
|
@ -101,15 +154,6 @@ func (v *ResourceVariable) FullKey() string {
|
||||||
return v.key
|
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) {
|
func NewUserVariable(key string) (*UserVariable, error) {
|
||||||
name := key[len("var."):]
|
name := key[len("var."):]
|
||||||
return &UserVariable{
|
return &UserVariable{
|
||||||
|
@ -122,15 +166,6 @@ func (v *UserVariable) FullKey() string {
|
||||||
return v.key
|
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) {
|
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,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