From b781c6c446ed78c61c1f06d597b07cfe07578866 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 2 Jun 2015 16:48:38 -0500 Subject: [PATCH] core: keys() and values() funcs for map variables they work on maps with both keys and values that are string types, which AFAICT are the only types of maps we have right now. closes #1915 --- config/interpolate_funcs.go | 71 +++++++++++++++++++++++ config/interpolate_funcs_test.go | 98 ++++++++++++++++++++++++++++++++ config/raw_config.go | 2 + 3 files changed, 171 insertions(+) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 7c4d76619..2249a9928 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 5432cdbd7..9b8726a3e 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 d51578e0b..ebb9f18dc 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{