Merge pull request #7905 from hashicorp/jbardin/merge
core: Add merge interpolation function
This commit is contained in:
commit
81bb6b7264
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue