From 7f95bee23ca6c1baf932aa9a83923f95cb8282fb Mon Sep 17 00:00:00 2001 From: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> Date: Thu, 18 Nov 2021 01:47:17 +0530 Subject: [PATCH] Added time_since() custom JMESPath function (#2680) * Added time_since() custom JMESPath function Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Remove time.Layout (not supported in Go 1.16) Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Modify time_since() for 3 arguments Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Add tests for functions_test.go Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Timestamp literals and tabulated tests Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Remove layout map and default to RFC3339 Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> --- pkg/engine/jmespath/functions.go | 54 +++++++++++++++++++++++++++ pkg/engine/jmespath/functions_test.go | 31 +++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/pkg/engine/jmespath/functions.go b/pkg/engine/jmespath/functions.go index 9799e80c51..746792c45b 100644 --- a/pkg/engine/jmespath/functions.go +++ b/pkg/engine/jmespath/functions.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" "strings" + "time" gojmespath "github.com/jmespath/go-jmespath" ) @@ -46,6 +47,7 @@ var ( modulo = "modulo" base64Decode = "base64_decode" base64Encode = "base64_encode" + timeSince = "time_since" ) const errorPrefix = "JMESPath function '%s': " @@ -210,6 +212,15 @@ func getFunctions() []*gojmespath.FunctionEntry { }, Handler: jpBase64Encode, }, + { + Name: timeSince, + Arguments: []ArgSpec{ + {Types: []JpType{JpString}}, + {Types: []JpType{JpString}}, + {Types: []JpType{JpString}}, + }, + Handler: jpTimeSince, + }, } } @@ -550,6 +561,49 @@ 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 +} + // InterfaceToString casts an interface to a string type func ifaceToString(iface interface{}) (string, error) { switch i := iface.(type) { diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go index 64d4acb0d3..b6a0d784bc 100644 --- a/pkg/engine/jmespath/functions_test.go +++ b/pkg/engine/jmespath/functions_test.go @@ -504,3 +504,34 @@ func Test_Base64Decode_Secret(t *testing.T) { assert.Assert(t, ok) 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) + }) + } +}