diff --git a/.crds/chainsaw.kyverno.io_tests.yaml b/.crds/chainsaw.kyverno.io_tests.yaml index 050b09251..b4d9e93b4 100644 --- a/.crds/chainsaw.kyverno.io_tests.yaml +++ b/.crds/chainsaw.kyverno.io_tests.yaml @@ -101,6 +101,30 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe + collector to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides + the global timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. @@ -227,6 +251,30 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe + collector to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides + the global timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. diff --git a/.crds/chainsaw.kyverno.io_teststeps.yaml b/.crds/chainsaw.kyverno.io_teststeps.yaml index 022ee3f8e..20a5ae8e5 100644 --- a/.crds/chainsaw.kyverno.io_teststeps.yaml +++ b/.crds/chainsaw.kyverno.io_teststeps.yaml @@ -68,6 +68,29 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe collector + to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides the global + timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. type: string @@ -184,6 +207,29 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe collector + to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides the global + timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. type: string diff --git a/.release-notes/main.md b/.release-notes/main.md index 446e1b571..3f66bc6c4 100644 --- a/.release-notes/main.md +++ b/.release-notes/main.md @@ -22,6 +22,7 @@ Release notes for `TODO`. - Allowed passing test folders by args (`chainsaw test ./folder` instead of `chainsaw test --test-dir ./folder`) - Added new binding `$namespace` containing the test namespace name +- Added new `describe` collector to invoke `kubectl describe ...` ## 🔧 Fixes 🔧 diff --git a/.schemas/json/test-chainsaw-v1alpha1.json b/.schemas/json/test-chainsaw-v1alpha1.json index 30a2cce3c..ba289e60c 100644 --- a/.schemas/json/test-chainsaw-v1alpha1.json +++ b/.schemas/json/test-chainsaw-v1alpha1.json @@ -401,6 +401,50 @@ } } }, + "describe": { + "description": "Describe determines the resource describe collector to execute.", + "type": [ + "object", + "null" + ], + "required": [ + "resource" + ], + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": [ + "string", + "null" + ] + }, + "namespace": { + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + "type": [ + "string", + "null" + ] + }, + "resource": { + "description": "Resource type.", + "type": "string" + }, + "selector": { + "description": "Selector defines labels selector.", + "type": [ + "string", + "null" + ] + }, + "timeout": { + "description": "Timeout for the operation. Overrides the global timeout set in the Configuration.", + "type": [ + "string", + "null" + ] + } + } + }, "description": { "description": "Description contains a description of the operation.", "type": [ @@ -616,6 +660,50 @@ } } }, + "describe": { + "description": "Describe determines the resource describe collector to execute.", + "type": [ + "object", + "null" + ], + "required": [ + "resource" + ], + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": [ + "string", + "null" + ] + }, + "namespace": { + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + "type": [ + "string", + "null" + ] + }, + "resource": { + "description": "Resource type.", + "type": "string" + }, + "selector": { + "description": "Selector defines labels selector.", + "type": [ + "string", + "null" + ] + }, + "timeout": { + "description": "Timeout for the operation. Overrides the global timeout set in the Configuration.", + "type": [ + "string", + "null" + ] + } + } + }, "description": { "description": "Description contains a description of the operation.", "type": [ diff --git a/pkg/apis/v1alpha1/catch.go b/pkg/apis/v1alpha1/catch.go index 40f875eae..0a7b50e0f 100644 --- a/pkg/apis/v1alpha1/catch.go +++ b/pkg/apis/v1alpha1/catch.go @@ -14,6 +14,10 @@ type Catch struct { // +optional Events *Events `json:"events,omitempty"` + // Describe determines the resource describe collector to execute. + // +optional + Describe *Describe `json:"describe,omitempty"` + // Command defines a command to run. // +optional Command *Command `json:"command,omitempty"` diff --git a/pkg/apis/v1alpha1/describe.go b/pkg/apis/v1alpha1/describe.go new file mode 100644 index 000000000..7e8be684f --- /dev/null +++ b/pkg/apis/v1alpha1/describe.go @@ -0,0 +1,29 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Describe defines how to describe resources. +type Describe struct { + // Timeout for the operation. Overrides the global timeout set in the Configuration. + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Resource type. + Resource string `json:"resource"` + + // Namespace of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + // +optional + Namespace string `json:"namespace,omitempty"` + + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + // +optional + Name string `json:"name,omitempty"` + + // Selector defines labels selector. + // +optional + Selector string `json:"selector,omitempty"` +} diff --git a/pkg/apis/v1alpha1/finally.go b/pkg/apis/v1alpha1/finally.go index aaf7e0d4b..758d094ed 100644 --- a/pkg/apis/v1alpha1/finally.go +++ b/pkg/apis/v1alpha1/finally.go @@ -14,6 +14,10 @@ type Finally struct { // +optional Events *Events `json:"events,omitempty"` + // Describe determines the resource describe collector to execute. + // +optional + Describe *Describe `json:"describe,omitempty"` + // Command defines a command to run. // +optional Command *Command `json:"command,omitempty"` diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index 9fe9dee4a..68c176974 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -95,6 +95,11 @@ func (in *Catch) DeepCopyInto(out *Catch) { *out = new(Events) (*in).DeepCopyInto(*out) } + if in.Describe != nil { + in, out := &in.Describe, &out.Describe + *out = new(Describe) + (*in).DeepCopyInto(*out) + } if in.Command != nil { in, out := &in.Command, &out.Command *out = new(Command) @@ -280,6 +285,27 @@ func (in *Delete) DeepCopy() *Delete { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Describe) DeepCopyInto(out *Describe) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Describe. +func (in *Describe) DeepCopy() *Describe { + if in == nil { + return nil + } + out := new(Describe) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Error) DeepCopyInto(out *Error) { *out = *in @@ -415,6 +441,11 @@ func (in *Finally) DeepCopyInto(out *Finally) { *out = new(Events) (*in).DeepCopyInto(*out) } + if in.Describe != nil { + in, out := &in.Describe, &out.Describe + *out = new(Describe) + (*in).DeepCopyInto(*out) + } if in.Command != nil { in, out := &in.Command, &out.Command *out = new(Command) diff --git a/pkg/commands/build/docs/docs.tmpl b/pkg/commands/build/docs/docs.tmpl index da2375ddf..9e5abf62f 100644 --- a/pkg/commands/build/docs/docs.tmpl +++ b/pkg/commands/build/docs/docs.tmpl @@ -25,6 +25,8 @@ command events {{- else if .PodLogs -}} pod logs +{{- else if .Describe -}} +describe {{- else if .Script -}} script {{- else if .Sleep -}} @@ -39,6 +41,8 @@ command events {{- else if .PodLogs -}} pod logs +{{- else if .Describe -}} +describe {{- else if .Script -}} script {{- else if .Sleep -}} diff --git a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml index 050b09251..b4d9e93b4 100644 --- a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml +++ b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml @@ -101,6 +101,30 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe + collector to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides + the global timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. @@ -227,6 +251,30 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe + collector to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides + the global timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. diff --git a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml index 022ee3f8e..20a5ae8e5 100644 --- a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml +++ b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml @@ -68,6 +68,29 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe collector + to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides the global + timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. type: string @@ -184,6 +207,29 @@ spec: required: - entrypoint type: object + describe: + description: Describe determines the resource describe collector + to execute. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resource: + description: Resource type. + type: string + selector: + description: Selector defines labels selector. + type: string + timeout: + description: Timeout for the operation. Overrides the global + timeout set in the Configuration. + type: string + required: + - resource + type: object description: description: Description contains a description of the operation. type: string diff --git a/pkg/data/schemas/json/test-chainsaw-v1alpha1.json b/pkg/data/schemas/json/test-chainsaw-v1alpha1.json index 30a2cce3c..ba289e60c 100644 --- a/pkg/data/schemas/json/test-chainsaw-v1alpha1.json +++ b/pkg/data/schemas/json/test-chainsaw-v1alpha1.json @@ -401,6 +401,50 @@ } } }, + "describe": { + "description": "Describe determines the resource describe collector to execute.", + "type": [ + "object", + "null" + ], + "required": [ + "resource" + ], + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": [ + "string", + "null" + ] + }, + "namespace": { + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + "type": [ + "string", + "null" + ] + }, + "resource": { + "description": "Resource type.", + "type": "string" + }, + "selector": { + "description": "Selector defines labels selector.", + "type": [ + "string", + "null" + ] + }, + "timeout": { + "description": "Timeout for the operation. Overrides the global timeout set in the Configuration.", + "type": [ + "string", + "null" + ] + } + } + }, "description": { "description": "Description contains a description of the operation.", "type": [ @@ -616,6 +660,50 @@ } } }, + "describe": { + "description": "Describe determines the resource describe collector to execute.", + "type": [ + "object", + "null" + ], + "required": [ + "resource" + ], + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": [ + "string", + "null" + ] + }, + "namespace": { + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + "type": [ + "string", + "null" + ] + }, + "resource": { + "description": "Resource type.", + "type": "string" + }, + "selector": { + "description": "Selector defines labels selector.", + "type": [ + "string", + "null" + ] + }, + "timeout": { + "description": "Timeout for the operation. Overrides the global timeout set in the Configuration.", + "type": [ + "string", + "null" + ] + } + } + }, "description": { "description": "Description contains a description of the operation.", "type": [ diff --git a/pkg/runner/collect/describe.go b/pkg/runner/collect/describe.go new file mode 100644 index 000000000..ea22a0117 --- /dev/null +++ b/pkg/runner/collect/describe.go @@ -0,0 +1,36 @@ +package collect + +import ( + "errors" + + "github.com/kyverno/chainsaw/pkg/apis/v1alpha1" +) + +func Describe(collector *v1alpha1.Describe) (*v1alpha1.Command, error) { + if collector == nil { + return nil, nil + } + if collector.Resource == "" { + return nil, errors.New("a resource must be specified") + } + if collector.Name != "" && collector.Selector != "" { + return nil, errors.New("name cannot be provided when a selector is specified") + } + cmd := v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", collector.Resource}, + } + if collector.Name != "" { + cmd.Args = append(cmd.Args, collector.Name) + } + if collector.Selector != "" { + cmd.Args = append(cmd.Args, "-l", collector.Selector) + } + // TODO: what if cluster scoped resource ? + namespace := collector.Namespace + if collector.Namespace == "" { + namespace = "$NAMESPACE" + } + cmd.Args = append(cmd.Args, "-n", namespace) + return &cmd, nil +} diff --git a/pkg/runner/collect/describe_test.go b/pkg/runner/collect/describe_test.go new file mode 100644 index 000000000..c7b8961ea --- /dev/null +++ b/pkg/runner/collect/describe_test.go @@ -0,0 +1,118 @@ +package collect + +import ( + "testing" + + "github.com/kyverno/chainsaw/pkg/apis/v1alpha1" + "github.com/stretchr/testify/assert" +) + +func TestDescribe(t *testing.T) { + tests := []struct { + name string + collector *v1alpha1.Describe + want *v1alpha1.Command + wantErr bool + }{{ + name: "nil", + collector: nil, + want: nil, + wantErr: false, + }, { + name: "empty", + collector: &v1alpha1.Describe{}, + wantErr: true, + }, { + name: "without resource", + collector: &v1alpha1.Describe{ + Name: "foo", + }, + wantErr: true, + }, { + name: "with resource", + collector: &v1alpha1.Describe{ + Resource: "foos", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "-n", "$NAMESPACE"}, + }, + wantErr: false, + }, { + name: "with name", + collector: &v1alpha1.Describe{ + Resource: "foos", + Name: "foo", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "foo", "-n", "$NAMESPACE"}, + }, + wantErr: false, + }, { + name: "with namespace", + collector: &v1alpha1.Describe{ + Resource: "foos", + Namespace: "bar", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "-n", "bar"}, + }, + wantErr: false, + }, { + name: "with name and namespace", + collector: &v1alpha1.Describe{ + Resource: "foos", + Name: "foo", + Namespace: "bar", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "foo", "-n", "bar"}, + }, + wantErr: false, + }, { + name: "with selector", + collector: &v1alpha1.Describe{ + Resource: "foos", + Selector: "foo=bar", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "-l", "foo=bar", "-n", "$NAMESPACE"}, + }, + wantErr: false, + }, { + name: "with name and selector", + collector: &v1alpha1.Describe{ + Resource: "foos", + Name: "foo", + Selector: "foo=bar", + }, + wantErr: true, + }, { + name: "with namespace and selector", + collector: &v1alpha1.Describe{ + Resource: "foos", + Namespace: "bar", + Selector: "foo=bar", + }, + want: &v1alpha1.Command{ + Entrypoint: "kubectl", + Args: []string{"describe", "foos", "-l", "foo=bar", "-n", "bar"}, + }, + wantErr: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Describe(tt.collector) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} diff --git a/pkg/runner/processors/step.go b/pkg/runner/processors/step.go index 23a68c084..280b738be 100644 --- a/pkg/runner/processors/step.go +++ b/pkg/runner/processors/step.go @@ -205,6 +205,12 @@ func (p *stepProcessor) catchOperations(ctx context.Context, handlers ...v1alpha return nil, err } register(p.commandOperation(ctx, *cmd)) + } else if handler.Describe != nil { + cmd, err := collect.Describe(handler.Describe) + if err != nil { + return nil, err + } + register(p.commandOperation(ctx, *cmd)) } else if handler.Command != nil { register(p.commandOperation(ctx, *handler.Command)) } else if handler.Script != nil { @@ -239,6 +245,12 @@ func (p *stepProcessor) finallyOperations(ctx context.Context, handlers ...v1alp return nil, err } register(p.commandOperation(ctx, *cmd)) + } else if handler.Describe != nil { + cmd, err := collect.Describe(handler.Describe) + if err != nil { + return nil, err + } + register(p.commandOperation(ctx, *cmd)) } else if handler.Command != nil { register(p.commandOperation(ctx, *handler.Command)) } else if handler.Script != nil { diff --git a/pkg/validation/catch.go b/pkg/validation/catch.go index 43cff9edf..4a8e72e7b 100644 --- a/pkg/validation/catch.go +++ b/pkg/validation/catch.go @@ -16,6 +16,9 @@ func ValidateCatch(path *field.Path, obj v1alpha1.Catch) field.ErrorList { if obj.Events != nil { count++ } + if obj.Describe != nil { + count++ + } if obj.Command != nil { count++ } @@ -34,6 +37,7 @@ func ValidateCatch(path *field.Path, obj v1alpha1.Catch) field.ErrorList { errs = append(errs, ValidateEvents(path.Child("events"), obj.Events)...) errs = append(errs, ValidateCommand(path.Child("command"), obj.Command)...) errs = append(errs, ValidateScript(path.Child("script"), obj.Script)...) + errs = append(errs, ValidateDescribe(path.Child("describe"), obj.Describe)...) } return errs } diff --git a/pkg/validation/catch_test.go b/pkg/validation/catch_test.go index 1ef48dcf3..4c3e5e03d 100644 --- a/pkg/validation/catch_test.go +++ b/pkg/validation/catch_test.go @@ -28,6 +28,9 @@ func TestValidateCatch(t *testing.T) { exampleSleep := &v1alpha1.Sleep{ Duration: metav1.Duration{Duration: 5 * time.Second}, } + exampleDescribe := &v1alpha1.Describe{ + Resource: "pods", + } tests := []struct { name string input v1alpha1.Catch @@ -77,6 +80,12 @@ func TestValidateCatch(t *testing.T) { Sleep: exampleSleep, }, expectErr: false, + }, { + name: "Only Describe statement provided", + input: v1alpha1.Catch{ + Describe: exampleDescribe, + }, + expectErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/validation/describe.go b/pkg/validation/describe.go new file mode 100644 index 000000000..62771df7f --- /dev/null +++ b/pkg/validation/describe.go @@ -0,0 +1,19 @@ +package validation + +import ( + "github.com/kyverno/chainsaw/pkg/apis/v1alpha1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func ValidateDescribe(path *field.Path, obj *v1alpha1.Describe) field.ErrorList { + var errs field.ErrorList + if obj != nil { + if obj.Resource == "" { + errs = append(errs, field.Invalid(path.Child("resource"), obj, "a resource must be specified")) + } + if obj.Name != "" && obj.Selector != "" { + errs = append(errs, field.Invalid(path, obj, "a name or label selector must be specified (found both)")) + } + } + return errs +} diff --git a/pkg/validation/describe_test.go b/pkg/validation/describe_test.go new file mode 100644 index 000000000..f48166b1e --- /dev/null +++ b/pkg/validation/describe_test.go @@ -0,0 +1,63 @@ +package validation + +import ( + "testing" + + v1alpha1 "github.com/kyverno/chainsaw/pkg/apis/v1alpha1" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestValidateDescribe(t *testing.T) { + tests := []struct { + name string + input *v1alpha1.Describe + expectErr bool + errMsg string + }{{ + name: "No resource provided", + input: &v1alpha1.Describe{}, + expectErr: true, + errMsg: "a resource must be specified", + }, { + name: "Neither Name nor Selector provided", + input: &v1alpha1.Describe{ + Resource: "pods", + }, + expectErr: false, + }, { + name: "Both Name and Selector provided", + input: &v1alpha1.Describe{ + Resource: "pods", + Name: "example-name", + Selector: "example-selector", + }, + expectErr: true, + errMsg: "a name or label selector must be specified (found both)", + }, { + name: "Only Name provided", + input: &v1alpha1.Describe{ + Resource: "pods", + Name: "example-name", + }, + expectErr: false, + }, { + name: "Only Selector provided", + input: &v1alpha1.Describe{ + Resource: "pods", + Selector: "example-selector", + }, + expectErr: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := ValidateDescribe(field.NewPath("testPath"), tt.input) + if tt.expectErr { + assert.NotEmpty(t, errs) + assert.Contains(t, errs.ToAggregate().Error(), tt.errMsg) + } else { + assert.Empty(t, errs) + } + }) + } +} diff --git a/pkg/validation/finally.go b/pkg/validation/finally.go index 8d62642a6..f73ced591 100644 --- a/pkg/validation/finally.go +++ b/pkg/validation/finally.go @@ -16,6 +16,9 @@ func ValidateFinally(path *field.Path, obj v1alpha1.Finally) field.ErrorList { if obj.Events != nil { count++ } + if obj.Describe != nil { + count++ + } if obj.Command != nil { count++ } @@ -34,6 +37,7 @@ func ValidateFinally(path *field.Path, obj v1alpha1.Finally) field.ErrorList { errs = append(errs, ValidateEvents(path.Child("events"), obj.Events)...) errs = append(errs, ValidateCommand(path.Child("command"), obj.Command)...) errs = append(errs, ValidateScript(path.Child("script"), obj.Script)...) + errs = append(errs, ValidateDescribe(path.Child("describe"), obj.Describe)...) } return errs } diff --git a/pkg/validation/finally_test.go b/pkg/validation/finally_test.go index cf52092ee..4b7eb772b 100644 --- a/pkg/validation/finally_test.go +++ b/pkg/validation/finally_test.go @@ -26,6 +26,9 @@ func TestValidateFinally(t *testing.T) { exampleSleep := &v1alpha1.Sleep{ Duration: metav1.Duration{Duration: 5 * time.Second}, } + exampleDescribe := &v1alpha1.Describe{ + Resource: "pods", + } tests := []struct { name string input v1alpha1.Finally @@ -75,6 +78,12 @@ func TestValidateFinally(t *testing.T) { Sleep: exampleSleep, }, expectErr: false, + }, { + name: "Only Describe statement provided", + input: v1alpha1.Finally{ + Describe: exampleDescribe, + }, + expectErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/validation/pod_logs_test.go b/pkg/validation/pod_logs_test.go index f6fa72e93..666388c25 100644 --- a/pkg/validation/pod_logs_test.go +++ b/pkg/validation/pod_logs_test.go @@ -14,43 +14,37 @@ func TestValidatePodLogs(t *testing.T) { input *v1alpha1.PodLogs expectErr bool errMsg string - }{ - { - name: "Neither Name nor Selector provided", - input: &v1alpha1.PodLogs{ - Name: "", - Selector: "", - }, - expectErr: true, - errMsg: "name or label selector must be specified", + }{{ + name: "Neither Name nor Selector provided", + input: &v1alpha1.PodLogs{ + Name: "", + Selector: "", }, - { - name: "Both Name and Selector provided", - input: &v1alpha1.PodLogs{ - Name: "example-name", - Selector: "example-selector", - }, - expectErr: true, - errMsg: "a name or label selector must be specified (found both)", + expectErr: true, + errMsg: "name or label selector must be specified", + }, { + name: "Both Name and Selector provided", + input: &v1alpha1.PodLogs{ + Name: "example-name", + Selector: "example-selector", }, - { - name: "Only Name provided", - input: &v1alpha1.PodLogs{ - Name: "example-name", - Selector: "", - }, - expectErr: false, + expectErr: true, + errMsg: "a name or label selector must be specified (found both)", + }, { + name: "Only Name provided", + input: &v1alpha1.PodLogs{ + Name: "example-name", + Selector: "", }, - { - name: "Only Selector provided", - input: &v1alpha1.PodLogs{ - Name: "", - Selector: "example-selector", - }, - expectErr: false, + expectErr: false, + }, { + name: "Only Selector provided", + input: &v1alpha1.PodLogs{ + Name: "", + Selector: "example-selector", }, - } - + expectErr: false, + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { errs := ValidatePodLogs(field.NewPath("testPath"), tt.input) diff --git a/testdata/e2e/examples/catch/README.md b/testdata/e2e/examples/catch/README.md index a11c6f1a7..55765aad9 100644 --- a/testdata/e2e/examples/catch/README.md +++ b/testdata/e2e/examples/catch/README.md @@ -6,7 +6,7 @@ | # | Name | Try | Catch | Finally | |:-:|---|:-:|:-:|:-:| -| 1 | [step-1](#step-step-1) | 2 | 1 | 0 | +| 1 | [step-1](#step-step-1) | 2 | 2 | 0 | ## Step: `step-1` @@ -24,3 +24,4 @@ | # | Operation | Description | |:-:|---|---| | 1 | `events` | *No description* | +| 2 | `describe` | *No description* | diff --git a/testdata/e2e/examples/catch/chainsaw-test.yaml b/testdata/e2e/examples/catch/chainsaw-test.yaml index dfa95a38f..3f27f956c 100644 --- a/testdata/e2e/examples/catch/chainsaw-test.yaml +++ b/testdata/e2e/examples/catch/chainsaw-test.yaml @@ -24,3 +24,5 @@ spec: foo: bar catch: - events: {} + - describe: + resource: crds diff --git a/website/docs/apis/chainsaw.v1alpha1.md b/website/docs/apis/chainsaw.v1alpha1.md index c7d527301..b7e7930d5 100644 --- a/website/docs/apis/chainsaw.v1alpha1.md +++ b/website/docs/apis/chainsaw.v1alpha1.md @@ -96,6 +96,7 @@ during the testing process.

