Skip to content

Commit

Permalink
feat: add assertion compiler
Browse files Browse the repository at this point in the history
Signed-off-by: Charles-Edouard Brétéché <[email protected]>
  • Loading branch information
eddycharly committed Sep 20, 2024
1 parent 7faca6c commit 741b0de
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 18 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/google/cel-go v0.20.1
github.com/google/go-cmp v0.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jmespath-community/go-jmespath v1.1.2-0.20240919193755-5e4e8ae73c8a
github.com/kyverno/pkg/ext v0.0.0-20240418121121-df8add26c55c
github.com/loopfz/gadgeto v0.11.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down
27 changes: 21 additions & 6 deletions pkg/apis/policy/v1alpha1/assertion_tree.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package v1alpha1

import (
"crypto/md5"

Check failure on line 4 in pkg/apis/policy/v1alpha1/assertion_tree.go

View workflow job for this annotation

GitHub Actions / lint

G501: Blocklisted import crypto/md5: weak cryptographic primitive (gosec)
"encoding/hex"

"github.com/kyverno/kyverno-json/pkg/core/assertion"
"github.com/kyverno/kyverno-json/pkg/core/templating"
"k8s.io/apimachinery/pkg/util/json"
)

Expand All @@ -12,19 +14,30 @@ import (
// AssertionTree represents an assertion tree.
type AssertionTree struct {
_tree any
_hash string
}

func hash(in any) string {
if in == nil {
return ""
}
bytes, err := json.Marshal(in)
if err != nil {
return ""
}
hash := md5.Sum(bytes) //nolint:gosec
return hex.EncodeToString(hash[:])
}

func NewAssertionTree(value any) AssertionTree {
return AssertionTree{
_tree: value,
_hash: hash(value),
}
}

func (t *AssertionTree) Assertion(compiler templating.Compiler) (assertion.Assertion, error) {
if t._tree == nil {
return nil, nil
}
return assertion.Parse(t._tree, compiler)
func (t *AssertionTree) Compile(compiler func(string, any) (assertion.Assertion, error)) (assertion.Assertion, error) {
return compiler(t._hash, t._tree)
}

func (a *AssertionTree) MarshalJSON() ([]byte, error) {
Expand All @@ -38,9 +51,11 @@ func (a *AssertionTree) UnmarshalJSON(data []byte) error {
return err
}
a._tree = v
a._hash = hash(a._tree)
return nil
}

func (in *AssertionTree) DeepCopyInto(out *AssertionTree) {
out._tree = deepCopy(in._tree)
out._hash = in._hash
}
8 changes: 5 additions & 3 deletions pkg/json-engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func New() engine.Engine[Request, Response] {
resource any
bindings jpbinding.Bindings
}
compiler := templating.NewCompiler(templating.CompilerOptions{})
compiler := matching.Compiler{
Compiler: templating.NewCompiler(templating.CompilerOptions{}),
}
ruleEngine := builder.
Function(func(ctx context.Context, r ruleRequest) []RuleResponse {
bindings := r.bindings.Register("$rule", jpbinding.NewBinding(r.rule))
Expand All @@ -78,7 +80,7 @@ func New() engine.Engine[Request, Response] {
}
identifier := ""
if r.rule.Identifier != "" {
result, err := templating.ExecuteJP(r.rule.Identifier, r.resource, bindings, compiler)
result, err := templating.ExecuteJP(r.rule.Identifier, r.resource, bindings, compiler.Compiler)
if err != nil {
identifier = fmt.Sprintf("(error: %s)", err)
} else {
Expand Down Expand Up @@ -117,7 +119,7 @@ func New() engine.Engine[Request, Response] {
}
var feedback map[string]Feedback
for _, f := range r.rule.Feedback {
result, err := templating.ExecuteJP(f.Value, r.resource, bindings, compiler)
result, err := templating.ExecuteJP(f.Value, r.resource, bindings, compiler.Compiler)
if feedback == nil {
feedback = map[string]Feedback{}
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/matching/compiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package matching

import (
"sync"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/kyverno/kyverno-json/pkg/core/assertion"
"github.com/kyverno/kyverno-json/pkg/core/templating"
)

type Compiler struct {
templating.Compiler
*lru.Cache[string, func() (assertion.Assertion, error)]
}

func NewCompiler(compiler templating.Compiler, cacheSize int) Compiler {
out := Compiler{
Compiler: compiler,
}
if cache, err := lru.New[string, func() (assertion.Assertion, error)](cacheSize); err == nil {
out.Cache = cache
}
return out
}
func (c Compiler) CompileAssertion(hash string, value any) (assertion.Assertion, error) {
if c.Cache == nil {
return assertion.Parse(value, c.Compiler)
}
entry, _ := c.Cache.Get(hash)
if entry == nil {
entry = sync.OnceValues(func() (assertion.Assertion, error) {
return assertion.Parse(value, c.Compiler)
})
c.Cache.Add(hash, entry)
}
return entry()
}
17 changes: 8 additions & 9 deletions pkg/matching/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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/templating"
"k8s.io/apimachinery/pkg/util/validation/field"
)

Expand Down Expand Up @@ -37,7 +36,7 @@ func (r Results) Error() string {
return strings.Join(lines, "\n")
}

func MatchAssert(path *field.Path, match v1alpha1.Assert, actual any, bindings binding.Bindings, compiler templating.Compiler) ([]Result, error) {
func MatchAssert(path *field.Path, match v1alpha1.Assert, actual any, bindings binding.Bindings, compiler Compiler) ([]Result, error) {
if len(match.Any) == 0 && len(match.All) == 0 {
return nil, field.Invalid(path, match, "an empty assert is not valid")
} else {
Expand All @@ -46,7 +45,7 @@ func MatchAssert(path *field.Path, match v1alpha1.Assert, actual any, bindings b
path := path.Child("any")
for i, assertion := range match.Any {
path := path.Index(i).Child("check")
parsed, err := assertion.Check.Assertion(compiler)
parsed, err := assertion.Check.Compile(compiler.CompileAssertion)
if err != nil {
return fails, err
}
Expand Down Expand Up @@ -75,7 +74,7 @@ func MatchAssert(path *field.Path, match v1alpha1.Assert, actual any, bindings b
path := path.Child("all")
for i, assertion := range match.All {
path := path.Index(i).Child("check")
parsed, err := assertion.Check.Assertion(compiler)
parsed, err := assertion.Check.Compile(compiler.CompileAssertion)
if err != nil {
return fails, err
}
Expand All @@ -99,7 +98,7 @@ func MatchAssert(path *field.Path, match v1alpha1.Assert, actual any, bindings b
}
}

func Match(path *field.Path, match *v1alpha1.Match, actual any, bindings binding.Bindings, compiler templating.Compiler) (field.ErrorList, error) {
func Match(path *field.Path, match *v1alpha1.Match, actual any, bindings binding.Bindings, compiler Compiler) (field.ErrorList, error) {
if match == nil || (len(match.Any) == 0 && len(match.All) == 0) {
return nil, field.Invalid(path, match, "an empty match is not valid")
} else {
Expand All @@ -122,11 +121,11 @@ func Match(path *field.Path, match *v1alpha1.Match, actual any, bindings binding
}
}

func MatchAny(path *field.Path, assertions []v1alpha1.AssertionTree, actual any, bindings binding.Bindings, compiler templating.Compiler) (field.ErrorList, error) {
func MatchAny(path *field.Path, assertions []v1alpha1.AssertionTree, actual any, bindings binding.Bindings, compiler Compiler) (field.ErrorList, error) {
var errs field.ErrorList
for i, assertion := range assertions {
path := path.Index(i)
assertion, err := assertion.Assertion(compiler)
assertion, err := assertion.Compile(compiler.CompileAssertion)
if err != nil {
return errs, err
}
Expand All @@ -142,11 +141,11 @@ func MatchAny(path *field.Path, assertions []v1alpha1.AssertionTree, actual any,
return errs, nil
}

func MatchAll(path *field.Path, assertions []v1alpha1.AssertionTree, actual any, bindings binding.Bindings, compiler templating.Compiler) (field.ErrorList, error) {
func MatchAll(path *field.Path, assertions []v1alpha1.AssertionTree, actual any, bindings binding.Bindings, compiler Compiler) (field.ErrorList, error) {
var errs field.ErrorList
for i, assertion := range assertions {
path := path.Index(i)
assertion, err := assertion.Assertion(compiler)
assertion, err := assertion.Compile(compiler.CompileAssertion)
if err != nil {
return errs, err
}
Expand Down
1 change: 1 addition & 0 deletions website/apis/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ hiddenMemberFields:
- _tree
- _assertion
- _message
- _hash

externalPackages:
- match: ^k8s\.io/apimachinery/pkg/apis/meta/v1\.Duration$
Expand Down

0 comments on commit 741b0de

Please sign in to comment.