mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Add JMESPath Function items
(#3777)
Co-authored-by: Jim Bugwadia <jim@nirmata.com> Co-authored-by: Sambhav Kothari <sambhavs.email@gmail.com> Co-authored-by: Sambhav Kothari <skothari44@bloomberg.net>
This commit is contained in:
parent
fca068d0f6
commit
af51ceb4ff
4 changed files with 132 additions and 3 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -62,6 +63,7 @@ var (
|
|||
semverCompare = "semver_compare"
|
||||
parseJson = "parse_json"
|
||||
parseYAML = "parse_yaml"
|
||||
items = "items"
|
||||
)
|
||||
|
||||
const errorPrefix = "JMESPath function '%s': "
|
||||
|
@ -368,6 +370,18 @@ func GetFunctions() []*FunctionEntry {
|
|||
ReturnType: []JpType{JpAny},
|
||||
Note: "decodes a valid YAML encoded string to the appropriate type provided it can be represented as JSON",
|
||||
},
|
||||
{
|
||||
Entry: &gojmespath.FunctionEntry{Name: items,
|
||||
Arguments: []ArgSpec{
|
||||
{Types: []JpType{JpObject}},
|
||||
{Types: []JpType{JpString}},
|
||||
{Types: []JpType{JpString}},
|
||||
},
|
||||
Handler: jpItems,
|
||||
},
|
||||
ReturnType: []JpType{JpArray},
|
||||
Note: "converts a map to an array of objects where each key:value is an item in the array",
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -795,6 +809,41 @@ func jpParseYAML(arguments []interface{}) (interface{}, error) {
|
|||
return output, err
|
||||
}
|
||||
|
||||
func jpItems(arguments []interface{}) (interface{}, error) {
|
||||
input, ok := arguments[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(invalidArgumentTypeError, arguments, 0, "Object")
|
||||
}
|
||||
keyName, ok := arguments[1].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(invalidArgumentTypeError, arguments, 1, "String")
|
||||
}
|
||||
valName, ok := arguments[2].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(invalidArgumentTypeError, arguments, 2, "String")
|
||||
}
|
||||
|
||||
arrayOfObj := make([]map[string]interface{}, 0)
|
||||
|
||||
keys := []string{}
|
||||
|
||||
// Sort the keys so that the output is deterministic
|
||||
for key := range input {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
m := make(map[string]interface{})
|
||||
m[keyName] = key
|
||||
m[valName] = input[key]
|
||||
arrayOfObj = append(arrayOfObj, m)
|
||||
}
|
||||
|
||||
return arrayOfObj, nil
|
||||
}
|
||||
|
||||
// InterfaceToString casts an interface to a string type
|
||||
func ifaceToString(iface interface{}) (string, error) {
|
||||
switch i := iface.(type) {
|
||||
|
|
|
@ -1284,3 +1284,53 @@ func Test_SemverCompare(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Items(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
object string
|
||||
keyName string
|
||||
valName string
|
||||
expectedResult string
|
||||
}{
|
||||
{
|
||||
object: `{ "key1": "value1" }`,
|
||||
keyName: `"key"`,
|
||||
valName: `"value"`,
|
||||
expectedResult: `[{ "key": "key1", "value": "value1" }]`,
|
||||
},
|
||||
{
|
||||
object: `{ "key1": "value1", "key2": "value2" }`,
|
||||
keyName: `"key"`,
|
||||
valName: `"value"`,
|
||||
expectedResult: `[{ "key": "key1", "value": "value1" }, { "key": "key2", "value": "value2" }]`,
|
||||
},
|
||||
{
|
||||
object: `{ "key1": "value1", "key2": "value2" }`,
|
||||
keyName: `"myKey"`,
|
||||
valName: `"myValue"`,
|
||||
expectedResult: `[{ "myKey": "key1", "myValue": "value1" }, { "myKey": "key2", "myValue": "value2" }]`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
|
||||
query, err := New("items(`" + tc.object + "`,`" + tc.keyName + "`,`" + tc.valName + "`)")
|
||||
assert.NilError(t, err)
|
||||
|
||||
res, err := query.Search("")
|
||||
assert.NilError(t, err)
|
||||
|
||||
result, ok := res.([]map[string]interface{})
|
||||
assert.Assert(t, ok)
|
||||
|
||||
var resource []map[string]interface{}
|
||||
err = json.Unmarshal([]byte(tc.expectedResult), &resource)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.DeepEqual(t, result, resource)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,3 +49,8 @@ results:
|
|||
resource: example
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: example
|
||||
rule: items
|
||||
resource: example
|
||||
kind: Pod
|
||||
result: pass
|
||||
|
|
|
@ -163,3 +163,28 @@ spec:
|
|||
- key: "A=*"
|
||||
operator: AnyNotIn
|
||||
value: "{{ obj }}"
|
||||
- name: items
|
||||
context:
|
||||
- name: obj
|
||||
variable:
|
||||
value:
|
||||
a: 1
|
||||
b: 2
|
||||
jmesPath: items(@, 'key', 'value')
|
||||
- name: expected
|
||||
variable:
|
||||
value:
|
||||
- key: a
|
||||
value: 1
|
||||
- key: b
|
||||
value: 2
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
deny:
|
||||
conditions:
|
||||
- key: "{{ obj }}"
|
||||
operator: NotEqual
|
||||
value: "{{ expected }}"
|
||||
|
|
Loading…
Add table
Reference in a new issue