1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-30 19:35:06 +00:00

feat: add more time jmespath filters (#5950)

* feat: add more time jmespath filters

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* add time_utc and time_now_utc

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* add time_diff

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more time filters

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* Update pkg/engine/jmespath/functions.go

Signed-off-by: shuting <shutting06@gmail.com>

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: shuting <shutting06@gmail.com>
Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-01-10 05:40:46 +01:00 committed by GitHub
parent e323e693b2
commit 91f2622384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 448 additions and 285 deletions

View file

@ -70,8 +70,6 @@ var (
modulo = "modulo"
base64Decode = "base64_decode"
base64Encode = "base64_encode"
timeSince = "time_since"
timeNow = "time_now"
pathCanonicalize = "path_canonicalize"
truncate = "truncate"
semverCompare = "semver_compare"
@ -81,9 +79,6 @@ var (
objectFromLists = "object_from_lists"
random = "random"
x509_decode = "x509_decode"
timeAdd = "time_add"
timeParse = "time_parse"
timeToCron = "time_to_cron"
)
const (
@ -386,6 +381,14 @@ func GetFunctions() []*FunctionEntry {
ReturnType: []JpType{JpString},
Note: "returns current time in RFC 3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeNowUtc,
Handler: jpTimeNowUtc,
},
ReturnType: []JpType{JpString},
Note: "returns current UTC time in RFC 3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: pathCanonicalize,
@ -499,7 +502,7 @@ func GetFunctions() []*FunctionEntry {
Handler: jpTimeToCron,
},
ReturnType: []JpType{JpString},
Note: "converts an absolute time (RFC 3339) to a cron expression (string).",
Note: "converts a time (RFC 3339) to a cron expression (string).",
},
{
Entry: &gojmespath.FunctionEntry{
@ -507,12 +510,11 @@ func GetFunctions() []*FunctionEntry {
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeAdd,
},
ReturnType: []JpType{JpString},
Note: "adds duration (third string) to a time value (second string) of a specified layout (first string)",
Note: "adds duration (second string) to a time value (first string)",
},
{
Entry: &gojmespath.FunctionEntry{
@ -526,6 +528,78 @@ func GetFunctions() []*FunctionEntry {
ReturnType: []JpType{JpString},
Note: "changes a time value of a given layout to RFC 3339",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeUtc,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
},
Handler: jpTimeUtc,
},
ReturnType: []JpType{JpString},
Note: "calcutes time in UTC from a given time in RFC 3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeDiff,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeDiff,
},
ReturnType: []JpType{JpString},
Note: "calculate the difference between a start and end date in RFC3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeBefore,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeBefore,
},
ReturnType: []JpType{JpBool},
Note: "checks if a time is before another time, both in RFC3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeAfter,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeAfter,
},
ReturnType: []JpType{JpBool},
Note: "checks if a time is after another time, both in RFC3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeBetween,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeBetween,
},
ReturnType: []JpType{JpBool},
Note: "checks if a time is between a start and end time, all in RFC3339 format",
},
{
Entry: &gojmespath.FunctionEntry{
Name: timeTruncate,
Arguments: []ArgSpec{
{Types: []JpType{JpString}},
{Types: []JpType{JpString}},
},
Handler: jpTimeTruncate,
},
ReturnType: []JpType{JpString},
Note: "returns the result of rounding time down to a multiple of duration",
},
}
}
@ -830,55 +904,6 @@ func jpBase64Encode(arguments []interface{}) (interface{}, error) {
return base64.StdEncoding.EncodeToString([]byte(str.String())), nil
}
func jpTimeSince(arguments []interface{}) (interface{}, error) {
var err error
layout, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
ts1, err := validateArg("", arguments, 1, reflect.String)
if err != nil {
return nil, err
}
ts2, err := validateArg("", arguments, 2, reflect.String)
if err != nil {
return nil, err
}
var t1, t2 time.Time
if layout.String() != "" {
t1, err = time.Parse(layout.String(), ts1.String())
} else {
t1, err = time.Parse(time.RFC3339, ts1.String())
}
if err != nil {
return nil, err
}
t2 = time.Now()
if ts2.String() != "" {
if layout.String() != "" {
t2, err = time.Parse(layout.String(), ts2.String())
} else {
t2, err = time.Parse(time.RFC3339, ts2.String())
}
if err != nil {
return nil, err
}
}
return t2.Sub(t1).String(), nil
}
func jpTimeNow(arguments []interface{}) (interface{}, error) {
var ts time.Time = time.Now()
var t string = ts.Format(time.RFC3339)
return t, nil
}
func jpPathCanonicalize(arguments []interface{}) (interface{}, error) {
var err error
str, err := validateArg(pathCanonicalize, arguments, 0, reflect.String)
@ -1113,85 +1138,3 @@ func jpX509Decode(arguments []interface{}) (interface{}, error) {
return res, nil
}
func jpTimeToCron(arguments []interface{}) (interface{}, error) {
var err error
ts, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
var t time.Time
t, err = time.Parse(time.RFC3339, ts.String())
if err != nil {
return nil, err
}
t = t.UTC()
var cron string = ""
cron += strconv.Itoa(t.Minute()) + " "
cron += strconv.Itoa(t.Hour()) + " "
cron += strconv.Itoa(t.Day()) + " "
cron += strconv.Itoa(int(t.Month())) + " "
cron += strconv.Itoa(int(t.Weekday()))
return cron, nil
}
func jpTimeAdd(arguments []interface{}) (interface{}, error) {
var err error
layout, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
ts, err := validateArg("", arguments, 1, reflect.String)
if err != nil {
return nil, err
}
dr, err := validateArg("", arguments, 2, reflect.String)
if err != nil {
return nil, err
}
var t time.Time
if layout.String() != "" {
t, err = time.Parse(layout.String(), ts.String())
} else {
t, err = time.Parse(time.RFC3339, ts.String())
}
if err != nil {
return nil, err
}
var d time.Duration
d, err = time.ParseDuration(dr.String())
if err != nil {
return nil, err
}
return t.Add(d).Format(time.RFC3339), nil
}
func jpTimeParse(arguments []interface{}) (interface{}, error) {
var err error
layout, err := validateArg("", arguments, 0, reflect.String)
if err != nil {
return nil, err
}
ts, err := validateArg("", arguments, 1, reflect.String)
if err != nil {
return nil, err
}
var t time.Time
t, err = time.Parse(layout.String(), ts.String())
if err != nil {
return nil, err
}
return t.Format(time.RFC3339), nil
}

View file

@ -5,7 +5,6 @@ import (
"fmt"
"runtime"
"testing"
"time"
"gotest.tools/assert"
)
@ -1162,36 +1161,6 @@ func Test_Base64Decode_Secret(t *testing.T) {
assert.Equal(t, string(result), "Hello, world!")
}
func Test_TimeSince(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_since('', '2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
expectedResult: "180h10m0s",
},
{
test: "time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021')",
expectedResult: "180h10m11s",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_PathCanonicalize(t *testing.T) {
testCases := []struct {
jmesPath string
@ -1550,118 +1519,3 @@ UFOZZVoELaasWS559wy8og39Eq21dDMynb8Bndn/
})
}
}
func Test_TimeToCron(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_to_cron('2023-02-02T15:04:05Z')",
expectedResult: "4 15 2 2 4",
},
{
test: "time_to_cron('2023-02-02T15:04:05-07:00')",
expectedResult: "4 22 2 2 4",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeAdd(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_add('', '2021-01-02T15:04:05-07:00', '3h')",
expectedResult: "2021-01-02T18:04:05-07:00",
},
{
test: "time_add('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021', '5h30m40s')",
expectedResult: "2021-01-02T20:34:45Z",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeParse(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_parse('2006-01-02T15:04:05Z07:00', '2021-01-02T15:04:05-07:00')",
expectedResult: "2021-01-02T15:04:05-07:00",
},
{
test: "time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021')",
expectedResult: "2021-01-02T15:04:05Z",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeNow(t *testing.T) {
testCases := []struct {
test string
layout string
}{
{
test: "time_now()",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, time.Now().Format(time.RFC3339))
})
}
}

182
pkg/engine/jmespath/time.go Normal file
View file

@ -0,0 +1,182 @@
package jmespath
import (
"reflect"
"strconv"
"time"
)
// function names
var (
timeSince = "time_since"
timeNow = "time_now"
timeNowUtc = "time_now_utc"
timeAdd = "time_add"
timeParse = "time_parse"
timeToCron = "time_to_cron"
timeUtc = "time_utc"
timeDiff = "time_diff"
timeBefore = "time_before"
timeAfter = "time_after"
timeBetween = "time_between"
timeTruncate = "time_truncate"
)
func getTimeArg(f string, arguments []interface{}, index int) (time.Time, error) {
var empty time.Time
arg, err := validateArg(f, arguments, index, reflect.String)
if err != nil {
return empty, err
}
return time.Parse(time.RFC3339, arg.String())
}
func getDurationArg(f string, arguments []interface{}, index int) (time.Duration, error) {
var empty time.Duration
arg, err := validateArg(f, arguments, index, reflect.String)
if err != nil {
return empty, err
}
return time.ParseDuration(arg.String())
}
func jpTimeSince(arguments []interface{}) (interface{}, error) {
var err error
layout, err := validateArg(timeSince, arguments, 0, reflect.String)
if err != nil {
return nil, err
}
ts1, err := validateArg(timeSince, arguments, 1, reflect.String)
if err != nil {
return nil, err
}
ts2, err := validateArg(timeSince, arguments, 2, reflect.String)
if err != nil {
return nil, err
}
var t1, t2 time.Time
if layout.String() != "" {
t1, err = time.Parse(layout.String(), ts1.String())
} else {
t1, err = time.Parse(time.RFC3339, ts1.String())
}
if err != nil {
return nil, err
}
t2 = time.Now()
if ts2.String() != "" {
if layout.String() != "" {
t2, err = time.Parse(layout.String(), ts2.String())
} else {
t2, err = time.Parse(time.RFC3339, ts2.String())
}
if err != nil {
return nil, err
}
}
return t2.Sub(t1).String(), nil
}
func jpTimeNow(arguments []interface{}) (interface{}, error) {
return time.Now().Format(time.RFC3339), nil
}
func jpTimeNowUtc(arguments []interface{}) (interface{}, error) {
return time.Now().UTC().Format(time.RFC3339), nil
}
func jpTimeToCron(arguments []interface{}) (interface{}, error) {
if t, err := getTimeArg(timeToCron, arguments, 0); err != nil {
return nil, err
} else {
var cron string = ""
cron += strconv.Itoa(t.Minute()) + " "
cron += strconv.Itoa(t.Hour()) + " "
cron += strconv.Itoa(t.Day()) + " "
cron += strconv.Itoa(int(t.Month())) + " "
cron += strconv.Itoa(int(t.Weekday()))
return cron, nil
}
}
func jpTimeAdd(arguments []interface{}) (interface{}, error) {
if t, err := getTimeArg(timeToCron, arguments, 0); err != nil {
return nil, err
} else if d, err := getDurationArg(timeToCron, arguments, 1); err != nil {
return nil, err
} else {
return t.Add(d).Format(time.RFC3339), nil
}
}
func jpTimeParse(arguments []interface{}) (interface{}, error) {
if layout, err := validateArg(timeParse, arguments, 0, reflect.String); err != nil {
return nil, err
} else if ts, err := validateArg(timeParse, arguments, 1, reflect.String); err != nil {
return nil, err
} else if t, err := time.Parse(layout.String(), ts.String()); err != nil {
return nil, err
} else {
return t.Format(time.RFC3339), nil
}
}
func jpTimeUtc(arguments []interface{}) (interface{}, error) {
if t, err := getTimeArg(timeUtc, arguments, 0); err != nil {
return nil, err
} else {
return t.UTC().Format(time.RFC3339), nil
}
}
func jpTimeDiff(arguments []interface{}) (interface{}, error) {
if t1, err := getTimeArg(timeDiff, arguments, 0); err != nil {
return nil, err
} else if t2, err := getTimeArg(timeDiff, arguments, 1); err != nil {
return nil, err
} else {
return t2.Sub(t1).String(), nil
}
}
func jpTimeBefore(arguments []interface{}) (interface{}, error) {
if t1, err := getTimeArg(timeBefore, arguments, 0); err != nil {
return nil, err
} else if t2, err := getTimeArg(timeBefore, arguments, 1); err != nil {
return nil, err
} else {
return t1.Before(t2), nil
}
}
func jpTimeAfter(arguments []interface{}) (interface{}, error) {
if t1, err := getTimeArg(timeAfter, arguments, 0); err != nil {
return nil, err
} else if t2, err := getTimeArg(timeAfter, arguments, 1); err != nil {
return nil, err
} else {
return t1.After(t2), nil
}
}
func jpTimeBetween(arguments []interface{}) (interface{}, error) {
if t, err := getTimeArg(timeBetween, arguments, 0); err != nil {
return nil, err
} else if start, err := getTimeArg(timeBetween, arguments, 1); err != nil {
return nil, err
} else if end, err := getTimeArg(timeBetween, arguments, 2); err != nil {
return nil, err
} else {
return t.After(start) && t.Before(end), nil
}
}
func jpTimeTruncate(arguments []interface{}) (interface{}, error) {
if t, err := getTimeArg(timeTruncate, arguments, 0); err != nil {
return nil, err
} else if d, err := getDurationArg(timeTruncate, arguments, 1); err != nil {
return nil, err
} else {
return t.Truncate(d).Format(time.RFC3339), nil
}
}

View file

@ -0,0 +1,184 @@
package jmespath
import (
"fmt"
"testing"
"gotest.tools/assert"
)
func Test_TimeSince(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_since('', '2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
expectedResult: "180h10m0s",
},
{
test: "time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021')",
expectedResult: "180h10m11s",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeToCron(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_to_cron('2023-02-02T15:04:05Z')",
expectedResult: "4 15 2 2 4",
},
{
test: "time_to_cron(time_utc('2023-02-02T15:04:05-07:00'))",
expectedResult: "4 22 2 2 4",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeAdd(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_add('2021-01-02T15:04:05-07:00', '3h')",
expectedResult: "2021-01-02T18:04:05-07:00",
},
{
test: "time_add(time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021'), '5h30m40s')",
expectedResult: "2021-01-02T20:34:45Z",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeParse(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_parse('2006-01-02T15:04:05Z07:00', '2021-01-02T15:04:05-07:00')",
expectedResult: "2021-01-02T15:04:05-07:00",
},
{
test: "time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021')",
expectedResult: "2021-01-02T15:04:05Z",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeUtc(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_utc('2023-02-02T15:04:05Z')",
expectedResult: "2023-02-02T15:04:05Z",
},
{
test: "time_utc('2023-02-02T15:04:05-07:00')",
expectedResult: "2023-02-02T22:04:05Z",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}
func Test_TimeDiff(t *testing.T) {
testCases := []struct {
test string
expectedResult string
}{
{
test: "time_diff('2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
expectedResult: "180h10m0s",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
query, err := New(tc.test)
assert.NilError(t, err)
res, err := query.Search("")
assert.NilError(t, err)
result, ok := res.(string)
assert.Assert(t, ok)
assert.Equal(t, result, tc.expectedResult)
})
}
}