Skip to content

Commit

Permalink
Merge branch 'main' into cel-support
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 authored Sep 19, 2024
2 parents b115f20 + 7cf1250 commit eb195fe
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 41 deletions.
3 changes: 2 additions & 1 deletion pkg/apis/policy/v1alpha1/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ package v1alpha1
type Assertion struct {
// Message is the message associated message.
// +optional
Message string `json:"message,omitempty"`
Message *Message `json:"message,omitempty"`

// Engine defines the default engine to use when evaluating expressions.
// +optional
Engine *Engine `json:"engine,omitempty"`

// Check is the assertion check definition.
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/policy/v1alpha1/engine.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package v1alpha1

// Engine defines the engine to use when evaluating expressions.
// +kubebuilder:validation:Enum:=jp;cel
type Engine string

Expand Down
42 changes: 42 additions & 0 deletions pkg/apis/policy/v1alpha1/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package v1alpha1

import (
"github.com/kyverno/kyverno-json/pkg/message"
"k8s.io/apimachinery/pkg/util/json"
)

type _message = message.Message

// Message stores a message template.
// +k8s:deepcopy-gen=false
// +kubebuilder:validation:Type:=string
type Message struct {
_message
}

func (a *Message) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Original())
}

func (a *Message) UnmarshalJSON(data []byte) error {
var v string
err := json.Unmarshal(data, &v)
if err != nil {
return err
}
a._message = message.Parse(v)
return nil
}

func (in *Message) DeepCopyInto(out *Message) {
out._message = in._message
}

