diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 7c4d76619e..2249a99289 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "regexp" + "sort" "strconv" "strings" @@ -278,3 +279,73 @@ func interpolationFuncElement() ast.Function { }, } } + +// 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 { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + // Prefix must include ending dot to be a map + prefix := fmt.Sprintf("var.%s.", args[0].(string)) + keys := make([]string, 0, len(vs)) + for k, _ := range vs { + if !strings.HasPrefix(k, prefix) { + continue + } + keys = append(keys, k[len(prefix):]) + } + + if len(keys) <= 0 { + return "", fmt.Errorf( + "failed to find map '%s'", + args[0].(string)) + } + + sort.Strings(keys) + + return strings.Join(keys, InterpSplitDelim), nil + }, + } +} + +// interpolationFuncValues implements the "values" function that yields a list of +// keys of map types within a Terraform configuration. +func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + // Prefix must include ending dot to be a map + prefix := fmt.Sprintf("var.%s.", args[0].(string)) + keys := make([]string, 0, len(vs)) + for k, _ := range vs { + if !strings.HasPrefix(k, prefix) { + continue + } + keys = append(keys, k) + } + + if len(keys) <= 0 { + return "", fmt.Errorf( + "failed to find map '%s'", + args[0].(string)) + } + + sort.Strings(keys) + + vals := make([]string, 0, len(keys)) + + for _, k := range keys { + v := vs[k] + if v.Type != ast.TypeString { + return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type) + } + vals = append(vals, vs[k].Value.(string)) + } + + return strings.Join(vals, InterpSplitDelim), nil + }, + } +} diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 5432cdbd78..9b8726a3ee 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -384,6 +384,104 @@ func TestInterpolateFuncLookup(t *testing.T) { }) } +func TestInterpolateFuncKeys(t *testing.T) { + testFunction(t, testFunctionConfig{ + Vars: map[string]ast.Variable{ + "var.foo.bar": ast.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + "var.foo.qux": ast.Variable{ + Value: "quack", + Type: ast.TypeString, + }, + "var.str": ast.Variable{ + Value: "astring", + Type: ast.TypeString, + }, + }, + Cases: []testFunctionCase{ + { + `${keys("foo")}`, + fmt.Sprintf( + "bar%squx", + InterpSplitDelim), + false, + }, + + // Invalid key + { + `${keys("not")}`, + nil, + true, + }, + + // Too many args + { + `${keys("foo", "bar")}`, + nil, + true, + }, + + // Not a map + { + `${keys("str")}`, + nil, + true, + }, + }, + }) +} + +func TestInterpolateFuncValues(t *testing.T) { + testFunction(t, testFunctionConfig{ + Vars: map[string]ast.Variable{ + "var.foo.bar": ast.Variable{ + Value: "quack", + Type: ast.TypeString, + }, + "var.foo.qux": ast.Variable{ + Value: "baz", + Type: ast.TypeString, + }, + "var.str": ast.Variable{ + Value: "astring", + Type: ast.TypeString, + }, + }, + Cases: []testFunctionCase{ + { + `${values("foo")}`, + fmt.Sprintf( + "quack%sbaz", + InterpSplitDelim), + false, + }, + + // Invalid key + { + `${values("not")}`, + nil, + true, + }, + + // Too many args + { + `${values("foo", "bar")}`, + nil, + true, + }, + + // Not a map + { + `${values("str")}`, + nil, + true, + }, + }, + }) +} + func TestInterpolateFuncElement(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/config/raw_config.go b/config/raw_config.go index d51578e0bc..ebb9f18dc6 100644 --- a/config/raw_config.go +++ b/config/raw_config.go @@ -304,6 +304,8 @@ func langEvalConfig(vs map[string]ast.Variable) *lang.EvalConfig { funcMap[k] = v } funcMap["lookup"] = interpolationFuncLookup(vs) + funcMap["keys"] = interpolationFuncKeys(vs) + funcMap["values"] = interpolationFuncValues(vs) return &lang.EvalConfig{ GlobalScope: &ast.BasicScope{