diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index f73df85e6c..a3cdf828ba 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -91,15 +91,34 @@ func interpolationFuncList() ast.Function { ArgTypes: []ast.Type{}, ReturnType: ast.TypeList, Variadic: true, - VariadicType: ast.TypeString, + VariadicType: ast.TypeAny, Callback: func(args []interface{}) (interface{}, error) { - var outputList []string + var outputList []ast.Variable - for _, val := range args { - outputList = append(outputList, val.(string)) + for i, val := range args { + switch v := val.(type) { + case string: + outputList = append(outputList, ast.Variable{Type: ast.TypeString, Value: v}) + case []ast.Variable: + outputList = append(outputList, ast.Variable{Type: ast.TypeList, Value: v}) + case map[string]ast.Variable: + outputList = append(outputList, ast.Variable{Type: ast.TypeMap, Value: v}) + default: + return nil, fmt.Errorf("unexpected type %T for argument %d in list", v, i) + } } - return stringSliceToVariableValue(outputList), nil + // we don't support heterogeneous types, so make sure all types match the first + if len(outputList) > 0 { + firstType := outputList[0].Type + for i, v := range outputList[1:] { + if v.Type != firstType { + return nil, fmt.Errorf("unexpected type %s for argument %d in list", v.Type, i+1) + } + } + } + + return outputList, nil }, } } diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 7ee10372f2..5b9800c14c 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -38,7 +38,28 @@ func TestInterpolateFuncList(t *testing.T) { // not a string input gives error { - `${list("hello", "${var.list}")}`, + `${list("hello", 42)}`, + nil, + true, + }, + + // list of lists + { + `${list("${var.list}", "${var.list2}")}`, + []interface{}{[]interface{}{"Hello", "World"}, []interface{}{"bar", "baz"}}, + false, + }, + + // list of maps + { + `${list("${var.map}", "${var.map2}")}`, + []interface{}{map[string]interface{}{"key": "bar"}, map[string]interface{}{"key2": "baz"}}, + false, + }, + + // error on a heterogeneous list + { + `${list("first", "${var.list}")}`, nil, true, }, @@ -57,6 +78,38 @@ func TestInterpolateFuncList(t *testing.T) { }, }, }, + "var.list2": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "bar", + }, + { + Type: ast.TypeString, + Value: "baz", + }, + }, + }, + + "var.map": { + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key": { + Type: ast.TypeString, + Value: "bar", + }, + }, + }, + "var.map2": { + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key2": { + Type: ast.TypeString, + Value: "baz", + }, + }, + }, }, }) } diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index b2f66efd2b..338550377f 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -172,7 +172,7 @@ The supported built-in functions are: This function provides a way of representing list literals in interpolation. * `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`. * `${list()}` returns an empty list. - + * `lookup(map, key [, default])` - Performs a dynamic lookup into a mapping variable. The `map` parameter should be another variable, such as `var.amis`. If `key` does not exist in `map`, the interpolation will