func (in *Message) DeepCopy() *Message {
if in == nil {
return nil
}
out := new(Message)
in.DeepCopyInto(out)
return out
}
1 change: 1 addition & 0 deletions pkg/apis/policy/v1alpha1/validating_policy_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1alpha1
// ValidatingPolicySpec contains the policy spec.
type ValidatingPolicySpec struct {
// Engine defines the default engine to use when evaluating expressions.
// +optional
Engine *Engine `json:"engine,omitempty"`

// Rules is a list of ValidatingRule instances.
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/policy/v1alpha1/validating_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type ValidatingRule struct {
Name string `json:"name"`

// Engine defines the default engine to use when evaluating expressions.
// +optional
Engine *Engine `json:"engine,omitempty"`

// Context defines variables and data sources that can be used during rule execution.
Expand All @@ -30,5 +31,5 @@ type ValidatingRule struct {
Feedback []Feedback `json:"feedback,omitempty"`

// Assert is used to validate matching resources.
Assert *Assert `json:"assert"`
Assert Assert `json:"assert"`
}
10 changes: 5 additions & 5 deletions pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 0 additions & 23 deletions pkg/engine/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,13 @@ package template

import (
"context"
"fmt"
"regexp"
"strings"

"github.com/google/cel-go/cel"
"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/jmespath-community/go-jmespath/pkg/interpreter"
"github.com/jmespath-community/go-jmespath/pkg/parsing"
)

var variable = regexp.MustCompile(`{{(.*?)}}`)

func String(ctx context.Context, in string, value any, bindings binding.Bindings, opts ...Option) string {
groups := variable.FindAllStringSubmatch(in, -1)
for _, group := range groups {
statement := strings.TrimSpace(group[1])
result, err := ExecuteJP(ctx, statement, value, bindings, opts...)
if err != nil {
in = strings.ReplaceAll(in, group[0], fmt.Sprintf("ERR (%s - %s)", statement, err))
} else if result == nil {
in = strings.ReplaceAll(in, group[0], fmt.Sprintf("ERR (%s not found)", statement))
} else if result, ok := result.(string); !ok {
in = strings.ReplaceAll(in, group[0], fmt.Sprintf("ERR (%s not a string)", statement))
} else {
in = strings.ReplaceAll(in, group[0], result)
}
}
return in
}

func ExecuteCEL(ctx context.Context, statement string, value any, bindings binding.Bindings) (any, error) {
env, err := cel.NewEnv(cel.Variable("object", cel.AnyType))
if err != nil {
Expand Down
12 changes: 6 additions & 6 deletions pkg/matching/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func (r Results) Error() string {
}

// func MatchAssert(ctx context.Context, path *field.Path, match *v1alpha1.Assert, actual any, bindings binding.Bindings, opts ...template.Option) ([]error, error) {
func MatchAssert(ctx context.Context, path *field.Path, match *v1alpha1.Assert, actual any, bindings binding.Bindings, opts ...template.Option) ([]Result, error) {
if match == nil || (len(match.Any) == 0 && len(match.All) == 0) {
func MatchAssert(ctx context.Context, path *field.Path, match v1alpha1.Assert, actual any, bindings binding.Bindings, opts ...template.Option) ([]Result, error) {
if len(match.Any) == 0 && len(match.All) == 0 {
return nil, field.Invalid(path, match, "an empty assert is not valid")
} else {
if len(match.Any) != 0 {
Expand All @@ -64,8 +64,8 @@ func MatchAssert(ctx context.Context, path *field.Path, match *v1alpha1.Assert,
fail := Result{
ErrorList: checkFails,
}
if assertion.Message != "" {
fail.Message = template.String(ctx, assertion.Message, actual, bindings, opts...)
if assertion.Message != nil {
fail.Message = assertion.Message.Format(actual, bindings, opts...)
}
fails = append(fails, fail)
}
Expand All @@ -90,8 +90,8 @@ func MatchAssert(ctx context.Context, path *field.Path, match *v1alpha1.Assert,
fail := Result{
ErrorList: checkFails,
}
if assertion.Message != "" {
fail.Message = template.String(ctx, assertion.Message, actual, bindings, opts...)
if assertion.Message != nil {
fail.Message = assertion.Message.Format(actual, bindings, opts...)
}
fails = append(fails, fail)
}
Expand Down
76 changes: 76 additions & 0 deletions pkg/message/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package message

import (
"context"
"fmt"
"regexp"
"strings"
"sync"

"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/jmespath-community/go-jmespath/pkg/parsing"
"github.com/kyverno/kyverno-json/pkg/engine/template"
)

var variable = regexp.MustCompile(`{{(.*?)}}`)

type Message interface {
Original() string
Format(any, binding.Bindings, ...template.Option) string
}

type substitution = func(string, any, binding.Bindings, ...template.Option) string

type message struct {
original string
substitutions []substitution
}

func (m *message) Original() string {
return m.original
}

func (m *message) Format(value any, bindings binding.Bindings, opts ...template.Option) string {
out := m.original
for _, substitution := range m.substitutions {
out = substitution(out, value, bindings, opts...)
}
return out
}

func Parse(in string) *message {
groups := variable.FindAllStringSubmatch(in, -1)
var substitutions []func(string, any, binding.Bindings, ...template.Option) string
for _, group := range groups {
statement := strings.TrimSpace(group[1])
parse := sync.OnceValues(func() (parsing.ASTNode, error) {
parser := parsing.NewParser()
return parser.Parse(statement)
})
evaluate := func(value any, bindings binding.Bindings, opts ...template.Option) (any, error) {
ast, err := parse()
if err != nil {
return nil, err
}
return template.ExecuteAST(context.TODO(), ast, value, bindings, opts...)
}
placeholder := group[0]
substitutions = append(substitutions, func(out string, value any, bindings binding.Bindings, opts ...template.Option) string {
result, err := evaluate(value, bindings, opts...)
if err != nil {
out = strings.ReplaceAll(out, placeholder, fmt.Sprintf("ERR (%s - %s)", statement, err))
} else if result == nil {
out = strings.ReplaceAll(out, placeholder, fmt.Sprintf("ERR (%s not found)", statement))
} else if result, ok := result.(string); !ok {
out = strings.ReplaceAll(out, placeholder, fmt.Sprintf("ERR (%s not a string)", statement))
} else {
out = strings.ReplaceAll(out, placeholder, result)
}
return out
})
}
return &message{
original: in,
substitutions: substitutions,
}
}
2 changes: 1 addition & 1 deletion pkg/policy/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestLoad(t *testing.T) {
),
},
},
Assert: &v1alpha1.Assert{
Assert: v1alpha1.Assert{
All: []v1alpha1.Assertion{{
Check: v1alpha1.NewAssertionTree(
map[string]any{
Expand Down
1 change: 1 addition & 0 deletions website/apis/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ hiddenMemberFields:
- _value
- _tree
- _assertion
- _message

externalPackages:
- match: ^k8s\.io/apimachinery/pkg/apis/meta/v1\.Duration$
Expand Down
23 changes: 19 additions & 4 deletions website/docs/apis/kyverno-json.v1alpha1.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ auto_generated: true

| Field | Type | Required | Inline | Description |
|---|---|---|---|---|
| `message` | `string` | | | <p>Message is the message associated message.</p> |
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | :white_check_mark: | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `message` | [`Message`](#json-kyverno-io-v1alpha1-Message) | | | <p>Message is the message associated message.</p> |
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `check` | [`AssertionTree`](#json-kyverno-io-v1alpha1-AssertionTree) | :white_check_mark: | | <p>Check is the assertion check definition.</p> |

## `AssertionTree` {#json-kyverno-io-v1alpha1-AssertionTree}
Expand Down Expand Up @@ -119,6 +119,9 @@ auto_generated: true
- [ValidatingPolicySpec](#json-kyverno-io-v1alpha1-ValidatingPolicySpec)
- [ValidatingRule](#json-kyverno-io-v1alpha1-ValidatingRule)

<p>Engine defines the engine to use when evaluating expressions.</p>


## `Feedback` {#json-kyverno-io-v1alpha1-Feedback}

**Appears in:**
Expand Down Expand Up @@ -147,6 +150,18 @@ auto_generated: true
| `any` | [`[]AssertionTree`](#json-kyverno-io-v1alpha1-AssertionTree) | | | <p>Any allows specifying assertion trees which will be ORed.</p> |
| `all` | [`[]AssertionTree`](#json-kyverno-io-v1alpha1-AssertionTree) | | | <p>All allows specifying assertion trees which will be ANDed.</p> |

## `Message` {#json-kyverno-io-v1alpha1-Message}

**Appears in:**

- [Assertion](#json-kyverno-io-v1alpha1-Assertion)

<p>Message stores a message template.</p>


| Field | Type | Required | Inline | Description |
|---|---|---|---|---|

## `ValidatingPolicySpec` {#json-kyverno-io-v1alpha1-ValidatingPolicySpec}

**Appears in:**
Expand All @@ -158,7 +173,7 @@ auto_generated: true

| Field | Type | Required | Inline | Description |
|---|---|---|---|---|
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | :white_check_mark: | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `rules` | [`[]ValidatingRule`](#json-kyverno-io-v1alpha1-ValidatingRule) | :white_check_mark: | | <p>Rules is a list of ValidatingRule instances.</p> |

## `ValidatingRule` {#json-kyverno-io-v1alpha1-ValidatingRule}
Expand All @@ -173,7 +188,7 @@ auto_generated: true
| Field | Type | Required | Inline | Description |
|---|---|---|---|---|
| `name` | `string` | :white_check_mark: | | <p>Name is a label to identify the rule, It must be unique within the policy.</p> |
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | :white_check_mark: | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `engine` | [`Engine`](#json-kyverno-io-v1alpha1-Engine) | | | <p>Engine defines the default engine to use when evaluating expressions.</p> |
| `context` | [`[]ContextEntry`](#json-kyverno-io-v1alpha1-ContextEntry) | | | <p>Context defines variables and data sources that can be used during rule execution.</p> |
| `match` | [`Match`](#json-kyverno-io-v1alpha1-Match) | | | <p>Match defines when this policy rule should be applied.</p> |
| `exclude` | [`Match`](#json-kyverno-io-v1alpha1-Match) | | | <p>Exclude defines when this policy rule should not be applied.</p> |
Expand Down

0 comments on commit eb195fe

Please sign in to comment.