Skip to content

Commit

Permalink
refactor: policy compilation
Browse files Browse the repository at this point in the history
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
  • Loading branch information
eddycharly committed Sep 24, 2024
1 parent 9d7931b commit 0494ca0
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 20 deletions.
11 changes: 11 additions & 0 deletions pkg/apis/compiler.go
Original file line number Diff line number Diff line change
@@ -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)
}
10 changes: 3 additions & 7 deletions pkg/apis/policy/v1alpha1/any.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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)

Check warning on line 24 in pkg/apis/policy/v1alpha1/any.go

View check run for this annotation

Codecov / codecov/patch

pkg/apis/policy/v1alpha1/any.go#L23-L24

Added lines #L23 - L24 were not covered by tests
}

func (a *Any) MarshalJSON() ([]byte, error) {
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/policy/v1alpha1/context_entry.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/policy/v1alpha1/validating_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions pkg/core/engine.go
Original file line number Diff line number Diff line change
@@ -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,
}

Check warning on line 19 in pkg/core/engine.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/engine.go#L16-L19

Added lines #L16 - L19 were not covered by tests
}

func NewEngine(defaultCompiler string) engine {
return NewDefaultEngine().WithDefaultCompiler(defaultCompiler)

Check warning on line 23 in pkg/core/engine.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/engine.go#L22-L23

Added lines #L22 - L23 were not covered by tests
}

func (e engine) WithDefaultCompiler(defaultCompiler string) engine {
e.defaultCompiler = defaultCompiler
return e

Check warning on line 28 in pkg/core/engine.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/engine.go#L26-L28

Added lines #L26 - L28 were not covered by tests
}
64 changes: 64 additions & 0 deletions pkg/json-engine/compiler.go
Original file line number Diff line number Diff line change
@@ -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{}

Check failure on line 13 in pkg/json-engine/compiler.go

View workflow job for this annotation

GitHub Actions / lint

type `compiler` is unused (unused)

func (c *compiler) compileContextEntry(

Check failure on line 15 in pkg/json-engine/compiler.go

View workflow job for this annotation

GitHub Actions / lint

func `(*compiler).compileContextEntry` is unused (unused)
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

Check warning on line 37 in pkg/json-engine/compiler.go

View check run for this annotation

Codecov / codecov/patch

pkg/json-engine/compiler.go#L19-L37

Added lines #L19 - L37 were not covered by tests
},
),
),
)
}, nil
}

func (c *compiler) compileContext(

Check failure on line 45 in pkg/json-engine/compiler.go

View workflow job for this annotation

GitHub Actions / lint

func `(*compiler).compileContext` is unused (unused)
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)

Check warning on line 56 in pkg/json-engine/compiler.go

View check run for this annotation

Codecov / codecov/patch

pkg/json-engine/compiler.go#L49-L56

Added lines #L49 - L56 were not covered by tests
}
return func(resource any, bindings jpbinding.Bindings) jpbinding.Bindings {
for _, entry := range out {
bindings = entry(resource, bindings)
}
return bindings

Check warning on line 62 in pkg/json-engine/compiler.go

View check run for this annotation

Codecov / codecov/patch

pkg/json-engine/compiler.go#L58-L62

Added lines #L58 - L62 were not covered by tests
}, nil
}
83 changes: 77 additions & 6 deletions pkg/json-engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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)
}
Expand All @@ -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)

Check warning on line 185 in pkg/json-engine/engine.go

View check run for this annotation

Codecov / codecov/patch

pkg/json-engine/engine.go#L185

Added line #L185 was not covered by tests
if err != nil {
identifier = fmt.Sprintf("(error: %s)", err)
} else {
Expand Down Expand Up @@ -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 {

Check warning on line 239 in pkg/json-engine/engine.go

View check run for this annotation

Codecov / codecov/patch

pkg/json-engine/engine.go#L238-L239

Added lines #L238 - L239 were not covered by tests
entry.Error = err
} else if projected, err := handler(r.resource, bindings); err != nil {
entry.Error = err
Expand Down
6 changes: 0 additions & 6 deletions pkg/matching/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}

0 comments on commit 0494ca0

Please sign in to comment.