mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Add parse_json function the decode json strings (#2941)
Signed-off-by: Sambhav Kothari <sambhavs.email@gmail.com>
This commit is contained in:
parent
8350aadc58
commit
6b9798f76f
5 changed files with 129 additions and 1 deletions
pkg/engine/jmespath
test/cli/test/custom-functions
|
@ -2,6 +2,7 @@ package jmespath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -57,6 +58,7 @@ var (
|
||||||
pathCanonicalize = "path_canonicalize"
|
pathCanonicalize = "path_canonicalize"
|
||||||
truncate = "truncate"
|
truncate = "truncate"
|
||||||
semverCompare = "semver_compare"
|
semverCompare = "semver_compare"
|
||||||
|
parseJson = "parse_json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorPrefix = "JMESPath function '%s': "
|
const errorPrefix = "JMESPath function '%s': "
|
||||||
|
@ -262,6 +264,13 @@ func getFunctions() []*gojmespath.FunctionEntry {
|
||||||
},
|
},
|
||||||
Handler: jpSemverCompare,
|
Handler: jpSemverCompare,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: parseJson,
|
||||||
|
Arguments: []ArgSpec{
|
||||||
|
{Types: []JpType{JpString}},
|
||||||
|
},
|
||||||
|
Handler: jpParseJson,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -665,6 +674,16 @@ func jpSemverCompare(arguments []interface{}) (interface{}, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jpParseJson(arguments []interface{}) (interface{}, error) {
|
||||||
|
input, err := validateArg(parseJson, arguments, 0, reflect.String)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var output interface{}
|
||||||
|
err = json.Unmarshal([]byte(input.String()), &output)
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
|
||||||
// InterfaceToString casts an interface to a string type
|
// InterfaceToString casts an interface to a string type
|
||||||
func ifaceToString(iface interface{}) (string, error) {
|
func ifaceToString(iface interface{}) (string, error) {
|
||||||
switch i := iface.(type) {
|
switch i := iface.(type) {
|
||||||
|
@ -686,7 +705,7 @@ func ifaceToString(iface interface{}) (string, error) {
|
||||||
func validateArg(f string, arguments []interface{}, index int, expectedType reflect.Kind) (reflect.Value, error) {
|
func validateArg(f string, arguments []interface{}, index int, expectedType reflect.Kind) (reflect.Value, error) {
|
||||||
arg := reflect.ValueOf(arguments[index])
|
arg := reflect.ValueOf(arguments[index])
|
||||||
if arg.Type().Kind() != expectedType {
|
if arg.Type().Kind() != expectedType {
|
||||||
return reflect.Value{}, fmt.Errorf(invalidArgumentTypeError, equalFold, index+1, expectedType.String())
|
return reflect.Value{}, fmt.Errorf(invalidArgumentTypeError, f, index+1, expectedType.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return arg, nil
|
return arg, nil
|
||||||
|
|
|
@ -43,6 +43,68 @@ func Test_Compare(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ParseJsonSerde(t *testing.T) {
|
||||||
|
testCases := []string{
|
||||||
|
`{"a":"b"}`,
|
||||||
|
`true`,
|
||||||
|
`[1,2,3,{"a":"b"}]`,
|
||||||
|
`null`,
|
||||||
|
`[]`,
|
||||||
|
`{}`,
|
||||||
|
`0`,
|
||||||
|
`1.2`,
|
||||||
|
`[1.2,true,{"a":{"a":"b"}}]`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc, func(t *testing.T) {
|
||||||
|
jp, err := New(fmt.Sprintf(`to_string(parse_json('%s'))`, tc))
|
||||||
|
fmt.Println(err)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
result, err := jp.Search("")
|
||||||
|
fmt.Println(err)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, result, tc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ParseJsonComplex(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
expectedResult interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `parse_json('{"a": "b"}').a`,
|
||||||
|
expectedResult: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `parse_json('{"a": [1, 2, 3, 4]}').a[0]`,
|
||||||
|
expectedResult: 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `parse_json('[1, 2, {"a": {"b": {"c": [1, 2]}}}]')[2].a.b.c[1]`,
|
||||||
|
expectedResult: 2.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
|
jp, err := New(tc.input)
|
||||||
|
fmt.Println(err)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
result, err := jp.Search("")
|
||||||
|
fmt.Println(err)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, result, tc.expectedResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_EqualFold(t *testing.T) {
|
func Test_EqualFold(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
jmesPath string
|
jmesPath string
|
||||||
|
|
|
@ -74,3 +74,24 @@ spec:
|
||||||
- key: "{{ path_canonicalize(element.hostPath.path) }}"
|
- key: "{{ path_canonicalize(element.hostPath.path) }}"
|
||||||
operator: Equals
|
operator: Equals
|
||||||
value: "\\var\\run\\containerd\\containerd.sock"
|
value: "\\var\\run\\containerd\\containerd.sock"
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: test-parse-json
|
||||||
|
spec:
|
||||||
|
validationFailureAction: enforce
|
||||||
|
background: false
|
||||||
|
rules:
|
||||||
|
- name: test-json-parsing-jmespath
|
||||||
|
match:
|
||||||
|
resources:
|
||||||
|
kinds:
|
||||||
|
- ConfigMap
|
||||||
|
validate:
|
||||||
|
message: "Test JMESPath"
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
- key: "{{request.object.metadata.annotations.test | parse_json(@).a }}"
|
||||||
|
operator: NotEquals
|
||||||
|
value: b
|
|
@ -51,3 +51,19 @@ spec:
|
||||||
mountPath: /sock
|
mountPath: /sock
|
||||||
- name: test
|
- name: test
|
||||||
mountPath: /test
|
mountPath: /test
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: valid-test
|
||||||
|
annotations:
|
||||||
|
test: |
|
||||||
|
{"a": "b"}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: invalid-test
|
||||||
|
annotations:
|
||||||
|
test: |
|
||||||
|
{"a": "not-b"}
|
|
@ -29,3 +29,13 @@ results:
|
||||||
resource: mount-containerd-sock
|
resource: mount-containerd-sock
|
||||||
kind: Pod
|
kind: Pod
|
||||||
status: fail
|
status: fail
|
||||||
|
- policy: test-parse-json
|
||||||
|
rule: test-json-parsing-jmespath
|
||||||
|
resource: valid-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: pass
|
||||||
|
- policy: test-parse-json
|
||||||
|
rule: test-json-parsing-jmespath
|
||||||
|
resource: invalid-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: fail
|
Loading…
Add table
Reference in a new issue