Skip to content

Commit

Permalink
core: keys() and values() funcs for map variables
Browse files Browse the repository at this point in the history
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
  • Loading branch information
phinze committed Jun 2, 2015
1 parent 79a18dc commit b781c6c
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
71 changes: 71 additions & 0 deletions config/interpolate_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"regexp"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -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
},
}
}
98 changes: 98 additions & 0 deletions config/interpolate_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
2 changes: 2 additions & 0 deletions config/raw_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down

0 comments on commit b781c6c

Please sign in to comment.