Skip to content

Commit

Permalink
feat: improve defaut compiler
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 21, 2024
1 parent a7d5579 commit deb69d3
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 111 deletions.
4 changes: 2 additions & 2 deletions pkg/apis/policy/v1alpha1/assertion_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func NewAssertionTree(value any) AssertionTree {
}
}

func (t *AssertionTree) Compile(compiler func(string, any) (assertion.Assertion, error)) (assertion.Assertion, error) {
return compiler(t._hash, t._tree)
func (t *AssertionTree) Compile(compiler func(string, any, string) (assertion.Assertion, error), defaultCompiler string) (assertion.Assertion, error) {
return compiler(t._hash, t._tree, defaultCompiler)

Check warning on line 26 in pkg/apis/policy/v1alpha1/assertion_tree.go

View check run for this annotation

Codecov / codecov/patch

pkg/apis/policy/v1alpha1/assertion_tree.go#L25-L26

Added lines #L25 - L26 were not covered by tests
}

func (a *AssertionTree) MarshalJSON() ([]byte, error) {
Expand Down
37 changes: 14 additions & 23 deletions pkg/core/assertion/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ type Assertion interface {
Assert(*field.Path, any, binding.Bindings) (field.ErrorList, error)
}

func Parse(assertion any, compiler compilers.Compilers) (node, error) {
func Parse(assertion any, compiler compilers.Compilers, defaultCompiler string) (node, error) {
switch reflectutils.GetKind(assertion) {
case reflect.Slice:
return parseSlice(assertion, compiler)
return parseSlice(assertion, compiler, defaultCompiler)

Check warning on line 25 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L25

Added line #L25 was not covered by tests
case reflect.Map:
return parseMap(assertion, compiler)
return parseMap(assertion, compiler, defaultCompiler)
default:
return parseScalar(assertion, compiler)
return parseScalar(assertion, compiler, defaultCompiler)

Check warning on line 29 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L29

Added line #L29 was not covered by tests
}
}

Expand All @@ -40,11 +40,11 @@ func (n node) Assert(path *field.Path, value any, bindings binding.Bindings) (fi
// parseSlice is the assertion represented by a slice.
// it first compares the length of the analysed resource with the length of the descendants.
// if lengths match all descendants are evaluated with their corresponding items.
func parseSlice(assertion any, compiler compilers.Compilers) (node, error) {
func parseSlice(assertion any, compiler compilers.Compilers, defaultCompiler string) (node, error) {

Check warning on line 43 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L43

Added line #L43 was not covered by tests
var assertions []node
valueOf := reflect.ValueOf(assertion)
for i := 0; i < valueOf.Len(); i++ {
sub, err := Parse(valueOf.Index(i).Interface(), compiler)
sub, err := Parse(valueOf.Index(i).Interface(), compiler, defaultCompiler)

Check warning on line 47 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L47

Added line #L47 was not covered by tests
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -76,7 +76,7 @@ func parseSlice(assertion any, compiler compilers.Compilers) (node, error) {

// parseMap is the assertion represented by a map.
// it is responsible for projecting the analysed resource and passing the result to the descendant
func parseMap(assertion any, compiler compilers.Compilers) (node, error) {
func parseMap(assertion any, compiler compilers.Compilers, defaultCompiler string) (node, error) {
assertions := map[any]struct {
projection.Projection
node
Expand All @@ -85,13 +85,13 @@ func parseMap(assertion any, compiler compilers.Compilers) (node, error) {
for iter.Next() {
key := iter.Key().Interface()
value := iter.Value().Interface()
assertion, err := Parse(value, compiler)
assertion, err := Parse(value, compiler, defaultCompiler)
if err != nil {
return nil, err
}
entry := assertions[key]
entry.node = assertion
entry.Projection = projection.Parse(key, compiler)
entry.Projection = projection.Parse(key, compiler, defaultCompiler)
assertions[key] = entry
}
return func(path *field.Path, value any, bindings binding.Bindings) (field.ErrorList, error) {
Expand Down Expand Up @@ -161,21 +161,20 @@ func parseMap(assertion any, compiler compilers.Compilers) (node, error) {
// parseScalar is the assertion represented by a leaf.
// it receives a value and compares it with an expected value.
// the expected value can be the result of an expression.
func parseScalar(assertion any, compiler compilers.Compilers) (node, error) {
func parseScalar(assertion any, compiler compilers.Compilers, defaultCompiler string) (node, error) {

Check warning on line 164 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L164

Added line #L164 was not covered by tests
var project func(value any, bindings binding.Bindings) (any, error)
switch typed := assertion.(type) {
case string:
expr := expression.Parse(typed)
expr := expression.Parse(defaultCompiler, typed)

Check warning on line 168 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L168

Added line #L168 was not covered by tests
if expr.Foreach {
return nil, errors.New("foreach is not supported on the RHS")
}
if expr.Binding != "" {
return nil, errors.New("binding is not supported on the RHS")
}
switch expr.Engine {
case expression.EngineJP:
if compiler := compiler.Compiler(expr.Compiler); compiler != nil {

Check warning on line 175 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L175

Added line #L175 was not covered by tests
parse := sync.OnceValues(func() (compilers.Program, error) {
return compiler.Jp.Compile(expr.Statement)
return compiler.Compile(expr.Statement)

Check warning on line 177 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L177

Added line #L177 was not covered by tests
})
project = func(value any, bindings binding.Bindings) (any, error) {
program, err := parse()
Expand All @@ -184,15 +183,7 @@ func parseScalar(assertion any, compiler compilers.Compilers) (node, error) {
}
return program(value, bindings)
}
case expression.EngineCEL:
project = func(value any, bindings binding.Bindings) (any, error) {
program, err := compiler.Cel.Compile(expr.Statement)
if err != nil {
return nil, err
}
return program(value, bindings)
}
default:
} else {

Check warning on line 186 in pkg/core/assertion/assertion.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/assertion/assertion.go#L186

Added line #L186 was not covered by tests
assertion = expr.Statement
}
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/core/assertion/assertion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/kyverno/kyverno-json/pkg/core/compilers"
"github.com/kyverno/kyverno-json/pkg/core/expression"
tassert "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/util/validation/field"
)
Expand Down Expand Up @@ -49,7 +50,7 @@ func TestAssert(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := compilers.DefaultCompiler
parsed, err := Parse(tt.assertion, compiler)
parsed, err := Parse(tt.assertion, compiler, expression.CompilerJP)
tassert.NoError(t, err)
got, err := parsed.Assert(nil, tt.value, tt.bindings)
if tt.wantErr {
Expand Down
14 changes: 14 additions & 0 deletions pkg/core/compilers/compiler.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
package compilers

import (
"github.com/jmespath-community/go-jmespath/pkg/binding"
)

type Program = func(any, binding.Bindings) (any, error)

type Compiler interface {
Compile(string) (Program, error)
}

func Execute(statement string, value any, bindings binding.Bindings, compiler Compiler) (any, error) {
program, err := compiler.Compile(statement)
if err != nil {
return nil, err
}

Check warning on line 17 in pkg/core/compilers/compiler.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/compilers/compiler.go#L16-L17

Added lines #L16 - L17 were not covered by tests
return program(value, bindings)
}
23 changes: 18 additions & 5 deletions pkg/core/compilers/compilers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,40 @@ type Compilers struct {
Cel cel.Compiler
}

func (c Compilers) NewBinding(path *field.Path, value any, bindings binding.Bindings, template any) binding.Binding {
func (c Compilers) Compiler(compiler string) Compiler {
switch compiler {
case "":
return nil
case expression.CompilerJP:
return c.Jp
case expression.CompilerCEL:
return c.Cel
default:
return c.Jp

Check warning on line 32 in pkg/core/compilers/compilers.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/compilers/compilers.go#L31-L32

Added lines #L31 - L32 were not covered by tests
}
}

func (c Compilers) NewBinding(path *field.Path, value any, bindings binding.Bindings, template any, compiler string) binding.Binding {
return binding.NewDelegate(
sync.OnceValues(
func() (any, error) {
switch typed := template.(type) {
case string:
expr := expression.Parse(typed)
expr := expression.Parse(compiler, typed)
if expr.Foreach {
return nil, field.Invalid(path.Child("variable"), typed, "foreach is not supported in context")
}
if expr.Binding != "" {
return nil, field.Invalid(path.Child("variable"), typed, "binding is not supported in context")
}
switch expr.Engine {
case expression.EngineJP:
switch expr.Compiler {
case expression.CompilerJP:
projected, err := Execute(expr.Statement, value, bindings, c.Jp)
if err != nil {
return nil, field.InternalError(path.Child("variable"), err)
}
return projected, nil
case expression.EngineCEL:
case expression.CompilerCEL:
projected, err := Execute(expr.Statement, value, bindings, c.Cel)
if err != nil {
return nil, field.InternalError(path.Child("variable"), err)
Expand Down
13 changes: 0 additions & 13 deletions pkg/core/compilers/execute.go

This file was deleted.

7 changes: 0 additions & 7 deletions pkg/core/compilers/program.go

This file was deleted.

29 changes: 14 additions & 15 deletions pkg/core/expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,27 @@ import (
"regexp"
)

var (
foreachRegex = regexp.MustCompile(`^~(\w+)?\.(.*)`)
bindingRegex = regexp.MustCompile(`(.*)\s*->\s*(\w+)$`)
escapeRegex = regexp.MustCompile(`^\\(.+)\\$`)
engineRegex = regexp.MustCompile(`^\((?:(\w+);)?(.+)\)$`)
const (
CompilerJP = "jp"
CompilerCEL = "cel"
)

const (
EngineJP = "jp"
EngineCEL = "cel"
EngineDefault = EngineJP
var (
foreachRegex = regexp.MustCompile(`^~(\w+)?\.(.*)`)
bindingRegex = regexp.MustCompile(`(.*)\s*->\s*(\w+)$`)
escapeRegex = regexp.MustCompile(`^\\(.+)\\$`)
compilerRegex = regexp.MustCompile(`^\((?:(\w+);)?(.+)\)$`)
)

type Expression struct {
Foreach bool
ForeachName string
Statement string
Binding string
Engine string
Compiler string
}

func Parse(in string) (expression Expression) {
func Parse(compiler string, in string) (expression Expression) {
// 1. match foreach
if match := foreachRegex.FindStringSubmatch(in); match != nil {
expression.Foreach = true
Expand All @@ -41,11 +40,11 @@ func Parse(in string) (expression Expression) {
if match := escapeRegex.FindStringSubmatch(in); match != nil {
in = match[1]
} else {
if match := engineRegex.FindStringSubmatch(in); match != nil {
expression.Engine = match[1]
if match := compilerRegex.FindStringSubmatch(in); match != nil {
expression.Compiler = match[1]
// account for default engine
if expression.Engine == "" {
expression.Engine = EngineDefault
if expression.Compiler == "" {
expression.Compiler = compiler
}
in = match[2]
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/core/expression/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestParse(t *testing.T) {
Foreach: false,
ForeachName: "",
Statement: "test",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "nested field",
Expand All @@ -47,7 +47,7 @@ func TestParse(t *testing.T) {
Foreach: false,
ForeachName: "",
Statement: "test.test",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "Foreach simple field",
Expand All @@ -64,7 +64,7 @@ func TestParse(t *testing.T) {
Foreach: true,
ForeachName: "",
Statement: "test",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "Foreach nested field",
Expand All @@ -73,7 +73,7 @@ func TestParse(t *testing.T) {
Foreach: true,
ForeachName: "",
Statement: "test.test",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "binding",
Expand All @@ -92,7 +92,7 @@ func TestParse(t *testing.T) {
ForeachName: "",
Statement: "test",
Binding: "foo",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "Foreach and binding",
Expand All @@ -111,7 +111,7 @@ func TestParse(t *testing.T) {
ForeachName: "",
Statement: "test",
Binding: "foo",
Engine: EngineDefault,
Compiler: CompilerJP,
},
}, {
name: "escape",
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestParse(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Parse(tt.in)
got := Parse(CompilerJP, tt.in)
assert.Equal(t, tt.want, got)
})
}
Expand Down
27 changes: 7 additions & 20 deletions pkg/core/projection/projection.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,22 @@ type Projection struct {
Handler
}

func Parse(in any, compiler compilers.Compilers) (projection Projection) {
func Parse(in any, compiler compilers.Compilers, defaultCompiler string) (projection Projection) {
switch typed := in.(type) {
case string:
// 1. if we have a string, parse the expression
expr := expression.Parse(typed)
expr := expression.Parse(defaultCompiler, typed)
// 2. record projection infos
projection.Foreach = expr.Foreach
projection.ForeachName = expr.ForeachName
projection.Binding = expr.Binding
// 3. compute the projection func
switch expr.Engine {
case expression.EngineJP:
parse := sync.OnceValues(func() (compilers.Program, error) {
return compiler.Jp.Compile(expr.Statement)
if compiler := compiler.Compiler(expr.Compiler); compiler != nil {
compile := sync.OnceValues(func() (compilers.Program, error) {
return compiler.Compile(expr.Statement)

Check warning on line 39 in pkg/core/projection/projection.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/projection/projection.go#L38-L39

Added lines #L38 - L39 were not covered by tests
})
projection.Handler = func(value any, bindings binding.Bindings) (any, bool, error) {
program, err := parse()
program, err := compile()

Check warning on line 42 in pkg/core/projection/projection.go

View check run for this annotation

Codecov / codecov/patch

pkg/core/projection/projection.go#L42

Added line #L42 was not covered by tests
if err != nil {
return nil, false, err
}
Expand All @@ -50,19 +49,7 @@ func Parse(in any, compiler compilers.Compilers) (projection Projection) {
}
return projected, true, err
}
case expression.EngineCEL:
projection.Handler = func(value any, bindings binding.Bindings) (any, bool, error) {
program, err := compiler.Cel.Compile(expr.Statement)
if err != nil {
return nil, false, err
}
projected, err := program(value, bindings)
if err != nil {
return nil, false, err
}
return projected, true, nil
}
default:
} else {
projection.Handler = func(value any, bindings binding.Bindings) (any, bool, error) {
if value == nil {
return nil, false, nil
Expand Down
Loading

0 comments on commit deb69d3

Please sign in to comment.