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(),
|
"distinct": interpolationFuncDistinct(),
|
||||||
"element": interpolationFuncElement(),
|
"element": interpolationFuncElement(),
|
||||||
"file": interpolationFuncFile(),
|
"file": interpolationFuncFile(),
|
||||||
|
"matchkeys": interpolationFuncMatchKeys(),
|
||||||
"floor": interpolationFuncFloor(),
|
"floor": interpolationFuncFloor(),
|
||||||
"format": interpolationFuncFormat(),
|
"format": interpolationFuncFormat(),
|
||||||
"formatlist": interpolationFuncFormatList(),
|
"formatlist": interpolationFuncFormatList(),
|
||||||
|
@ -668,6 +669,57 @@ func appendIfMissing(slice []string, element string) []string {
|
||||||
return append(slice, element)
|
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
|
// interpolationFuncJoin implements the "join" function that allows
|
||||||
// multi-variable values to be joined by some character.
|
// multi-variable values to be joined by some character.
|
||||||
func interpolationFuncJoin() ast.Function {
|
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) {
|
func TestInterpolateFuncFile(t *testing.T) {
|
||||||
tf, err := ioutil.TempFile("", "tf")
|
tf, err := ioutil.TempFile("", "tf")
|
||||||
if err != nil {
|
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,
|
module, you generally want to make the path relative to the module base,
|
||||||
like this: `file("${path.module}/file")`.
|
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
|
* `floor(float)` - Returns the greatest integer value less than or equal to
|
||||||
the argument.
|
the argument.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue