Repository: kyverno/chainsaw
Pull request #513 (2023-11-27 17:24:07)

feat: add force grace termination period support
Add force grace termination period support.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kyverno/chainsaw/pull/513?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`e600391`)](https://app.codecov.io/gh/kyverno/chainsaw/commit/e600391e113c9c619f1555d482d7fa596869df1a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) 87.71% compared to head [(`fdd8cb8`)](https://app.codecov.io/gh/kyverno/chainsaw/pull/513?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) 87.71%.


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #513   +/-   ##
=======================================
  Coverage   87.71%   87.71%           
=======================================
  Files          74       74           
  Lines        2246     2246           
=======================================
  Hits         1970     1970           
  Misses        211      211           
  Partials       65       65           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/kyverno/chainsaw/pull/513?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno).


Patch:
diff --git a/.crds/chainsaw.kyverno.io_configurations.yaml b/.crds/chainsaw.kyverno.io_configurations.yaml
index 6a28b276..7cbfafb0 100644
--- a/.crds/chainsaw.kyverno.io_configurations.yaml
+++ b/.crds/chainsaw.kyverno.io_configurations.yaml
@@ -43,6 +43,10 @@ spec:
                 description: FailFast determines whether the test should stop upon
                   encountering the first failure.
                 type: boolean
+              forceTerminationGracePeriod:
+                description: ForceTerminationGracePeriod forces the termination grace
+                  period on pods, statefulsets, daemonsets and deployments.
+                type: string
               fullName:
                 description: FullName makes use of the full test case folder path
                   instead of the folder name.
diff --git a/.release-notes/main.md b/.release-notes/main.md
index 3011a063..df16ec78 100644
--- a/.release-notes/main.md
+++ b/.release-notes/main.md
@@ -24,6 +24,7 @@ Release notes for `TODO`.
 
 - Added timeout support in `try` and `catch` handlers
 - Added assertion tree check in `delete` operation
+- Added a new configuration option to force termination graceful period on `Pod`, `Deployment`, `StatefulSet`, `DaemonSet`, `Job` and `CronJob`
 
 ## :wrench: Fixes :wrench:
 
diff --git a/pkg/apis/v1alpha1/configuration_spec.go b/pkg/apis/v1alpha1/configuration_spec.go
index c83ee6c6..a54b6505 100644
--- a/pkg/apis/v1alpha1/configuration_spec.go
+++ b/pkg/apis/v1alpha1/configuration_spec.go
@@ -1,5 +1,7 @@
 package v1alpha1
 
+import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 type ReportFormatType string
 
 const (
@@ -71,4 +73,8 @@ type ConfigurationSpec struct {
 	// +kubebuilder:default:="chainsaw-test.yaml"
 	// +optional
 	TestFile string `json:"testFile,omitempty"`
+
+	// ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.
+	// +optional
+	ForceTerminationGracePeriod *metav1.Duration `json:"forceTerminationGracePeriod,omitempty"`
 }
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index 373d895d..4f53a7a3 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -184,6 +184,11 @@ func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) {
 		*out = new(int)
 		**out = **in
 	}
+	if in.ForceTerminationGracePeriod != nil {
+		in, out := &in.ForceTerminationGracePeriod, &out.ForceTerminationGracePeriod
+		*out = new(v1.Duration)
+		**out = **in
+	}
 	return
 }
 
diff --git a/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml b/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
index 6a28b276..7cbfafb0 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
@@ -43,6 +43,10 @@ spec:
                 description: FailFast determines whether the test should stop upon
                   encountering the first failure.
                 type: boolean
+              forceTerminationGracePeriod:
+                description: ForceTerminationGracePeriod forces the termination grace
+                  period on pods, statefulsets, daemonsets and deployments.
+                type: string
               fullName:
                 description: FullName makes use of the full test case folder path
                   instead of the folder name.
diff --git a/pkg/runner/processors/step.go b/pkg/runner/processors/step.go
index f33d9ae0..defb7ea2 100644
--- a/pkg/runner/processors/step.go
+++ b/pkg/runner/processors/step.go
@@ -248,6 +248,9 @@ func (p *stepProcessor) applyOperation(ctx context.Context, op v1alpha1.Apply, t
 	}
 	dryRun := op.DryRun != nil && *op.DryRun
 	for _, resource := range resources {
+		if err := p.prepareResource(resource); err != nil {
+			return nil, err
+		}
 		ops = append(ops, operation{
 			timeout:   timeout.Get(timeout.DefaultApplyTimeout, p.config.Timeouts.Apply, p.test.Spec.Timeouts.Apply, p.step.Spec.Timeouts.Apply, to),
 			operation: opapply.New(p.getClient(dryRun), resource, p.namespacer, p.getCleaner(ctx, dryRun), op.Expect...),
@@ -300,6 +303,9 @@ func (p *stepProcessor) createOperation(ctx context.Context, op v1alpha1.Create,
 	}
 	dryRun := op.DryRun != nil && *op.DryRun
 	for _, resource := range resources {
+		if err := p.prepareResource(resource); err != nil {
+			return nil, err
+		}
 		ops = append(ops, operation{
 			timeout:   timeout.Get(timeout.DefaultApplyTimeout, p.config.Timeouts.Apply, p.test.Spec.Timeouts.Apply, p.step.Spec.Timeouts.Apply, to),
 			operation: opcreate.New(p.getClient(dryRun), resource, p.namespacer, p.getCleaner(ctx, dryRun), op.Expect...),
@@ -375,6 +381,27 @@ func (p *stepProcessor) fileRefOrResource(ref v1alpha1.FileRefOrResource) ([]uns
 	return nil, errors.New("file or resource must be set")
 }
 
+func (p *stepProcessor) prepareResource(resource unstructured.Unstructured) error {
+	if p.config.ForceTerminationGracePeriod != nil {
+		seconds := int64(p.config.ForceTerminationGracePeriod.Duration.Seconds())
+		switch resource.GetKind() {
+		case "Pod":
+			if err := unstructured.SetNestedField(resource.UnstructuredContent(), &seconds, "spec", "terminationGracePeriodSeconds"); err != nil {
+				return err
+			}
+		case "Deployment", "StatefulSet", "DaemonSet", "Job":
+			if err := unstructured.SetNestedField(resource.UnstructuredContent(), &seconds, "spec", "template", "spec", "terminationGracePeriodSeconds"); err != nil {
+				return err
+			}
+		case "CronJob":
+			if err := unstructured.SetNestedField(resource.UnstructuredContent(), &seconds, "spec", "jobTemplate", "spec", "template", "spec", "terminationGracePeriodSeconds"); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
 func (p *stepProcessor) getClient(dryRun bool) client.Client {
 	if !dryRun {
 		return p.client
diff --git a/website/docs/apis/chainsaw.v1alpha1.md b/website/docs/apis/chainsaw.v1alpha1.md
index 97b43f1b..7a5cce4b 100644
--- a/website/docs/apis/chainsaw.v1alpha1.md
+++ b/website/docs/apis/chainsaw.v1alpha1.md
@@ -139,6 +139,7 @@ during the testing process.</p>
 | `includeTestRegex` | `string` |  |  | <p>IncludeTestRegex is used to include tests based on a regular expression.</p> |
 | `repeatCount` | `int` |  |  | <p>RepeatCount indicates how many times the tests should be executed.</p> |
 | `testFile` | `string` |  |  | <p>TestFile is the name of the file containing the test to run.</p> |
+| `forceTerminationGracePeriod` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.</p> |
 
 ## `Create`     {#chainsaw-kyverno-io-v1alpha1-Create}
 


Test patch:
diff --git a/pkg/commands/test/command.go b/pkg/commands/test/command.go
index 8b8bae1f..e36c6d5c 100644
--- a/pkg/commands/test/command.go
+++ b/pkg/commands/test/command.go
@@ -23,27 +23,28 @@ import (
 )
 
 type options struct {
-	config              string
-	testFile            string
-	applyTimeout        metav1.Duration
-	assertTimeout       metav1.Duration
-	errorTimeout        metav1.Duration
-	deleteTimeout       metav1.Duration
-	cleanupTimeout      metav1.Duration
-	execTimeout         metav1.Duration
-	testDirs            []string
-	skipDelete          bool
-	failFast            bool
-	parallel            int
-	repeatCount         int
-	reportFormat        string
-	reportName          string
-	namespace           string
-	fullName            bool
-	excludeTestRegex    string
-	includeTestRegex    string
-	noColor             bool
-	kubeConfigOverrides clientcmd.ConfigOverrides
+	config                      string
+	testFile                    string
+	applyTimeout                metav1.Duration
+	assertTimeout               metav1.Duration
+	errorTimeout                metav1.Duration
+	deleteTimeout               metav1.Duration
+	cleanupTimeout              metav1.Duration
+	execTimeout                 metav1.Duration
+	testDirs                    []string
+	skipDelete                  bool
+	failFast                    bool
+	parallel                    int
+	repeatCount                 int
+	reportFormat                string
+	reportName                  string
+	namespace                   string
+	fullName                    bool
+	excludeTestRegex            string
+	includeTestRegex            string
+	noColor                     bool
+	kubeConfigOverrides         clientcmd.ConfigOverrides
+	forceTerminationGracePeriod metav1.Duration
 }
 
 func Command() *cobra.Command {
@@ -141,6 +142,9 @@ func Command() *cobra.Command {
 			if flagutils.IsSet(flags, "exclude-test-regex") {
 				configuration.Spec.ExcludeTestRegex = options.excludeTestRegex
 			}
+			if flagutils.IsSet(flags, "force-termination-grace-period") {
+				configuration.Spec.ForceTerminationGracePeriod = &options.forceTerminationGracePeriod
+			}
 			fmt.Fprintf(out, "- Using test file: %s\n", configuration.Spec.TestFile)
 			fmt.Fprintf(out, "- TestDirs %v\n", configuration.Spec.TestDirs)
 			fmt.Fprintf(out, "- SkipDelete %v\n", configuration.Spec.SkipDelete)
@@ -175,6 +179,9 @@ func Command() *cobra.Command {
 			if configuration.Spec.Timeouts.Exec != nil {
 				fmt.Fprintf(out, "- ExecTimeout %v\n", configuration.Spec.Timeouts.Exec.Duration)
 			}
+			if configuration.Spec.ForceTerminationGracePeriod != nil {
+				fmt.Fprintf(out, "- ForceTerminationGracePeriod %v\n", configuration.Spec.ForceTerminationGracePeriod.Duration)
+			}
 			// loading tests
 			fmt.Fprintln(out, "Loading tests...")
 			tests, err := discovery.DiscoverTests(configuration.Spec.TestFile, configuration.Spec.TestDirs...)
@@ -234,6 +241,7 @@ func Command() *cobra.Command {
 	cmd.Flags().StringVar(&options.includeTestRegex, "include-test-regex", "", "Regular expression to include tests.")
 	cmd.Flags().StringVar(&options.excludeTestRegex, "exclude-test-regex", "", "Regular expression to exclude tests.")
 	cmd.Flags().BoolVar(&options.noColor, "no-color", false, "Removes output colors.")
+	cmd.Flags().DurationVar(&options.forceTerminationGracePeriod.Duration, "force-termination-grace-period", 0, "If specified, overrides termination grace periods in applicable resources.")
 	clientcmd.BindOverrideFlags(&options.kubeConfigOverrides, cmd.Flags(), clientcmd.RecommendedConfigOverrideFlags("kube-"))
 	if err := cmd.MarkFlagFilename("config"); err != nil {
 		panic(err)
diff --git a/pkg/commands/test/command_test.go b/pkg/commands/test/command_test.go
index 00c69e08..13c42751 100644
--- a/pkg/commands/test/command_test.go
+++ b/pkg/commands/test/command_test.go
@@ -131,6 +131,7 @@ func TestChainsawCommand(t *testing.T) {
 			"--full-name=true",
 			"--include-test-regex=^.*$",
 			"--exclude-test-regex=^.*$",
+			"--force-termination-grace-period=5s",
 		},
 		wantErr: false,
 		out:     filepath.Join(basePath, "all_flags.txt"),
diff --git a/pkg/config/load_test.go b/pkg/config/load_test.go
index 6e16418c..e11362d9 100644
--- a/pkg/config/load_test.go
+++ b/pkg/config/load_test.go
@@ -78,15 +78,16 @@ func TestLoad(t *testing.T) {
 					Cleanup: &metav1.Duration{Duration: 5 * time.Second},
 					Exec:    &metav1.Duration{Duration: 10 * time.Second},
 				},
-				TestFile:         "custom-test.yaml",
-				SkipDelete:       true,
-				FailFast:         true,
-				Parallel:         ptr.To(4),
-				ReportFormat:     "JSON",
-				ReportName:       "custom-report",
-				FullName:         true,
-				IncludeTestRegex: "include-*",
-				ExcludeTestRegex: "exclude-*",
+				TestFile:                    "custom-test.yaml",
+				SkipDelete:                  true,
+				FailFast:                    true,
+				Parallel:                    ptr.To(4),
+				ReportFormat:                "JSON",
+				ReportName:                  "custom-report",
+				FullName:                    true,
+				IncludeTestRegex:            "include-*",
+				ExcludeTestRegex:            "exclude-*",
+				ForceTerminationGracePeriod: &metav1.Duration{Duration: 10 * time.Second},
 			},
 		},
 	}, {
diff --git a/testdata/commands/test/all_flags.txt b/testdata/commands/test/all_flags.txt
index fae6000a..b1820947 100644
--- a/testdata/commands/test/all_flags.txt
+++ b/testdata/commands/test/all_flags.txt
@@ -17,6 +17,7 @@ Loading default configuration...
 - DeleteTimeout 1m40s
 - CleanupTimeout 1m40s
 - ExecTimeout 1m40s
+- ForceTerminationGracePeriod 5s
 Loading tests...
 Running tests...
 Tests Summary...
diff --git a/testdata/config/custom-config.yaml b/testdata/config/custom-config.yaml
index d345afab..90507a24 100644
--- a/testdata/config/custom-config.yaml
+++ b/testdata/config/custom-config.yaml
@@ -19,3 +19,4 @@ spec:
   fullName: true
   includeTestRegex: "include-*"
   excludeTestRegex: "exclude-*"
+  forceTerminationGracePeriod: 10s
diff --git a/website/docs/commands/chainsaw_test.md b/website/docs/commands/chainsaw_test.md
index 23e0d9e0..7d7fe714 100644
--- a/website/docs/commands/chainsaw_test.md
+++ b/website/docs/commands/chainsaw_test.md
@@ -9,46 +9,47 @@ chainsaw test [flags]... [test directories]...
 ### Options
 
 ```
-      --apply-timeout duration              The apply timeout to use as default for configuration. (default 5s)
-      --assert-timeout duration             The assert timeout to use as default for configuration. (default 30s)
-      --cleanup-timeout duration            The cleanup timeout to use as default for configuration. (default 30s)
-      --config string                       Chainsaw configuration file.
-      --delete-timeout duration             The delete timeout to use as default for configuration. (default 15s)
-      --error-timeout duration              The error timeout to use as default for configuration. (default 30s)
-      --exclude-test-regex string           Regular expression to exclude tests.
-      --exec-timeout duration               The exec timeout to use as default for configuration. (default 5s)
-      --fail-fast                           Stop the test upon encountering the first failure.
-      --full-name                           Use full test case folder path instead of folder name.
-  -h, --help                                help for test
-      --include-test-regex string           Regular expression to include tests.
-      --kube-as string                      Username to impersonate for the operation
-      --kube-as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
-      --kube-as-uid string                  UID to impersonate for the operation
-      --kube-certificate-authority string   Path to a cert file for the certificate authority
-      --kube-client-certificate string      Path to a client certificate file for TLS
-      --kube-client-key string              Path to a client key file for TLS
-      --kube-cluster string                 The name of the kubeconfig cluster to use
-      --kube-context string                 The name of the kubeconfig context to use
-      --kube-disable-compression            If true, opt-out of response compression for all requests to the server
-      --kube-insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
-  -n, --kube-namespace string               If present, the namespace scope for this CLI request
-      --kube-password string                Password for basic authentication to the API server
-      --kube-proxy-url string               If provided, this URL will be used to connect via proxy
-      --kube-request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-      --kube-server string                  The address and port of the Kubernetes API server
-      --kube-tls-server-name string         If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
-      --kube-token string                   Bearer token for authentication to the API server
-      --kube-user string                    The name of the kubeconfig user to use
-      --kube-username string                Username for basic authentication to the API server
-      --namespace string                    Namespace to use for tests.
-      --no-color                            Removes output colors.
-      --parallel int                        The maximum number of tests to run at once.
-      --repeat-count int                    Number of times to repeat each test. (default 1)
-      --report-format string                Test report format (JSON|XML|nil).
-      --report-name string                  The name of the report to create. (default "chainsaw-report")
-      --skip-delete                         If set, do not delete the resources after running the tests.
-      --test-dir stringArray                Directories containing test cases to run.
-      --test-file string                    Name of the test file. (default "chainsaw-test.yaml")
+      --apply-timeout duration                    The apply timeout to use as default for configuration. (default 5s)
+      --assert-timeout duration                   The assert timeout to use as default for configuration. (default 30s)
+      --cleanup-timeout duration                  The cleanup timeout to use as default for configuration. (default 30s)
+      --config string                             Chainsaw configuration file.
+      --delete-timeout duration                   The delete timeout to use as default for configuration. (default 15s)
+      --error-timeout duration                    The error timeout to use as default for configuration. (default 30s)
+      --exclude-test-regex string                 Regular expression to exclude tests.
+      --exec-timeout duration                     The exec timeout to use as default for configuration. (default 5s)
+      --fail-fast                                 Stop the test upon encountering the first failure.
+      --force-termination-grace-period duration   If specified, overrides termination grace periods in applicable resources.
+      --full-name                                 Use full test case folder path instead of folder name.
+  -h, --help                                      help for test
+      --include-test-regex string                 Regular expression to include tests.
+      --kube-as string                            Username to impersonate for the operation
+      --kube-as-group stringArray                 Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
+      --kube-as-uid string                        UID to impersonate for the operation
+      --kube-certificate-authority string         Path to a cert file for the certificate authority
+      --kube-client-certificate string            Path to a client certificate file for TLS
+      --kube-client-key string                    Path to a client key file for TLS
+      --kube-cluster string                       The name of the kubeconfig cluster to use
+      --kube-context string                       The name of the kubeconfig context to use
+      --kube-disable-compression                  If true, opt-out of response compression for all requests to the server
+      --kube-insecure-skip-tls-verify             If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
+  -n, --kube-namespace string                     If present, the namespace scope for this CLI request
+      --kube-password string                      Password for basic authentication to the API server
+      --kube-proxy-url string                     If provided, this URL will be used to connect via proxy
+      --kube-request-timeout string               The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
+      --kube-server string                        The address and port of the Kubernetes API server
+      --kube-tls-server-name string               If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
+      --kube-token string                         Bearer token for authentication to the API server
+      --kube-user string                          The name of the kubeconfig user to use
+      --kube-username string                      Username for basic authentication to the API server
+      --namespace string                          Namespace to use for tests.
+      --no-color                                  Removes output colors.
+      --parallel int                              The maximum number of tests to run at once.
+      --repeat-count int                          Number of times to repeat each test. (default 1)
+      --report-format string                      Test report format (JSON|XML|nil).
+      --report-name string                        The name of the report to create. (default "chainsaw-report")
+      --skip-delete                               If set, do not delete the resources after running the tests.
+      --test-dir stringArray                      Directories containing test cases to run.
+      --test-file string                          Name of the test file. (default "chainsaw-test.yaml")
 ```
 
 ### SEE ALSO