mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
Add a parse_yaml function (#2999)
Signed-off-by: Sambhav Kothari <sambhavs.email@gmail.com> Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
parent
de6c6f2199
commit
f5e00ee034
5 changed files with 186 additions and 0 deletions
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/blang/semver/v4"
|
"github.com/blang/semver/v4"
|
||||||
gojmespath "github.com/jmespath/go-jmespath"
|
gojmespath "github.com/jmespath/go-jmespath"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/wildcard"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -59,6 +60,7 @@ var (
|
||||||
truncate = "truncate"
|
truncate = "truncate"
|
||||||
semverCompare = "semver_compare"
|
semverCompare = "semver_compare"
|
||||||
parseJson = "parse_json"
|
parseJson = "parse_json"
|
||||||
|
parseYAML = "parse_yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorPrefix = "JMESPath function '%s': "
|
const errorPrefix = "JMESPath function '%s': "
|
||||||
|
@ -271,6 +273,13 @@ func getFunctions() []*gojmespath.FunctionEntry {
|
||||||
},
|
},
|
||||||
Handler: jpParseJson,
|
Handler: jpParseJson,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: parseYAML,
|
||||||
|
Arguments: []ArgSpec{
|
||||||
|
{Types: []JpType{JpString}},
|
||||||
|
},
|
||||||
|
Handler: jpParseYAML,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -684,6 +693,20 @@ func jpParseJson(arguments []interface{}) (interface{}, error) {
|
||||||
return output, err
|
return output, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jpParseYAML(arguments []interface{}) (interface{}, error) {
|
||||||
|
input, err := validateArg(parseYAML, arguments, 0, reflect.String)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jsonData, err := yaml.YAMLToJSON([]byte(input.String()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var output interface{}
|
||||||
|
err = json.Unmarshal(jsonData, &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) {
|
||||||
|
|
|
@ -101,6 +101,82 @@ func Test_ParseJsonComplex(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ParseYAML(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
output interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `a: b`,
|
||||||
|
output: map[string]interface{}{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- a: b`,
|
||||||
|
output: []interface{}{
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
3.0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
spec:
|
||||||
|
test: 1
|
||||||
|
test2:
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
`,
|
||||||
|
output: map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"test": 1.0,
|
||||||
|
"test2": []interface{}{2.0, 3.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
bar: >
|
||||||
|
this is not a normal string it
|
||||||
|
spans more than
|
||||||
|
one line
|
||||||
|
see?`,
|
||||||
|
output: map[string]interface{}{
|
||||||
|
"bar": "this is not a normal string it spans more than one line see?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
---
|
||||||
|
foo: ~
|
||||||
|
bar: null
|
||||||
|
`,
|
||||||
|
output: map[string]interface{}{
|
||||||
|
"bar": nil,
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
|
jp, err := New(fmt.Sprintf(`parse_yaml('%s')`, tc.input))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
result, err := jp.Search("")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, result, tc.output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_EqualFold(t *testing.T) {
|
func Test_EqualFold(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
jmesPath string
|
jmesPath string
|
||||||
|
|
|
@ -39,3 +39,23 @@ results:
|
||||||
resource: invalid-test
|
resource: invalid-test
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
result: fail
|
result: fail
|
||||||
|
- policy: test-parse-yaml
|
||||||
|
rule: test-yaml-parsing-jmespath
|
||||||
|
resource: valid-yaml-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: pass
|
||||||
|
- policy: test-parse-yaml
|
||||||
|
rule: test-yaml-parsing-jmespath
|
||||||
|
resource: invalid-yaml-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: fail
|
||||||
|
- policy: test-parse-yaml-array
|
||||||
|
rule: test-yaml-parsing-jmespath
|
||||||
|
resource: valid-yaml-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: pass
|
||||||
|
- policy: test-parse-yaml-array
|
||||||
|
rule: test-yaml-parsing-jmespath
|
||||||
|
resource: invalid-yaml-test
|
||||||
|
kind: ConfigMap
|
||||||
|
result: fail
|
||||||
|
|
|
@ -95,3 +95,45 @@ spec:
|
||||||
- key: "{{request.object.metadata.annotations.test | parse_json(@).a }}"
|
- key: "{{request.object.metadata.annotations.test | parse_json(@).a }}"
|
||||||
operator: NotEquals
|
operator: NotEquals
|
||||||
value: b
|
value: b
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: test-parse-yaml
|
||||||
|
spec:
|
||||||
|
validationFailureAction: enforce
|
||||||
|
background: false
|
||||||
|
rules:
|
||||||
|
- name: test-yaml-parsing-jmespath
|
||||||
|
match:
|
||||||
|
resources:
|
||||||
|
kinds:
|
||||||
|
- ConfigMap
|
||||||
|
validate:
|
||||||
|
message: "Test JMESPath"
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
- key: "{{request.object.metadata.annotations.test | parse_yaml(@).value }}"
|
||||||
|
operator: NotEquals
|
||||||
|
value: "a"
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: test-parse-yaml-array
|
||||||
|
spec:
|
||||||
|
validationFailureAction: enforce
|
||||||
|
background: false
|
||||||
|
rules:
|
||||||
|
- name: test-yaml-parsing-jmespath
|
||||||
|
match:
|
||||||
|
resources:
|
||||||
|
kinds:
|
||||||
|
- ConfigMap
|
||||||
|
validate:
|
||||||
|
message: "Test JMESPath"
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
- key: a
|
||||||
|
operator: NotIn
|
||||||
|
value: "{{request.object.metadata.annotations.test | parse_yaml(@).array }}"
|
||||||
|
|
|
@ -67,3 +67,28 @@ metadata:
|
||||||
annotations:
|
annotations:
|
||||||
test: |
|
test: |
|
||||||
{"a": "not-b"}
|
{"a": "not-b"}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: valid-yaml-test
|
||||||
|
annotations:
|
||||||
|
test: |
|
||||||
|
value: a
|
||||||
|
array:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: invalid-yaml-test
|
||||||
|
annotations:
|
||||||
|
test: |
|
||||||
|
value: b
|
||||||
|
array:
|
||||||
|
- d
|
||||||
|
- e
|
||||||
|
- f
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue