1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

JMESPath custom functions (#1772)

* JMESPath: Support regex expressions

Signed-off-by: Max Goncharenko <kacejot@fex.net>

* JMESPath: Add string functions

Signed-off-by: Max Goncharenko <kacejot@fex.net>

* Removed {{$}} variable handling logic

Signed-off-by: Max Goncharenko <kacejot@fex.net>

* Name all functions in snake case; Update error message; Fix {{@}} behavior

Signed-off-by: Max Goncharenko <kacejot@fex.net>
This commit is contained in:
Max Goncharenko 2021-04-17 02:17:00 +03:00 committed by GitHub
parent 4a4fdc54ee
commit 6a0305674a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 808 additions and 9 deletions

2
go.mod
View file

@ -25,7 +25,6 @@ require (
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
@ -51,6 +50,7 @@ require (
// Added for go1.13 migration https://github.com/golang/go/issues/32805
replace (
github.com/gorilla/rpc v1.2.0+incompatible => github.com/gorilla/rpc v1.2.0
github.com/jmespath/go-jmespath => github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200306081859-6a048a382944
k8s.io/component-base => k8s.io/component-base v0.0.0-20190612130303-4062e14deebe
)

8
go.sum
View file

@ -476,8 +476,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -523,6 +523,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6 h1:9rJUAc/XxL6wV4i+3X56wcRM8tDhoo0l7DIieuXPGfk=
github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7 h1:k/1ku0yehLCPqERCHkIHMDqDg1R02AcCScRuHbamU3s=
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7/go.mod h1:YR/zYthNdWfO8+0IOyHDcIDBBBS2JMnYUIwSsnwmRqU=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -652,8 +654,6 @@ github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=

View file

@ -5,7 +5,7 @@ import (
"fmt"
"strings"
jmespath "github.com/jmespath/go-jmespath"
jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
)
//Query the JSON context with JMESPATH search path
@ -25,7 +25,7 @@ func (ctx *Context) Query(query string) (interface{}, error) {
}
// compile the query
queryPath, err := jmespath.Compile(query)
queryPath, err := jmespath.New(query)
if err != nil {
ctx.log.Error(err, "incorrect query", "query", query)
return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err)

View file

@ -0,0 +1,381 @@
package jmespath
import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
gojmespath "github.com/jmespath/go-jmespath"
)
var (
JpObject = gojmespath.JpObject
JpString = gojmespath.JpString
JpNumber = gojmespath.JpNumber
JpArray = gojmespath.JpArray
JpArrayString = gojmespath.JpArrayString
)
type (
JpType = gojmespath.JpType
ArgSpec = gojmespath.ArgSpec
)
// function names
var (
compare = "compare"
contains = "contains"
equalFold = "equal_fold"
replace = "replace"
replaceAll = "replace_all"
toUpper = "to_upper"
toLower = "to_lower"
trim = "trim"
split = "split"
equals = "equals"
regexReplaceAll = "regex_replace_all"
regexReplaceAllLiteral = "regex_replace_all_literal"
regexMatch = "regex_match"
)
const errorPrefix = "JMESPath function '%s': "
const invalidArgumentTypeError = errorPrefix + "%d argument is expected of %s type"
const genericError = errorPrefix + "%s"
func getFunctions() []*gojmespath.FunctionEntry {
return []*gojmespath.FunctionEntry{
{
Name: compare,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfCompare,
},
{
Name: contains,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfContains,
},
{
Name: equalFold,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfEqualFold,
},
{
Name: replace,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
{Types: []JpType{JpNumber}},
},
Handler: jpfReplace,
},
{
Name: replaceAll,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfReplaceAll,
},
{
Name: toUpper,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
},
Handler: jpfToUpper,
},
{
Name: toLower,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
},
Handler: jpfToLower,
},
{
Name: trim,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfTrim,
},
{
Name: split,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpfSplit,
},
{
Name: regexReplaceAll,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString, JpNumber}},
{Types: []JpType{JpString, JpNumber}},
},
Handler: jpRegexReplaceAll,
},
{
Name: regexReplaceAllLiteral,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString, JpNumber}},
{Types: []JpType{JpString, JpNumber}},
},
Handler: jpRegexReplaceAllLiteral,
},
{
Name: regexMatch,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString, JpNumber}},
},
Handler: jpRegexMatch,
},
}
}
func jpfCompare(arguments []interface{}) (interface{}, error) {
var err error
a, err := validateArg(compare, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
b, err := validateArg(compare, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.Compare(a.String(), b.String()), nil
}
func jpfContains(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(contains, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
substr, err := validateArg(contains, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.Contains(str.String(), substr.String()), nil
}
func jpfEqualFold(arguments []interface{}) (interface{}, error) {
var err error
a, err := validateArg(equalFold, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
b, err := validateArg(equalFold, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.EqualFold(a.String(), b.String()), nil
}
func jpfReplace(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(replace, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
old, err := validateArg(replace, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
new, err := validateArg(replace, arguments, 2, reflect.String)
if err != nil {
return nil, err
}
n, err := validateArg(replace, arguments, 3, reflect.Float64)
if err != nil {
return nil, err
}
return strings.Replace(str.String(), old.String(), new.String(), int(n.Float())), nil
}
func jpfReplaceAll(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(replaceAll, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
old, err := validateArg(replaceAll, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
new, err := validateArg(replaceAll, arguments, 2, reflect.String)
if err != nil {
return nil, err
}
return strings.ReplaceAll(str.String(), old.String(), new.String()), nil
}
func jpfToUpper(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(toUpper, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
return strings.ToUpper(str.String()), nil
}
func jpfToLower(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(toLower, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
return strings.ToLower(str.String()), nil
}
func jpfTrim(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(trim, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
cutset, err := validateArg(trim, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.Trim(str.String(), cutset.String()), nil
}
func jpfSplit(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(split, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
sep, err := validateArg(split, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
return strings.Split(str.String(), sep.String()), nil
}
func jpRegexReplaceAll(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexReplaceAll, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAll, 2, "String or Real")
}
repl, err := ifaceToString(arguments[2])
if err != nil {
return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAll, 3, "String or Real")
}
reg, err := regexp.Compile(regex.String())
if err != nil {
return nil, fmt.Errorf(genericError, regexReplaceAll, err.Error())
}
return string(reg.ReplaceAll([]byte(src), []byte(repl))), nil
}
func jpRegexReplaceAllLiteral(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexReplaceAllLiteral, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAllLiteral, 2, "String or Real")
}
repl, err := ifaceToString(arguments[2])
if err != nil {
return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAllLiteral, 3, "String or Real")
}
reg, err := regexp.Compile(regex.String())
if err != nil {
return nil, fmt.Errorf(genericError, regexReplaceAllLiteral, err.Error())
}
return string(reg.ReplaceAllLiteral([]byte(src), []byte(repl))), nil
}
func jpRegexMatch(arguments []interface{}) (interface{}, error) {
var err error
regex, err := validateArg(regexMatch, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
src, err := ifaceToString(arguments[1])
if err != nil {
return nil, fmt.Errorf(invalidArgumentTypeError, regexMatch, 2, "String or Real")
}
return regexp.Match(regex.String(), []byte(src))
}
// InterfaceToString casts an interface to a string type
func ifaceToString(iface interface{}) (string, error) {
switch iface.(type) {
case int:
return strconv.Itoa(iface.(int)), nil
case float64:
return strconv.FormatFloat(iface.(float64), 'f', -1, 32), nil
case float32:
return strconv.FormatFloat(iface.(float64), 'f', -1, 32), nil
case string:
return iface.(string), nil
case bool:
return strconv.FormatBool(iface.(bool)), nil
default:
return "", errors.New("error, undefined type cast")
}
}
func validateArg(f string, arguments []interface{}, index int, expectedType reflect.Kind) (reflect.Value, error) {
arg := reflect.ValueOf(arguments[index])
if arg.Type().Kind() != expectedType {
return reflect.Value{}, fmt.Errorf(invalidArgumentTypeError, equalFold, index+1, expectedType.String())
}
return arg, nil
}

View file

@ -0,0 +1,245 @@
package jmespath
import (
"encoding/json"
"testing"
"gotest.tools/assert"
)
func TestJMESPathFunctions_CompareEqualStrings(t *testing.T) {
jp, err := New("compare('a', 'a')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
equal, ok := result.(int)
assert.Assert(t, ok)
assert.Equal(t, equal, 0)
}
func TestJMESPathFunctions_CompareDifferentStrings(t *testing.T) {
jp, err := New("compare('a', 'b')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
equal, ok := result.(int)
assert.Assert(t, ok)
assert.Equal(t, equal, -1)
}
func TestJMESPathFunctions_Contains(t *testing.T) {
jp, err := New("contains('string', 'str')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
contains, ok := result.(bool)
assert.Assert(t, ok)
assert.Assert(t, contains)
}
func TestJMESPathFunctions_EqualFold(t *testing.T) {
jp, err := New("equal_fold('Go', 'go')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
equal, ok := result.(bool)
assert.Assert(t, ok)
assert.Assert(t, equal)
}
func TestJMESPathFunctions_Replace(t *testing.T) {
// Can't use integer literals due to
// https://github.com/jmespath/go-jmespath/issues/27
//
// TODO: fix this in https://github.com/kyverno/go-jmespath
//
jp, err := New("replace('Lorem ipsum dolor sit amet', 'ipsum', 'muspi', `-1`)")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
replaced, ok := result.(string)
assert.Assert(t, ok)
assert.Equal(t, replaced, "Lorem muspi dolor sit amet")
}
func TestJMESPathFunctions_ReplaceAll(t *testing.T) {
jp, err := New("replace_all('Lorem ipsum dolor sit amet', 'ipsum', 'muspi')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
replaced, ok := result.(string)
assert.Assert(t, ok)
assert.Equal(t, replaced, "Lorem muspi dolor sit amet")
}
func TestJMESPathFunctions_ToUpper(t *testing.T) {
jp, err := New("to_upper('abc')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
upper, ok := result.(string)
assert.Assert(t, ok)
assert.Equal(t, upper, "ABC")
}
func TestJMESPathFunctions_ToLower(t *testing.T) {
jp, err := New("to_lower('AbC')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
lower, ok := result.(string)
assert.Assert(t, ok)
assert.Equal(t, lower, "abc")
}
func TestJMESPathFunctions_Trim(t *testing.T) {
jp, err := New("trim('¡¡¡Hello, Gophers!!!', '!¡')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
trim, ok := result.(string)
assert.Assert(t, ok)
assert.Equal(t, trim, "Hello, Gophers")
}
func TestJMESPathFunctions_Split(t *testing.T) {
jp, err := New("split('Hello, Gophers', ', ')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
split, ok := result.([]string)
assert.Assert(t, ok)
assert.Equal(t, split[0], "Hello")
assert.Equal(t, split[1], "Gophers")
}
func TestJMESPathFunctions_HasPrefix(t *testing.T) {
jp, err := New("starts_with('Gophers', 'Go')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
split, ok := result.(bool)
assert.Assert(t, ok)
assert.Equal(t, split, true)
}
func TestJMESPathFunctions_HasSuffix(t *testing.T) {
jp, err := New("ends_with('Amigo', 'go')")
assert.NilError(t, err)
result, err := jp.Search("")
assert.NilError(t, err)
split, ok := result.(bool)
assert.Assert(t, ok)
assert.Equal(t, split, true)
}
func Test_regexMatch(t *testing.T) {
data := make(map[string]interface{})
data["foo"] = "hgf'b1a2r'b12g"
query, err := New("regex_match('12.*', foo)")
assert.NilError(t, err)
result, err := query.Search(data)
assert.NilError(t, err)
assert.Equal(t, true, result)
}
func Test_regexMatchWithNumber(t *testing.T) {
data := make(map[string]interface{})
data["foo"] = -12.0
query, err := New("regex_match('12.*', abs(foo))")
assert.NilError(t, err)
result, err := query.Search(data)
assert.NilError(t, err)
assert.Equal(t, true, result)
}
func Test_regexReplaceAll(t *testing.T) {
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "ns_first"
},
"spec": {
"namespace": "ns_first",
"name": "temp_other",
"field" : "Hello world, helworldlo"
}
}
`)
expected := "Glo world, Gworldlo"
var resource interface{}
err := json.Unmarshal(resourceRaw, &resource)
assert.NilError(t, err)
query, err := New(`regex_replace_all('([Hh]e|G)l', spec.field, '${2}G')`)
assert.NilError(t, err)
res, err := query.Search(resource)
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, string(result), expected)
}
func Test_regexReplaceAllLiteral(t *testing.T) {
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "ns_first"
},
"spec": {
"namespace": "ns_first",
"name": "temp_other",
"field" : "Hello world, helworldlo"
}
}
`)
expected := "Glo world, Gworldlo"
var resource interface{}
err := json.Unmarshal(resourceRaw, &resource)
assert.NilError(t, err)
query, err := New(`regex_replace_all_literal('[Hh]el?', spec.field, 'G')`)
assert.NilError(t, err)
res, err := query.Search(resource)
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, string(result), expected)
}

View file

@ -0,0 +1,18 @@
package jmespath
import (
gojmespath "github.com/jmespath/go-jmespath"
)
func New(query string) (*gojmespath.JMESPath, error) {
jp, err := gojmespath.Compile(query)
if err != nil {
return nil, err
}
for _, function := range getFunctions() {
jp.Register(function)
}
return jp, nil
}

View file

@ -8,9 +8,9 @@ import (
"strings"
"github.com/go-logr/logr"
"github.com/jmespath/go-jmespath"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/resourcecache"
"k8s.io/client-go/dynamic/dynamiclister"
@ -88,7 +88,7 @@ func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCont
}
func applyJMESPath(jmesPath string, jsonData []byte) (interface{}, error) {
jp, err := jmespath.Compile(jmesPath)
jp, err := jmespath.New(jmesPath)
if err != nil {
return nil, fmt.Errorf("failed to compile JMESPath: %s, error: %v", jmesPath, err)
}

View file

@ -185,6 +185,11 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface) jsonUt
for _, v := range vars {
variable := replaceBracesAndTrimSpaces(v)
if variable == "@" {
currentPath := getJMESPath(data.Path)
variable = strings.Replace(variable, "@", fmt.Sprintf("request.object%s", currentPath), -1)
}
operation, err := ctx.Query("request.operation")
if err == nil && operation == "DELETE" {
variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
@ -228,6 +233,13 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface) jsonUt
})
}
// getJMESPath converts path to JMES format
func getJMESPath(rawPath string) string {
path := strings.ReplaceAll(rawPath, "/", ".")
regex := regexp.MustCompile(`\.([\d])\.`)
return string(regex.ReplaceAll([]byte(path), []byte("[$1].")))
}
func substituteVarInPattern(pattern, variable string, value interface{}) (string, error) {
var stringToSubstitute string

View file

@ -135,6 +135,149 @@ func Test_subVars_failed(t *testing.T) {
}
}
func Test_subVars_with_JMESPath_At(t *testing.T) {
patternMap := []byte(`
{
"kind": "{{@}}",
"data": {
"rules": [
{
"apiGroups": [
"{{request.object.metadata.name}}"
],
"resources": [
"namespaces"
],
"verbs": [
"*"
],
"resourceNames": [
"{{request.object.metadata.name}}"
]
}
]
}
}
`)
resourceRaw := []byte(`
{
"kind": "foo",
"name": "bar",
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
expected := []byte(`{"data":{"rules":[{"apiGroups":["temp"],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"foo"}`)
var pattern, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
assert.NilError(t, err)
err = json.Unmarshal(resourceRaw, &resource)
assert.NilError(t, err)
// context
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
output, err := SubstituteAll(log.Log, ctx, pattern)
assert.NilError(t, err)
out, err := json.Marshal(output)
assert.NilError(t, err)
assert.Equal(t, string(out), string(expected))
}
func Test_subVars_withRegexMatch(t *testing.T) {
patternMap := []byte(`
{
"port": "{{ regex_match('(443)', '{{@}}') }}",
"name": "ns-owner-{{request.object.metadata.name}}"
}
`)
resourceRaw := []byte(`
{
"port": "443",
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
expected := []byte(`{"name":"ns-owner-temp","port":true}`)
var pattern, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
assert.NilError(t, err)
err = json.Unmarshal(resourceRaw, &resource)
assert.NilError(t, err)
// context
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
output, err := SubstituteAll(log.Log, ctx, pattern)
assert.NilError(t, err)
out, err := json.Marshal(output)
assert.NilError(t, err)
fmt.Print(string(out))
assert.Equal(t, string(out), string(expected))
}
func Test_subVars_withRegexReplaceAll(t *testing.T) {
patternMap := []byte(`
{
"port": "{{ regex_replace_all_literal('.*', '{{@}}', '1313') }}",
"name": "ns-owner-{{request.object.metadata.name}}"
}
`)
resourceRaw := []byte(`
{
"port": "43123",
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
expected := []byte(`{"name":"ns-owner-temp","port":"1313"}`)
var pattern, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
assert.NilError(t, err)
err = json.Unmarshal(resourceRaw, &resource)
assert.NilError(t, err)
// context
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
output, err := SubstituteAll(log.Log, ctx, pattern)
assert.NilError(t, err)
out, err := json.Marshal(output)
assert.NilError(t, err)
assert.Equal(t, string(out), string(expected))
}
func Test_ReplacingPathWhenDeleting(t *testing.T) {
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)