config: "matchkeys" interpolation function
This new function allows using a search within one list to filter another list. For example, it can be used to find the ids of EC2 instances in a particular AZ. The interface is made slightly awkward by the constraints of HIL's featureset. #13847
This commit is contained in:
parent
7ff92746a2
commit
f9fb6010ee
|
@ -69,6 +69,7 @@ func Funcs() map[string]ast.Function {
|
|||
"distinct": interpolationFuncDistinct(),
|
||||
"element": interpolationFuncElement(),
|
||||
"file": interpolationFuncFile(),
|
||||
"matchkeys": interpolationFuncMatchKeys(),
|
||||
"floor": interpolationFuncFloor(),
|
||||
"format": interpolationFuncFormat(),
|
||||
"formatlist": interpolationFuncFormatList(),
|
||||
|
@ -668,6 +669,57 @@ func appendIfMissing(slice []string, element string) []string {
|
|||
return append(slice, element)
|
||||
}
|
||||
|
||||
// for two lists `keys` and `values` of equal length, returns all elements
|
||||
// from `values` where the corresponding element from `keys` is in `searchset`.
|
||||
func interpolationFuncMatchKeys() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeList, ast.TypeList, ast.TypeList},
|
||||
ReturnType: ast.TypeList,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
output := make([]ast.Variable, 0)
|
||||
|
||||
values, _ := args[0].([]ast.Variable)
|
||||
keys, _ := args[1].([]ast.Variable)
|
||||
searchset, _ := args[2].([]ast.Variable)
|
||||
|
||||
if len(keys) != len(values) {
|
||||
return nil, fmt.Errorf("length of keys and values should be equal")
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
for _, search := range searchset {
|
||||
if res, err := compareSimpleVariables(key, search); err != nil {
|
||||
return nil, err
|
||||
} else if res == true {
|
||||
output = append(output, values[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if searchset is empty, then output is an empty list as well.
|
||||
// if we haven't matched any key, then output is an empty list.
|
||||
return output, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// compare two variables of the same type, i.e. non complex one, such as TypeList or TypeMap
|
||||
func compareSimpleVariables(a, b ast.Variable) (bool, error) {
|
||||
if a.Type != b.Type {
|
||||
return false, fmt.Errorf(
|
||||
"won't compare items of different types %s and %s",
|
||||
a.Type.Printable(), b.Type.Printable())
|
||||
}
|
||||
switch a.Type {
|
||||
case ast.TypeString:
|
||||
return a.Value.(string) == b.Value.(string), nil
|
||||
default:
|
||||
return false, fmt.Errorf(
|
||||
"can't compare items of type %s",
|
||||
a.Type.Printable())
|
||||
}
|
||||
}
|
||||
|
||||
// interpolationFuncJoin implements the "join" function that allows
|
||||
// multi-variable values to be joined by some character.
|
||||
func interpolationFuncJoin() ast.Function {
|
||||
|
|
|
@ -964,6 +964,74 @@ func TestInterpolateFuncDistinct(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncMatchKeys(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
// normal usage
|
||||
{
|
||||
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2"))}`,
|
||||
[]interface{}{"b"},
|
||||
false,
|
||||
},
|
||||
// normal usage 2, check the order
|
||||
{
|
||||
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref1"))}`,
|
||||
[]interface{}{"a", "b"},
|
||||
false,
|
||||
},
|
||||
// duplicate item in searchset
|
||||
{
|
||||
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref2"))}`,
|
||||
[]interface{}{"b"},
|
||||
false,
|
||||
},
|
||||
// no matches
|
||||
{
|
||||
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref4"))}`,
|
||||
[]interface{}{},
|
||||
false,
|
||||
},
|
||||
// no matches 2
|
||||
{
|
||||
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list())}`,
|
||||
[]interface{}{},
|
||||
false,
|
||||
},
|
||||
// zero case
|
||||
{
|
||||
`${matchkeys(list(), list(), list("nope"))}`,
|
||||
[]interface{}{},
|
||||
false,
|
||||
},
|
||||
// complex values
|
||||
{
|
||||
`${matchkeys(list(list("a", "a")), list("a"), list("a"))}`,
|
||||
[]interface{}{[]interface{}{"a", "a"}},
|
||||
false,
|
||||
},
|
||||
// errors
|
||||
// different types
|
||||
{
|
||||
`${matchkeys(list("a"), list(1), list("a"))}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
// different types
|
||||
{
|
||||
`${matchkeys(list("a"), list(list("a"), list("a")), list("a"))}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
// lists of different length is an error
|
||||
{
|
||||
`${matchkeys(list("a"), list("a", "b"), list("a"))}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncFile(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "tf")
|
||||
if err != nil {
|
||||
|
|
|
@ -208,6 +208,15 @@ The supported built-in functions are:
|
|||
module, you generally want to make the path relative to the module base,
|
||||
like this: `file("${path.module}/file")`.
|
||||
|
||||
* `matchkeys(values, keys, searchset)` - For two lists `values` and `keys` of
|
||||
equal length, returns all elements from `values` where the corresponding
|
||||
element from `keys` exists in the `searchset` list. E.g.
|
||||
`matchkeys(aws_instance.example.*.id,
|
||||
aws_instance.example.*.availability_zone, list("us-west-2a"))` will return a
|
||||
list of the instance IDs of the `aws_instance.example` instances in
|
||||
`"us-west-2a"`. No match will result in empty list. Items of `keys` are
|
||||
processed sequentially, so the order of returned `values` is preserved.
|
||||
|
||||
* `floor(float)` - Returns the greatest integer value less than or equal to
|
||||
the argument.
|
||||
|
||||
|
|
Loading…
Reference in New Issue