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
This commit is contained in:
Paul Hinze 2015-06-02 16:48:38 -05:00
parent 79a18dc16b
commit b781c6c446
3 changed files with 171 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "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
},
}
}

View File

@ -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) { func TestInterpolateFuncElement(t *testing.T) {
testFunction(t, testFunctionConfig{ testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{ Cases: []testFunctionCase{

View File

@ -304,6 +304,8 @@ func langEvalConfig(vs map[string]ast.Variable) *lang.EvalConfig {
funcMap[k] = v funcMap[k] = v
} }
funcMap["lookup"] = interpolationFuncLookup(vs) funcMap["lookup"] = interpolationFuncLookup(vs)
funcMap["keys"] = interpolationFuncKeys(vs)
funcMap["values"] = interpolationFuncValues(vs)
return &lang.EvalConfig{ return &lang.EvalConfig{
GlobalScope: &ast.BasicScope{ GlobalScope: &ast.BasicScope{