Merge pull request #7832 from hashicorp/f-map-func

config: Add map() interpolation function
This commit is contained in:
Paul Hinze 2016-07-27 13:52:32 -05:00 committed by GitHub
commit 4cdebd7a60
3 changed files with 127 additions and 0 deletions

View File

@ -71,6 +71,7 @@ func Funcs() map[string]ast.Function {
"length": interpolationFuncLength(),
"list": interpolationFuncList(),
"lower": interpolationFuncLower(),
"map": interpolationFuncMap(),
"md5": interpolationFuncMd5(),
"uuid": interpolationFuncUUID(),
"replace": interpolationFuncReplace(),
@ -123,6 +124,50 @@ func interpolationFuncList() ast.Function {
}
}
// interpolationFuncMap creates a map from the parameters passed
// to it.
func interpolationFuncMap() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{},
ReturnType: ast.TypeMap,
Variadic: true,
VariadicType: ast.TypeAny,
Callback: func(args []interface{}) (interface{}, error) {
outputMap := make(map[string]ast.Variable)
if len(args)%2 != 0 {
return nil, fmt.Errorf("requires an even number of arguments, got %d", len(args))
}
var firstType *ast.Type
for i := 0; i < len(args); i += 2 {
key, ok := args[i].(string)
if !ok {
return nil, fmt.Errorf("argument %d represents a key, so it must be a string", i+1)
}
val := args[i+1]
variable, err := hil.InterfaceToVariable(val)
if err != nil {
return nil, err
}
// Enforce map type homogeneity
if firstType == nil {
firstType = &variable.Type
} else if variable.Type != *firstType {
return nil, fmt.Errorf("all map values must have the same type, got %s then %s", firstType.Printable(), variable.Type.Printable())
}
// Check for duplicate keys
if _, ok := outputMap[key]; ok {
return nil, fmt.Errorf("argument %d is a duplicate key: %q", i+1, key)
}
outputMap[key] = variable
}
return outputMap, nil
},
}
}
// interpolationFuncCompact strips a list of multi-variable values
// (e.g. as returned by "split") of any empty strings.
func interpolationFuncCompact() ast.Function {

View File

@ -113,6 +113,81 @@ func TestInterpolateFuncList(t *testing.T) {
})
}
func TestInterpolateFuncMap(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
// empty input returns empty map
{
`${map()}`,
map[string]interface{}{},
false,
},
// odd args is error
{
`${map("odd")}`,
nil,
true,
},
// two args returns map w/ one k/v
{
`${map("hello", "world")}`,
map[string]interface{}{"hello": "world"},
false,
},
// four args get two k/v
{
`${map("hello", "world", "what's", "up?")}`,
map[string]interface{}{"hello": "world", "what's": "up?"},
false,
},
// map of lists is okay
{
`${map("hello", list("world"), "what's", list("up?"))}`,
map[string]interface{}{
"hello": []interface{}{"world"},
"what's": []interface{}{"up?"},
},
false,
},
// map of maps is okay
{
`${map("hello", map("there", "world"), "what's", map("really", "up?"))}`,
map[string]interface{}{
"hello": map[string]interface{}{"there": "world"},
"what's": map[string]interface{}{"really": "up?"},
},
false,
},
// keys have to be strings
{
`${map(list("listkey"), "val")}`,
nil,
true,
},
// types have to match
{
`${map("some", "strings", "also", list("lists"))}`,
nil,
true,
},
// duplicate keys are an error
{
`${map("key", "val", "key", "again")}`,
nil,
true,
},
},
})
}
func TestInterpolateFuncCompact(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{

View File

@ -181,6 +181,13 @@ The supported built-in functions are:
* `lower(string)` - Returns a copy of the string with all Unicode letters mapped to their lower case.
* `map(key, value, ...)` - Returns a map consisting of the key/value pairs
specified as arguments. Every odd argument must be a string key, and every
even argument must have the same type as the other values specified.
Duplicate keys are not allowed. Examples:
* `map("hello", "world")`
* `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))`
* `md5(string)` - Returns a (conventional) hexadecimal representation of the
MD5 hash of the given string.