From 91e66f5e8398d2d99422ce5c909e356701fb9dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 3 Oct 2023 19:07:02 +0200 Subject: [PATCH] chore: add unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- pkg/apis/v1alpha1/variable.go | 8 +- pkg/apis/v1alpha1/zz_generated.deepcopy.go | 20 +-- pkg/commands/root_test.go | 181 ++++++++++----------- pkg/engine/assert/expression.go | 4 +- pkg/utils/file/ext_test.go | 4 +- testdata/foo-bar/out.txt | 6 + testdata/foo-bar/policy.yaml | 7 +- testdata/jim/out.txt | 6 + testdata/payload-yaml/out.txt | 6 + testdata/payload-yaml/payload.yaml | 2 - testdata/payload-yaml/policy.yaml | 8 +- testdata/pod-all-latest/out.txt | 6 + testdata/pod-all-latest/policy.yaml | 14 +- testdata/pod-no-latest/out.txt | 6 + testdata/pod-no-latest/policy.yaml | 35 ++-- testdata/scripted/out.txt | 6 + testdata/scripted/policy.yaml | 14 +- testdata/tf-plan/out.txt | 6 + testdata/tf-plan/policy.yaml | 8 +- testdata/tf-plan/tf.plan.json | 6 +- 20 files changed, 197 insertions(+), 156 deletions(-) create mode 100644 testdata/foo-bar/out.txt create mode 100644 testdata/jim/out.txt create mode 100644 testdata/payload-yaml/out.txt create mode 100644 testdata/pod-all-latest/out.txt create mode 100644 testdata/pod-no-latest/out.txt create mode 100644 testdata/scripted/out.txt create mode 100644 testdata/tf-plan/out.txt diff --git a/pkg/apis/v1alpha1/variable.go b/pkg/apis/v1alpha1/variable.go index 328b50db..233b7c82 100644 --- a/pkg/apis/v1alpha1/variable.go +++ b/pkg/apis/v1alpha1/variable.go @@ -1,9 +1,15 @@ package v1alpha1 // Variable defines an arbitrary JMESPath context variable that can be defined inline. +// +k8s:deepcopy-gen=false type Variable struct { // Value is any arbitrary object. // +kubebuilder:pruning:PreserveUnknownFields // +kubebuilder:validation:Schemaless - Value Any `json:"value,omitempty"` + Value interface{} `json:"value,omitempty"` +} + +func (in *Variable) DeepCopy() *Variable { + // TODO + return nil } diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index edd76c5f..f104bb7a 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -52,8 +52,7 @@ func (in *ContextEntry) DeepCopyInto(out *ContextEntry) { *out = *in if in.Variable != nil { in, out := &in.Variable, &out.Variable - *out = new(Variable) - (*in).DeepCopyInto(*out) + *out = (*in).DeepCopy() } if in.ImageRegistry != nil { in, out := &in.ImageRegistry, &out.ImageRegistry @@ -258,20 +257,3 @@ func (in *Validation) DeepCopy() *Validation { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Variable) DeepCopyInto(out *Variable) { - *out = *in - in.Value.DeepCopyInto(&out.Value) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variable. -func (in *Variable) DeepCopy() *Variable { - if in == nil { - return nil - } - out := new(Variable) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/commands/root_test.go b/pkg/commands/root_test.go index aab36e23..3227c7ca 100644 --- a/pkg/commands/root_test.go +++ b/pkg/commands/root_test.go @@ -1,96 +1,93 @@ package commands -// func Test_TfPlan(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/tf-plan/tf.plan.json", -// "--pre-process", -// "planned_values.root_module.resources", -// "--policy", -// "../../testdata/tf-plan/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } +import ( + "bytes" + "io" + "os" + "testing" -// func Test_PayloadYaml(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/payload-yaml/payload.yaml", -// "--pre-process", -// "planned_values.root_module.resources", -// "--policy", -// "../../testdata/payload-yaml/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } + "github.com/stretchr/testify/assert" +) -// func Test_FooBar(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/foo-bar/payload.yaml", -// "--policy", -// "../../testdata/foo-bar/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } - -// func Test_Scripted(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/scripted/payload.yaml", -// "--policy", -// "../../testdata/scripted/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } - -// func Test_PodNoLatest(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/pod-no-latest/payload.yaml", -// "--policy", -// "../../testdata/pod-no-latest/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } - -// func Test_PodAllLatest(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/pod-all-latest/payload.yaml", -// "--policy", -// "../../testdata/pod-all-latest/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } - -// func Test_Jim(t *testing.T) { -// cmd := NewRootCommand() -// assert.NotNil(t, cmd) -// cmd.SetArgs([]string{ -// "--payload", -// "../../testdata/jim/payload.json", -// "--policy", -// "../../testdata/jim/policy.yaml", -// }) -// err := cmd.Execute() -// assert.NoError(t, err) -// } +func Test_Execute(t *testing.T) { + tests := []struct { + name string + payload string + preprocessors []string + policies []string + wantErr bool + out string + }{{ + name: "foo-bar", + payload: "../../testdata/foo-bar/payload.yaml", + policies: []string{"../../testdata/foo-bar/policy.yaml"}, + out: "../../testdata/foo-bar/out.txt", + wantErr: false, + }, { + name: "jim", + payload: "../../testdata/jim/payload.json", + policies: []string{"../../testdata/jim/policy.yaml"}, + out: "../../testdata/jim/out.txt", + wantErr: false, + }, { + name: "pod-no-latest", + payload: "../../testdata/pod-no-latest/payload.yaml", + policies: []string{"../../testdata/pod-no-latest/policy.yaml"}, + out: "../../testdata/pod-no-latest/out.txt", + wantErr: false, + }, { + name: "pod-all-latest", + payload: "../../testdata/pod-all-latest/payload.yaml", + policies: []string{"../../testdata/pod-all-latest/policy.yaml"}, + out: "../../testdata/pod-all-latest/out.txt", + wantErr: false, + }, { + name: "scripted", + payload: "../../testdata/scripted/payload.yaml", + policies: []string{"../../testdata/scripted/policy.yaml"}, + out: "../../testdata/scripted/out.txt", + wantErr: false, + }, { + name: "payload-yaml", + payload: "../../testdata/payload-yaml/payload.yaml", + preprocessors: []string{"planned_values.root_module.resources"}, + policies: []string{"../../testdata/payload-yaml/policy.yaml"}, + out: "../../testdata/payload-yaml/out.txt", + wantErr: false, + }, { + name: "tf-plan", + payload: "../../testdata/tf-plan/tf.plan.json", + preprocessors: []string{"planned_values.root_module.resources"}, + policies: []string{"../../testdata/tf-plan/policy.yaml"}, + out: "../../testdata/tf-plan/out.txt", + wantErr: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := NewRootCommand() + assert.NotNil(t, cmd) + var args []string + args = append(args, "--payload", tt.payload) + for _, preprocessor := range tt.preprocessors { + args = append(args, "--pre-process", preprocessor) + } + for _, policy := range tt.policies { + args = append(args, "--policy", policy) + } + args = append(args, "--payload", tt.payload) + cmd.SetArgs(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + if err := cmd.Execute(); (err != nil) != tt.wantErr { + t.Errorf("command.Run() error = %v, wantErr %v", err, tt.wantErr) + } + actual, err := io.ReadAll(b) + assert.NoError(t, err) + if tt.out != "" { + expected, err := os.ReadFile(tt.out) + assert.NoError(t, err) + assert.Equal(t, string(expected), string(actual)) + } + }) + } +} diff --git a/pkg/engine/assert/expression.go b/pkg/engine/assert/expression.go index 9818a9a5..f3fd7cb3 100644 --- a/pkg/engine/assert/expression.go +++ b/pkg/engine/assert/expression.go @@ -55,9 +55,9 @@ func parseExpression(value interface{}) *expression { statement = strings.TrimPrefix(statement, expressionPrefix) statement = strings.TrimSuffix(statement, expressionSuffix) engine = "jp" - } else if binding == "" { + } /* else if binding == "" { binding = strings.TrimSpace(statement) - } + }*/ return &expression{ foreach: foreach, statement: strings.TrimSpace(statement), diff --git a/pkg/utils/file/ext_test.go b/pkg/utils/file/ext_test.go index ec380ae4..43e8d55e 100644 --- a/pkg/utils/file/ext_test.go +++ b/pkg/utils/file/ext_test.go @@ -1,6 +1,8 @@ package file -import "testing" +import ( + "testing" +) func TestIsYaml(t *testing.T) { tests := []struct { diff --git a/testdata/foo-bar/out.txt b/testdata/foo-bar/out.txt new file mode 100644 index 00000000..f6392cc4 --- /dev/null +++ b/testdata/foo-bar/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- test / foo-bar-4 / ERROR: all[0].foo: Internal error: failed to find the map index `foo` +Done diff --git a/testdata/foo-bar/policy.yaml b/testdata/foo-bar/policy.yaml index 38f3b865..1df68ef7 100644 --- a/testdata/foo-bar/policy.yaml +++ b/testdata/foo-bar/policy.yaml @@ -6,6 +6,7 @@ spec: rules: - name: foo-bar-4 validate: - pattern: - foo: - bar: 4 + assert: + all: + - foo: + bar: 4 diff --git a/testdata/jim/out.txt b/testdata/jim/out.txt new file mode 100644 index 00000000..6c76f35b --- /dev/null +++ b/testdata/jim/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- required-s3-tags / require-team-tag / FAILED: any[0].resource.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false +Done diff --git a/testdata/payload-yaml/out.txt b/testdata/payload-yaml/out.txt new file mode 100644 index 00000000..afc4a272 --- /dev/null +++ b/testdata/payload-yaml/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"} +Done diff --git a/testdata/payload-yaml/payload.yaml b/testdata/payload-yaml/payload.yaml index d6233532..dca90756 100644 --- a/testdata/payload-yaml/payload.yaml +++ b/testdata/payload-yaml/payload.yaml @@ -15,11 +15,9 @@ planned_values: tags: Environment: Dev Name: My bucket - Team: Kyverno tags_all: Environment: Dev Name: My bucket - Team: Kyverno timeouts: sensitive_values: cors_rule: [] diff --git a/testdata/payload-yaml/policy.yaml b/testdata/payload-yaml/policy.yaml index a4833e29..222d49b7 100644 --- a/testdata/payload-yaml/policy.yaml +++ b/testdata/payload-yaml/policy.yaml @@ -16,6 +16,8 @@ spec: Team: Kyverno validate: message: Bucket `{{ resource.name }}` ({{ resource.address }}) does not have the required tags {{ to_string($tags) }} - pattern: - values: - tags: '{{ $tags }}' + assert: + all: + - resource: + values: + tags: ($tags) diff --git a/testdata/pod-all-latest/out.txt b/testdata/pod-all-latest/out.txt new file mode 100644 index 00000000..1665e958 --- /dev/null +++ b/testdata/pod-all-latest/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- test / pod-no-latest / PASSED +Done diff --git a/testdata/pod-all-latest/policy.yaml b/testdata/pod-all-latest/policy.yaml index b79d0283..c6f1d430 100644 --- a/testdata/pod-all-latest/policy.yaml +++ b/testdata/pod-all-latest/policy.yaml @@ -18,9 +18,11 @@ spec: apiVersion: v1 kind: Pod validate: - pattern: - ~(spec.containers[*].image): - # an image tag is required - (contains(@, ':')): true - # using a mutable image tag e.g. 'latest' is not allowed - (ends_with(@, $tag)): true + assert: + all: + - resource: + ~(spec.containers[*].image): + # an image tag is required + (contains(@, ':')): true + # using a mutable image tag e.g. 'latest' is not allowed + (ends_with(@, $tag)): true diff --git a/testdata/pod-no-latest/out.txt b/testdata/pod-no-latest/out.txt new file mode 100644 index 00000000..8837cfb6 --- /dev/null +++ b/testdata/pod-no-latest/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- test / pod-no-latest / FAILED: [all[0].resource.spec.~foo.containers@foos[0].(at($foos, $foo).image)@foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false, all[0].resource.spec.~foo.containers@foos[1].(at($foos, $foo).image)@foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false, all[0].resource.spec.~foo.containers@foos[2].(at($foos, $foo).image)@foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false, all[1].resource.spec.~.containers@foo[0].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[1].resource.spec.~.containers@foo[1].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[1].resource.spec.~.containers@foo[2].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[2].resource.~index.(spec.containers[*].image)@images[0].(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[2].resource.~index.(spec.containers[*].image)@images[1].(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[2].resource.~index.(spec.containers[*].image)@images[2].(ends_with(@, ':latest')): Invalid value: true: Expected value: false] +Done diff --git a/testdata/pod-no-latest/policy.yaml b/testdata/pod-no-latest/policy.yaml index 01fdcc70..fbaffc96 100644 --- a/testdata/pod-no-latest/policy.yaml +++ b/testdata/pod-no-latest/policy.yaml @@ -15,22 +15,27 @@ spec: apiVersion: v1 kind: Pod validate: - pattern: - spec: - ~foo.containers@foos: - (at($foos, $foo).image)@foo: - # an image tag is required - (contains($foo, ':')): true - # using a mutable image tag e.g. 'latest' is not allowed - (ends_with($foo, $tag)): false - ~.containers@foo: - image: + assert: + all: + - resource: + spec: + ~foo.containers@foos: + (at($foos, $foo).image)@foo: + # an image tag is required + (contains($foo, ':')): true + # using a mutable image tag e.g. 'latest' is not allowed + (ends_with($foo, $tag)): false + - resource: + spec: + ~.containers@foo: + image: + # an image tag is required + (contains(@, ':')): true + # using a mutable image tag e.g. 'latest' is not allowed + (ends_with(@, ':latest')): false + - resource: + ~index.(spec.containers[*].image)@images: # an image tag is required (contains(@, ':')): true # using a mutable image tag e.g. 'latest' is not allowed (ends_with(@, ':latest')): false - ~index.(spec.containers[*].image)@images: - # an image tag is required - (contains(@, ':')): true - # using a mutable image tag e.g. 'latest' is not allowed - (ends_with(@, ':latest')): false diff --git a/testdata/scripted/out.txt b/testdata/scripted/out.txt new file mode 100644 index 00000000..7592126b --- /dev/null +++ b/testdata/scripted/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- test / foo-bar-4 / PASSED +Done diff --git a/testdata/scripted/policy.yaml b/testdata/scripted/policy.yaml index f03153ec..0ef11b4c 100644 --- a/testdata/scripted/policy.yaml +++ b/testdata/scripted/policy.yaml @@ -6,8 +6,12 @@ spec: rules: - name: foo-bar-4 validate: - pattern: - foo: - '{{ bar > `3` }}': true - '{{ !baz }}': false - '{{ bar + bat }}': 10 + assert: + all: + - resource: + foo: + (bar > `3`): true + (!baz): false + - resource: + foo: + (bar + bat): 10 diff --git a/testdata/tf-plan/out.txt b/testdata/tf-plan/out.txt new file mode 100644 index 00000000..afc4a272 --- /dev/null +++ b/testdata/tf-plan/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"} +Done diff --git a/testdata/tf-plan/policy.yaml b/testdata/tf-plan/policy.yaml index a4833e29..222d49b7 100644 --- a/testdata/tf-plan/policy.yaml +++ b/testdata/tf-plan/policy.yaml @@ -16,6 +16,8 @@ spec: Team: Kyverno validate: message: Bucket `{{ resource.name }}` ({{ resource.address }}) does not have the required tags {{ to_string($tags) }} - pattern: - values: - tags: '{{ $tags }}' + assert: + all: + - resource: + values: + tags: ($tags) diff --git a/testdata/tf-plan/tf.plan.json b/testdata/tf-plan/tf.plan.json index cc9b8cfb..8c07434c 100644 --- a/testdata/tf-plan/tf.plan.json +++ b/testdata/tf-plan/tf.plan.json @@ -16,13 +16,11 @@ "force_destroy": false, "tags": { "Environment": "Dev", - "Name": "My bucket", - "Team": "Kyverno" + "Name": "My bucket" }, "tags_all": { "Environment": "Dev", - "Name": "My bucket", - "Team": "Kyverno" + "Name": "My bucket" }, "timeouts": null },