config: get rid of the variable*Walkers, replace with more generic
This commit is contained in:
parent
4099c64833
commit
cabc007ec4
|
@ -23,6 +23,7 @@ type interpolationWalker struct {
|
|||
loc reflectwalk.Location
|
||||
cs []reflect.Value
|
||||
csData interface{}
|
||||
unknownKeys []string
|
||||
}
|
||||
|
||||
// interpolationWalkerFunc is the callback called by interpolationWalk.
|
||||
|
@ -105,6 +106,13 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
|||
}
|
||||
|
||||
if w.Replace {
|
||||
// If this is an unknown variable, then we remove it from
|
||||
// the configuration.
|
||||
if replaceVal == UnknownVariableValue {
|
||||
w.removeCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
result = strings.Replace(result, match[0], replaceVal, -1)
|
||||
}
|
||||
}
|
||||
|
@ -125,3 +133,19 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *interpolationWalker) removeCurrent() {
|
||||
c := w.cs[len(w.cs)-1]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Zero value so that we delete the map key
|
||||
var val reflect.Value
|
||||
|
||||
// Get the key and delete it
|
||||
k := w.csData.(reflect.Value)
|
||||
c.SetMapIndex(k, val)
|
||||
}
|
||||
|
||||
// Append the key to the unknown keys
|
||||
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||
}
|
||||
|
|
|
@ -70,10 +70,10 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
|||
|
||||
{
|
||||
Input: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
"foo": "hello, ${var.foo}",
|
||||
},
|
||||
Output: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"foo": "hello, bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -69,26 +69,43 @@ func (r *RawConfig) Interpolate(vs map[string]string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := &variableReplaceWalker{Values: vs}
|
||||
r.config = config.(map[string]interface{})
|
||||
|
||||
fn := func(i Interpolation) (string, error) {
|
||||
return i.Interpolate(vs)
|
||||
}
|
||||
|
||||
w := &interpolationWalker{F: fn, Replace: true}
|
||||
err = reflectwalk.Walk(r.config, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.unknownKeys = w.UnknownKeys
|
||||
r.unknownKeys = w.unknownKeys
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RawConfig) init() error {
|
||||
walker := new(variableDetectWalker)
|
||||
r.config = r.Raw
|
||||
r.Variables = nil
|
||||
|
||||
fn := func(i Interpolation) (string, error) {
|
||||
for k, v := range i.Variables() {
|
||||
if r.Variables == nil {
|
||||
r.Variables = make(map[string]InterpolatedVariable)
|
||||
}
|
||||
|
||||
r.Variables[k] = v
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
walker := &interpolationWalker{F: fn}
|
||||
if err := reflectwalk.Walk(r.Raw, walker); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Variables = walker.Variables
|
||||
r.config = r.Raw
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// varRegexp is a regexp that matches variables such as ${foo.bar}
|
||||
var varRegexp *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
|
||||
}
|
||||
|
||||
// ReplaceVariables takes a configuration and a mapping of variables
|
||||
// and performs the structure walking necessary to properly replace
|
||||
// all the variables.
|
||||
func ReplaceVariables(
|
||||
c interface{},
|
||||
vs map[string]string) ([]string, error) {
|
||||
w := &variableReplaceWalker{Values: vs}
|
||||
if err := reflectwalk.Walk(c, w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w.UnknownKeys, nil
|
||||
}
|
||||
|
||||
// variableDetectWalker implements interfaces for the reflectwalk package
|
||||
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||
// pull out the variables that need replacing.
|
||||
type variableDetectWalker struct {
|
||||
Variables map[string]InterpolatedVariable
|
||||
}
|
||||
|
||||
func (w *variableDetectWalker) 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 := varRegexp.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
|
||||
}
|
||||
|
||||
// Otherwise, record it
|
||||
key := match[2]
|
||||
if w.Variables == nil {
|
||||
w.Variables = make(map[string]InterpolatedVariable)
|
||||
}
|
||||
if _, ok := w.Variables[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
var iv InterpolatedVariable
|
||||
if strings.Index(key, ".") == -1 {
|
||||
return fmt.Errorf(
|
||||
"Interpolated variable '%s' has bad format. "+
|
||||
"Did you mean 'var.%s'?",
|
||||
key, key)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(key, "var.") {
|
||||
iv, err = NewResourceVariable(key)
|
||||
} else {
|
||||
varKey := key[len("var."):]
|
||||
if strings.Index(varKey, ".") == -1 {
|
||||
iv, err = NewUserVariable(key)
|
||||
} else {
|
||||
iv, err = NewUserMapVariable(key)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Variables[key] = iv
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// variableReplaceWalker implements interfaces for reflectwalk that
|
||||
// is used to replace variables with their values.
|
||||
//
|
||||
// If Values does not have every available value, then the program
|
||||
// will _panic_. The variableDetectWalker will tell you all variables
|
||||
// you need.
|
||||
type variableReplaceWalker struct {
|
||||
Variables map[string]InterpolatedVariable
|
||||
Values map[string]string
|
||||
UnknownKeys []string
|
||||
|
||||
key []string
|
||||
loc reflectwalk.Location
|
||||
cs []reflect.Value
|
||||
csData interface{}
|
||||
}
|
||||
|
||||
func (w *variableReplaceWalker) Enter(loc reflectwalk.Location) error {
|
||||
w.loc = loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *variableReplaceWalker) 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 *variableReplaceWalker) Map(m reflect.Value) error {
|
||||
w.cs = append(w.cs, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *variableReplaceWalker) MapElem(m, k, v reflect.Value) error {
|
||||
w.csData = k
|
||||
w.key = append(w.key, k.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *variableReplaceWalker) Primitive(v reflect.Value) error {
|
||||
// We only care about strings
|
||||
setV := v
|
||||
if v.Kind() == reflect.Interface {
|
||||
setV = v
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.String {
|
||||
return nil
|
||||
}
|
||||
|
||||
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := v.String()
|
||||
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
|
||||
}
|
||||
|
||||
// Get the key
|
||||
key := match[2]
|
||||
value, ok := w.Values[key]
|
||||
if !ok {
|
||||
panic("no value for variable key: " + key)
|
||||
}
|
||||
|
||||
// If this is an unknown variable, then we remove it from
|
||||
// the configuration.
|
||||
if value == UnknownVariableValue {
|
||||
w.removeCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace
|
||||
result = strings.Replace(result, match[0], value, -1)
|
||||
}
|
||||
|
||||
resultVal := reflect.ValueOf(result)
|
||||
if w.loc == reflectwalk.MapValue {
|
||||
// If we're in a map, then the only way to set a map value is
|
||||
// to set it directly.
|
||||
m := w.cs[len(w.cs)-1]
|
||||
mk := w.csData.(reflect.Value)
|
||||
m.SetMapIndex(mk, resultVal)
|
||||
} else {
|
||||
// Otherwise, we should be addressable
|
||||
setV.Set(resultVal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *variableReplaceWalker) removeCurrent() {
|
||||
c := w.cs[len(w.cs)-1]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Zero value so that we delete the map key
|
||||
var val reflect.Value
|
||||
|
||||
// Get the key and delete it
|
||||
k := w.csData.(reflect.Value)
|
||||
c.SetMapIndex(k, val)
|
||||
}
|
||||
|
||||
// Append the key to the unknown keys
|
||||
w.UnknownKeys = append(w.UnknownKeys, strings.Join(w.key, "."))
|
||||
}
|
|
@ -1,306 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
func BenchmarkVariableDetectWalker(b *testing.B) {
|
||||
w := new(variableDetectWalker)
|
||||
str := reflect.ValueOf(`foo ${var.bar} bar ${bar.baz.bing} $${escaped}`)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
w.Variables = nil
|
||||
w.Primitive(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVariableReplaceWalker(b *testing.B) {
|
||||
w := &variableReplaceWalker{
|
||||
Values: map[string]string{
|
||||
"var.bar": "bar",
|
||||
"bar.baz.bing": "baz",
|
||||
},
|
||||
}
|
||||
|
||||
str := `foo ${var.bar} bar ${bar.baz.bing} $${escaped}`
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := reflectwalk.Walk(&str, w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceVariables(t *testing.T) {
|
||||
input := "foo-${var.bar}"
|
||||
expected := "foo-bar"
|
||||
|
||||
unk, err := ReplaceVariables(&input, map[string]string{
|
||||
"var.bar": "bar",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(unk) > 0 {
|
||||
t.Fatal("bad: %#v", unk)
|
||||
}
|
||||
|
||||
if input != expected {
|
||||
t.Fatalf("bad: %#v", input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${var.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) != 1 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if w.Variables["var.bar"].(*UserVariable).FullKey() != "var.bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_resource(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${ec2.foo.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) != 1 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if w.Variables["ec2.foo.bar"].(*ResourceVariable).FullKey() != "ec2.foo.bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_resourceMulti(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${ec2.foo.*.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) != 1 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if w.Variables["ec2.foo.*.bar"].(*ResourceVariable).FullKey() != "ec2.foo.*.bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_bad(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_escaped(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo $${var.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) > 0 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_empty(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) > 0 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_userMap(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${var.foo.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) != 1 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
|
||||
v := w.Variables["var.foo.bar"].(*UserMapVariable)
|
||||
if v.FullKey() != "var.foo.bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if v.Name != "foo" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if v.Elem != "bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableReplaceWalker(t *testing.T) {
|
||||
w := &variableReplaceWalker{
|
||||
Values: map[string]string{
|
||||
"var.bar": "bar",
|
||||
"var.unknown": UnknownVariableValue,
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
}{
|
||||
{
|
||||
`foo ${var.bar}`,
|
||||
"foo bar",
|
||||
},
|
||||
{
|
||||
[]string{"foo", "${var.bar}"},
|
||||
[]string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"ami": "${var.bar}",
|
||||
"security_groups": []interface{}{
|
||||
"foo",
|
||||
"${var.bar}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"ami": "bar",
|
||||
"security_groups": []interface{}{
|
||||
"foo",
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo": []string{"${var.bar}"},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo": []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "hello${var.unknown}world",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": []string{"foo", "${var.unknown}", "bar"},
|
||||
},
|
||||
map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
var input interface{} = tc.Input
|
||||
if reflect.ValueOf(tc.Input).Kind() == reflect.String {
|
||||
input = &tc.Input
|
||||
}
|
||||
|
||||
if err := reflectwalk.Walk(input, w); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
||||
t.Fatalf("bad %d: %#v", i, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableReplaceWalker_unknown(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
Keys []string
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "hello${var.unknown}world",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
[]string{"bar"},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": []string{"foo", "${var.unknown}", "bar"},
|
||||
},
|
||||
map[string]interface{}{},
|
||||
[]string{"foo"},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "${var.unknown}",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{},
|
||||
},
|
||||
[]string{"foo.bar"},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
var input interface{} = tc.Input
|
||||
w := &variableReplaceWalker{
|
||||
Values: map[string]string{
|
||||
"var.unknown": UnknownVariableValue,
|
||||
},
|
||||
}
|
||||
|
||||
if reflect.ValueOf(tc.Input).Kind() == reflect.String {
|
||||
input = &tc.Input
|
||||
}
|
||||
|
||||
if err := reflectwalk.Walk(input, w); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
||||
t.Fatalf("bad %d: %#v", i, tc.Input)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Keys, w.UnknownKeys) {
|
||||
t.Fatalf("bad: %#v", w.UnknownKeys)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue