From c51d50299e34573c76b9ad197351ac0b216408d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 24 Sep 2024 10:25:24 +0200 Subject: [PATCH] refactor: policy compilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- pkg/apis/policy/v1alpha1/any.go | 10 +- pkg/apis/policy/v1alpha1/assertion_tree.go | 10 +- pkg/apis/policy/v1alpha1/context_entry.go | 2 + pkg/apis/policy/v1alpha1/validating_rule.go | 2 +- .../policy/v1alpha1/zz_generated.deepcopy.go | 24 +- pkg/json-engine/compiler.go | 311 ++++++++++++++++++ pkg/json-engine/engine.go | 222 ++++++------- pkg/matching/compiler.go | 94 +++--- pkg/matching/match.go | 41 +-- website/docs/apis/kyverno-json.v1alpha1.md | 17 +- 10 files changed, 520 insertions(+), 213 deletions(-) create mode 100644 pkg/json-engine/compiler.go diff --git a/pkg/apis/policy/v1alpha1/any.go b/pkg/apis/policy/v1alpha1/any.go index bea3c96d..cf7c89d1 100644 --- a/pkg/apis/policy/v1alpha1/any.go +++ b/pkg/apis/policy/v1alpha1/any.go @@ -1,8 +1,8 @@ package v1alpha1 import ( + "github.com/kyverno/kyverno-json/pkg/core/compilers" "github.com/kyverno/kyverno-json/pkg/core/projection" - hashutils "github.com/kyverno/kyverno-json/pkg/utils/hash" "k8s.io/apimachinery/pkg/util/json" ) @@ -12,18 +12,16 @@ import ( // +kubebuilder:validation:Type:="" type Any struct { _value any - _hash string } func NewAny(value any) Any { return Any{ _value: value, - _hash: hashutils.Hash(value), } } -func (t *Any) Compile(compiler func(string, any, string) (projection.ScalarHandler, error), defaultCompiler string) (projection.ScalarHandler, error) { - return compiler(t._hash, t._value, defaultCompiler) +func (t *Any) Compile(compilers compilers.Compilers) (projection.ScalarHandler, error) { + return projection.ParseScalar(t._value, compilers) } func (a *Any) MarshalJSON() ([]byte, error) { @@ -37,13 +35,11 @@ func (a *Any) UnmarshalJSON(data []byte) error { return err } a._value = v - a._hash = hashutils.Hash(a._value) return nil } func (in *Any) DeepCopyInto(out *Any) { out._value = deepCopy(in._value) - out._hash = in._hash } func (in *Any) DeepCopy() *Any { diff --git a/pkg/apis/policy/v1alpha1/assertion_tree.go b/pkg/apis/policy/v1alpha1/assertion_tree.go index 24e763ac..79efd8d8 100644 --- a/pkg/apis/policy/v1alpha1/assertion_tree.go +++ b/pkg/apis/policy/v1alpha1/assertion_tree.go @@ -2,7 +2,7 @@ package v1alpha1 import ( "github.com/kyverno/kyverno-json/pkg/core/assertion" - hashutils "github.com/kyverno/kyverno-json/pkg/utils/hash" + "github.com/kyverno/kyverno-json/pkg/core/compilers" "k8s.io/apimachinery/pkg/util/json" ) @@ -12,18 +12,16 @@ import ( // AssertionTree represents an assertion tree. type AssertionTree struct { _tree any - _hash string } func NewAssertionTree(value any) AssertionTree { return AssertionTree{ _tree: value, - _hash: hashutils.Hash(value), } } -func (t *AssertionTree) Compile(compiler func(string, any, string) (assertion.Assertion, error), defaultCompiler string) (assertion.Assertion, error) { - return compiler(t._hash, t._tree, defaultCompiler) +func (t *AssertionTree) Compile(compilers compilers.Compilers) (assertion.Assertion, error) { + return assertion.Parse(t._tree, compilers) } func (a *AssertionTree) MarshalJSON() ([]byte, error) { @@ -37,11 +35,9 @@ func (a *AssertionTree) UnmarshalJSON(data []byte) error { return err } a._tree = v - a._hash = hashutils.Hash(a._tree) return nil } func (in *AssertionTree) DeepCopyInto(out *AssertionTree) { out._tree = deepCopy(in._tree) - out._hash = in._hash } diff --git a/pkg/apis/policy/v1alpha1/context_entry.go b/pkg/apis/policy/v1alpha1/context_entry.go index 85b8c3b5..b83123f9 100644 --- a/pkg/apis/policy/v1alpha1/context_entry.go +++ b/pkg/apis/policy/v1alpha1/context_entry.go @@ -1,5 +1,7 @@ package v1alpha1 +type Context []ContextEntry + // ContextEntry adds variables and data sources to a rule context. type ContextEntry struct { // Compiler defines the default compiler to use when evaluating expressions. diff --git a/pkg/apis/policy/v1alpha1/validating_rule.go b/pkg/apis/policy/v1alpha1/validating_rule.go index 06700acc..8fb75f6b 100644 --- a/pkg/apis/policy/v1alpha1/validating_rule.go +++ b/pkg/apis/policy/v1alpha1/validating_rule.go @@ -12,7 +12,7 @@ type ValidatingRule struct { // Context defines variables and data sources that can be used during rule execution. // +optional - Context []ContextEntry `json:"context,omitempty"` + Context Context `json:"context,omitempty"` // Match defines when this policy rule should be applied. // +optional diff --git a/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go index a86e5f7e..55813c8a 100644 --- a/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go @@ -86,6 +86,28 @@ func (in *Assertion) DeepCopy() *Assertion { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Context) DeepCopyInto(out *Context) { + { + in := &in + *out = make(Context, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Context. +func (in Context) DeepCopy() Context { + if in == nil { + return nil + } + out := new(Context) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContextEntry) DeepCopyInto(out *ContextEntry) { *out = *in @@ -266,7 +288,7 @@ func (in *ValidatingRule) DeepCopyInto(out *ValidatingRule) { } if in.Context != nil { in, out := &in.Context, &out.Context - *out = make([]ContextEntry, len(*in)) + *out = make(Context, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/json-engine/compiler.go b/pkg/json-engine/compiler.go new file mode 100644 index 00000000..84ca282c --- /dev/null +++ b/pkg/json-engine/compiler.go @@ -0,0 +1,311 @@ +package jsonengine + +import ( + "sync" + "time" + + "github.com/jmespath-community/go-jmespath/pkg/binding" + "github.com/kyverno/kyverno-json/pkg/apis/policy/v1alpha1" + "github.com/kyverno/kyverno-json/pkg/core/compilers" + "github.com/kyverno/kyverno-json/pkg/matching" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type compiler struct{} + +func (c *compiler) compileContextEntry( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.ContextEntry, +) (func(any, binding.Bindings) binding.Bindings, error) { + if in.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) + } + handler, err := in.Variable.Compile(compilers) + if err != nil { + return nil, field.InternalError(path.Child("variable"), err) + } + return func(resource any, bindings binding.Bindings) binding.Bindings { + return bindings.Register( + "$"+in.Name, + binding.NewDelegate( + sync.OnceValues( + func() (any, error) { + projected, err := handler(resource, bindings) + if err != nil { + return nil, field.InternalError(path.Child("variable"), err) + } + return projected, nil + }, + ), + ), + ) + }, nil +} + +func (c *compiler) compileContext( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.Context, +) (func(any, binding.Bindings) binding.Bindings, error) { + var out []func(any, binding.Bindings) binding.Bindings + for i, entry := range in { + entry, err := c.compileContextEntry(path.Index(i), compilers, entry) + if err != nil { + return nil, err + } + out = append(out, entry) + } + return func(resource any, bindings binding.Bindings) binding.Bindings { + for _, entry := range out { + bindings = entry(resource, bindings) + } + return bindings + }, nil +} + +func (c *compiler) compileMatch( + path *field.Path, + compilers compilers.Compilers, + in *v1alpha1.Match, +) (func(any, binding.Bindings) (field.ErrorList, error), error) { + if in == nil { + return nil, nil + } + if len(in.Any) == 0 && len(in.All) == 0 { + return nil, field.Invalid(path, in, "an empty match is not valid") + } + if in.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) + } + _any, err := c.compileAssertionTrees(path.Child("any"), compilers, in.Any...) + if err != nil { + return nil, err + } + _all, err := c.compileAssertionTrees(path.Child("all"), compilers, in.All...) + if err != nil { + return nil, err + } + return func(resource any, bindings binding.Bindings) (field.ErrorList, error) { + var errs field.ErrorList + for _, assertion := range _any { + _errs, err := assertion(resource, bindings) + if err != nil { + return errs, err + } + if len(_errs) == 0 { + return nil, nil + } + errs = append(errs, _errs...) + } + for _, assertion := range _all { + _errs, err := assertion(resource, bindings) + if err != nil { + return errs, err + } + errs = append(errs, _errs...) + } + return errs, nil + }, nil +} + +func (c *compiler) compileAssert( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.Assert, +) (func(any, binding.Bindings) (matching.Results, error), error) { + if in.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) + } + if len(in.Any) == 0 && len(in.All) == 0 { + return nil, field.Invalid(path, in, "an empty assert is not valid") + } + _any, err := c.compileAssertions(path.Child("any"), compilers, in.Any...) + if err != nil { + return nil, err + } + _all, err := c.compileAssertions(path.Child("all"), compilers, in.All...) + if err != nil { + return nil, err + } + return func(resource any, bindings binding.Bindings) (matching.Results, error) { + if len(_any) != 0 { + var fails matching.Results + for _, assertion := range _any { + result, err := assertion(resource, bindings) + if err != nil { + return fails, err + } + if len(result.ErrorList) == 0 { + fails = nil + break + } + fails = append(fails, result) + } + if fails != nil { + return fails, nil + } + } + if len(_all) != 0 { + var fails matching.Results + for _, assertion := range _all { + result, err := assertion(resource, bindings) + if err != nil { + return fails, err + } + if len(result.ErrorList) > 0 { + fails = append(fails, result) + } + } + return fails, nil + } + return nil, nil + }, nil +} + +func (c *compiler) compileAssertions( + path *field.Path, + compilers compilers.Compilers, + in ...v1alpha1.Assertion, +) ([]func(any, binding.Bindings) (matching.Result, error), error) { + var out []func(any, binding.Bindings) (matching.Result, error) + for i, in := range in { + if in, err := c.compileAssertion(path.Index(i), compilers, in); err != nil { + return nil, err + } else { + out = append(out, in) + } + } + return out, nil +} + +func (c *compiler) compileAssertion( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.Assertion, +) (func(any, binding.Bindings) (matching.Result, error), error) { + if in.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) + } + check, err := c.compileAssertionTree(path.Child("check"), compilers, in.Check) + if err != nil { + return nil, err + } + return func(resource any, bindings binding.Bindings) (matching.Result, error) { + var result matching.Result + errs, err := check(resource, bindings) + result.ErrorList = errs + // TODO: message + return result, err + }, nil +} + +func (c *compiler) compileAssertionTrees( + path *field.Path, + compilers compilers.Compilers, + in ...v1alpha1.AssertionTree, +) ([]func(any, binding.Bindings) (field.ErrorList, error), error) { + var out []func(any, binding.Bindings) (field.ErrorList, error) + for i, in := range in { + if in, err := c.compileAssertionTree(path.Index(i), compilers, in); err != nil { + return nil, err + } else { + out = append(out, in) + } + } + return out, nil +} + +func (c *compiler) compileAssertionTree( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.AssertionTree, +) (func(any, binding.Bindings) (field.ErrorList, error), error) { + check, err := in.Compile(compilers) + if err != nil { + return nil, err + } + return func(resource any, bindings binding.Bindings) (field.ErrorList, error) { + return check.Assert(path, resource, bindings) + }, nil +} + +func (c *compiler) compileRule( + path *field.Path, + compilers compilers.Compilers, + in v1alpha1.ValidatingRule, +) (func(any, binding.Bindings) []RuleResponse, error) { + if in.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) + } + context, err := c.compileContext(path.Child("context"), compilers, in.Context) + if err != nil { + return nil, err + } + match, err := c.compileMatch(path.Child("match"), compilers, in.Match) + if err != nil { + return nil, err + } + exclude, err := c.compileMatch(path.Child("exclude"), compilers, in.Exclude) + if err != nil { + return nil, err + } + assert, err := c.compileAssert(path.Child("assert"), compilers, in.Assert) + if err != nil { + return nil, err + } + return func(resource any, bindings binding.Bindings) []RuleResponse { + // 1. register rule binding + bindings = bindings.Register("$rule", binding.NewBinding(in)) + // 2. register context bindings + bindings = context(resource, bindings) + // 3. compute identifier if any + // 4. process match clause + if match != nil { + if errs, err := match(resource, bindings); err != nil { + return []RuleResponse{{ + Rule: in, + Timestamp: time.Now(), + // Identifier: identifier, + Error: err, + }} + } else if len(errs) != 0 { + // didn't match + return nil + } + } + // 5. process exclude clause + if exclude != nil { + if errs, err := exclude(resource, bindings); err != nil { + return []RuleResponse{{ + Rule: in, + Timestamp: time.Now(), + // Identifier: identifier, + Error: err, + }} + } else if len(errs) != 0 { + // matched + return nil + } + } + // 6. compute feedback + // 7. evaluate assertions + violations, err := assert(resource, bindings) + if err != nil { + return []RuleResponse{{ + Rule: in, + Timestamp: time.Now(), + // Identifier: identifier, + // Feedback: feedback, + Error: err, + }} + } + return []RuleResponse{{ + Rule: in, + Timestamp: time.Now(), + // Identifier: identifier, + // Feedback: feedback, + Violations: violations, + }} + }, nil +} diff --git a/pkg/json-engine/engine.go b/pkg/json-engine/engine.go index 8df8ecc3..a168d271 100644 --- a/pkg/json-engine/engine.go +++ b/pkg/json-engine/engine.go @@ -2,14 +2,11 @@ package jsonengine import ( "context" - "fmt" - "sync" "time" - "github.com/jmespath-community/go-jmespath/pkg/binding" jpbinding "github.com/jmespath-community/go-jmespath/pkg/binding" "github.com/kyverno/kyverno-json/pkg/apis/policy/v1alpha1" - "github.com/kyverno/kyverno-json/pkg/core/compilers" + corecompilers "github.com/kyverno/kyverno-json/pkg/core/compilers" "github.com/kyverno/kyverno-json/pkg/core/expression" "github.com/kyverno/kyverno-json/pkg/engine" "github.com/kyverno/kyverno-json/pkg/engine/builder" @@ -58,6 +55,23 @@ const ( // StatusSkip PolicyResult = "skip" ) +// func compileMatch( +// path *field.Path, +// compiler matching.Compiler, +// defaultCompiler string, +// match *v1alpha1.Match, +// ) func(any, jpbinding.Bindings) (field.ErrorList, error) { +// if match == nil { +// return nil +// } +// if match.Compiler != nil { +// defaultCompiler = string(*match.Compiler) +// } +// return func(resource any, bindings jpbinding.Bindings) (field.ErrorList, error) { +// return matching.Match(path, match, resource, bindings, compiler, defaultCompiler) +// } +// } + func New() engine.Engine[Request, Response] { type ruleRequest struct { policy v1alpha1.ValidatingPolicy @@ -70,10 +84,11 @@ func New() engine.Engine[Request, Response] { resource any bindings jpbinding.Bindings } - compiler := matching.NewCompiler(compilers.DefaultCompilers, 256) + compilers := corecompilers.DefaultCompilers + ruleCompiler := compiler{} + // compiler := matching.NewCompiler(compilers, 256) ruleEngine := builder. Function(func(ctx context.Context, r ruleRequest) []RuleResponse { - bindings := r.bindings.Register("$rule", jpbinding.NewBinding(r.rule)) defaultCompiler := expression.CompilerJP if r.policy.Spec.Compiler != nil { defaultCompiler = string(*r.policy.Spec.Compiler) @@ -81,120 +96,99 @@ func New() engine.Engine[Request, Response] { if r.rule.Compiler != nil { defaultCompiler = string(*r.rule.Compiler) } + compilers := compilers.WithDefaultCompiler(defaultCompiler) // TODO: this doesn't seem to be the right path var path *field.Path - path = path.Child("context") - for _, entry := range r.rule.Context { - defaultCompiler := defaultCompiler - if entry.Compiler != nil { - defaultCompiler = string(*entry.Compiler) - } - bindings = func(variable v1alpha1.Any, bindings jpbinding.Bindings) jpbinding.Bindings { - return bindings.Register( - "$"+entry.Name, - binding.NewDelegate( - sync.OnceValues( - func() (any, error) { - handler, err := variable.Compile(compiler.CompileProjection, defaultCompiler) - if err != nil { - return nil, field.InternalError(path.Child("variable"), err) - } - projected, err := handler(r.resource, bindings) - if err != nil { - return nil, field.InternalError(path.Child("variable"), err) - } - return projected, nil - }, - ), - ), - ) - }(entry.Variable, bindings) - } - identifier := "" - if r.rule.Identifier != "" { - result, err := compilers.Execute(r.rule.Identifier, r.resource, bindings, compiler.Jp) - if err != nil { - identifier = fmt.Sprintf("(error: %s)", err) - } else { - identifier = fmt.Sprint(result) - } - } - if r.rule.Match != nil { - defaultCompiler := defaultCompiler - if r.rule.Match.Compiler != nil { - defaultCompiler = string(*r.rule.Match.Compiler) - } - errs, err := matching.Match(nil, r.rule.Match, r.resource, bindings, compiler, defaultCompiler) - if err != nil { - return []RuleResponse{{ - Rule: r.rule, - Timestamp: time.Now(), - Identifier: identifier, - Error: err, - }} - } - // didn't match - if len(errs) != 0 { - return nil - } - } - if r.rule.Exclude != nil { - defaultCompiler := defaultCompiler - if r.rule.Exclude.Compiler != nil { - defaultCompiler = string(*r.rule.Exclude.Compiler) - } - errs, err := matching.Match(nil, r.rule.Exclude, r.resource, bindings, compiler, defaultCompiler) - if err != nil { - return []RuleResponse{{ - Rule: r.rule, - Timestamp: time.Now(), - Identifier: identifier, - Error: err, - }} - } - // matched - if len(errs) == 0 { - return nil - } - } - var feedback map[string]Feedback - for _, f := range r.rule.Feedback { - entry := Feedback{} - if f.Value != nil { - defaultCompiler := defaultCompiler - if f.Compiler != nil { - defaultCompiler = string(*f.Compiler) - } - if handler, err := f.Value.Compile(compiler.CompileProjection, defaultCompiler); err != nil { - entry.Error = err - } else if projected, err := handler(r.resource, bindings); err != nil { - entry.Error = err - } else { - entry.Value = projected - } - } - if feedback == nil { - feedback = map[string]Feedback{} - } - feedback[f.Name] = entry - } - violations, err := matching.Assert(nil, r.rule.Assert, r.resource, bindings, compiler, defaultCompiler) + compiled, err := ruleCompiler.compileRule(path, compilers, r.rule) if err != nil { return []RuleResponse{{ - Rule: r.rule, - Timestamp: time.Now(), - Identifier: identifier, - Feedback: feedback, - Error: err, + Rule: r.rule, + Timestamp: time.Now(), + // Identifier: identifier, + Error: err, }} } - return []RuleResponse{{ - Rule: r.rule, - Timestamp: time.Now(), - Identifier: identifier, - Feedback: feedback, - Violations: violations, - }} + bindings := r.bindings.Register("$rule", jpbinding.NewBinding(r.rule)) + out := compiled(r.resource, bindings) + return out + // identifier := "" + // if r.rule.Identifier != "" { + // result, err := corecompilers.Execute(r.rule.Identifier, r.resource, bindings, compilers.Jp) + // if err != nil { + // identifier = fmt.Sprintf("(error: %s)", err) + // } else { + // identifier = fmt.Sprint(result) + // } + // } + // if r.rule.Match != nil { + // errs, err := matching.Match(nil, r.rule.Match, r.resource, bindings, compilers) + // if err != nil { + // return []RuleResponse{{ + // Rule: r.rule, + // Timestamp: time.Now(), + // Identifier: identifier, + // Error: err, + // }} + // } + // // didn't match + // if len(errs) != 0 { + // return nil + // } + // } + // if r.rule.Exclude != nil { + // errs, err := matching.Match(nil, r.rule.Exclude, r.resource, bindings, compilers) + // if err != nil { + // return []RuleResponse{{ + // Rule: r.rule, + // Timestamp: time.Now(), + // Identifier: identifier, + // Error: err, + // }} + // } + // // matched + // if len(errs) == 0 { + // return nil + // } + // } + // var feedback map[string]Feedback + // for _, f := range r.rule.Feedback { + // entry := Feedback{} + // if f.Value != nil { + // defaultCompiler := defaultCompiler + // if f.Compiler != nil { + // defaultCompiler = string(*f.Compiler) + // } + // compilers := compilers.WithDefaultCompiler(defaultCompiler) + // if handler, err := f.Value.Compile(compilers); err != nil { + // entry.Error = err + // } else if projected, err := handler(r.resource, bindings); err != nil { + // entry.Error = err + // } else { + // entry.Value = projected + // } + // } + // if feedback == nil { + // feedback = map[string]Feedback{} + // } + // feedback[f.Name] = entry + // } + // violations, err := matching.Assert(nil, r.rule.Assert, r.resource, bindings, compilers) + // if err != nil { + // return []RuleResponse{{ + // Rule: r.rule, + // Timestamp: time.Now(), + // Identifier: identifier, + // Feedback: feedback, + // Error: err, + // }} + // } + // return []RuleResponse{{ + // Rule: r.rule, + // Timestamp: time.Now(), + // Identifier: identifier, + // Feedback: feedback, + // Violations: violations, + // }} }) policyEngine := builder. Function(func(ctx context.Context, r policyRequest) PolicyResponse { diff --git a/pkg/matching/compiler.go b/pkg/matching/compiler.go index 088ae2b4..8e4ef6ec 100644 --- a/pkg/matching/compiler.go +++ b/pkg/matching/compiler.go @@ -1,52 +1,46 @@ package matching -import ( - "sync" - - "github.com/cespare/xxhash/v2" - "github.com/elastic/go-freelru" - "github.com/kyverno/kyverno-json/pkg/core/assertion" - "github.com/kyverno/kyverno-json/pkg/core/compilers" - "github.com/kyverno/kyverno-json/pkg/core/projection" -) - -type _compilers = compilers.Compilers - -type Compiler struct { - _compilers - *freelru.SyncedLRU[string, func() (assertion.Assertion, error)] -} - -func hashStringXXHASH(s string) uint32 { - sum := xxhash.Sum64String(s) - return uint32(sum) //nolint:gosec -} - -func NewCompiler(compiler compilers.Compilers, cacheSize uint32) Compiler { - out := Compiler{ - _compilers: compiler, - } - if cache, err := freelru.NewSynced[string, func() (assertion.Assertion, error)](cacheSize, hashStringXXHASH); err == nil { - out.SyncedLRU = cache - } - return out -} - -func (c Compiler) CompileAssertion(hash string, value any, defaultCompiler string) (assertion.Assertion, error) { - if c.SyncedLRU == nil || hash == "" { - return assertion.Parse(value, c._compilers.WithDefaultCompiler(defaultCompiler)) - } - entry, _ := c.SyncedLRU.Get(hash) - if entry == nil { - entry = sync.OnceValues(func() (assertion.Assertion, error) { - return assertion.Parse(value, c._compilers.WithDefaultCompiler(defaultCompiler)) - }) - c.SyncedLRU.Add(hash, entry) - } - return entry() -} - -func (c Compiler) CompileProjection(hash string, value any, defaultCompiler string) (projection.ScalarHandler, error) { - // TODO: cache - return projection.ParseScalar(value, c._compilers.WithDefaultCompiler(defaultCompiler)) -} +// import ( +// "sync" + +// "github.com/cespare/xxhash/v2" +// "github.com/elastic/go-freelru" +// "github.com/kyverno/kyverno-json/pkg/core/assertion" +// "github.com/kyverno/kyverno-json/pkg/core/compilers" +// ) + +// type _compilers = compilers.Compilers + +// type Compiler struct { +// _compilers +// *freelru.SyncedLRU[string, func() (assertion.Assertion, error)] +// } + +// func hashStringXXHASH(s string) uint32 { +// sum := xxhash.Sum64String(s) +// return uint32(sum) //nolint:gosec +// } + +// func NewCompiler(compiler compilers.Compilers, cacheSize uint32) Compiler { +// out := Compiler{ +// _compilers: compiler, +// } +// if cache, err := freelru.NewSynced[string, func() (assertion.Assertion, error)](cacheSize, hashStringXXHASH); err == nil { +// out.SyncedLRU = cache +// } +// return out +// } + +// func (c Compiler) CompileAssertion(hash string, value any, defaultCompiler string) (assertion.Assertion, error) { +// if c.SyncedLRU == nil || hash == "" { +// return assertion.Parse(value, c._compilers.WithDefaultCompiler(defaultCompiler)) +// } +// entry, _ := c.SyncedLRU.Get(hash) +// if entry == nil { +// entry = sync.OnceValues(func() (assertion.Assertion, error) { +// return assertion.Parse(value, c._compilers.WithDefaultCompiler(defaultCompiler)) +// }) +// c.SyncedLRU.Add(hash, entry) +// } +// return entry() +// } diff --git a/pkg/matching/match.go b/pkg/matching/match.go index 9a91c5c9..d59876cd 100644 --- a/pkg/matching/match.go +++ b/pkg/matching/match.go @@ -5,6 +5,7 @@ import ( "github.com/jmespath-community/go-jmespath/pkg/binding" "github.com/kyverno/kyverno-json/pkg/apis/policy/v1alpha1" + "github.com/kyverno/kyverno-json/pkg/core/compilers" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -36,9 +37,9 @@ func (r Results) Error() string { return strings.Join(lines, "\n") } -func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.Bindings, compiler Compiler, defaultCompiler string) ([]Result, error) { +func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.Bindings, compilers compilers.Compilers) (Results, error) { if in.Compiler != nil { - defaultCompiler = string(*in.Compiler) + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) } if len(in.Any) == 0 && len(in.All) == 0 { return nil, field.Invalid(path, in, "an empty assert is not valid") @@ -47,11 +48,11 @@ func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.B var fails []Result path := path.Child("any") for i, assertion := range in.Any { - defaultCompiler := defaultCompiler + compilers := compilers if assertion.Compiler != nil { - defaultCompiler = string(*assertion.Compiler) + compilers = compilers.WithDefaultCompiler(string(*assertion.Compiler)) } - checkFails, err := assert(path.Index(i).Child("check"), assertion.Check, actual, bindings, compiler, defaultCompiler) + checkFails, err := assert(path.Index(i).Child("check"), assertion.Check, actual, bindings, compilers) if err != nil { return fails, err } @@ -63,7 +64,7 @@ func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.B ErrorList: checkFails, } if assertion.Message != nil { - fail.Message = assertion.Message.Format(actual, bindings, compiler.Jp.Options()...) + fail.Message = assertion.Message.Format(actual, bindings, compilers.Jp.Options()...) } fails = append(fails, fail) } @@ -75,11 +76,11 @@ func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.B var fails []Result path := path.Child("all") for i, assertion := range in.All { - defaultCompiler := defaultCompiler + compilers := compilers if assertion.Compiler != nil { - defaultCompiler = string(*assertion.Compiler) + compilers = compilers.WithDefaultCompiler(string(*assertion.Compiler)) } - checkFails, err := assert(path.Index(i).Child("check"), assertion.Check, actual, bindings, compiler, defaultCompiler) + checkFails, err := assert(path.Index(i).Child("check"), assertion.Check, actual, bindings, compilers) if err != nil { return fails, err } @@ -88,7 +89,7 @@ func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.B ErrorList: checkFails, } if assertion.Message != nil { - fail.Message = assertion.Message.Format(actual, bindings, compiler.Jp.Options()...) + fail.Message = assertion.Message.Format(actual, bindings, compilers.Jp.Options()...) } fails = append(fails, fail) } @@ -99,23 +100,23 @@ func Assert(path *field.Path, in v1alpha1.Assert, actual any, bindings binding.B } } -func Match(path *field.Path, in *v1alpha1.Match, actual any, bindings binding.Bindings, compiler Compiler, defaultCompiler string) (field.ErrorList, error) { +func Match(path *field.Path, in *v1alpha1.Match, actual any, bindings binding.Bindings, compilers compilers.Compilers) (field.ErrorList, error) { if in.Compiler != nil { - defaultCompiler = string(*in.Compiler) + compilers = compilers.WithDefaultCompiler(string(*in.Compiler)) } if in == nil || (len(in.Any) == 0 && len(in.All) == 0) { return nil, field.Invalid(path, in, "an empty match is not valid") } else { var errs field.ErrorList if len(in.Any) != 0 { - _errs, err := matchAny(path.Child("any"), in.Any, actual, bindings, compiler, defaultCompiler) + _errs, err := matchAny(path.Child("any"), in.Any, actual, bindings, compilers) if err != nil { return errs, err } errs = append(errs, _errs...) } if len(in.All) != 0 { - _errs, err := matchAll(path.Child("all"), in.All, actual, bindings, compiler, defaultCompiler) + _errs, err := matchAll(path.Child("all"), in.All, actual, bindings, compilers) if err != nil { return errs, err } @@ -125,10 +126,10 @@ func Match(path *field.Path, in *v1alpha1.Match, actual any, bindings binding.Bi } } -func matchAny(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings binding.Bindings, compiler Compiler, defaultCompiler string) (field.ErrorList, error) { +func matchAny(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings binding.Bindings, compilers compilers.Compilers) (field.ErrorList, error) { var errs field.ErrorList for i, assertion := range in { - _errs, err := assert(path.Index(i), assertion, value, bindings, compiler, defaultCompiler) + _errs, err := assert(path.Index(i), assertion, value, bindings, compilers) if err != nil { return errs, err } @@ -140,10 +141,10 @@ func matchAny(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings return errs, nil } -func matchAll(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings binding.Bindings, compiler Compiler, defaultCompiler string) (field.ErrorList, error) { +func matchAll(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings binding.Bindings, compilers compilers.Compilers) (field.ErrorList, error) { var errs field.ErrorList for i, assertion := range in { - _errs, err := assert(path.Index(i), assertion, value, bindings, compiler, defaultCompiler) + _errs, err := assert(path.Index(i), assertion, value, bindings, compilers) if err != nil { return errs, err } @@ -152,8 +153,8 @@ func matchAll(path *field.Path, in []v1alpha1.AssertionTree, value any, bindings return errs, nil } -func assert(path *field.Path, assertion v1alpha1.AssertionTree, value any, bindings binding.Bindings, compiler Compiler, defaultCompiler string) (field.ErrorList, error) { - check, err := assertion.Compile(compiler.CompileAssertion, defaultCompiler) +func assert(path *field.Path, assertion v1alpha1.AssertionTree, value any, bindings binding.Bindings, compilers compilers.Compilers) (field.ErrorList, error) { + check, err := assertion.Compile(compilers) if err != nil { return nil, err } diff --git a/website/docs/apis/kyverno-json.v1alpha1.md b/website/docs/apis/kyverno-json.v1alpha1.md index 383f2c9c..3acf9026 100644 --- a/website/docs/apis/kyverno-json.v1alpha1.md +++ b/website/docs/apis/kyverno-json.v1alpha1.md @@ -45,7 +45,6 @@ auto_generated: true **Appears in:** -- [ContextEntry](#json-kyverno-io-v1alpha1-ContextEntry) - [Feedback](#json-kyverno-io-v1alpha1-Feedback)

Any can be any type.

@@ -105,7 +104,6 @@ auto_generated: true - [Assert](#json-kyverno-io-v1alpha1-Assert) - [Assertion](#json-kyverno-io-v1alpha1-Assertion) -- [ContextEntry](#json-kyverno-io-v1alpha1-ContextEntry) - [Feedback](#json-kyverno-io-v1alpha1-Feedback) - [Match](#json-kyverno-io-v1alpha1-Match) - [ValidatingPolicySpec](#json-kyverno-io-v1alpha1-ValidatingPolicySpec) @@ -114,21 +112,14 @@ auto_generated: true

Compiler defines the compiler to use when evaluating expressions.

-## `ContextEntry` {#json-kyverno-io-v1alpha1-ContextEntry} +## `Context` {#json-kyverno-io-v1alpha1-Context} + +(Alias of `[]github.com/kyverno/kyverno-json/pkg/apis/policy/v1alpha1.ContextEntry`) **Appears in:** - [ValidatingRule](#json-kyverno-io-v1alpha1-ValidatingRule) -

ContextEntry adds variables and data sources to a rule context.

- - -| Field | Type | Required | Inline | Description | -|---|---|---|---|---| -| `compiler` | [`Compiler`](#json-kyverno-io-v1alpha1-Compiler) | | |

Compiler defines the default compiler to use when evaluating expressions.

| -| `name` | `string` | :white_check_mark: | |

Name is the entry name.

| -| `variable` | [`Any`](#json-kyverno-io-v1alpha1-Any) | | |

Variable defines an arbitrary variable.

| - ## `Feedback` {#json-kyverno-io-v1alpha1-Feedback} **Appears in:** @@ -198,7 +189,7 @@ auto_generated: true |---|---|---|---|---| | `name` | `string` | :white_check_mark: | |

Name is a label to identify the rule, It must be unique within the policy.

| | `compiler` | [`Compiler`](#json-kyverno-io-v1alpha1-Compiler) | | |

Compiler defines the default compiler to use when evaluating expressions.

| -| `context` | [`[]ContextEntry`](#json-kyverno-io-v1alpha1-ContextEntry) | | |

Context defines variables and data sources that can be used during rule execution.

| +| `context` | [`Context`](#json-kyverno-io-v1alpha1-Context) | | |

Context defines variables and data sources that can be used during rule execution.

| | `match` | [`Match`](#json-kyverno-io-v1alpha1-Match) | | |

Match defines when this policy rule should be applied.

| | `exclude` | [`Match`](#json-kyverno-io-v1alpha1-Match) | | |

Exclude defines when this policy rule should not be applied.

| | `identifier` | `string` | | |

Identifier declares a JMESPath expression to extract a name from the payload.

|