Repository: kubernetes-sigs/security-profiles-operator
Pull request #342 (2021-03-04 10:29:49)

Handle Updates of SPOD object
#### What type of PR is this?

/kind cleanup
/kind feature

#### What this PR does / why we need it:

This PR ensures that updates to the SPOD object are handled appropriately.
Thus, when the SPOD object contains new values, such as new images, flags, or
tolerations, the DS that the SPOD object owns will get updated as well.

#### Which issue(s) this PR fixes:

None

#### Does this PR have test?

Yes

#### Special notes for your reviewer:

The "ready" condition now flicks with "updating" when the SPOD object is indeed
updating.

#### Does this PR introduce a user-facing change?

```release-note
Updates to the SecurityProfilesOperatorDaemon object are now reflected in the
daemonset.
```

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/342#" title="Author self-approved">JAORMX</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **hasheddan** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @hasheddan` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["hasheddan"]} -->
k8s-ci-robot: @JAORMX: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | 01d8368f04856b708ce2f72940aad1d76aee3f07 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/342/pull-security-profiles-operator-verify/1367422025836007424) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=342). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3AJAORMX). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
codecov-io: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=h1) Report
> Merging [#342](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=desc) (f30d184) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/637d00de79fcb9936994e6aedcbcc0628fda5322?el=desc) (637d00d) will **decrease** coverage by `1.96%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342/graphs/tree.svg?width=650&height=150&src=pr&token=37VIWSZ1ZT)](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #342      +/-   ##
==========================================
- Coverage   29.66%   27.70%   -1.97%     
==========================================
  Files           9        9              
  Lines         691      740      +49     
==========================================
  Hits          205      205              
- Misses        467      516      +49     
  Partials       19       19              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `27.70% <0.00%> (-1.97%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [internal/pkg/manager/spod/spod\_controller.go](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGtnL21hbmFnZXIvc3BvZC9zcG9kX2NvbnRyb2xsZXIuZ28=) | `0.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=footer). Last update [637d00d...f30d184](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/342?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/api/spod/v1alpha1/spod_types.go b/api/spod/v1alpha1/spod_types.go
index 0be494b43..ae83ce5a8 100644
--- a/api/spod/v1alpha1/spod_types.go
+++ b/api/spod/v1alpha1/spod_types.go
@@ -39,13 +39,15 @@ type SPODSpec struct {
 type SPODState string
 
 const (
-	// The policy is pending installation.
+	// The SPOD instance is pending installation.
 	SPODStatePending SPODState = "PENDING"
-	// The policy is being installed.
+	// The SPOD instance is being created.
 	SPODStateCreating SPODState = "CREATING"
-	// The policy was installed successfully.
+	// The SPOD instance is being updated.
+	SPODStateUpdating SPODState = "UPDATING"
+	// The SPOD instance was installed successfully.
 	SPODStateRunning SPODState = "RUNNING"
-	// The policy couldn't be installed.
+	// The SPOD instance couldn't be installed.
 	SPODStateError SPODState = "ERROR"
 )
 
@@ -95,6 +97,16 @@ func (s *SPODStatus) StateCreating() {
 	s.ConditionedStatus.SetConditions(rcommonv1.Creating())
 }
 
+func (s *SPODStatus) StateUpdating() {
+	s.State = SPODStateUpdating
+	s.ConditionedStatus.SetConditions(rcommonv1.Condition{
+		Type:               rcommonv1.TypeReady,
+		Status:             corev1.ConditionFalse,
+		LastTransitionTime: metav1.Now(),
+		Reason:             "Updating",
+	})
+}
+
 func (s *SPODStatus) StateRunning() {
 	s.State = SPODStateRunning
 	s.ConditionedStatus.SetConditions(rcommonv1.Available())
diff --git a/deploy/base/role.yaml b/deploy/base/role.yaml
index a063fa509..4093f815a 100644
--- a/deploy/base/role.yaml
+++ b/deploy/base/role.yaml
@@ -25,7 +25,6 @@ rules:
   - create
   - get
   - list
-  - patch
   - update
   - watch
 - apiGroups:
@@ -62,6 +61,7 @@ rules:
   - create
   - get
   - list
+  - patch
   - update
   - watch
 - apiGroups:
diff --git a/deploy/namespace-operator.yaml b/deploy/namespace-operator.yaml
index 9b4b2abfd..d521b0ad2 100644
--- a/deploy/namespace-operator.yaml
+++ b/deploy/namespace-operator.yaml
@@ -461,7 +461,6 @@ rules:
   - create
   - get
   - list
-  - patch
   - update
   - watch
 - apiGroups:
@@ -499,6 +498,7 @@ rules:
   - create
   - get
   - list
+  - patch
   - update
   - watch
 - apiGroups:
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
index 6ba563f28..af919e0e0 100644
--- a/deploy/operator.yaml
+++ b/deploy/operator.yaml
@@ -461,7 +461,6 @@ rules:
   - create
   - get
   - list
-  - patch
   - update
   - watch
 - apiGroups:
@@ -499,6 +498,7 @@ rules:
   - create
   - get
   - list
+  - patch
   - update
   - watch
 - apiGroups:
diff --git a/internal/pkg/manager/spod/bindata/spod.go b/internal/pkg/manager/spod/bindata/spod.go
index a3eccef3b..1bd1d184c 100644
--- a/internal/pkg/manager/spod/bindata/spod.go
+++ b/internal/pkg/manager/spod/bindata/spod.go
@@ -272,12 +272,12 @@ var Manifest = &appsv1.DaemonSet{
 						Resources: v1.ResourceRequirements{
 							Requests: v1.ResourceList{
 								v1.ResourceMemory:           resource.MustParse("512Mi"),
-								v1.ResourceCPU:              resource.MustParse("1000m"),
+								v1.ResourceCPU:              resource.MustParse("100m"),
 								v1.ResourceEphemeralStorage: resource.MustParse("200Mi"),
 							},
 							Limits: v1.ResourceList{
 								v1.ResourceMemory:           resource.MustParse("1024Mi"),
-								v1.ResourceCPU:              resource.MustParse("2000m"),
+								v1.ResourceCPU:              resource.MustParse("1000m"),
 								v1.ResourceEphemeralStorage: resource.MustParse("400Mi"),
 							},
 						},
diff --git a/internal/pkg/manager/spod/spod_controller.go b/internal/pkg/manager/spod/spod_controller.go
index 9b67d0bd8..7b29a859b 100644
--- a/internal/pkg/manager/spod/spod_controller.go
+++ b/internal/pkg/manager/spod/spod_controller.go
@@ -25,6 +25,7 @@ import (
 	"github.com/pkg/errors"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
@@ -32,6 +33,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
+	sccmpv1alpha1 "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
 	spodv1alpha1 "sigs.k8s.io/security-profiles-operator/api/spod/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod/bindata"
@@ -39,6 +41,7 @@ import (
 
 const (
 	reasonCannotCreateSPOD event.Reason = "CannotCreateSPOD"
+	reasonCannotUpdateSPOD event.Reason = "CannotUpdateSPOD"
 )
 
 // blank assignment to verify that ReconcileSPOd implements `reconcile.Reconciler`.
@@ -63,9 +66,9 @@ type ReconcileSPOd struct {
 // +kubebuilder:rbac:groups=core,namespace="security-profiles-operator",resources=configmaps;events,verbs=get;list;watch;create;update;patch
 //
 // Operand:
-// +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=daemonsets,verbs=get;list;watch;create;update
+// +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=daemonsets,verbs=get;list;watch;create;update;patch
 // +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=daemonsets/finalizers,verbs=delete;get;update;patch
-// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilesoperatordaemons,verbs=get;list;watch;create;update;patch
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilesoperatordaemons,verbs=get;list;watch;create;update
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilesoperatordaemons/status,verbs=get;update;patch
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilesoperatordaemons/finalizers,verbs=delete;get;update;patch
 // Helpers:
@@ -112,14 +115,17 @@ func (r *ReconcileSPOd) Reconcile(_ context.Context, req reconcile.Request) (rec
 	image := foundDeployment.Spec.Template.Spec.Containers[0].Image
 	pullPolicy := foundDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy
 
+	configuredSPOd := r.getConfiguredSPOd(spod, image, pullPolicy)
+
 	spodKey := types.NamespacedName{
 		Name:      spod.GetName(),
 		Namespace: config.GetOperatorNamespace(),
 	}
+
 	foundSPOd := &appsv1.DaemonSet{}
 	if err := r.client.Get(ctx, spodKey, foundSPOd); err != nil {
 		if kerrors.IsNotFound(err) {
-			createErr := r.handleCreate(ctx, spod, image, pullPolicy)
+			createErr := r.handleCreate(ctx, spod, configuredSPOd)
 			if createErr != nil {
 				r.record.Event(spod, event.Warning(reasonCannotCreateSPOD, createErr))
 				return reconcile.Result{}, createErr
@@ -129,10 +135,23 @@ func (r *ReconcileSPOd) Reconcile(_ context.Context, req reconcile.Request) (rec
 		return reconcile.Result{}, errors.Wrap(err, "getting spod DaemonSet")
 	}
 
-	// NOTE(jaosorior): We gotta handle updates
+	if spodNeedsUpdate(configuredSPOd, foundSPOd) {
+		updatedSPod := foundSPOd.DeepCopy()
+		updatedSPod.Spec.Template = configuredSPOd.Spec.Template
+		updateErr := r.handleUpdate(ctx, updatedSPod)
+		if updateErr != nil {
+			r.record.Event(spod, event.Warning(reasonCannotUpdateSPOD, updateErr))
+			return reconcile.Result{}, updateErr
+		}
+		return r.handleUpdatingStatus(ctx, spod, logger)
+	}
 
 	if foundSPOd.Status.NumberReady == foundSPOd.Status.DesiredNumberScheduled {
-		return r.handleRunningStatus(ctx, spod, logger)
+		condready := spod.Status.GetCondition("Ready")
+		// Don't pollute the logs. Let's only update when needed.
+		if condready.Status != corev1.ConditionTrue {
+			return r.handleRunningStatus(ctx, spod, logger)
+		}
 	}
 	return reconcile.Result{}, nil
 }
@@ -167,6 +186,21 @@ func (r *ReconcileSPOd) handleCreatingStatus(
 	return reconcile.Result{}, nil
 }
 
+func (r *ReconcileSPOd) handleUpdatingStatus(
+	ctx context.Context,
+	spod *spodv1alpha1.SecurityProfilesOperatorDaemon,
+	l logr.Logger,
+) (res reconcile.Result, err error) {
+	l.Info("Adding 'Updating' status to the SPOD Instance")
+	sCopy := spod.DeepCopy()
+	sCopy.Status.StateUpdating()
+	updateErr := r.client.Status().Update(ctx, sCopy)
+	if updateErr != nil {
+		return reconcile.Result{}, errors.Wrap(updateErr, "updating spod status to 'updating'")
+	}
+	return reconcile.Result{}, nil
+}
+
 func (r *ReconcileSPOd) handleRunningStatus(
 	ctx context.Context,
 	spod *spodv1alpha1.SecurityProfilesOperatorDaemon,
@@ -185,11 +219,9 @@ func (r *ReconcileSPOd) handleRunningStatus(
 func (r *ReconcileSPOd) handleCreate(
 	ctx context.Context,
 	cfg *spodv1alpha1.SecurityProfilesOperatorDaemon,
-	image string,
-	pullPolicy corev1.PullPolicy,
+	newSPOd *appsv1.DaemonSet,
 ) error {
 	r.log.Info("Creating operator resources")
-	newSPOd := r.getConfiguredSPOd(cfg, image, pullPolicy)
 
 	if err := controllerutil.SetControllerReference(cfg, newSPOd, r.scheme); err != nil {
 		return errors.Wrap(err, "setting spod controller reference")
@@ -212,7 +244,7 @@ func (r *ReconcileSPOd) handleCreate(
 
 		if err := r.client.Create(ctx, profile); err != nil {
 			if kerrors.IsAlreadyExists(err) {
-				return nil
+				continue
 			}
 			return errors.Wrapf(
 				err, "creating operator default profile %s", profile.Name,
@@ -223,13 +255,72 @@ func (r *ReconcileSPOd) handleCreate(
 	return nil
 }
 
+func (r *ReconcileSPOd) handleUpdate(
+	ctx context.Context,
+	spodInstance *appsv1.DaemonSet,
+) error {
+	r.log.Info("Updating operator daemonset")
+	if err := r.client.Patch(ctx, spodInstance, client.Merge); err != nil {
+		if kerrors.IsAlreadyExists(err) {
+			return nil
+		}
+		return errors.Wrap(err, "creating operator DaemonSet")
+	}
+
+	r.log.Info("Updating operator default profiles")
+	for _, profile := range bindata.DefaultProfiles() {
+		// Adapt the namespace if we watch only a single one
+		if r.watchNamespace != "" {
+			profile.Namespace = r.watchNamespace
+		}
+
+		pKey := types.NamespacedName{
+			Name:      profile.GetName(),
+			Namespace: profile.GetNamespace(),
+		}
+		foundProfile := &sccmpv1alpha1.SeccompProfile{}
+		var err error
+		if err = r.client.Get(ctx, pKey, foundProfile); err == nil {
+			if updateErr := r.client.Update(ctx, profile); updateErr != nil {
+				return errors.Wrapf(
+					updateErr, "updating operator default profile %s", profile.Name,
+				)
+			}
+			continue
+		}
+
+		if kerrors.IsNotFound(err) {
+			// Handle new default profile
+			if createErr := r.client.Create(ctx, profile); err != nil {
+				if kerrors.IsAlreadyExists(createErr) {
+					return nil
+				}
+				return errors.Wrapf(
+					createErr, "creating operator default profile %s", profile.Name,
+				)
+			}
+			continue
+		}
+
+		return errors.Wrapf(
+			err, "getting operator default profile %s", profile.Name,
+		)
+	}
+
+	return nil
+}
+
+// getConfiguredSPOd gets a fully configured SPOd instance from a desired
+// configuration and the reference base SPOd.
 func (r *ReconcileSPOd) getConfiguredSPOd(
 	cfg *spodv1alpha1.SecurityProfilesOperatorDaemon,
 	image string,
 	pullPolicy corev1.PullPolicy,
 ) *appsv1.DaemonSet {
 	newSPOd := r.baseSPOd.DeepCopy()
+
 	newSPOd.SetName(cfg.GetName())
+	newSPOd.SetNamespace(config.GetOperatorNamespace())
 	templateSpec := &newSPOd.Spec.Template.Spec
 
 	// Set Images
@@ -253,14 +344,14 @@ func (r *ReconcileSPOd) getConfiguredSPOd(
 
 	// Log enricher parameters
 	if cfg.Spec.EnableLogEnricher {
-		r.baseSPOd.Spec.Template.Spec.Containers[2].Image = image
-		newSPOd.Spec.Template.Spec.Containers = append(
-			newSPOd.Spec.Template.Spec.Containers,
+		templateSpec.Containers = append(
+			templateSpec.Containers,
 			r.baseSPOd.Spec.Template.Spec.Containers[2])
+		templateSpec.Containers[2].Image = image
 
 		// HostPID is only required for the log-enricher
 		// and is used to access cgroup files to map Process IDs to Pod IDs
-		newSPOd.Spec.Template.Spec.HostPID = true
+		templateSpec.HostPID = true
 	}
 
 	// Set image pull policy
@@ -276,3 +367,11 @@ func (r *ReconcileSPOd) getConfiguredSPOd(
 
 	return newSPOd
 }
+
+func spodNeedsUpdate(configured, found *appsv1.DaemonSet) bool {
+	// If the length of the containers don't match, we clearly need an update.
+	// This way we avoid the expensive DeepDerivative check.
+	return (len(configured.Spec.Template.Spec.InitContainers) != len(found.Spec.Template.Spec.InitContainers) ||
+		len(configured.Spec.Template.Spec.Containers) != len(found.Spec.Template.Spec.Containers) ||
+		!apiequality.Semantic.DeepDerivative(configured.Spec.Template, found.Spec.Template))
+}


Test patch:
diff --git a/test/e2e_test.go b/test/e2e_test.go
index e7c00f3f3..c769883a5 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -85,6 +85,10 @@ func (e *e2e) TestSecurityProfilesOperator() {
 			"SELinux: base case (install policy, run pod and delete)",
 			e.testCaseSelinuxBaseUsage,
 		},
+		{
+			"SPOD: Update SELinux flag",
+			e.testCaseSPODUpdateSelinux,
+		},
 	}
 	for _, testCase := range testCases {
 		tc := testCase
diff --git a/test/suite_test.go b/test/suite_test.go
index ae3303a3a..3e127e0bc 100644
--- a/test/suite_test.go
+++ b/test/suite_test.go
@@ -423,8 +423,8 @@ func (e *e2e) kubectlFailure(args ...string) string {
 	return e.runFailure(e.kubectlPath, args...)
 }
 
-func (e *e2e) kubectlOperatorNS(args ...string) {
-	e.kubectl(
+func (e *e2e) kubectlOperatorNS(args ...string) string {
+	return e.kubectl(
 		append([]string{"-n", config.OperatorName}, args...)...,
 	)
 }
diff --git a/test/tc_spod_update_selinux_test.go b/test/tc_spod_update_selinux_test.go
new file mode 100644
index 000000000..079c48138
--- /dev/null
+++ b/test/tc_spod_update_selinux_test.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package e2e_test
+
+import "time"
+
+func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
+	e.selinuxtOnlyTestCase()
+
+	e.logf("assert selinux is enabled in the spod object")
+	selinuxEnabledInSPODObj := e.kubectl("get", "spod", "spod", "-o", "jsonpath={.spec.enableSelinux}")
+	e.Equal(selinuxEnabledInSPODObj, "true")
+
+	e.logf("assert selinux is enabled in the spod DS")
+	selinuxEnabledInSPODDS := e.kubectlOperatorNS("get", "ds", "spod", "-o", "yaml")
+	e.Contains(selinuxEnabledInSPODDS, "--with-selinux=true")
+
+	e.logf("Disable selinux from SPOD")
+	e.kubectl("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": false}}`, "--type=merge")
+
+	time.Sleep(defaultWaitTime)
+	e.waitInOperatorNSFor("condition=ready", "spod", "spod")
+
+	e.logf("assert selinux is disabled in the spod DS")
+	selinuxDisabledInSPODDS := e.kubectlOperatorNS("get", "ds", "spod", "-o", "yaml")
+	e.NotContains(selinuxDisabledInSPODDS, "--with-selinux=true")
+
+	e.logf("Re-enable selinux in SPOD")
+	e.kubectl("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": true}}`, "--type=merge")
+
+	time.Sleep(defaultWaitTime)
+	e.waitInOperatorNSFor("condition=ready", "spod", "spod")
+
+	e.logf("assert selinux is enabled in the spod DS")
+	selinuxEnabledInSPODDS = e.kubectlOperatorNS("get", "ds", "spod", "-o", "yaml")
+	e.Contains(selinuxEnabledInSPODDS, "--with-selinux=true")
+}