diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 89c83ca61d..94894ffee4 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -79,6 +79,7 @@ func Funcs() map[string]ast.Function { "dirname": interpolationFuncDirname(), "distinct": interpolationFuncDistinct(), "element": interpolationFuncElement(), + "chunklist": interpolationFuncChunklist(), "file": interpolationFuncFile(), "matchkeys": interpolationFuncMatchKeys(), "flatten": interpolationFuncFlatten(), @@ -1129,6 +1130,56 @@ func interpolationFuncElement() ast.Function { } } +// returns the `list` items chunked by `size`. +func interpolationFuncChunklist() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ + ast.TypeList, // inputList + ast.TypeInt, // size + }, + ReturnType: ast.TypeList, + Callback: func(args []interface{}) (interface{}, error) { + output := make([]ast.Variable, 0) + + values, _ := args[0].([]ast.Variable) + size, _ := args[1].(int) + + // errors if size is negative + if size < 0 { + return nil, fmt.Errorf("The size argument must be positive") + } + + // if size is 0, returns a list made of the initial list + if size == 0 { + output = append(output, ast.Variable{ + Type: ast.TypeList, + Value: values, + }) + return output, nil + } + + variables := make([]ast.Variable, 0) + chunk := ast.Variable{ + Type: ast.TypeList, + Value: variables, + } + l := len(values) + for i, v := range values { + variables = append(variables, v) + + // Chunk when index isn't 0, or when reaching the values's length + if (i+1)%size == 0 || (i+1) == l { + chunk.Value = variables + output = append(output, chunk) + variables = make([]ast.Variable, 0) + } + } + + return output, nil + }, + } +} + // interpolationFuncKeys implements the "keys" function that yields a list of // keys of map types within a Terraform configuration. func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 04fcf1dec9..fc7540e814 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -2106,6 +2106,44 @@ func TestInterpolateFuncElement(t *testing.T) { }) } +func TestInterpolateFuncChunklist(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + // normal usage + { + `${chunklist(list("a", "b", "c"), 1)}`, + []interface{}{ + []interface{}{"a"}, + []interface{}{"b"}, + []interface{}{"c"}, + }, + false, + }, + // `size` is pair and the list has an impair number of items + { + `${chunklist(list("a", "b", "c"), 2)}`, + []interface{}{ + []interface{}{"a", "b"}, + []interface{}{"c"}, + }, + false, + }, + // list made of the same list, since size is 0 + { + `${chunklist(list("a", "b", "c"), 0)}`, + []interface{}{[]interface{}{"a", "b", "c"}}, + false, + }, + // negative size of chunks + { + `${chunklist(list("a", "b", "c"), -1)}`, + nil, + true, + }, + }, + }) +} + func TestInterpolateFuncBasename(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/website/docs/configuration/interpolation.html.md b/website/docs/configuration/interpolation.html.md index 4afdf1fa5e..7d161e5006 100644 --- a/website/docs/configuration/interpolation.html.md +++ b/website/docs/configuration/interpolation.html.md @@ -230,6 +230,11 @@ The supported built-in functions are: * `element(aws_subnet.foo.*.id, count.index)` * `element(var.list_of_strings, 2)` + * `chunklist(list, size)` - Returns the `list` items chunked by `size`. + Examples: + * `list(aws_subnet.foo.*.id, 1)`: will outputs `[["id1"], ["id2"], ["id3"]]` + * `list(var.list_of_strings, 2)`: will outputs `[["id1", "id2"], ["id3", "id4"], ["id5"]` + * `file(path)` - Reads the contents of a file into the string. Variables in this file are _not_ interpolated. The contents of the file are read as-is. The `path` is interpreted relative to the working directory.