From cabc007ec45f1b49218a4cd4b0d2f807293589fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2014 11:45:56 -0700 Subject: [PATCH] config: get rid of the variable*Walkers, replace with more generic --- config/interpolate_walk.go | 32 +++- config/interpolate_walk_test.go | 4 +- config/raw_config.go | 29 ++- config/variable.go | 222 ----------------------- config/variable_test.go | 306 -------------------------------- 5 files changed, 53 insertions(+), 540 deletions(-) delete mode 100644 config/variable.go delete mode 100644 config/variable_test.go diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index b4ee9ab98..90b98e1ec 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -19,10 +19,11 @@ type interpolationWalker struct { F interpolationWalkerFunc Replace bool - key []string - loc reflectwalk.Location - cs []reflect.Value - csData interface{} + key []string + 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, ".")) +} diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index 77249d71d..255e0de5b 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -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", }, }, } diff --git a/config/raw_config.go b/config/raw_config.go index f16921fd1..c97b29c52 100644 --- a/config/raw_config.go +++ b/config/raw_config.go @@ -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 } diff --git a/config/variable.go b/config/variable.go deleted file mode 100644 index d57463ae8..000000000 --- a/config/variable.go +++ /dev/null @@ -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, ".")) -} diff --git a/config/variable_test.go b/config/variable_test.go deleted file mode 100644 index 803857880..000000000 --- a/config/variable_test.go +++ /dev/null @@ -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) - } - } -}