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:
parent
79a18dc16b
commit
b781c6c446
|
@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in New Issue