Merge pull request #7905 from hashicorp/jbardin/merge

core: Add merge interpolation function
This commit is contained in:
James Bardin 2016-08-02 09:45:20 -04:00 committed by GitHub
commit 81bb6b7264
3 changed files with 127 additions and 5 deletions

View File

@ -73,6 +73,7 @@ func Funcs() map[string]ast.Function {
"lower": interpolationFuncLower(), "lower": interpolationFuncLower(),
"map": interpolationFuncMap(), "map": interpolationFuncMap(),
"md5": interpolationFuncMd5(), "md5": interpolationFuncMd5(),
"merge": interpolationFuncMerge(),
"uuid": interpolationFuncUUID(), "uuid": interpolationFuncUUID(),
"replace": interpolationFuncReplace(), "replace": interpolationFuncReplace(),
"sha1": interpolationFuncSha1(), "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 // interpolationFuncUpper implements the "upper" function that does
// string upper casing. // string upper casing.
func interpolationFuncUpper() ast.Function { func interpolationFuncUpper() ast.Function {

View File

@ -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) { func TestInterpolateFuncDistinct(t *testing.T) {
testFunction(t, testFunctionConfig{ testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{ Cases: []testFunctionCase{
@ -1542,7 +1639,6 @@ type testFunctionCase struct {
func testFunction(t *testing.T, config testFunctionConfig) { func testFunction(t *testing.T, config testFunctionConfig) {
for i, tc := range config.Cases { for i, tc := range config.Cases {
fmt.Println("running", i)
ast, err := hil.Parse(tc.Input) ast, err := hil.Parse(tc.Input)
if err != nil { if err != nil {
t.Fatalf("Case #%d: input: %#v\nerr: %v", i, tc.Input, err) t.Fatalf("Case #%d: input: %#v\nerr: %v", i, tc.Input, err)

View File

@ -110,7 +110,7 @@ The supported built-in functions are:
variables or when parsing module outputs. variables or when parsing module outputs.
Example: `compact(module.my_asg.load_balancer_names)` Example: `compact(module.my_asg.load_balancer_names)`
* `concat(list1, list2)` - Combines two or more lists into a single list. * `concat(list1, list2, ...)` - Combines two or more lists into a single list.
Example: `concat(aws_instance.db.*.tags.Name, aws_instance.web.*.tags.Name)` Example: `concat(aws_instance.db.*.tags.Name, aws_instance.web.*.tags.Name)`
* `distinct(list)` - Removes duplicate items from a list. Keeps the first * `distinct(list)` - Removes duplicate items from a list. Keeps the first
@ -132,13 +132,13 @@ The supported built-in functions are:
module, you generally want to make the path relative to the module base, module, you generally want to make the path relative to the module base,
like this: `file("${path.module}/file")`. like this: `file("${path.module}/file")`.
* `format(format, args...)` - Formats a string according to the given * `format(format, args, ...)` - Formats a string according to the given
format. The syntax for the format is standard `sprintf` syntax. format. The syntax for the format is standard `sprintf` syntax.
Good documentation for the syntax can be [found here](https://golang.org/pkg/fmt/). Good documentation for the syntax can be [found here](https://golang.org/pkg/fmt/).
Example to zero-prefix a count, used commonly for naming servers: Example to zero-prefix a count, used commonly for naming servers:
`format("web-%03d", count.index + 1)`. `format("web-%03d", count.index + 1)`.
* `formatlist(format, args...)` - Formats each element of a list * `formatlist(format, args, ...)` - Formats each element of a list
according to the given format, similarly to `format`, and returns a list. according to the given format, similarly to `format`, and returns a list.
Non-list arguments are repeated for each list element. Non-list arguments are repeated for each list element.
For example, to convert a list of DNS addresses to a list of URLs, you might use: For example, to convert a list of DNS addresses to a list of URLs, you might use:
@ -171,7 +171,7 @@ The supported built-in functions are:
* `${length("a,b,c")}` = 5 * `${length("a,b,c")}` = 5
* `${length(map("key", "val"))}` = 1 * `${length(map("key", "val"))}` = 1
* `list(items...)` - Returns a list consisting of the arguments to the function. * `list(items, ...)` - Returns a list consisting of the arguments to the function.
This function provides a way of representing list literals in interpolation. This function provides a way of representing list literals in interpolation.
* `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`. * `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`.
* `${list()}` returns an empty list. * `${list()}` returns an empty list.
@ -193,6 +193,11 @@ The supported built-in functions are:
* `map("hello", "world")` * `map("hello", "world")`
* `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))` * `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))`
* `merge(map1, map2, ...)` - Returns the union of 2 or more maps. The maps
are consumed in the order provided, and duplciate keys overwrite previous
entries.
* `${merge(map("a", "b"), map("c", "d"))}` returns `{"a": "b", "c": "d"}`
* `md5(string)` - Returns a (conventional) hexadecimal representation of the * `md5(string)` - Returns a (conventional) hexadecimal representation of the
MD5 hash of the given string. MD5 hash of the given string.