Repository: kyverno/chainsaw
Pull request #222 (2023-10-27 00:14:59)

refactor: script and commands
Refactor script and commands.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kyverno/chainsaw/pull/222?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) Report
> Merging [#222](https://app.codecov.io/gh/kyverno/chainsaw/pull/222?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) (b1d93a2) into [main](https://app.codecov.io/gh/kyverno/chainsaw/commit/fb71bfae7489a6e1c9bdd28ed306317f2c562864?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) (fb71bfa) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main     #222   +/-   ##
=======================================
  Coverage   56.29%   56.29%           
=======================================
  Files          15       15           
  Lines         627      627           
=======================================
  Hits          353      353           
  Misses        238      238           
  Partials       36       36           
```



:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno)


Patch:
diff --git a/pkg/apis/v1alpha1/collect.go b/pkg/apis/v1alpha1/collect.go
new file mode 100644
index 00000000..7773be41
--- /dev/null
+++ b/pkg/apis/v1alpha1/collect.go
@@ -0,0 +1,12 @@
+package v1alpha1
+
+// Collect defines a set of collectors.
+type Collect struct {
+	// PodLogs determines the pod logs collector to execute.
+	// +optional
+	PodLogs *PodLogs `json:"podLogs,omitempty"`
+
+	// Events determines the events collector to execute.
+	// +optional
+	Events *Events `json:"events,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/collector.go b/pkg/apis/v1alpha1/collector.go
deleted file mode 100644
index 6fdf68f4..00000000
--- a/pkg/apis/v1alpha1/collector.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package v1alpha1
-
-// Collector defines a set of collectors.
-type Collector struct {
-	// PodLogs determines the pod logs collector to execute.
-	// +optional
-	PodLogs *PodLogsCollector `json:"podLogs,omitempty"`
-
-	// Events determines the events collector to execute.
-	// +optional
-	Events *EventsCollector `json:"events,omitempty"`
-}
diff --git a/pkg/apis/v1alpha1/command.go b/pkg/apis/v1alpha1/command.go
index 253692f7..213ac3cb 100644
--- a/pkg/apis/v1alpha1/command.go
+++ b/pkg/apis/v1alpha1/command.go
@@ -1,25 +1,12 @@
 package v1alpha1
 
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
 // Command describes a command to run as a part of a test step.
 type Command struct {
-	// Timeout for the command. Overrides the global timeout set in the Configuration.
-	// +optional
-	Timeout *metav1.Duration `json:"timeout,omitempty"`
-
-	// Command is the command and argument to run as a string.
-	// +optional
-	Command string `json:"command,omitempty"`
-
-	// SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.
+	// EntryPoint is the command entry point to run.
 	// +optional
-	SkipLogOutput bool `json:"skipLogOutput,omitempty"`
+	EntryPoint string `json:"entryPoint,omitempty"`
 
-	// ContinueOnError determines whether a test should continue or not in case the operation was not successful.
-	// Even if the test continues executing, it will still be reported as failed.
+	// Args is the command arguments.
 	// +optional
-	ContinueOnError *bool `json:"continueOnError,omitempty"`
+	Args []string `json:"args,omitempty"`
 }
diff --git a/pkg/apis/v1alpha1/events_collector.go b/pkg/apis/v1alpha1/events.go
similarity index 85%
rename from pkg/apis/v1alpha1/events_collector.go
rename to pkg/apis/v1alpha1/events.go
index 0a872603..8bcdc0f6 100644
--- a/pkg/apis/v1alpha1/events_collector.go
+++ b/pkg/apis/v1alpha1/events.go
@@ -1,7 +1,7 @@
 package v1alpha1
 
-// EventsCollector defines how to collects events.
-type EventsCollector struct {
+// Events defines how to collects events.
+type Events struct {
 	// Namespace of the referent.
 	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
 	// +optional
diff --git a/pkg/apis/v1alpha1/exec.go b/pkg/apis/v1alpha1/exec.go
new file mode 100644
index 00000000..db6546da
--- /dev/null
+++ b/pkg/apis/v1alpha1/exec.go
@@ -0,0 +1,20 @@
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// Exec describes a command or script.
+type Exec struct {
+	// Timeout for the command. Overrides the global timeout set in the Configuration.
+	// +optional
+	Timeout *metav1.Duration `json:"timeout,omitempty"`
+
+	// Command defines a command to run.
+	// +optional
+	Command *Command `json:"command,omitempty"`
+
+	// Script defines a script to run.
+	// +optional
+	Script *Script `json:"script,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/exec_operation.go b/pkg/apis/v1alpha1/exec_operation.go
new file mode 100644
index 00000000..7ff30883
--- /dev/null
+++ b/pkg/apis/v1alpha1/exec_operation.go
@@ -0,0 +1,17 @@
+package v1alpha1
+
+// Exec describes a command and/or script operation.
+type ExecOperation struct {
+	// Exec defines the command and/or script.
+	// +optional
+	Exec `json:",inline"`
+
+	// SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.
+	// +optional
+	SkipLogOutput bool `json:"skipLogOutput,omitempty"`
+
+	// ContinueOnError determines whether a test should continue or not in case the operation was not successful.
+	// Even if the test continues executing, it will still be reported as failed.
+	// +optional
+	ContinueOnError *bool `json:"continueOnError,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/on_failure.go b/pkg/apis/v1alpha1/on_failure.go
index b848d54f..ef506acf 100644
--- a/pkg/apis/v1alpha1/on_failure.go
+++ b/pkg/apis/v1alpha1/on_failure.go
@@ -4,5 +4,9 @@ package v1alpha1
 type OnFailure struct {
 	// Collect define the collectors to run.
 	// +optional
-	Collect []Collector `json:"collect,omitempty"`
+	Collect *Collect `json:"collect,omitempty"`
+
+	// Exec define the commands and/or scripts to run.
+	// +optional
+	Exec *Exec `json:"exec,omitempty"`
 }
diff --git a/pkg/apis/v1alpha1/pod_logs_collector.go b/pkg/apis/v1alpha1/pod_logs.go
similarity index 91%
rename from pkg/apis/v1alpha1/pod_logs_collector.go
rename to pkg/apis/v1alpha1/pod_logs.go
index 34939140..c9dccd63 100644
--- a/pkg/apis/v1alpha1/pod_logs_collector.go
+++ b/pkg/apis/v1alpha1/pod_logs.go
@@ -1,7 +1,7 @@
 package v1alpha1
 
-// PodLogsCollector defines how to collects pod logs.
-type PodLogsCollector struct {
+// PodLogs defines how to collects pod logs.
+type PodLogs struct {
 	// Namespace of the referent.
 	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
 	// +optional
diff --git a/pkg/apis/v1alpha1/script.go b/pkg/apis/v1alpha1/script.go
index 7f020736..5bd70506 100644
--- a/pkg/apis/v1alpha1/script.go
+++ b/pkg/apis/v1alpha1/script.go
@@ -1,25 +1,8 @@
 package v1alpha1
 
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
 // Script describes a script to run as a part of a test step.
 type Script struct {
-	// Timeout for the command. Overrides the global timeout set in the Configuration.
-	// +optional
-	Timeout *metav1.Duration `json:"timeout,omitempty"`
-
-	// Script defines a shell script (run with "sh -c ...").
-	// +optional
-	Script string `json:"script,omitempty"`
-
-	// SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.
-	// +optional
-	SkipLogOutput bool `json:"skipLogOutput,omitempty"`
-
-	// ContinueOnError determines whether a test should continue or not in case the operation was not successful.
-	// Even if the test continues executing, it will still be reported as failed.
+	// Content defines a shell script (run with "sh -c ...").
 	// +optional
-	ContinueOnError *bool `json:"continueOnError,omitempty"`
+	Content string `json:"content,omitempty"`
 }
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index 3214934c..c36bf3a1 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -71,27 +71,27 @@ func (in *Assert) DeepCopy() *Assert {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Collector) DeepCopyInto(out *Collector) {
+func (in *Collect) DeepCopyInto(out *Collect) {
 	*out = *in
 	if in.PodLogs != nil {
 		in, out := &in.PodLogs, &out.PodLogs
-		*out = new(PodLogsCollector)
+		*out = new(PodLogs)
 		(*in).DeepCopyInto(*out)
 	}
 	if in.Events != nil {
 		in, out := &in.Events, &out.Events
-		*out = new(EventsCollector)
+		*out = new(Events)
 		**out = **in
 	}
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collector.
-func (in *Collector) DeepCopy() *Collector {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
+func (in *Collect) DeepCopy() *Collect {
 	if in == nil {
 		return nil
 	}
-	out := new(Collector)
+	out := new(Collect)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -99,15 +99,10 @@ func (in *Collector) DeepCopy() *Collector {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Command) DeepCopyInto(out *Command) {
 	*out = *in
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(v1.Duration)
-		**out = **in
-	}
-	if in.ContinueOnError != nil {
-		in, out := &in.ContinueOnError, &out.ContinueOnError
-		*out = new(bool)
-		**out = **in
+	if in.Args != nil {
+		in, out := &in.Args, &out.Args
+		*out = make([]string, len(*in))
+		copy(*out, *in)
 	}
 	return
 }
@@ -225,17 +220,70 @@ func (in *Error) DeepCopy() *Error {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *EventsCollector) DeepCopyInto(out *EventsCollector) {
+func (in *Events) DeepCopyInto(out *Events) {
 	*out = *in
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsCollector.
-func (in *EventsCollector) DeepCopy() *EventsCollector {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Events.
+func (in *Events) DeepCopy() *Events {
 	if in == nil {
 		return nil
 	}
-	out := new(EventsCollector)
+	out := new(Events)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Exec) DeepCopyInto(out *Exec) {
+	*out = *in
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Command != nil {
+		in, out := &in.Command, &out.Command
+		*out = new(Command)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Script != nil {
+		in, out := &in.Script, &out.Script
+		*out = new(Script)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Exec.
+func (in *Exec) DeepCopy() *Exec {
+	if in == nil {
+		return nil
+	}
+	out := new(Exec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExecOperation) DeepCopyInto(out *ExecOperation) {
+	*out = *in
+	in.Exec.DeepCopyInto(&out.Exec)
+	if in.ContinueOnError != nil {
+		in, out := &in.ContinueOnError, &out.ContinueOnError
+		*out = new(bool)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecOperation.
+func (in *ExecOperation) DeepCopy() *ExecOperation {
+	if in == nil {
+		return nil
+	}
+	out := new(ExecOperation)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -301,10 +349,13 @@ func (in *OnFailure) DeepCopyInto(out *OnFailure) {
 	*out = *in
 	if in.Collect != nil {
 		in, out := &in.Collect, &out.Collect
-		*out = make([]Collector, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
+		*out = new(Collect)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Exec != nil {
+		in, out := &in.Exec, &out.Exec
+		*out = new(Exec)
+		(*in).DeepCopyInto(*out)
 	}
 	return
 }
@@ -320,7 +371,7 @@ func (in *OnFailure) DeepCopy() *OnFailure {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *PodLogsCollector) DeepCopyInto(out *PodLogsCollector) {
+func (in *PodLogs) DeepCopyInto(out *PodLogs) {
 	*out = *in
 	if in.Tail != nil {
 		in, out := &in.Tail, &out.Tail
@@ -330,12 +381,12 @@ func (in *PodLogsCollector) DeepCopyInto(out *PodLogsCollector) {
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLogsCollector.
-func (in *PodLogsCollector) DeepCopy() *PodLogsCollector {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLogs.
+func (in *PodLogs) DeepCopy() *PodLogs {
 	if in == nil {
 		return nil
 	}
-	out := new(PodLogsCollector)
+	out := new(PodLogs)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -343,16 +394,6 @@ func (in *PodLogsCollector) DeepCopy() *PodLogsCollector {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Script) DeepCopyInto(out *Script) {
 	*out = *in
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(v1.Duration)
-		**out = **in
-	}
-	if in.ContinueOnError != nil {
-		in, out := &in.ContinueOnError, &out.ContinueOnError
-		*out = new(bool)
-		**out = **in
-	}
 	return
 }
 
@@ -521,17 +562,19 @@ func (in *TestStepSpec) DeepCopyInto(out *TestStepSpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	if in.Command != nil {
-		in, out := &in.Command, &out.Command
-		*out = make([]Command, len(*in))
+	if in.Exec != nil {
+		in, out := &in.Exec, &out.Exec
+		*out = make([]ExecOperation, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
 	if in.OnFailure != nil {
 		in, out := &in.OnFailure, &out.OnFailure
-		*out = new(OnFailure)
-		(*in).DeepCopyInto(*out)
+		*out = make([]OnFailure, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	return
 }
diff --git a/pkg/runner/collect.go b/pkg/runner/collect.go
index b5d6aa50..055ce539 100644
--- a/pkg/runner/collect.go
+++ b/pkg/runner/collect.go
@@ -2,62 +2,63 @@ package runner
 
 import (
 	"fmt"
-	"strings"
 
 	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
 )
 
-func collectors(collector v1alpha1.Collector) []string {
-	var commands []string
+func collect(collector v1alpha1.Collect) []v1alpha1.Command {
+	var commands []v1alpha1.Command
 	if collector.Events != nil {
-		commands = append(commands, eventsCollector(*collector.Events))
+		commands = append(commands, events(*collector.Events))
 	}
 	if collector.PodLogs != nil {
-		commands = append(commands, podLogsCollector(*collector.PodLogs))
+		commands = append(commands, podLogs(*collector.PodLogs))
 	}
 	return commands
 }
 
-func eventsCollector(collector v1alpha1.EventsCollector) string {
-	var b strings.Builder
-	b.WriteString("kubectl get events")
+func events(collector v1alpha1.Events) v1alpha1.Command {
+	cmd := v1alpha1.Command{
+		EntryPoint: "kubectl",
+		Args:       []string{"get", "events"},
+	}
 	if collector.Name != "" {
-		fmt.Fprintf(&b, " %s", collector.Name)
+		cmd.Args = append(cmd.Args, collector.Name)
 	}
 	if collector.Selector != "" {
-		fmt.Fprintf(&b, " -l %s", collector.Selector)
+		cmd.Args = append(cmd.Args, "-l", collector.Selector)
 	}
-	ns := collector.Namespace
-	// TODO
+	namespace := collector.Namespace
 	if collector.Namespace == "" {
-		ns = "$NAMESPACE"
+		namespace = "$NAMESPACE"
 	}
-	fmt.Fprintf(&b, " -n %s", ns)
-	return b.String()
+	cmd.Args = append(cmd.Args, "-n", namespace)
+	return cmd
 }
 
-func podLogsCollector(collector v1alpha1.PodLogsCollector) string {
-	var b strings.Builder
-	b.WriteString("kubectl logs --prefix")
+func podLogs(collector v1alpha1.PodLogs) v1alpha1.Command {
+	cmd := v1alpha1.Command{
+		EntryPoint: "kubectl",
+		Args:       []string{"logs", "--prefix"},
+	}
 	if collector.Name != "" {
-		fmt.Fprintf(&b, " %s", collector.Name)
+		cmd.Args = append(cmd.Args, collector.Name)
 	}
 	if collector.Selector != "" {
-		fmt.Fprintf(&b, " -l %s", collector.Selector)
+		cmd.Args = append(cmd.Args, "-l", collector.Selector)
 	}
-	ns := collector.Namespace
-	// TODO
+	namespace := collector.Namespace
 	if collector.Namespace == "" {
-		ns = "$NAMESPACE"
+		namespace = "$NAMESPACE"
 	}
-	fmt.Fprintf(&b, " -n %s", ns)
-	if len(collector.Container) > 0 {
-		fmt.Fprintf(&b, " -c %s", collector.Container)
+	cmd.Args = append(cmd.Args, "-n", namespace)
+	if collector.Container == "" {
+		cmd.Args = append(cmd.Args, "--all-containers")
 	} else {
-		b.WriteString(" --all-containers")
+		cmd.Args = append(cmd.Args, "-c", collector.Container)
 	}
 	if collector.Tail != nil {
-		fmt.Fprintf(&b, " --tail=%d", *collector.Tail)
+		cmd.Args = append(cmd.Args, "--tail", fmt.Sprint(*collector.Tail))
 	}
-	return b.String()
+	return cmd
 }
diff --git a/pkg/runner/operations/command.go b/pkg/runner/operations/command.go
deleted file mode 100644
index 514f6147..00000000
--- a/pkg/runner/operations/command.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package operations
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"os/exec"
-	"strings"
-
-	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
-	"github.com/kyverno/chainsaw/pkg/runner/logging"
-)
-
-func Command(ctx context.Context, logger logging.Logger, command v1alpha1.Command, namespace string) (*CommandOutput, error) {
-	logger = logger.WithName("CMD   ")
-	logger.Log(command.Command, "...")
-	args := strings.Fields(command.Command)
-	cmd := exec.CommandContext(ctx, args[0], args[1:]...) //nolint:gosec
-	var output *CommandOutput
-	if !command.SkipLogOutput {
-		output = &CommandOutput{}
-		cmd.Stdout = &output.stdout
-		cmd.Stderr = &output.stderr
-	}
-	cwd, err := os.Getwd()
-	if err != nil {
-		return nil, fmt.Errorf("failed to get current working directory (%w)", err)
-	}
-	env := os.Environ()
-	env = append(env, fmt.Sprintf("NAMESPACE=%s", namespace))
-	env = append(env, fmt.Sprintf("PATH=%s/bin/:%s", cwd, os.Getenv("PATH")))
-	// TODO
-	// env = append(env, fmt.Sprintf("KUBECONFIG=%s/bin/:%s", cwd, os.Getenv("PATH")))
-	cmd.Env = env
-	return output, cmd.Run()
-}
diff --git a/pkg/runner/operations/exec.go b/pkg/runner/operations/exec.go
new file mode 100644
index 00000000..1686e5de
--- /dev/null
+++ b/pkg/runner/operations/exec.go
@@ -0,0 +1,60 @@
+package operations
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+
+	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
+	"github.com/kyverno/chainsaw/pkg/runner/logging"
+)
+
+func Exec(ctx context.Context, logger logging.Logger, exec v1alpha1.Exec, namespace string) (CommandOutput, error) {
+	if exec.Command != nil {
+		return command(ctx, logger, *exec.Command, namespace)
+	} else if exec.Script != nil {
+		return script(ctx, logger, *exec.Script, namespace)
+	}
+	return CommandOutput{}, nil
+}
+
+func command(ctx context.Context, logger logging.Logger, command v1alpha1.Command, namespace string) (CommandOutput, error) {
+	cmd := exec.CommandContext(ctx, command.EntryPoint, command.Args...) //nolint:gosec
+	logger = logger.WithName("CMD   ")
+	logger.Log(cmd, "...")
+	var output CommandOutput
+	cmd.Stdout = &output.stdout
+	cmd.Stderr = &output.stderr
+	cwd, err := os.Getwd()
+	if err != nil {
+		return output, fmt.Errorf("failed to get current working directory (%w)", err)
+	}
+	env := os.Environ()
+	env = append(env, fmt.Sprintf("NAMESPACE=%s", namespace))
+	env = append(env, fmt.Sprintf("PATH=%s/bin/:%s", cwd, os.Getenv("PATH")))
+	// TODO
+	// env = append(env, fmt.Sprintf("KUBECONFIG=%s/bin/:%s", cwd, os.Getenv("PATH")))
+	cmd.Env = env
+	return output, cmd.Run()
+}
+
+func script(ctx context.Context, logger logging.Logger, script v1alpha1.Script, namespace string) (CommandOutput, error) {
+	logger = logger.WithName("SCRIPT")
+	logger.Log("executing...")
+	cmd := exec.CommandContext(ctx, "sh", "-c", script.Content) //nolint:gosec
+	var output CommandOutput
+	cmd.Stdout = &output.stdout
+	cmd.Stderr = &output.stderr
+	cwd, err := os.Getwd()
+	if err != nil {
+		return output, fmt.Errorf("failed to get current working directory (%w)", err)
+	}
+	env := os.Environ()
+	env = append(env, fmt.Sprintf("NAMESPACE=%s", namespace))
+	env = append(env, fmt.Sprintf("PATH=%s/bin/:%s", cwd, os.Getenv("PATH")))
+	// TODO
+	// env = append(env, fmt.Sprintf("KUBECONFIG=%s/bin/:%s", cwd, os.Getenv("PATH")))
+	cmd.Env = env
+	return output, cmd.Run()
+}
diff --git a/pkg/runner/step.go b/pkg/runner/step.go
index 47690f48..0bcb0fc2 100644
--- a/pkg/runner/step.go
+++ b/pkg/runner/step.go
@@ -32,21 +32,22 @@ func executeStep(t *testing.T, logger logging.Logger, ctx Context, basePath stri
 				if step.Spec.OnFailure == nil {
 					return
 				}
-				for _, collector := range step.Spec.OnFailure.Collect {
-					for _, collector := range collectors(collector) {
-						cmd := v1alpha1.Command{
-							Command: collector,
-						}
-						output, err := operations.Command(context.Background(), logger, cmd, ctx.namespacer.GetNamespace())
-						if err != nil {
-							logger.Log(err)
-						}
-						if output != nil {
+				for _, failures := range step.Spec.OnFailure {
+					if failures.Collect != nil {
+						for _, collector := range collect(*failures.Collect) {
+							cmd := collector
+							exec := v1alpha1.Exec{
+								Command: &cmd,
+							}
+							output, err := operations.Exec(context.Background(), logger, exec, ctx.namespacer.GetNamespace())
+							if err != nil {
+								logger.Log(err)
+							}
 							if out := output.Out(); out != "" {
-								logger.WithName("STDOUT").Log(out)
+								logger.WithName("STDOUT").Log("\n" + out)
 							}
 							if err := output.Err(); err != "" {
-								logger.WithName("STDERR").Log(err)
+								logger.WithName("STDERR").Log("\n" + err)
 							}
 						}
 					}
@@ -85,24 +86,24 @@ func executeStep(t *testing.T, logger logging.Logger, ctx Context, basePath stri
 			})
 		}
 	}
-	for _, operation := range step.Spec.Command {
+	for _, operation := range step.Spec.Exec {
 		func() {
-			cmdCtx, cancel := timeoutCmdCtx(operation, config, test, step.Spec)
+			cmdCtx, cancel := timeoutExecCtx(operation.Exec, config, test, step.Spec)
 			defer cancel()
-			output, err := operations.Command(cmdCtx, logger, operation, ctx.namespacer.GetNamespace())
+			output, err := operations.Exec(cmdCtx, logger, operation.Exec, ctx.namespacer.GetNamespace())
 			if err != nil {
 				logger.Log(err)
 			}
-			if output != nil {
+			if !operation.SkipLogOutput {
 				if out := output.Out(); out != "" {
-					logger.WithName("STDOUT").Log(out)
+					logger.WithName("STDOUT").Log("\n" + out)
 				}
 				if err := output.Err(); err != "" {
-					logger.WithName("STDERR").Log(err)
+					logger.WithName("STDERR").Log("\n" + err)
+				}
+				if err != nil {
+					fail(t, operation.ContinueOnError)
 				}
-			}
-			if err != nil {
-				fail(t, operation.ContinueOnError)
 			}
 		}()
 	}
diff --git a/pkg/runner/timeout.go b/pkg/runner/timeout.go
index de79c401..6cd0c844 100644
--- a/pkg/runner/timeout.go
+++ b/pkg/runner/timeout.go
@@ -30,9 +30,9 @@ func timeoutCtx(config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step
 	return ctx, cancelNoOp
 }
 
-func timeoutCmdCtx(cmd v1alpha1.Command, config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step v1alpha1.TestStepSpec) (context.Context, context.CancelFunc) {
-	if cmd.Timeout != nil && cmd.Timeout.Abs() > 0 {
-		return context.WithTimeout(context.Background(), cmd.Timeout.Duration)
+func timeoutExecCtx(exec v1alpha1.Exec, config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step v1alpha1.TestStepSpec) (context.Context, context.CancelFunc) {
+	if exec.Timeout != nil && exec.Timeout.Abs() > 0 {
+		return context.WithTimeout(context.Background(), exec.Timeout.Duration)
 	} else {
 		return timeoutCtx(config, test, step)
 	}
diff --git a/website/docs/apis/chainsaw.v1alpha1.md b/website/docs/apis/chainsaw.v1alpha1.md
index 39fad290..d3fc3019 100644
--- a/website/docs/apis/chainsaw.v1alpha1.md
+++ b/website/docs/apis/chainsaw.v1alpha1.md
@@ -80,35 +80,33 @@ during the testing process.</p>
 | `FileRef` | [`FileRef`](#chainsaw-kyverno-io-v1alpha1-FileRef) | :white_check_mark: | :white_check_mark: | <p>FileRef provides a reference to the file containing the assertion.</p> |
 | `continueOnError` | `bool` |  |  | <p>ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.</p> |
 
-## `Collector`     {#chainsaw-kyverno-io-v1alpha1-Collector}
+## `Collect`     {#chainsaw-kyverno-io-v1alpha1-Collect}
 
 **Appears in:**
     
 - [OnFailure](#chainsaw-kyverno-io-v1alpha1-OnFailure)
 
-<p>Collector defines a set of collectors.</p>
+<p>Collect defines a set of collectors.</p>
 
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `podLogs` | [`PodLogsCollector`](#chainsaw-kyverno-io-v1alpha1-PodLogsCollector) |  |  | <p>PodLogs determines the pod logs collector to execute.</p> |
-| `events` | [`EventsCollector`](#chainsaw-kyverno-io-v1alpha1-EventsCollector) |  |  | <p>Events determines the events collector to execute.</p> |
+| `podLogs` | [`PodLogs`](#chainsaw-kyverno-io-v1alpha1-PodLogs) |  |  | <p>PodLogs determines the pod logs collector to execute.</p> |
+| `events` | [`Events`](#chainsaw-kyverno-io-v1alpha1-Events) |  |  | <p>Events determines the events collector to execute.</p> |
 
 ## `Command`     {#chainsaw-kyverno-io-v1alpha1-Command}
 
 **Appears in:**
     
-- [TestStepSpec](#chainsaw-kyverno-io-v1alpha1-TestStepSpec)
+- [Exec](#chainsaw-kyverno-io-v1alpha1-Exec)
 
 <p>Command describes a command to run as a part of a test step.</p>
 
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>Timeout for the command. Overrides the global timeout set in the Configuration.</p> |
-| `command` | `string` |  |  | <p>Command is the command and argument to run as a string.</p> |
-| `skipLogOutput` | `bool` |  |  | <p>SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.</p> |
-| `continueOnError` | `bool` |  |  | <p>ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.</p> |
+| `entryPoint` | `string` |  |  | <p>EntryPoint is the command entry point to run.</p> |
+| `args` | `[]string` |  |  | <p>Args is the command arguments.</p> |
 
 ## `ConfigurationSpec`     {#chainsaw-kyverno-io-v1alpha1-ConfigurationSpec}
 
@@ -163,13 +161,13 @@ Instead of treating such an error as a test failure, it acknowledges it as expec
 | `FileRef` | [`FileRef`](#chainsaw-kyverno-io-v1alpha1-FileRef) | :white_check_mark: | :white_check_mark: | <p>FileRef provides a reference to the file containing the expected error.</p> |
 | `continueOnError` | `bool` |  |  | <p>ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.</p> |
 
-## `EventsCollector`     {#chainsaw-kyverno-io-v1alpha1-EventsCollector}
+## `Events`     {#chainsaw-kyverno-io-v1alpha1-Events}
 
 **Appears in:**
     
-- [Collector](#chainsaw-kyverno-io-v1alpha1-Collector)
+- [Collect](#chainsaw-kyverno-io-v1alpha1-Collect)
 
-<p>EventsCollector defines how to collects events.</p>
+<p>Events defines how to collects events.</p>
 
 
 | Field | Type | Required | Inline | Description |
@@ -178,6 +176,37 @@ Instead of treating such an error as a test failure, it acknowledges it as expec
 | `name` | `string` |  |  | <p>Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names</p> |
 | `selector` | `string` |  |  | <p>Selector defines labels selector.</p> |
 
+## `Exec`     {#chainsaw-kyverno-io-v1alpha1-Exec}
+
+**Appears in:**
+    
+- [ExecOperation](#chainsaw-kyverno-io-v1alpha1-ExecOperation)
+- [OnFailure](#chainsaw-kyverno-io-v1alpha1-OnFailure)
+
+<p>Exec describes a command or script.</p>
+
+
+| Field | Type | Required | Inline | Description |
+|---|---|---|---|---|
+| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>Timeout for the command. Overrides the global timeout set in the Configuration.</p> |
+| `command` | [`Command`](#chainsaw-kyverno-io-v1alpha1-Command) |  |  | <p>Command defines a command to run.</p> |
+| `script` | [`Script`](#chainsaw-kyverno-io-v1alpha1-Script) |  |  | <p>Script defines a script to run.</p> |
+
+## `ExecOperation`     {#chainsaw-kyverno-io-v1alpha1-ExecOperation}
+
+**Appears in:**
+    
+- [TestStepSpec](#chainsaw-kyverno-io-v1alpha1-TestStepSpec)
+
+<p>Exec describes a command and/or script operation.</p>
+
+
+| Field | Type | Required | Inline | Description |
+|---|---|---|---|---|
+| `Exec` | [`Exec`](#chainsaw-kyverno-io-v1alpha1-Exec) |  | :white_check_mark: | <p>Exec defines the command and/or script.</p> |
+| `skipLogOutput` | `bool` |  |  | <p>SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.</p> |
+| `continueOnError` | `bool` |  |  | <p>ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.</p> |
+
 ## `FileRef`     {#chainsaw-kyverno-io-v1alpha1-FileRef}
 
 **Appears in:**
@@ -238,15 +267,16 @@ For multiple objects use labels.</p>
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `collect` | [`[]Collector`](#chainsaw-kyverno-io-v1alpha1-Collector) |  |  | <p>Collect define the collectors to run.</p> |
+| `collect` | [`Collect`](#chainsaw-kyverno-io-v1alpha1-Collect) |  |  | <p>Collect define the collectors to run.</p> |
+| `exec` | [`Exec`](#chainsaw-kyverno-io-v1alpha1-Exec) |  |  | <p>Exec define the commands and/or scripts to run.</p> |
 
-## `PodLogsCollector`     {#chainsaw-kyverno-io-v1alpha1-PodLogsCollector}
+## `PodLogs`     {#chainsaw-kyverno-io-v1alpha1-PodLogs}
 
 **Appears in:**
     
-- [Collector](#chainsaw-kyverno-io-v1alpha1-Collector)
+- [Collect](#chainsaw-kyverno-io-v1alpha1-Collect)
 
-<p>PodLogsCollector defines how to collects pod logs.</p>
+<p>PodLogs defines how to collects pod logs.</p>
 
 
 | Field | Type | Required | Inline | Description |
@@ -265,6 +295,19 @@ For multiple objects use labels.</p>
     
 - [ConfigurationSpec](#chainsaw-kyverno-io-v1alpha1-ConfigurationSpec)
 
+## `Script`     {#chainsaw-kyverno-io-v1alpha1-Script}
+
+**Appears in:**
+    
+- [Exec](#chainsaw-kyverno-io-v1alpha1-Exec)
+
+<p>Script describes a script to run as a part of a test step.</p>
+
+
+| Field | Type | Required | Inline | Description |
+|---|---|---|---|---|
+| `content` | `string` |  |  | <p>Content defines a shell script (run with "sh -c ...").</p> |
+
 ## `TestSpec`     {#chainsaw-kyverno-io-v1alpha1-TestSpec}
 
 **Appears in:**
@@ -315,7 +358,7 @@ For multiple objects use labels.</p>
 | `apply` | [`[]Apply`](#chainsaw-kyverno-io-v1alpha1-Apply) |  |  | <p>Apply lists the resources that should be applied for this test step. This can include things like configuration settings or any other resources that need to be available during the test.</p> |
 | `error` | [`[]Error`](#chainsaw-kyverno-io-v1alpha1-Error) |  |  | <p>Error lists the expected errors for this test step. If any of these errors occur, the test will consider them as expected; otherwise, they will be treated as test failures.</p> |
 | `delete` | [`[]Delete`](#chainsaw-kyverno-io-v1alpha1-Delete) |  |  | <p>Delete provides a list of objects that should be deleted before this test step is executed. This helps in ensuring that the environment is set up correctly before the test step runs.</p> |
-| `command` | [`[]Command`](#chainsaw-kyverno-io-v1alpha1-Command) |  |  | <p>Command provides a list of commands that should be executed as a part of this test step.</p> |
-| `onFailure` | [`OnFailure`](#chainsaw-kyverno-io-v1alpha1-OnFailure) |  |  | <p>OnFailure defines actions to be executed in case of step failure.</p> |
+| `exec` | [`[]ExecOperation`](#chainsaw-kyverno-io-v1alpha1-ExecOperation) |  |  | <p>Exec provides a list of commands and/or scripts that should be executed as a part of this test step.</p> |
+| `onFailure` | [`[]OnFailure`](#chainsaw-kyverno-io-v1alpha1-OnFailure) |  |  | <p>OnFailure defines actions to be executed in case of step failure.</p> |
 
   
\ No newline at end of file


Test patch:
diff --git a/.crds/chainsaw.kyverno.io_tests.yaml b/.crds/chainsaw.kyverno.io_tests.yaml
index d6d773f3..d48c716c 100644
--- a/.crds/chainsaw.kyverno.io_tests.yaml
+++ b/.crds/chainsaw.kyverno.io_tests.yaml
@@ -104,34 +104,6 @@ spec:
                             - file
                             type: object
                           type: array
-                        command:
-                          description: Command provides a list of commands that should
-                            be executed as a part of this test step.
-                          items:
-                            description: Command describes a command to run as a part
-                              of a test step.
-                            properties:
-                              command:
-                                description: Command is the command and argument to
-                                  run as a string.
-                                type: string
-                              continueOnError:
-                                description: ContinueOnError determines whether a
-                                  test should continue or not in case the operation
-                                  was not successful. Even if the test continues executing,
-                                  it will still be reported as failed.
-                                type: boolean
-                              skipLogOutput:
-                                description: SkipLogOutput removes the output from
-                                  the command. Useful for sensitive logs or to reduce
-                                  noise.
-                                type: boolean
-                              timeout:
-                                description: Timeout for the command. Overrides the
-                                  global timeout set in the Configuration.
-                                type: string
-                            type: object
-                          type: array
                         delete:
                           description: Delete provides a list of objects that should
                             be deleted before this test step is executed. This helps
@@ -193,14 +165,59 @@ spec:
                             - file
                             type: object
                           type: array
+                        exec:
+                          description: Exec provides a list of commands and/or scripts
+                            that should be executed as a part of this test step.
+                          items:
+                            description: Exec describes a command and/or script operation.
+                            properties:
+                              command:
+                                description: Command defines a command to run.
+                                properties:
+                                  args:
+                                    description: Args is the command arguments.
+                                    items:
+                                      type: string
+                                    type: array
+                                  entryPoint:
+                                    description: EntryPoint is the command entry point
+                                      to run.
+                                    type: string
+                                type: object
+                              continueOnError:
+                                description: ContinueOnError determines whether a
+                                  test should continue or not in case the operation
+                                  was not successful. Even if the test continues executing,
+                                  it will still be reported as failed.
+                                type: boolean
+                              script:
+                                description: Script defines a script to run.
+                                properties:
+                                  content:
+                                    description: Content defines a shell script (run
+                                      with "sh -c ...").
+                                    type: string
+                                type: object
+                              skipLogOutput:
+                                description: SkipLogOutput removes the output from
+                                  the command. Useful for sensitive logs or to reduce
+                                  noise.
+                                type: boolean
+                              timeout:
+                                description: Timeout for the command. Overrides the
+                                  global timeout set in the Configuration.
+                                type: string
+                            type: object
+                          type: array
                         onFailure:
                           description: OnFailure defines actions to be executed in
                             case of step failure.
-                          properties:
-                            collect:
-                              description: Collect define the collectors to run.
-                              items:
-                                description: Collector defines a set of collectors.
+                          items:
+                            description: OnFailure defines actions to be executed
+                              on failure.
+                            properties:
+                              collect:
+                                description: Collect define the collectors to run.
                                 properties:
                                   events:
                                     description: Events determines the events collector
@@ -246,8 +263,38 @@ spec:
                                         type: integer
                                     type: object
                                 type: object
-                              type: array
-                          type: object
+                              exec:
+                                description: Exec define the commands and/or scripts
+                                  to run.
+                                properties:
+                                  command:
+                                    description: Command defines a command to run.
+                                    properties:
+                                      args:
+                                        description: Args is the command arguments.
+                                        items:
+                                          type: string
+                                        type: array
+                                      entryPoint:
+                                        description: EntryPoint is the command entry
+                                          point to run.
+                                        type: string
+                                    type: object
+                                  script:
+                                    description: Script defines a script to run.
+                                    properties:
+                                      content:
+                                        description: Content defines a shell script
+                                          (run with "sh -c ...").
+                                        type: string
+                                    type: object
+                                  timeout:
+                                    description: Timeout for the command. Overrides
+                                      the global timeout set in the Configuration.
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
                         skipDelete:
                           description: SkipDelete determines whether the resources
                             created by the step should be deleted after the test step
diff --git a/.crds/chainsaw.kyverno.io_teststeps.yaml b/.crds/chainsaw.kyverno.io_teststeps.yaml
index eb4a16a9..0c7b7700 100644
--- a/.crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/.crds/chainsaw.kyverno.io_teststeps.yaml
@@ -77,33 +77,6 @@ spec:
                   - file
                   type: object
                 type: array
-              command:
-                description: Command provides a list of commands that should be executed
-                  as a part of this test step.
-                items:
-                  description: Command describes a command to run as a part of a test
-                    step.
-                  properties:
-                    command:
-                      description: Command is the command and argument to run as a
-                        string.
-                      type: string
-                    continueOnError:
-                      description: ContinueOnError determines whether a test should
-                        continue or not in case the operation was not successful.
-                        Even if the test continues executing, it will still be reported
-                        as failed.
-                      type: boolean
-                    skipLogOutput:
-                      description: SkipLogOutput removes the output from the command.
-                        Useful for sensitive logs or to reduce noise.
-                      type: boolean
-                    timeout:
-                      description: Timeout for the command. Overrides the global timeout
-                        set in the Configuration.
-                      type: string
-                  type: object
-                type: array
               delete:
                 description: Delete provides a list of objects that should be deleted
                   before this test step is executed. This helps in ensuring that the
@@ -161,14 +134,56 @@ spec:
                   - file
                   type: object
                 type: array
+              exec:
+                description: Exec provides a list of commands and/or scripts that
+                  should be executed as a part of this test step.
+                items:
+                  description: Exec describes a command and/or script operation.
+                  properties:
+                    command:
+                      description: Command defines a command to run.
+                      properties:
+                        args:
+                          description: Args is the command arguments.
+                          items:
+                            type: string
+                          type: array
+                        entryPoint:
+                          description: EntryPoint is the command entry point to run.
+                          type: string
+                      type: object
+                    continueOnError:
+                      description: ContinueOnError determines whether a test should
+                        continue or not in case the operation was not successful.
+                        Even if the test continues executing, it will still be reported
+                        as failed.
+                      type: boolean
+                    script:
+                      description: Script defines a script to run.
+                      properties:
+                        content:
+                          description: Content defines a shell script (run with "sh
+                            -c ...").
+                          type: string
+                      type: object
+                    skipLogOutput:
+                      description: SkipLogOutput removes the output from the command.
+                        Useful for sensitive logs or to reduce noise.
+                      type: boolean
+                    timeout:
+                      description: Timeout for the command. Overrides the global timeout
+                        set in the Configuration.
+                      type: string
+                  type: object
+                type: array
               onFailure:
                 description: OnFailure defines actions to be executed in case of step
                   failure.
-                properties:
-                  collect:
-                    description: Collect define the collectors to run.
-                    items:
-                      description: Collector defines a set of collectors.
+                items:
+                  description: OnFailure defines actions to be executed on failure.
+                  properties:
+                    collect:
+                      description: Collect define the collectors to run.
                       properties:
                         events:
                           description: Events determines the events collector to execute.
@@ -211,8 +226,37 @@ spec:
                               type: integer
                           type: object
                       type: object
-                    type: array
-                type: object
+                    exec:
+                      description: Exec define the commands and/or scripts to run.
+                      properties:
+                        command:
+                          description: Command defines a command to run.
+                          properties:
+                            args:
+                              description: Args is the command arguments.
+                              items:
+                                type: string
+                              type: array
+                            entryPoint:
+                              description: EntryPoint is the command entry point to
+                                run.
+                              type: string
+                          type: object
+                        script:
+                          description: Script defines a script to run.
+                          properties:
+                            content:
+                              description: Content defines a shell script (run with
+                                "sh -c ...").
+                              type: string
+                          type: object
+                        timeout:
+                          description: Timeout for the command. Overrides the global
+                            timeout set in the Configuration.
+                          type: string
+                      type: object
+                  type: object
+                type: array
               skipDelete:
                 description: SkipDelete determines whether the resources created by
                   the step should be deleted after the test step is executed.
diff --git a/pkg/apis/v1alpha1/test_step_spec.go b/pkg/apis/v1alpha1/test_step_spec.go
index 56fcb9aa..97fc0767 100644
--- a/pkg/apis/v1alpha1/test_step_spec.go
+++ b/pkg/apis/v1alpha1/test_step_spec.go
@@ -34,11 +34,11 @@ type TestStepSpec struct {
 	// +optional
 	Delete []Delete `json:"delete,omitempty"`
 
-	// Command provides a list of commands that should be executed as a part of this test step.
+	// Exec provides a list of commands and/or scripts that should be executed as a part of this test step.
 	// +optional
-	Command []Command `json:"command,omitempty"`
+	Exec []ExecOperation `json:"exec,omitempty"`
 
 	// OnFailure defines actions to be executed in case of step failure.
 	// +optional
-	OnFailure *OnFailure `json:"onFailure,omitempty"`
+	OnFailure []OnFailure `json:"onFailure,omitempty"`
 }
diff --git a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
index d6d773f3..d48c716c 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
@@ -104,34 +104,6 @@ spec:
                             - file
                             type: object
                           type: array
-                        command:
-                          description: Command provides a list of commands that should
-                            be executed as a part of this test step.
-                          items:
-                            description: Command describes a command to run as a part
-                              of a test step.
-                            properties:
-                              command:
-                                description: Command is the command and argument to
-                                  run as a string.
-                                type: string
-                              continueOnError:
-                                description: ContinueOnError determines whether a
-                                  test should continue or not in case the operation
-                                  was not successful. Even if the test continues executing,
-                                  it will still be reported as failed.
-                                type: boolean
-                              skipLogOutput:
-                                description: SkipLogOutput removes the output from
-                                  the command. Useful for sensitive logs or to reduce
-                                  noise.
-                                type: boolean
-                              timeout:
-                                description: Timeout for the command. Overrides the
-                                  global timeout set in the Configuration.
-                                type: string
-                            type: object
-                          type: array
                         delete:
                           description: Delete provides a list of objects that should
                             be deleted before this test step is executed. This helps
@@ -193,14 +165,59 @@ spec:
                             - file
                             type: object
                           type: array
+                        exec:
+                          description: Exec provides a list of commands and/or scripts
+                            that should be executed as a part of this test step.
+                          items:
+                            description: Exec describes a command and/or script operation.
+                            properties:
+                              command:
+                                description: Command defines a command to run.
+                                properties:
+                                  args:
+                                    description: Args is the command arguments.
+                                    items:
+                                      type: string
+                                    type: array
+                                  entryPoint:
+                                    description: EntryPoint is the command entry point
+                                      to run.
+                                    type: string
+                                type: object
+                              continueOnError:
+                                description: ContinueOnError determines whether a
+                                  test should continue or not in case the operation
+                                  was not successful. Even if the test continues executing,
+                                  it will still be reported as failed.
+                                type: boolean
+                              script:
+                                description: Script defines a script to run.
+                                properties:
+                                  content:
+                                    description: Content defines a shell script (run
+                                      with "sh -c ...").
+                                    type: string
+                                type: object
+                              skipLogOutput:
+                                description: SkipLogOutput removes the output from
+                                  the command. Useful for sensitive logs or to reduce
+                                  noise.
+                                type: boolean
+                              timeout:
+                                description: Timeout for the command. Overrides the
+                                  global timeout set in the Configuration.
+                                type: string
+                            type: object
+                          type: array
                         onFailure:
                           description: OnFailure defines actions to be executed in
                             case of step failure.
-                          properties:
-                            collect:
-                              description: Collect define the collectors to run.
-                              items:
-                                description: Collector defines a set of collectors.
+                          items:
+                            description: OnFailure defines actions to be executed
+                              on failure.
+                            properties:
+                              collect:
+                                description: Collect define the collectors to run.
                                 properties:
                                   events:
                                     description: Events determines the events collector
@@ -246,8 +263,38 @@ spec:
                                         type: integer
                                     type: object
                                 type: object
-                              type: array
-                          type: object
+                              exec:
+                                description: Exec define the commands and/or scripts
+                                  to run.
+                                properties:
+                                  command:
+                                    description: Command defines a command to run.
+                                    properties:
+                                      args:
+                                        description: Args is the command arguments.
+                                        items:
+                                          type: string
+                                        type: array
+                                      entryPoint:
+                                        description: EntryPoint is the command entry
+                                          point to run.
+                                        type: string
+                                    type: object
+                                  script:
+                                    description: Script defines a script to run.
+                                    properties:
+                                      content:
+                                        description: Content defines a shell script
+                                          (run with "sh -c ...").
+                                        type: string
+                                    type: object
+                                  timeout:
+                                    description: Timeout for the command. Overrides
+                                      the global timeout set in the Configuration.
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
                         skipDelete:
                           description: SkipDelete determines whether the resources
                             created by the step should be deleted after the test step
diff --git a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
index eb4a16a9..0c7b7700 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
@@ -77,33 +77,6 @@ spec:
                   - file
                   type: object
                 type: array
-              command:
-                description: Command provides a list of commands that should be executed
-                  as a part of this test step.
-                items:
-                  description: Command describes a command to run as a part of a test
-                    step.
-                  properties:
-                    command:
-                      description: Command is the command and argument to run as a
-                        string.
-                      type: string
-                    continueOnError:
-                      description: ContinueOnError determines whether a test should
-                        continue or not in case the operation was not successful.
-                        Even if the test continues executing, it will still be reported
-                        as failed.
-                      type: boolean
-                    skipLogOutput:
-                      description: SkipLogOutput removes the output from the command.
-                        Useful for sensitive logs or to reduce noise.
-                      type: boolean
-                    timeout:
-                      description: Timeout for the command. Overrides the global timeout
-                        set in the Configuration.
-                      type: string
-                  type: object
-                type: array
               delete:
                 description: Delete provides a list of objects that should be deleted
                   before this test step is executed. This helps in ensuring that the
@@ -161,14 +134,56 @@ spec:
                   - file
                   type: object
                 type: array
+              exec:
+                description: Exec provides a list of commands and/or scripts that
+                  should be executed as a part of this test step.
+                items:
+                  description: Exec describes a command and/or script operation.
+                  properties:
+                    command:
+                      description: Command defines a command to run.
+                      properties:
+                        args:
+                          description: Args is the command arguments.
+                          items:
+                            type: string
+                          type: array
+                        entryPoint:
+                          description: EntryPoint is the command entry point to run.
+                          type: string
+                      type: object
+                    continueOnError:
+                      description: ContinueOnError determines whether a test should
+                        continue or not in case the operation was not successful.
+                        Even if the test continues executing, it will still be reported
+                        as failed.
+                      type: boolean
+                    script:
+                      description: Script defines a script to run.
+                      properties:
+                        content:
+                          description: Content defines a shell script (run with "sh
+                            -c ...").
+                          type: string
+                      type: object
+                    skipLogOutput:
+                      description: SkipLogOutput removes the output from the command.
+                        Useful for sensitive logs or to reduce noise.
+                      type: boolean
+                    timeout:
+                      description: Timeout for the command. Overrides the global timeout
+                        set in the Configuration.
+                      type: string
+                  type: object
+                type: array
               onFailure:
                 description: OnFailure defines actions to be executed in case of step
                   failure.
-                properties:
-                  collect:
-                    description: Collect define the collectors to run.
-                    items:
-                      description: Collector defines a set of collectors.
+                items:
+                  description: OnFailure defines actions to be executed on failure.
+                  properties:
+                    collect:
+                      description: Collect define the collectors to run.
                       properties:
                         events:
                           description: Events determines the events collector to execute.
@@ -211,8 +226,37 @@ spec:
                               type: integer
                           type: object
                       type: object
-                    type: array
-                type: object
+                    exec:
+                      description: Exec define the commands and/or scripts to run.
+                      properties:
+                        command:
+                          description: Command defines a command to run.
+                          properties:
+                            args:
+                              description: Args is the command arguments.
+                              items:
+                                type: string
+                              type: array
+                            entryPoint:
+                              description: EntryPoint is the command entry point to
+                                run.
+                              type: string
+                          type: object
+                        script:
+                          description: Script defines a script to run.
+                          properties:
+                            content:
+                              description: Content defines a shell script (run with
+                                "sh -c ...").
+                              type: string
+                          type: object
+                        timeout:
+                          description: Timeout for the command. Overrides the global
+                            timeout set in the Configuration.
+                          type: string
+                      type: object
+                  type: object
+                type: array
               skipDelete:
                 description: SkipDelete determines whether the resources created by
                   the step should be deleted after the test step is executed.