diff --git a/pkg/apis/compiler.go b/pkg/apis/compiler.go new file mode 100644 index 00000000..6bea18eb --- /dev/null +++ b/pkg/apis/compiler.go @@ -0,0 +1,11 @@ +package apis + +import ( + "github.com/kyverno/kyverno-json/pkg/core/assertion" + "github.com/kyverno/kyverno-json/pkg/core/projection" +) + +type Compiler interface { + CompileAssertion(any) (assertion.Assertion, error) + CompileProjection(any) (projection.ScalarHandler, error) +} 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/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/core/engine.go b/pkg/core/engine.go new file mode 100644 index 00000000..f2ba1861 --- /dev/null +++ b/pkg/core/engine.go @@ -0,0 +1,29 @@ +package core + +import "github.com/kyverno/kyverno-json/pkg/core/expression" + +const ( + CompilerJP = expression.CompilerJP + CompilerCEL = expression.CompilerCEL +) + +type Engine interface{} + +type engine struct { + defaultCompiler string +} + +func NewDefaultEngine() engine { + return engine{ + defaultCompiler: CompilerJP, + } +} + +func NewEngine(defaultCompiler string) engine { + return NewDefaultEngine().WithDefaultCompiler(defaultCompiler) +} + +func (e engine) WithDefaultCompiler(defaultCompiler string) engine { + e.defaultCompiler = defaultCompiler + return e +} diff --git a/pkg/json-engine/compiler.go b/pkg/json-engine/compiler.go new file mode 100644 index 00000000..5f41c867 --- /dev/null +++ b/pkg/json-engine/compiler.go @@ -0,0 +1,64 @@ +package jsonengine + +import ( + "sync" + + "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" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type compiler struct{} + +func (c *compiler) compileContextEntry( + path *field.Path, + compilers compilers.Compilers, + entry v1alpha1.ContextEntry, +) (func(any, jpbinding.Bindings) jpbinding.Bindings, error) { + if entry.Compiler != nil { + compilers = compilers.WithDefaultCompiler(string(*entry.Compiler)) + } + handler, err := entry.Variable.Compile(compilers) + if err != nil { + return nil, field.InternalError(path.Child("variable"), err) + } + return func(resource any, bindings jpbinding.Bindings) jpbinding.Bindings { + return bindings.Register( + "$"+entry.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, + entries v1alpha1.Context, +) (func(any, jpbinding.Bindings) jpbinding.Bindings, error) { + var out []func(any, jpbinding.Bindings) jpbinding.Bindings + for _, entry := range entries { + entry, err := c.compileContextEntry(path, compilers, entry) + if err != nil { + return nil, err + } + out = append(out, entry) + } + return func(resource any, bindings jpbinding.Bindings) jpbinding.Bindings { + for _, entry := range out { + bindings = entry(resource, bindings) + } + return bindings + }, nil +} diff --git a/pkg/json-engine/engine.go b/pkg/json-engine/engine.go index 8df8ecc3..d51cd480 100644 --- a/pkg/json-engine/engine.go +++ b/pkg/json-engine/engine.go @@ -9,7 +9,7 @@ import ( "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 +58,72 @@ 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 compileRule( +// path *field.Path, +// compiler matching.Compiler, +// defaultCompiler string, +// rule v1alpha1.ValidatingRule, +// ) func(any, jpbinding.Bindings) []RuleResponse { +// context := compileContext(path, compiler, defaultCompiler, rule.Context...) +// match := compileMatch(path, compiler, defaultCompiler, rule.Match) +// exclude := compileMatch(path, compiler, defaultCompiler, rule.Exclude) +// return func(resource any, bindings jpbinding.Bindings) []RuleResponse { +// // 1. register rule binding +// bindings = bindings.Register("$rule", jpbinding.NewBinding(rule)) +// // 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: rule, +// 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: rule, +// Timestamp: time.Now(), +// // Identifier: identifier, +// Error: err, +// }} +// } else if len(errs) != 0 { +// // matched +// return nil +// } +// } +// // 6. compute feedback +// // 7. evaluate assertions +// return nil +// } +// } + func New() engine.Engine[Request, Response] { type ruleRequest struct { policy v1alpha1.ValidatingPolicy @@ -70,17 +136,20 @@ func New() engine.Engine[Request, Response] { resource any bindings jpbinding.Bindings } - compiler := matching.NewCompiler(compilers.DefaultCompilers, 256) + compilers := corecompilers.DefaultCompilers + 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 + // compiled := compileRule(nil, compiler, defaultCompiler, r.rule) + // compiled(r.resource, r.bindings) if r.policy.Spec.Compiler != nil { defaultCompiler = string(*r.policy.Spec.Compiler) } if r.rule.Compiler != nil { defaultCompiler = string(*r.rule.Compiler) } + bindings := r.bindings.Register("$rule", jpbinding.NewBinding(r.rule)) // TODO: this doesn't seem to be the right path var path *field.Path path = path.Child("context") @@ -89,13 +158,14 @@ func New() engine.Engine[Request, Response] { if entry.Compiler != nil { defaultCompiler = string(*entry.Compiler) } + compilers := compilers.WithDefaultCompiler(defaultCompiler) 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) + handler, err := variable.Compile(compilers) if err != nil { return nil, field.InternalError(path.Child("variable"), err) } @@ -112,7 +182,7 @@ func New() engine.Engine[Request, Response] { } identifier := "" if r.rule.Identifier != "" { - result, err := compilers.Execute(r.rule.Identifier, r.resource, bindings, compiler.Jp) + result, err := corecompilers.Execute(r.rule.Identifier, r.resource, bindings, compiler.Jp) if err != nil { identifier = fmt.Sprintf("(error: %s)", err) } else { @@ -165,7 +235,8 @@ func New() engine.Engine[Request, Response] { if f.Compiler != nil { defaultCompiler = string(*f.Compiler) } - if handler, err := f.Value.Compile(compiler.CompileProjection, defaultCompiler); err != nil { + 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 diff --git a/pkg/matching/compiler.go b/pkg/matching/compiler.go index 088ae2b4..dabee57f 100644 --- a/pkg/matching/compiler.go +++ b/pkg/matching/compiler.go @@ -7,7 +7,6 @@ import ( "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 @@ -45,8 +44,3 @@ func (c Compiler) CompileAssertion(hash string, value any, defaultCompiler strin } 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)) -}