From fb2114f6f5d466ab6dd9ddb721343978d1123fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Thu, 5 Oct 2023 19:40:04 +0200 Subject: [PATCH] feat: add context propagation (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- pkg/commands/docs/command.go | 12 ++++++---- pkg/commands/jp/function/command.go | 3 ++- pkg/commands/jp/query/command.go | 3 ++- pkg/commands/scan/options.go | 5 ++-- pkg/engine/assert/assert.go | 18 ++++++++------- pkg/engine/assert/assertion.go | 4 +++- pkg/engine/assert/binding.go | 6 +++-- pkg/engine/assert/expression.go | 7 +++--- pkg/engine/assert/expression_test.go | 3 ++- pkg/engine/assert/match.go | 16 +++++++------ pkg/engine/assert/parse.go | 29 ++++++++++++------------ pkg/engine/assert/project.go | 7 +++--- pkg/engine/blocks/constant/constant.go | 4 +++- pkg/engine/blocks/function/function.go | 10 ++++---- pkg/engine/blocks/loop/loop.go | 6 +++-- pkg/engine/blocks/null/null.go | 4 +++- pkg/engine/blocks/predicate/predicate.go | 12 ++++++---- pkg/engine/builder/builder.go | 6 +++-- pkg/engine/engine.go | 6 ++++- pkg/engine/match/match.go | 7 +++--- pkg/engine/template/functions.go | 4 +++- pkg/engine/template/template.go | 9 ++++---- pkg/json-engine/engine.go | 15 ++++++------ 23 files changed, 117 insertions(+), 79 deletions(-) diff --git a/pkg/commands/docs/command.go b/pkg/commands/docs/command.go index 9d11ba214..c9eb31c27 100644 --- a/pkg/commands/docs/command.go +++ b/pkg/commands/docs/command.go @@ -33,12 +33,14 @@ func Command(parent *cobra.Command) *cobra.Command { Args: cobra.NoArgs, SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { - root := cmd - for { - if !root.HasParent() { - break + root := parent + if root != nil { + for { + if !root.HasParent() { + break + } + root = root.Parent() } - root = root.Parent() } if err := options.validate(root); err != nil { return err diff --git a/pkg/commands/jp/function/command.go b/pkg/commands/jp/function/command.go index 51aa0dac1..1d8ffe72a 100644 --- a/pkg/commands/jp/function/command.go +++ b/pkg/commands/jp/function/command.go @@ -2,6 +2,7 @@ package function import ( "cmp" + "context" "fmt" "io" "slices" @@ -58,7 +59,7 @@ func functionString(f jpfunctions.FunctionEntry) string { } func printFunctions(out io.Writer, names ...string) { - funcs := template.GetFunctions() + funcs := template.GetFunctions(context.Background()) slices.SortFunc(funcs, func(a, b jpfunctions.FunctionEntry) int { return cmp.Compare(functionString(a), functionString(b)) }) diff --git a/pkg/commands/jp/query/command.go b/pkg/commands/jp/query/command.go index d8f778578..e859d494e 100644 --- a/pkg/commands/jp/query/command.go +++ b/pkg/commands/jp/query/command.go @@ -1,6 +1,7 @@ package query import ( + "context" "encoding/json" "errors" "fmt" @@ -146,7 +147,7 @@ func loadInput(cmd *cobra.Command, file string) (interface{}, error) { } func evaluate(input interface{}, query string) (interface{}, error) { - result, err := template.Execute(query, input, nil) + result, err := template.Execute(context.Background(), query, input, nil) if err != nil { if syntaxError, ok := err.(parsing.SyntaxError); ok { return nil, fmt.Errorf("%s\n%s", syntaxError, syntaxError.HighlightLocation()) diff --git a/pkg/commands/scan/options.go b/pkg/commands/scan/options.go index 10199e83f..8388e7b86 100644 --- a/pkg/commands/scan/options.go +++ b/pkg/commands/scan/options.go @@ -1,6 +1,7 @@ package scan import ( + "context" "errors" "fmt" @@ -36,7 +37,7 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { } fmt.Fprintln(out, "Pre processing ...") for _, preprocessor := range c.preprocessors { - result, err := template.Execute(preprocessor, payload, nil) + result, err := template.Execute(context.Background(), preprocessor, payload, nil) if err != nil { return err } @@ -53,7 +54,7 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { } fmt.Fprintln(out, "Running", "(", "evaluating", len(resources), pluralize.Pluralize(len(resources), "resource", "resources"), "against", len(policies), pluralize.Pluralize(len(policies), "policy", "policies"), ")", "...") e := jsonengine.New() - responses := e.Run(jsonengine.JsonEngineRequest{ + responses := e.Run(context.Background(), jsonengine.JsonEngineRequest{ Resources: resources, Policies: policies, }) diff --git a/pkg/engine/assert/assert.go b/pkg/engine/assert/assert.go index 91aa010d4..cfd8ac95a 100644 --- a/pkg/engine/assert/assert.go +++ b/pkg/engine/assert/assert.go @@ -1,22 +1,24 @@ package assert import ( + "context" + "github.com/jmespath-community/go-jmespath/pkg/binding" "k8s.io/apimachinery/pkg/util/validation/field" ) -func Validate(assertion interface{}, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { - return validate(nil, assertion, value, bindings) +func Validate(ctx context.Context, assertion interface{}, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { + return validate(ctx, nil, assertion, value, bindings) } -func Assert(assertion Assertion, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { - return assert(nil, assertion, value, bindings) +func Assert(ctx context.Context, assertion Assertion, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { + return assert(ctx, nil, assertion, value, bindings) } -func validate(path *field.Path, assertion interface{}, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { - return assert(path, Parse(assertion), value, bindings) +func validate(ctx context.Context, path *field.Path, assertion interface{}, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { + return assert(ctx, path, Parse(ctx, assertion), value, bindings) } -func assert(path *field.Path, assertion Assertion, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { - return assertion.assert(path, value, bindings) +func assert(ctx context.Context, path *field.Path, assertion Assertion, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { + return assertion.assert(ctx, path, value, bindings) } diff --git a/pkg/engine/assert/assertion.go b/pkg/engine/assert/assertion.go index e5dea5e30..3277a9f5a 100644 --- a/pkg/engine/assert/assertion.go +++ b/pkg/engine/assert/assertion.go @@ -1,10 +1,12 @@ package assert import ( + "context" + "github.com/jmespath-community/go-jmespath/pkg/binding" "k8s.io/apimachinery/pkg/util/validation/field" ) type Assertion interface { - assert(*field.Path, interface{}, binding.Bindings) (field.ErrorList, error) + assert(context.Context, *field.Path, interface{}, binding.Bindings) (field.ErrorList, error) } diff --git a/pkg/engine/assert/binding.go b/pkg/engine/assert/binding.go index 2b52c7635..922dec060 100644 --- a/pkg/engine/assert/binding.go +++ b/pkg/engine/assert/binding.go @@ -1,6 +1,8 @@ package assert import ( + "context" + "github.com/jmespath-community/go-jmespath/pkg/binding" "github.com/kyverno/kyverno-json/pkg/apis/v1alpha1" "github.com/kyverno/kyverno-json/pkg/engine/template" @@ -19,7 +21,7 @@ func NewContextBindings(bindings binding.Bindings, value interface{}, entries .. func NewContextBinding(path *field.Path, bindings binding.Bindings, value interface{}, entry v1alpha1.ContextEntry) binding.Binding { return template.NewLazyBinding( func() (interface{}, error) { - expression := parseExpression(entry.Variable.Value) + expression := parseExpression(context.TODO(), entry.Variable.Value) if expression != nil && expression.engine != "" { if expression.foreach { return nil, field.Invalid(path.Child("variable"), entry.Variable.Value, "foreach is not supported in context") @@ -27,7 +29,7 @@ func NewContextBinding(path *field.Path, bindings binding.Bindings, value interf if expression.binding != "" { return nil, field.Invalid(path.Child("variable"), entry.Variable.Value, "binding is not supported in context") } - projected, err := template.Execute(expression.statement, value, bindings) + projected, err := template.Execute(context.Background(), expression.statement, value, bindings) if err != nil { return nil, field.InternalError(path.Child("variable"), err) } diff --git a/pkg/engine/assert/expression.go b/pkg/engine/assert/expression.go index 009f73afe..3366f78e6 100644 --- a/pkg/engine/assert/expression.go +++ b/pkg/engine/assert/expression.go @@ -1,6 +1,7 @@ package assert import ( + "context" "reflect" "regexp" @@ -22,7 +23,7 @@ type expression struct { engine string } -func parseExpressionRegex(in string) *expression { +func parseExpressionRegex(ctx context.Context, in string) *expression { expression := &expression{} // 1. match foreach if match := foreachRegex.FindStringSubmatch(in); match != nil { @@ -56,9 +57,9 @@ func parseExpressionRegex(in string) *expression { return expression } -func parseExpression(value interface{}) *expression { +func parseExpression(ctx context.Context, value interface{}) *expression { if reflectutils.GetKind(value) != reflect.String { return nil } - return parseExpressionRegex(reflect.ValueOf(value).String()) + return parseExpressionRegex(ctx, reflect.ValueOf(value).String()) } diff --git a/pkg/engine/assert/expression_test.go b/pkg/engine/assert/expression_test.go index 9e5d6b288..42bbba5da 100644 --- a/pkg/engine/assert/expression_test.go +++ b/pkg/engine/assert/expression_test.go @@ -1,6 +1,7 @@ package assert import ( + "context" "reflect" "testing" ) @@ -169,7 +170,7 @@ func Test_parseExpressionRegex(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := parseExpressionRegex(tt.in); !reflect.DeepEqual(got, tt.want) { + if got := parseExpressionRegex(context.Background(), tt.in); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseExpressionRegex() = %v, want %v", got, tt.want) } }) diff --git a/pkg/engine/assert/match.go b/pkg/engine/assert/match.go index 9c9d38e6c..8b6f37137 100644 --- a/pkg/engine/assert/match.go +++ b/pkg/engine/assert/match.go @@ -1,6 +1,8 @@ package assert import ( + "context" + "github.com/jmespath-community/go-jmespath/pkg/binding" "github.com/kyverno/kyverno-json/pkg/apis/v1alpha1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -10,20 +12,20 @@ import ( // We should remove this dependency. // Either move the Match struct in this package or move this file in a more specific package. -func Match(path *field.Path, match *v1alpha1.Match, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func Match(ctx context.Context, path *field.Path, match *v1alpha1.Match, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { var errs field.ErrorList if match == nil || (len(match.Any) == 0 && len(match.All) == 0) { errs = append(errs, field.Invalid(path, match, "an empty match is not valid")) } else { if len(match.Any) != 0 { - _errs, err := MatchAny(path.Child("any"), match.Any, actual, bindings) + _errs, err := MatchAny(ctx, path.Child("any"), match.Any, actual, bindings) if err != nil { return errs, err } errs = append(errs, _errs...) } if len(match.All) != 0 { - _errs, err := MatchAll(path.Child("all"), match.All, actual, bindings) + _errs, err := MatchAll(ctx, path.Child("all"), match.All, actual, bindings) if err != nil { return errs, err } @@ -33,10 +35,10 @@ func Match(path *field.Path, match *v1alpha1.Match, actual interface{}, bindings return errs, nil } -func MatchAny(path *field.Path, assertions v1alpha1.Assertions, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func MatchAny(ctx context.Context, path *field.Path, assertions v1alpha1.Assertions, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { var errs field.ErrorList for i, assertion := range assertions { - _errs, err := validate(path.Index(i), assertion.Value, actual, bindings) + _errs, err := validate(ctx, path.Index(i), assertion.Value, actual, bindings) if err != nil { return errs, err } @@ -45,10 +47,10 @@ func MatchAny(path *field.Path, assertions v1alpha1.Assertions, actual interface return errs, nil } -func MatchAll(path *field.Path, assertions v1alpha1.Assertions, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func MatchAll(ctx context.Context, path *field.Path, assertions v1alpha1.Assertions, actual interface{}, bindings binding.Bindings) (field.ErrorList, error) { var errs field.ErrorList for i, assertion := range assertions { - _errs, err := validate(path.Index(i), assertion.Value, actual, bindings) + _errs, err := validate(ctx, path.Index(i), assertion.Value, actual, bindings) if err != nil { return errs, err } diff --git a/pkg/engine/assert/parse.go b/pkg/engine/assert/parse.go index ebcf5c02b..8ad84b032 100644 --- a/pkg/engine/assert/parse.go +++ b/pkg/engine/assert/parse.go @@ -1,6 +1,7 @@ package assert import ( + "context" "fmt" "reflect" @@ -12,20 +13,20 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) -func Parse(assertion interface{}) Assertion { +func Parse(ctx context.Context, assertion interface{}) Assertion { switch reflectutils.GetKind(assertion) { case reflect.Slice: node := sliceNode{} valueOf := reflect.ValueOf(assertion) for i := 0; i < valueOf.Len(); i++ { - node = append(node, Parse(valueOf.Index(i).Interface())) + node = append(node, Parse(ctx, valueOf.Index(i).Interface())) } return node case reflect.Map: node := mapNode{} iter := reflect.ValueOf(assertion).MapRange() for iter.Next() { - node[iter.Key().Interface()] = Parse(iter.Value().Interface()) + node[iter.Key().Interface()] = Parse(ctx, iter.Value().Interface()) } return node default: @@ -37,10 +38,10 @@ func Parse(assertion interface{}) Assertion { // it is responsible for projecting the analysed resource and passing the result to the descendant type mapNode map[interface{}]Assertion -func (n mapNode) assert(path *field.Path, value interface{}, bindings jpbinding.Bindings) (field.ErrorList, error) { +func (n mapNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings jpbinding.Bindings) (field.ErrorList, error) { var errs field.ErrorList for k, v := range n { - projection, err := project(k, value, bindings) + projection, err := project(ctx, k, value, bindings) if err != nil { return nil, field.InternalError(path.Child(fmt.Sprint(k)), err) } else { @@ -56,7 +57,7 @@ func (n mapNode) assert(path *field.Path, value interface{}, bindings jpbinding. if projection.foreachName != "" { bindings = bindings.Register("$"+projection.foreachName, jpbinding.NewBinding(i)) } - if _errs, err := v.assert(path.Child(fmt.Sprint(k)).Index(i), valueOf.Index(i).Interface(), bindings); err != nil { + if _errs, err := v.assert(ctx, path.Child(fmt.Sprint(k)).Index(i), valueOf.Index(i).Interface(), bindings); err != nil { return nil, err } else { errs = append(errs, _errs...) @@ -70,7 +71,7 @@ func (n mapNode) assert(path *field.Path, value interface{}, bindings jpbinding. if projection.foreachName != "" { bindings = bindings.Register("$"+projection.foreachName, jpbinding.NewBinding(key)) } - if _errs, err := v.assert(path.Child(fmt.Sprint(k)).Key(fmt.Sprint(key)), iter.Value().Interface(), bindings); err != nil { + if _errs, err := v.assert(ctx, path.Child(fmt.Sprint(k)).Key(fmt.Sprint(key)), iter.Value().Interface(), bindings); err != nil { return nil, err } else { errs = append(errs, _errs...) @@ -80,7 +81,7 @@ func (n mapNode) assert(path *field.Path, value interface{}, bindings jpbinding. return nil, field.TypeInvalid(path.Child(fmt.Sprint(k)), projection.result, "expected a slice or a map") } } else { - if _errs, err := v.assert(path.Child(fmt.Sprint(k)), projection.result, bindings); err != nil { + if _errs, err := v.assert(ctx, path.Child(fmt.Sprint(k)), projection.result, bindings); err != nil { return nil, err } else { errs = append(errs, _errs...) @@ -96,7 +97,7 @@ func (n mapNode) assert(path *field.Path, value interface{}, bindings jpbinding. // if lengths match all descendants are evaluated with their corresponding items. type sliceNode []Assertion -func (n sliceNode) assert(path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func (n sliceNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { var errs field.ErrorList if reflectutils.GetKind(value) != reflect.Slice { return nil, field.TypeInvalid(path, value, "expected a slice") @@ -106,7 +107,7 @@ func (n sliceNode) assert(path *field.Path, value interface{}, bindings binding. errs = append(errs, field.Invalid(path, value, "lengths of slices don't match")) } else { for i := range n { - if _errs, err := n[i].assert(path.Index(i), valueOf.Index(i).Interface(), bindings); err != nil { + if _errs, err := n[i].assert(ctx, path.Index(i), valueOf.Index(i).Interface(), bindings); err != nil { return nil, err } else { errs = append(errs, _errs...) @@ -124,9 +125,9 @@ type scalarNode struct { rhs interface{} } -func (n *scalarNode) assert(path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func (n *scalarNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { rhs := n.rhs - expression := parseExpression(rhs) + expression := parseExpression(ctx, rhs) // we only project if the expression uses the engine syntax // this is to avoid the case where the value is a map and the RHS is a string if expression != nil && expression.engine != "" { @@ -136,14 +137,14 @@ func (n *scalarNode) assert(path *field.Path, value interface{}, bindings bindin if expression.binding != "" { return nil, field.Invalid(path, rhs, "binding is not supported on the RHS") } - projected, err := template.Execute(expression.statement, value, bindings) + projected, err := template.Execute(ctx, expression.statement, value, bindings) if err != nil { return nil, field.InternalError(path, err) } rhs = projected } var errs field.ErrorList - if match, err := match.Match(rhs, value); err != nil { + if match, err := match.Match(ctx, rhs, value); err != nil { return nil, field.InternalError(path, err) } else if !match { errs = append(errs, field.Invalid(path, value, expectValueMessage(rhs))) diff --git a/pkg/engine/assert/project.go b/pkg/engine/assert/project.go index 8ad7abb84..788934c8c 100644 --- a/pkg/engine/assert/project.go +++ b/pkg/engine/assert/project.go @@ -1,6 +1,7 @@ package assert import ( + "context" "fmt" "reflect" @@ -16,11 +17,11 @@ type projection struct { result interface{} } -func project(key interface{}, value interface{}, bindings binding.Bindings) (*projection, error) { - expression := parseExpression(key) +func project(ctx context.Context, key interface{}, value interface{}, bindings binding.Bindings) (*projection, error) { + expression := parseExpression(ctx, key) if expression != nil { if expression.engine != "" { - projected, err := template.Execute(expression.statement, value, bindings) + projected, err := template.Execute(ctx, expression.statement, value, bindings) if err != nil { return nil, err } diff --git a/pkg/engine/blocks/constant/constant.go b/pkg/engine/blocks/constant/constant.go index 7f3bd9bab..7c7ce9b08 100644 --- a/pkg/engine/blocks/constant/constant.go +++ b/pkg/engine/blocks/constant/constant.go @@ -1,6 +1,8 @@ package constant import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" ) @@ -8,7 +10,7 @@ type constant[TREQUEST any, TRESPONSE any] struct { responses []TRESPONSE } -func (b *constant[TREQUEST, TRESPONSE]) Run(_ TREQUEST) []TRESPONSE { +func (b *constant[TREQUEST, TRESPONSE]) Run(_ context.Context, _ TREQUEST) []TRESPONSE { return b.responses } diff --git a/pkg/engine/blocks/function/function.go b/pkg/engine/blocks/function/function.go index 86ac49300..c2505a00e 100644 --- a/pkg/engine/blocks/function/function.go +++ b/pkg/engine/blocks/function/function.go @@ -1,18 +1,20 @@ package function import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" ) type function[TREQUEST any, TRESPONSE any] struct { - function func(TREQUEST) TRESPONSE + function func(context.Context, TREQUEST) TRESPONSE } -func (b *function[TREQUEST, TRESPONSE]) Run(request TREQUEST) []TRESPONSE { - return []TRESPONSE{b.function(request)} +func (b *function[TREQUEST, TRESPONSE]) Run(ctx context.Context, request TREQUEST) []TRESPONSE { + return []TRESPONSE{b.function(ctx, request)} } -func New[TREQUEST any, TRESPONSE any](f func(TREQUEST) TRESPONSE) engine.Engine[TREQUEST, TRESPONSE] { +func New[TREQUEST any, TRESPONSE any](f func(context.Context, TREQUEST) TRESPONSE) engine.Engine[TREQUEST, TRESPONSE] { return &function[TREQUEST, TRESPONSE]{ function: f, } diff --git a/pkg/engine/blocks/loop/loop.go b/pkg/engine/blocks/loop/loop.go index 81c68356a..4da87a55d 100644 --- a/pkg/engine/blocks/loop/loop.go +++ b/pkg/engine/blocks/loop/loop.go @@ -1,6 +1,8 @@ package loop import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" ) @@ -9,10 +11,10 @@ type loop[TPARENT any, TCHILD any, TRESPONSE any] struct { looper func(TPARENT) []TCHILD } -func (b *loop[TPARENT, TCHILD, TRESPONSE]) Run(parent TPARENT) []TRESPONSE { +func (b *loop[TPARENT, TCHILD, TRESPONSE]) Run(ctx context.Context, parent TPARENT) []TRESPONSE { var responses []TRESPONSE for _, child := range b.looper(parent) { - responses = append(responses, b.inner.Run(child)...) + responses = append(responses, b.inner.Run(ctx, child)...) } return responses } diff --git a/pkg/engine/blocks/null/null.go b/pkg/engine/blocks/null/null.go index a04808ff4..34bf32c5d 100644 --- a/pkg/engine/blocks/null/null.go +++ b/pkg/engine/blocks/null/null.go @@ -1,12 +1,14 @@ package null import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" ) type null[TREQUEST any, TRESPONSE any] struct{} -func (b *null[TREQUEST, TRESPONSE]) Run(_ TREQUEST) []TRESPONSE { +func (b *null[TREQUEST, TRESPONSE]) Run(_ context.Context, _ TREQUEST) []TRESPONSE { return nil } diff --git a/pkg/engine/blocks/predicate/predicate.go b/pkg/engine/blocks/predicate/predicate.go index 97829d8a2..9e8b92748 100644 --- a/pkg/engine/blocks/predicate/predicate.go +++ b/pkg/engine/blocks/predicate/predicate.go @@ -1,22 +1,24 @@ package predicate import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" ) type predicate[TREQUEST any, TRESPONSE any] struct { inner engine.Engine[TREQUEST, TRESPONSE] - predicate func(TREQUEST) bool + predicate func(context.Context, TREQUEST) bool } -func (b *predicate[TREQUEST, TRESPONSE]) Run(request TREQUEST) []TRESPONSE { - if !b.predicate(request) { +func (b *predicate[TREQUEST, TRESPONSE]) Run(ctx context.Context, request TREQUEST) []TRESPONSE { + if !b.predicate(ctx, request) { return nil } - return b.inner.Run(request) + return b.inner.Run(ctx, request) } -func New[TREQUEST any, TRESPONSE any](inner engine.Engine[TREQUEST, TRESPONSE], condition func(TREQUEST) bool) engine.Engine[TREQUEST, TRESPONSE] { +func New[TREQUEST any, TRESPONSE any](inner engine.Engine[TREQUEST, TRESPONSE], condition func(context.Context, TREQUEST) bool) engine.Engine[TREQUEST, TRESPONSE] { return &predicate[TREQUEST, TRESPONSE]{ inner: inner, predicate: condition, diff --git a/pkg/engine/builder/builder.go b/pkg/engine/builder/builder.go index cb7b26217..4ca4bc22f 100644 --- a/pkg/engine/builder/builder.go +++ b/pkg/engine/builder/builder.go @@ -1,6 +1,8 @@ package builder import ( + "context" + "github.com/kyverno/kyverno-json/pkg/engine" "github.com/kyverno/kyverno-json/pkg/engine/blocks/constant" "github.com/kyverno/kyverno-json/pkg/engine/blocks/function" @@ -19,10 +21,10 @@ func Constant[TREQUEST any, TRESPONSE any](responses ...TRESPONSE) Engine[TREQUE return new(constant.New[TREQUEST](responses...)) } -func (inner Engine[TREQUEST, TRESPONSE]) Predicate(condition func(TREQUEST) bool) Engine[TREQUEST, TRESPONSE] { +func (inner Engine[TREQUEST, TRESPONSE]) Predicate(condition func(context.Context, TREQUEST) bool) Engine[TREQUEST, TRESPONSE] { return new(predicate.New(inner, condition)) } -func Function[TREQUEST any, TRESPONSE any](f func(TREQUEST) TRESPONSE) Engine[TREQUEST, TRESPONSE] { +func Function[TREQUEST any, TRESPONSE any](f func(context.Context, TREQUEST) TRESPONSE) Engine[TREQUEST, TRESPONSE] { return new(function.New(f)) } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 729adc406..3a3329893 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -1,9 +1,13 @@ package engine +import ( + "context" +) + // TODO: // - tracing // - explain type Engine[TREQUEST any, TRESPONSE any] interface { - Run(TREQUEST) []TRESPONSE + Run(context.Context, TREQUEST) []TRESPONSE } diff --git a/pkg/engine/match/match.go b/pkg/engine/match/match.go index 100f7808c..7f4b9ca87 100644 --- a/pkg/engine/match/match.go +++ b/pkg/engine/match/match.go @@ -1,13 +1,14 @@ package match import ( + "context" "fmt" "reflect" reflectutils "github.com/kyverno/kyverno-json/pkg/utils/reflect" ) -func Match(expected, actual interface{}) (bool, error) { +func Match(ctx context.Context, expected, actual interface{}) (bool, error) { if expected != nil { switch reflectutils.GetKind(expected) { case reflect.Slice: @@ -18,7 +19,7 @@ func Match(expected, actual interface{}) (bool, error) { return false, nil } for i := 0; i < reflect.ValueOf(expected).Len(); i++ { - if inner, err := Match(reflect.ValueOf(expected).Index(i).Interface(), reflect.ValueOf(actual).Index(i).Interface()); err != nil { + if inner, err := Match(ctx, reflect.ValueOf(expected).Index(i).Interface(), reflect.ValueOf(actual).Index(i).Interface()); err != nil { return false, err } else if !inner { return false, nil @@ -35,7 +36,7 @@ func Match(expected, actual interface{}) (bool, error) { if !actualValue.IsValid() { return false, nil } - if inner, err := Match(iter.Value().Interface(), actualValue.Interface()); err != nil { + if inner, err := Match(ctx, iter.Value().Interface(), actualValue.Interface()); err != nil { return false, err } else if !inner { return false, nil diff --git a/pkg/engine/template/functions.go b/pkg/engine/template/functions.go index 5b34261b3..146bf0021 100644 --- a/pkg/engine/template/functions.go +++ b/pkg/engine/template/functions.go @@ -1,11 +1,13 @@ package template import ( + "context" + jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" "github.com/kyverno/kyverno-json/pkg/engine/template/functions" ) -func GetFunctions() []jpfunctions.FunctionEntry { +func GetFunctions(ctx context.Context) []jpfunctions.FunctionEntry { var funcs []jpfunctions.FunctionEntry funcs = append(funcs, jpfunctions.GetDefaultFunctions()...) funcs = append(funcs, functions.GetFunctions()...) diff --git a/pkg/engine/template/template.go b/pkg/engine/template/template.go index ae6b927d4..df70d87a5 100644 --- a/pkg/engine/template/template.go +++ b/pkg/engine/template/template.go @@ -1,6 +1,7 @@ package template import ( + "context" "fmt" "regexp" "strings" @@ -13,14 +14,14 @@ import ( var ( variable = regexp.MustCompile(`{{(.*?)}}`) parser = parsing.NewParser() - caller = interpreter.NewFunctionCaller(GetFunctions()...) + caller = interpreter.NewFunctionCaller(GetFunctions(context.Background())...) ) -func String(in string, value interface{}, bindings binding.Bindings) string { +func String(ctx context.Context, in string, value interface{}, bindings binding.Bindings) string { groups := variable.FindAllStringSubmatch(in, -1) for _, group := range groups { statement := strings.TrimSpace(group[1]) - result, err := Execute(statement, value, bindings) + result, err := Execute(ctx, statement, value, bindings) if err != nil { in = strings.ReplaceAll(in, group[0], fmt.Sprintf("ERR (%s - %s)", statement, err)) } else if result == nil { @@ -34,7 +35,7 @@ func String(in string, value interface{}, bindings binding.Bindings) string { return in } -func Execute(statement string, value interface{}, bindings binding.Bindings) (interface{}, error) { +func Execute(ctx context.Context, statement string, value interface{}, bindings binding.Bindings) (interface{}, error) { interpreter := interpreter.NewInterpreter(nil, caller, bindings) compiled, err := parser.Parse(statement) if err != nil { diff --git a/pkg/json-engine/engine.go b/pkg/json-engine/engine.go index c299e60e1..478241d3b 100644 --- a/pkg/json-engine/engine.go +++ b/pkg/json-engine/engine.go @@ -1,6 +1,7 @@ package jsonengine import ( + "context" "errors" "github.com/jmespath-community/go-jmespath/pkg/binding" @@ -55,33 +56,33 @@ func New() engine.Engine[JsonEngineRequest, JsonEngineResponse] { return requests } inner := builder. - Function(func(r request) JsonEngineResponse { + Function(func(ctx context.Context, r request) JsonEngineResponse { response := JsonEngineResponse{ Policy: r.policy, Rule: r.rule, Resource: r.value, } - errs, err := assert.Match(nil, r.rule.Validation.Assert, r.value, r.bindings) + errs, err := assert.Match(ctx, nil, r.rule.Validation.Assert, r.value, r.bindings) if err != nil { response.Failure = err } else if err := errs.ToAggregate(); err != nil { if r.rule.Validation.Message != "" { - response.Error = errors.New(template.String(r.rule.Validation.Message, r.value, r.bindings)) + response.Error = errors.New(template.String(ctx, r.rule.Validation.Message, r.value, r.bindings)) } else { response.Error = err } } return response }). - Predicate(func(r request) bool { - errs, err := assert.Match(nil, r.rule.Exclude, r.value, r.bindings) + Predicate(func(ctx context.Context, r request) bool { + errs, err := assert.Match(ctx, nil, r.rule.Exclude, r.value, r.bindings) return err == nil && len(errs) != 0 }). - Predicate(func(r request) bool { + Predicate(func(ctx context.Context, r request) bool { if r.rule.Match == nil { return true } - errs, err := assert.Match(nil, r.rule.Match, r.value, r.bindings) + errs, err := assert.Match(ctx, nil, r.rule.Match, r.value, r.bindings) return err == nil && len(errs) == 0 }) // TODO: we can't use the builder package for loops :(