From 39bbbb8da6230f517f9e992cc591942d823a0da6 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 1 Aug 2016 18:10:53 -0400 Subject: [PATCH] Add merge interpolation function Add a `merge` interpolation function, which merges any number of maps. Duplicate keys are OK, with the last write winning. --- config/interpolate_funcs.go | 21 +++++++ config/interpolate_funcs_test.go | 98 +++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 099369064..d66073ec6 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -73,6 +73,7 @@ func Funcs() map[string]ast.Function { "lower": interpolationFuncLower(), "map": interpolationFuncMap(), "md5": interpolationFuncMd5(), + "merge": interpolationFuncMerge(), "uuid": interpolationFuncUUID(), "replace": interpolationFuncReplace(), "sha1": interpolationFuncSha1(), @@ -898,6 +899,26 @@ func interpolationFuncMd5() ast.Function { } } +func interpolationFuncMerge() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeMap}, + ReturnType: ast.TypeMap, + Variadic: true, + VariadicType: ast.TypeMap, + Callback: func(args []interface{}) (interface{}, error) { + outputMap := make(map[string]ast.Variable) + + for _, arg := range args { + for k, v := range arg.(map[string]ast.Variable) { + outputMap[k] = v + } + } + + return outputMap, nil + }, + } +} + // interpolationFuncUpper implements the "upper" function that does // string upper casing. func interpolationFuncUpper() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 0d4f38d9c..0b77af328 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -498,6 +498,103 @@ func TestInterpolateFuncConcat(t *testing.T) { }) } +func TestInterpolateFuncMerge(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + // basic merge + { + `${merge(map("a", "b"), map("c", "d"))}`, + map[string]interface{}{"a": "b", "c": "d"}, + false, + }, + + // merge with conflicts is ok, last in wins. + { + `${merge(map("a", "b", "c", "X"), map("c", "d"))}`, + map[string]interface{}{"a": "b", "c": "d"}, + false, + }, + + // merge variadic + { + `${merge(map("a", "b"), map("c", "d"), map("e", "f"))}`, + map[string]interface{}{"a": "b", "c": "d", "e": "f"}, + false, + }, + + // merge with variables + { + `${merge(var.maps[0], map("c", "d"))}`, + map[string]interface{}{"key1": "a", "key2": "b", "c": "d"}, + false, + }, + + // only accept maps + { + `${merge(map("a", "b"), list("c", "d"))}`, + nil, + true, + }, + + // merge maps of maps + { + `${merge(map("a", var.maps[0]), map("b", var.maps[1]))}`, + map[string]interface{}{ + "b": map[string]interface{}{"key3": "d", "key4": "c"}, + "a": map[string]interface{}{"key1": "a", "key2": "b"}, + }, + false, + }, + // merge maps of lists + { + `${merge(map("a", list("b")), map("c", list("d", "e")))}`, + map[string]interface{}{"a": []interface{}{"b"}, "c": []interface{}{"d", "e"}}, + false, + }, + // merge map of various kinds + { + `${merge(map("a", var.maps[0]), map("b", list("c", "d")))}`, + map[string]interface{}{"a": map[string]interface{}{"key1": "a", "key2": "b"}, "b": []interface{}{"c", "d"}}, + false, + }, + }, + Vars: map[string]ast.Variable{ + "var.maps": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key1": { + Type: ast.TypeString, + Value: "a", + }, + "key2": { + Type: ast.TypeString, + Value: "b", + }, + }, + }, + { + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key3": { + Type: ast.TypeString, + Value: "d", + }, + "key4": { + Type: ast.TypeString, + Value: "c", + }, + }, + }, + }, + }, + }, + }) + +} + func TestInterpolateFuncDistinct(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ @@ -1542,7 +1639,6 @@ type testFunctionCase struct { func testFunction(t *testing.T, config testFunctionConfig) { for i, tc := range config.Cases { - fmt.Println("running", i) ast, err := hil.Parse(tc.Input) if err != nil { t.Fatalf("Case #%d: input: %#v\nerr: %v", i, tc.Input, err)