| `description` | `string` | | |

Description contains a description of the operation.

| | `podLogs` | [`PodLogs`](#chainsaw-kyverno-io-v1alpha1-PodLogs) | | |

PodLogs determines the pod logs collector to execute.

| | `events` | [`Events`](#chainsaw-kyverno-io-v1alpha1-Events) | | |

Events determines the events collector to execute.

| +| `describe` | [`Describe`](#chainsaw-kyverno-io-v1alpha1-Describe) | | |

Describe determines the resource describe collector to execute.

| | `command` | [`Command`](#chainsaw-kyverno-io-v1alpha1-Command) | | |

Command defines a command to run.

| | `script` | [`Script`](#chainsaw-kyverno-io-v1alpha1-Script) | | |

Script defines a script to run.

| | `sleep` | [`Sleep`](#chainsaw-kyverno-io-v1alpha1-Sleep) | | |

Sleep defines zzzz.

| @@ -177,6 +178,24 @@ If a resource already exists in the cluster it will fail.

| `ref` | [`ObjectReference`](#chainsaw-kyverno-io-v1alpha1-ObjectReference) | :white_check_mark: | |

ObjectReference determines objects to be deleted.

| | `expect` | [`[]Expectation`](#chainsaw-kyverno-io-v1alpha1-Expectation) | | |

Expect defines a list of matched checks to validate the operation outcome.

| +## `Describe` {#chainsaw-kyverno-io-v1alpha1-Describe} + +**Appears in:** + +- [Catch](#chainsaw-kyverno-io-v1alpha1-Catch) +- [Finally](#chainsaw-kyverno-io-v1alpha1-Finally) + +

Describe defines how to describe resources.

+ + +| Field | Type | Required | Inline | Description | +|---|---|---|---|---| +| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | | |

Timeout for the operation. Overrides the global timeout set in the Configuration.

| +| `resource` | `string` | :white_check_mark: | |

Resource type.

| +| `namespace` | `string` | | |

Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

| +| `name` | `string` | | |

Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

| +| `selector` | `string` | | |

Selector defines labels selector.

| + ## `Error` {#chainsaw-kyverno-io-v1alpha1-Error} **Appears in:** @@ -284,6 +303,7 @@ with a match filter to determine if the verification should be considered.

| `description` | `string` | | |

Description contains a description of the operation.

| | `podLogs` | [`PodLogs`](#chainsaw-kyverno-io-v1alpha1-PodLogs) | | |

PodLogs determines the pod logs collector to execute.

| | `events` | [`Events`](#chainsaw-kyverno-io-v1alpha1-Events) | | |

Events determines the events collector to execute.

| +| `describe` | [`Describe`](#chainsaw-kyverno-io-v1alpha1-Describe) | | |

Describe determines the resource describe collector to execute.

| | `command` | [`Command`](#chainsaw-kyverno-io-v1alpha1-Command) | | |

Command defines a command to run.

| | `script` | [`Script`](#chainsaw-kyverno-io-v1alpha1-Script) | | |

Script defines a script to run.

| | `sleep` | [`Sleep`](#chainsaw-kyverno-io-v1alpha1-Sleep) | | |

Sleep defines zzzz.

|