Repository: kubernetes-sigs/security-profiles-operator
Pull request #1714 (2023-05-30 00:02:21)

build(deps): bump github.com/urfave/cli/v2 from 2.25.3 to 2.25.5
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.25.3 to 2.25.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/urfave/cli/releases">github.com/urfave/cli/v2's releases</a>.</em></p>
<blockquote>
<h2>v2.25.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix:(issue_1737) Set bool count by taking care of num of aliases by <a href="https://github.com/dearchap"><code>@​dearchap</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1740">urfave/cli#1740</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/urfave/cli/compare/v2.25.4...v2.25.5">https://github.com/urfave/cli/compare/v2.25.4...v2.25.5</a></p>
<h2>v2.25.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Bug/fix issue 1703 by <a href="https://github.com/jojje"><code>@​jojje</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1728">urfave/cli#1728</a></li>
<li>Fix:(issue_1734) Show categories for subcommands by <a href="https://github.com/dearchap"><code>@​dearchap</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1735">urfave/cli#1735</a></li>
<li>Fix:(issue_1610). Keep RunAsSubcommand behaviour as before by <a href="https://github.com/dearchap"><code>@​dearchap</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1736">urfave/cli#1736</a></li>
<li>Fix:(issue_1731) Add fix for checking if aliases are set by <a href="https://github.com/dearchap"><code>@​dearchap</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1732">urfave/cli#1732</a></li>
<li>Fix func name referenced in doc comment by <a href="https://github.com/meatballhat"><code>@​meatballhat</code></a> in <a href="https://redirect.github.com/urfave/cli/pull/1738">urfave/cli#1738</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jojje"><code>@​jojje</code></a> made their first contribution in <a href="https://redirect.github.com/urfave/cli/pull/1728">urfave/cli#1728</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/urfave/cli/compare/v2.25.3...v2.25.4">https://github.com/urfave/cli/compare/v2.25.3...v2.25.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/urfave/cli/commit/60247a75d701dd881749eb22fc862f26d604ce72"><code>60247a7</code></a> Merge pull request <a href="https://redirect.github.com/urfave/cli/issues/1740">#1740</a> from dearchap/issue_1737</li>
<li><a href="https://github.com/urfave/cli/commit/8dfee9e74789c450660f59db3ce38382c9a012cc"><code>8dfee9e</code></a> Fix:(issue_1737) Set bool count by taking care of num of aliases</li>
<li><a href="https://github.com/urfave/cli/commit/00a9d31a5d2c91ac88347899d7abfa97d511aa5a"><code>00a9d31</code></a> Merge pull request <a href="https://redirect.github.com/urfave/cli/issues/1738">#1738</a> from urfave/v2-fix-doc-func-name</li>
<li><a href="https://github.com/urfave/cli/commit/a35e560116931e810e750f74dac680d5575bdb00"><code>a35e560</code></a> Fix func name referenced in doc comment</li>
<li><a href="https://github.com/urfave/cli/commit/24fa0fda676c313fc1c3e6f5f644afb5d3ee5676"><code>24fa0fd</code></a> Merge pull request <a href="https://redirect.github.com/urfave/cli/issues/1732">#1732</a> from dearchap/issue_1731</li>
<li><a href="https://github.com/urfave/cli/commit/1ba66cfd2fc11e68a30adaf5fc5cf629158fecd0"><code>1ba66cf</code></a> Approve api change</li>
<li><a href="https://github.com/urfave/cli/commit/d9d9022cc0aa6e3de0020908d4c644f66aea7d54"><code>d9d9022</code></a> Merge branch 'v2-maint' into issue_1731</li>
<li><a href="https://github.com/urfave/cli/commit/c1bfd144e5a9ff1c172e35eea23ed6844859dc12"><code>c1bfd14</code></a> Merge pull request <a href="https://redirect.github.com/urfave/cli/issues/1736">#1736</a> from dearchap/issue_1610</li>
<li><a href="https://github.com/urfave/cli/commit/218c1f4af8edf87ecc168e9f2496509a99548a24"><code>218c1f4</code></a> Merge branch 'v2-maint' into issue_1610</li>
<li><a href="https://github.com/urfave/cli/commit/55f63b615c4a3096d9dd9a016d7032ce325e0deb"><code>55f63b6</code></a> Remove deprecation of App.RunAsSubCommand</li>
<li>Additional commits viewable in <a href="https://github.com/urfave/cli/compare/v2.25.3...v2.25.5">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/urfave/cli/v2&package-manager=go_modules&previous-version=2.25.3&new-version=2.25.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

k8s-ci-robot: Hi @dependabot[bot]. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<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.
</details>

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/1714#" title="Author self-approved">dependabot[bot]</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign jaormx for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

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/main/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":["jaormx"]} -->
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/1714?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) Report
> Merging [#1714](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/1714?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) (1e81c86) into [main](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/5fb41495a41f6fb39ffa91f539c5c34644154706?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) (5fb4149) will **not change** coverage.
> The diff coverage is `n/a`.

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


```diff
@@           Coverage Diff           @@
##             main    #1714   +/-   ##
=======================================
  Coverage   49.23%   49.23%           
=======================================
  Files          71       71           
  Lines        6946     6946           
=======================================
  Hits         3420     3420           
  Misses       3396     3396           
  Partials      130      130           
```

</details>
saschagrunert: /retest
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/1714#" title="Author self-approved">dependabot[bot]</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/1714#pullrequestreview-1450232316" title="Approved">saschagrunert</a>*

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).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/main/OWNERS)~~ [saschagrunert]

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":[]} -->

Patch:
diff --git a/api/apparmorprofile/v1alpha1/apparmorprofile_types.go b/api/apparmorprofile/v1alpha1/apparmorprofile_types.go
index 82e6d923c..b48078a2b 100644
--- a/api/apparmorprofile/v1alpha1/apparmorprofile_types.go
+++ b/api/apparmorprofile/v1alpha1/apparmorprofile_types.go
@@ -33,6 +33,9 @@ var (
 
 // AppArmorProfileSpec defines the desired state of AppArmorProfile.
 type AppArmorProfileSpec struct {
+	// Common spec fields for all profiles.
+	profilebasev1alpha1.SpecBase `json:",inline"`
+
 	Policy string `json:"policy,omitempty"`
 }
 
@@ -78,6 +81,14 @@ func (sp *AppArmorProfile) IsPartial() bool {
 	return profilebasev1alpha1.IsPartial(sp)
 }
 
+func (sp *AppArmorProfile) IsDisabled() bool {
+	return profilebasev1alpha1.IsDisabled(&sp.Spec.SpecBase)
+}
+
+func (sp *AppArmorProfile) IsReconcilable() bool {
+	return profilebasev1alpha1.IsReconcilable(sp)
+}
+
 // +kubebuilder:object:root=true
 
 // AppArmorProfileList contains a list of AppArmorProfile.
diff --git a/api/apparmorprofile/v1alpha1/zz_generated.deepcopy.go b/api/apparmorprofile/v1alpha1/zz_generated.deepcopy.go
index 5f6e7518c..422131a54 100644
--- a/api/apparmorprofile/v1alpha1/zz_generated.deepcopy.go
+++ b/api/apparmorprofile/v1alpha1/zz_generated.deepcopy.go
@@ -87,6 +87,7 @@ func (in *AppArmorProfileList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AppArmorProfileSpec) DeepCopyInto(out *AppArmorProfileSpec) {
 	*out = *in
+	out.SpecBase = in.SpecBase
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppArmorProfileSpec.
diff --git a/api/profilebase/v1alpha1/profilebase.go b/api/profilebase/v1alpha1/profilebase.go
index e025b169b..79ed1d83d 100644
--- a/api/profilebase/v1alpha1/profilebase.go
+++ b/api/profilebase/v1alpha1/profilebase.go
@@ -37,6 +37,8 @@ type SecurityProfileBase interface {
 
 	ListProfilesByRecording(ctx context.Context, cli client.Client, recording string) ([]metav1.Object, error)
 	IsPartial() bool
+	IsDisabled() bool
+	IsReconcilable() bool
 }
 
 func IsPartial(obj metav1.Object) bool {
@@ -44,6 +46,14 @@ func IsPartial(obj metav1.Object) bool {
 	return ok
 }
 
+func IsDisabled(prfSpec *SpecBase) bool {
+	return prfSpec.Disabled
+}
+
+func IsReconcilable(prfBase SecurityProfileBase) bool {
+	return !(prfBase.IsDisabled() || prfBase.IsPartial())
+}
+
 func ListProfilesByRecording(
 	ctx context.Context,
 	cli client.Client,
@@ -92,3 +102,10 @@ type StatusBaseUser interface {
 	// implementation
 	SetImplementationStatus()
 }
+
+// SpecBase contains common attributes for a profile's spec.
+type SpecBase struct {
+	// Whether the profile is disabled and should be skipped during reconciliation.
+	// +kubebuilder:default=false
+	Disabled bool `json:"disabled"`
+}
diff --git a/api/profilerecording/v1alpha1/profilerecording_types.go b/api/profilerecording/v1alpha1/profilerecording_types.go
index 4c427d07a..ec4b392ac 100644
--- a/api/profilerecording/v1alpha1/profilerecording_types.go
+++ b/api/profilerecording/v1alpha1/profilerecording_types.go
@@ -84,6 +84,13 @@ type ProfileRecordingSpec struct {
 	// in the pod.
 	// +optional
 	Containers []string `json:"containers,omitempty"`
+
+	// DisableProfileAfterRecording indicates whether the profile should be disabled
+	// after recording and thus skipped during reconcile. In case of SELinux profiles,
+	// reconcile can take a significant amount of time and for all profiles might not be needed.
+	// This Defaults to false.
+	// +kubebuilder:default=false
+	DisableProfileAfterRecording bool `json:"disableProfileAfterRecording"`
 }
 
 // ProfileRecordingStatus contains status of the ProfileRecording.
diff --git a/api/seccompprofile/v1beta1/seccompprofile_types.go b/api/seccompprofile/v1beta1/seccompprofile_types.go
index 4ff579b1c..bc333bc7d 100644
--- a/api/seccompprofile/v1beta1/seccompprofile_types.go
+++ b/api/seccompprofile/v1beta1/seccompprofile_types.go
@@ -40,6 +40,9 @@ const ExtJSON = ".json"
 
 // SeccompProfileSpec defines the desired state of SeccompProfile.
 type SeccompProfileSpec struct {
+	// Common spec fields for all profiles.
+	profilebase.SpecBase `json:",inline"`
+
 	// BaseProfileName is the name of base profile (in the same namespace) that
 	// will be unioned into this profile. Base profiles can be references as
 	// remote OCI artifacts as well when prefixed with `oci://`.
@@ -190,6 +193,14 @@ func (sp *SeccompProfile) IsPartial() bool {
 	return profilebase.IsPartial(sp)
 }
 
+func (sp *SeccompProfile) IsDisabled() bool {
+	return profilebase.IsDisabled(&sp.Spec.SpecBase)
+}
+
+func (sp *SeccompProfile) IsReconcilable() bool {
+	return profilebase.IsReconcilable(sp)
+}
+
 // +kubebuilder:object:root=true
 
 // SeccompProfileList contains a list of SeccompProfile.
diff --git a/api/seccompprofile/v1beta1/zz_generated.deepcopy.go b/api/seccompprofile/v1beta1/zz_generated.deepcopy.go
index 9646ceabb..67234d98e 100644
--- a/api/seccompprofile/v1beta1/zz_generated.deepcopy.go
+++ b/api/seccompprofile/v1beta1/zz_generated.deepcopy.go
@@ -102,6 +102,7 @@ func (in *SeccompProfileList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SeccompProfileSpec) DeepCopyInto(out *SeccompProfileSpec) {
 	*out = *in
+	out.SpecBase = in.SpecBase
 	if in.Architectures != nil {
 		in, out := &in.Architectures, &out.Architectures
 		*out = make([]Arch, len(*in))
diff --git a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
index 74c9be6cf..d29051e44 100644
--- a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
+++ b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
@@ -28,6 +28,8 @@ type ProfileState string
 const (
 	// The profile is pending installation.
 	ProfileStatePartial ProfileState = "Partial"
+	// The profile is not enabled and won't be reconciled.
+	ProfileStateDisabled ProfileState = "Disabled"
 	// The profile is pending installation.
 	ProfileStatePending ProfileState = "Pending"
 	// The profile is being installed.
@@ -66,9 +68,10 @@ func LowerOfTwoStates(currentLowest, candidate ProfileState) ProfileState {
 	orderedStates[ProfileStateError] = 0       // error must always have the lowest index
 	orderedStates[ProfileStateTerminating] = 1 // If one is set as terminating; all the statuses will end here too
 	orderedStates[ProfileStatePartial] = 2
-	orderedStates[ProfileStatePending] = 3
-	orderedStates[ProfileStateInProgress] = 4
-	orderedStates[ProfileStateInstalled] = 5
+	orderedStates[ProfileStateDisabled] = 3
+	orderedStates[ProfileStatePending] = 4
+	orderedStates[ProfileStateInProgress] = 5
+	orderedStates[ProfileStateInstalled] = 6
 
 	if orderedStates[currentLowest] > orderedStates[candidate] {
 		return candidate
diff --git a/api/selinuxprofile/v1alpha2/rawselinuxprofile_types.go b/api/selinuxprofile/v1alpha2/rawselinuxprofile_types.go
index 1bc9ffbe2..dd40598d9 100644
--- a/api/selinuxprofile/v1alpha2/rawselinuxprofile_types.go
+++ b/api/selinuxprofile/v1alpha2/rawselinuxprofile_types.go
@@ -33,6 +33,9 @@ var (
 
 // RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
 type RawSelinuxProfileSpec struct {
+	// Common spec fields for all profiles.
+	profilebasev1alpha1.SpecBase `json:",inline"`
+
 	Policy string `json:"policy,omitempty"`
 }
 
@@ -87,6 +90,14 @@ func (sp *RawSelinuxProfile) IsPartial() bool {
 	return profilebasev1alpha1.IsPartial(sp)
 }
 
+func (sp *RawSelinuxProfile) IsDisabled() bool {
+	return profilebasev1alpha1.IsDisabled(&sp.Spec.SpecBase)
+}
+
+func (sp *RawSelinuxProfile) IsReconcilable() bool {
+	return profilebasev1alpha1.IsReconcilable(sp)
+}
+
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // RawSelinuxProfileList contains a list of RawSelinuxProfile.
diff --git a/api/selinuxprofile/v1alpha2/selinuxprofile_types.go b/api/selinuxprofile/v1alpha2/selinuxprofile_types.go
index a93f0a7e7..4740792d5 100644
--- a/api/selinuxprofile/v1alpha2/selinuxprofile_types.go
+++ b/api/selinuxprofile/v1alpha2/selinuxprofile_types.go
@@ -52,6 +52,9 @@ type PolicyRef struct {
 
 // SelinuxProfileSpec defines the desired state of SelinuxProfile.
 type SelinuxProfileSpec struct {
+	// Common spec fields for all profiles.
+	profilebasev1alpha1.SpecBase `json:",inline"`
+
 	// A SELinuxProfile or set of profiles that this inherits from.
 	// Note that they need to be in the same namespace.
 	// +optional
@@ -81,7 +84,9 @@ type Allow map[LabelKey]map[ObjectClassKey]PermissionSet
 
 // SelinuxProfileStatus defines the observed state of SelinuxProfile.
 type SelinuxProfileStatus struct {
+	// Common status fields for all profiles.
 	profilebasev1alpha1.StatusBase `json:",inline"`
+
 	// Represents the string that the SelinuxProfile object can be
 	// referenced as in a pod seLinuxOptions section.
 	Usage           string   `json:"usage,omitempty"`
@@ -140,6 +145,14 @@ func (sp *SelinuxProfile) IsPartial() bool {
 	return profilebasev1alpha1.IsPartial(sp)
 }
 
+func (sp *SelinuxProfile) IsDisabled() bool {
+	return profilebasev1alpha1.IsDisabled(&sp.Spec.SpecBase)
+}
+
+func (sp *SelinuxProfile) IsReconcilable() bool {
+	return profilebasev1alpha1.IsReconcilable(sp)
+}
+
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // SelinuxProfileList contains a list of SelinuxProfile.
diff --git a/api/selinuxprofile/v1alpha2/zz_generated.deepcopy.go b/api/selinuxprofile/v1alpha2/zz_generated.deepcopy.go
index fdb5b31ec..47cd11791 100644
--- a/api/selinuxprofile/v1alpha2/zz_generated.deepcopy.go
+++ b/api/selinuxprofile/v1alpha2/zz_generated.deepcopy.go
@@ -160,6 +160,7 @@ func (in *RawSelinuxProfileList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RawSelinuxProfileSpec) DeepCopyInto(out *RawSelinuxProfileSpec) {
 	*out = *in
+	out.SpecBase = in.SpecBase
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawSelinuxProfileSpec.
@@ -234,6 +235,7 @@ func (in *SelinuxProfileList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SelinuxProfileSpec) DeepCopyInto(out *SelinuxProfileSpec) {
 	*out = *in
+	out.SpecBase = in.SpecBase
 	if in.Inherit != nil {
 		in, out := &in.Inherit, &out.Inherit
 		*out = make([]PolicyRef, len(*in))
diff --git a/bundle/manifests/security-profiles-operator.x-k8s.io_profilerecordings.yaml b/bundle/manifests/security-profiles-operator.x-k8s.io_profilerecordings.yaml
index d2db30c7c..1b9a372ae 100644
--- a/bundle/manifests/security-profiles-operator.x-k8s.io_profilerecordings.yaml
+++ b/bundle/manifests/security-profiles-operator.x-k8s.io_profilerecordings.yaml
@@ -48,6 +48,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -116,6 +124,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
diff --git a/bundle/manifests/security-profiles-operator.x-k8s.io_rawselinuxprofiles.yaml b/bundle/manifests/security-profiles-operator.x-k8s.io_rawselinuxprofiles.yaml
index 65eda44e2..544ad05ac 100644
--- a/bundle/manifests/security-profiles-operator.x-k8s.io_rawselinuxprofiles.yaml
+++ b/bundle/manifests/security-profiles-operator.x-k8s.io_rawselinuxprofiles.yaml
@@ -43,8 +43,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/bundle/manifests/security-profiles-operator.x-k8s.io_seccompprofiles.yaml b/bundle/manifests/security-profiles-operator.x-k8s.io_seccompprofiles.yaml
index 66afa1e1f..2d9ab2f23 100644
--- a/bundle/manifests/security-profiles-operator.x-k8s.io_seccompprofiles.yaml
+++ b/bundle/manifests/security-profiles-operator.x-k8s.io_seccompprofiles.yaml
@@ -95,6 +95,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -187,6 +192,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
diff --git a/bundle/manifests/security-profiles-operator.x-k8s.io_selinuxprofiles.yaml b/bundle/manifests/security-profiles-operator.x-k8s.io_selinuxprofiles.yaml
index 7f1c2f8ce..563d31265 100644
--- a/bundle/manifests/security-profiles-operator.x-k8s.io_selinuxprofiles.yaml
+++ b/bundle/manifests/security-profiles-operator.x-k8s.io_selinuxprofiles.yaml
@@ -52,6 +52,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -82,6 +87,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/base-crds/crds/profilerecording.yaml b/deploy/base-crds/crds/profilerecording.yaml
index 5008b6a5a..e5ba24e2a 100644
--- a/deploy/base-crds/crds/profilerecording.yaml
+++ b/deploy/base-crds/crds/profilerecording.yaml
@@ -45,6 +45,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -113,6 +121,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
diff --git a/deploy/base-crds/crds/seccompprofile.yaml b/deploy/base-crds/crds/seccompprofile.yaml
index ccb00f753..19b879775 100644
--- a/deploy/base-crds/crds/seccompprofile.yaml
+++ b/deploy/base-crds/crds/seccompprofile.yaml
@@ -92,6 +92,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -184,6 +189,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
diff --git a/deploy/base-crds/crds/selinuxpolicy.yaml b/deploy/base-crds/crds/selinuxpolicy.yaml
index 5dfa81590..7c3651a67 100644
--- a/deploy/base-crds/crds/selinuxpolicy.yaml
+++ b/deploy/base-crds/crds/selinuxpolicy.yaml
@@ -40,8 +40,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -187,6 +194,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -217,6 +229,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/helm/crds/crds.yaml b/deploy/helm/crds/crds.yaml
index 1fe608c40..5d0db1173 100644
--- a/deploy/helm/crds/crds.yaml
+++ b/deploy/helm/crds/crds.yaml
@@ -123,6 +123,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -191,6 +199,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -305,6 +314,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -397,6 +411,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -1844,8 +1859,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -1993,6 +2015,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2023,6 +2050,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/namespace-operator.yaml b/deploy/namespace-operator.yaml
index e901c6ac7..5714a91c9 100644
--- a/deploy/namespace-operator.yaml
+++ b/deploy/namespace-operator.yaml
@@ -123,6 +123,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -191,6 +199,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -305,6 +314,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -397,6 +411,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -1844,8 +1859,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -1993,6 +2015,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2023,6 +2050,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/openshift-dev.yaml b/deploy/openshift-dev.yaml
index ac9297ef6..a53106c15 100644
--- a/deploy/openshift-dev.yaml
+++ b/deploy/openshift-dev.yaml
@@ -194,6 +194,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -262,6 +270,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -324,8 +333,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -516,6 +532,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -608,6 +629,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -2064,6 +2086,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2094,6 +2121,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/openshift-downstream.yaml b/deploy/openshift-downstream.yaml
index d5bbec491..a66a25dc4 100644
--- a/deploy/openshift-downstream.yaml
+++ b/deploy/openshift-downstream.yaml
@@ -123,6 +123,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -191,6 +199,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -305,6 +314,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -397,6 +411,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -1844,8 +1859,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -1993,6 +2015,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2023,6 +2050,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
index dd26788ae..5ed35dcdf 100644
--- a/deploy/operator.yaml
+++ b/deploy/operator.yaml
@@ -123,6 +123,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -191,6 +199,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -305,6 +314,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -397,6 +411,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -1844,8 +1859,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -1993,6 +2015,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2023,6 +2050,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/deploy/webhook-operator.yaml b/deploy/webhook-operator.yaml
index 8a7808ec1..e896d525e 100644
--- a/deploy/webhook-operator.yaml
+++ b/deploy/webhook-operator.yaml
@@ -194,6 +194,14 @@ spec:
                 items:
                   type: string
                 type: array
+              disableProfileAfterRecording:
+                default: false
+                description: DisableProfileAfterRecording indicates whether the profile
+                  should be disabled after recording and thus skipped during reconcile.
+                  In case of SELinux profiles, reconcile can take a significant amount
+                  of time and for all profiles might not be needed. This Defaults
+                  to false.
+                type: boolean
               kind:
                 description: Kind of object to be recorded.
                 enum:
@@ -262,6 +270,7 @@ spec:
                 - logs
                 type: string
             required:
+            - disableProfileAfterRecording
             - kind
             - podSelector
             - recorder
@@ -324,8 +333,15 @@ spec:
           spec:
             description: RawSelinuxProfileSpec defines the desired state of RawSelinuxProfile.
             properties:
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               policy:
                 type: string
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
@@ -516,6 +532,11 @@ spec:
                 - SCMP_ACT_LOG
                 - SCMP_ACT_NOTIFY
                 type: string
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               flags:
                 description: list of flags to use with seccomp(2)
                 items:
@@ -608,6 +629,7 @@ spec:
                 type: array
             required:
             - defaultAction
+            - disabled
             type: object
           status:
             description: SeccompProfileStatus contains status of the deployed SeccompProfile.
@@ -2064,6 +2086,11 @@ spec:
                   type: object
                 description: Defines the allow policy for the profile
                 type: object
+              disabled:
+                default: false
+                description: Whether the profile is disabled and should be skipped
+                  during reconciliation.
+                type: boolean
               inherit:
                 default:
                 - kind: System
@@ -2094,6 +2121,8 @@ spec:
                 description: Permissive, when true will cause the SELinux profile
                   to only log violations instead of enforcing them.
                 type: boolean
+            required:
+            - disabled
             type: object
           status:
             description: SelinuxProfileStatus defines the observed state of SelinuxProfile.
diff --git a/go.mod b/go.mod
index b8fbe0151..d65526484 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/seccomp/libseccomp-golang v0.10.0
 	github.com/sigstore/cosign/v2 v2.0.2
 	github.com/stretchr/testify v1.8.3
-	github.com/urfave/cli/v2 v2.25.3
+	github.com/urfave/cli/v2 v2.25.5
 	golang.org/x/mod v0.10.0
 	golang.org/x/net v0.10.0
 	golang.org/x/sync v0.2.0
diff --git a/go.sum b/go.sum
index 312c92afa..415927d91 100644
--- a/go.sum
+++ b/go.sum
@@ -910,8 +910,8 @@ github.com/transparency-dev/merkle v0.0.1 h1:T9/9gYB8uZl7VOJIhdwjALeRWlxUxSfDEys
 github.com/transparency-dev/merkle v0.0.1/go.mod h1:B8FIw5LTq6DaULoHsVFRzYIUDkl8yuSwCdZnOZGKL/A=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
-github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
-github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc=
+github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
 github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
 github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
 github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
diff --git a/installation-usage.md b/installation-usage.md
index 6440043ef..91c88d08b 100644
--- a/installation-usage.md
+++ b/installation-usage.md
@@ -1,7 +1,6 @@
 # Installation and Usage
 
 <!-- toc -->
-
 - [Features](#features)
 - [Architecture](#architecture)
 - [Tutorials and Demos](#tutorials-and-demos)
@@ -32,6 +31,7 @@
     - [Log enricher based recording](#log-enricher-based-recording)
     - [eBPF based recording](#ebpf-based-recording)
     - [Merging per-container profile instances](#merging-per-container-profile-instances)
+    - [Recording profiles without applying them](#recording-profiles-without-applying-them)
     - [Disable profile recording](#disable-profile-recording)
 - [Create a SELinux Profile](#create-a-selinux-profile)
   - [Apply a SELinux profile to a pod](#apply-a-selinux-profile-to-a-pod)
@@ -973,6 +973,25 @@ including the `mknod` syscall:
   - mknod
 ```
 
+#### Recording profiles without applying them
+
+In some cases, it might be desirable to record security profiles, but not install them.
+Use-cases might include recording profiles in a CI system where the profiles would be
+deployed in a subsequent verify run or recording profiles as part of a build process where the
+profile would be deployed by the end-user.
+
+To record profiles without installing them, set the `disableProfileAfterRecording`
+attribute to `true` in the `ProfileRecording` CR.  This option defaults to `false`, which
+is the default behavior of the operator to install the profiles. When `disableProfileAfterRecording`
+is set to `true`, the operator will not reconcile the profiles and will not install them. Partial
+disabled profiles can still be merged and the resulting merged profile will be disabled.
+
+On the profile level, this functionality is controlled by the `disabled` flag - it is also possible
+to create profile CRs disabled, although this functionality is probably less interesting to end users
+and is mostly used for testing purposes. The `disabled` flag is set to `false` by default. Profiles
+that are disabled, either explicitly or by the `disableProfileAfterRecording` flag, can be enabled 
+by setting the `disabled` flag to `false` in the profile CR.
+
 #### Disable profile recording
 
 Profile recorder controller along with the corresponding sidecar container is disabled
diff --git a/internal/pkg/daemon/profilerecorder/impl.go b/internal/pkg/daemon/profilerecorder/impl.go
index 6afce7e51..9ac7704e2 100644
--- a/internal/pkg/daemon/profilerecorder/impl.go
+++ b/internal/pkg/daemon/profilerecorder/impl.go
@@ -34,6 +34,7 @@ import (
 
 	bpfrecorderapi "sigs.k8s.io/security-profiles-operator/api/grpc/bpfrecorder"
 	enricherapi "sigs.k8s.io/security-profiles-operator/api/grpc/enricher"
+	profilerecording1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1"
 	spodapi "sigs.k8s.io/security-profiles-operator/api/spod/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/bpfrecorder"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/common"
@@ -79,6 +80,7 @@ type impl interface {
 		context.Context, enricherapi.EnricherClient, *enricherapi.AvcRequest,
 	) error
 	DialEnricher() (*grpc.ClientConn, context.CancelFunc, error)
+	GetRecording(context.Context, client.Client, client.ObjectKey) (*profilerecording1alpha1.ProfileRecording, error)
 }
 
 func (*defaultImpl) NewClient(mgr ctrl.Manager) (client.Client, error) {
@@ -202,3 +204,14 @@ func (*defaultImpl) ResetAvcs(
 func (*defaultImpl) DialEnricher() (*grpc.ClientConn, context.CancelFunc, error) {
 	return enricher.Dial()
 }
+
+func (*defaultImpl) GetRecording(
+	ctx context.Context,
+	cli client.Client,
+	key client.ObjectKey,
+) (*profilerecording1alpha1.ProfileRecording, error) {
+	recording := profilerecording1alpha1.ProfileRecording{}
+
+	err := cli.Get(ctx, key, &recording)
+	return &recording, err
+}
diff --git a/internal/pkg/daemon/profilerecorder/profilerecorder.go b/internal/pkg/daemon/profilerecorder/profilerecorder.go
index 9415aff48..3c2421da8 100644
--- a/internal/pkg/daemon/profilerecorder/profilerecorder.go
+++ b/internal/pkg/daemon/profilerecorder/profilerecorder.go
@@ -524,6 +524,14 @@ func (r *RecorderReconciler) collectLogSeccompProfile(
 		Spec: profileSpec,
 	}
 
+	if err := r.setDisabled(ctx, r.client,
+		parsedProfileName.profileName, profileNamespacedName.Namespace,
+		&profileSpec.SpecBase); err != nil {
+		r.log.Error(err, "Cannot set the enabled flag")
+		r.record.Event(profile, util.EventTypeWarning, reasonProfileCreationFailed, err.Error())
+		return fmt.Errorf("format selinuxprofile resource: %w", err)
+	}
+
 	res, err := r.CreateOrUpdate(ctx, r.client, profile,
 		func() error {
 			profile.Spec = profileSpec
@@ -613,6 +621,14 @@ func (r *RecorderReconciler) collectLogSelinuxProfile(
 	}
 	r.log.Info("Created", "profile", profile)
 
+	if err := r.setDisabled(ctx, r.client,
+		parsedProfileName.profileName, profileNamespacedName.Namespace,
+		&selinuxProfileSpec.SpecBase); err != nil {
+		r.log.Error(err, "Cannot set the enabled flag")
+		r.record.Event(profile, util.EventTypeWarning, reasonProfileCreationFailed, err.Error())
+		return fmt.Errorf("format selinuxprofile resource: %w", err)
+	}
+
 	res, err := r.CreateOrUpdate(ctx, r.client, profile,
 		func() error {
 			profile.Spec = selinuxProfileSpec
@@ -731,6 +747,14 @@ func (r *RecorderReconciler) collectBpfProfiles(
 			Spec: profileSpec,
 		}
 
+		if err := r.setDisabled(ctx, r.client,
+			parsedProfileName.profileName, profileNamespacedName.Namespace,
+			&profileSpec.SpecBase); err != nil {
+			r.log.Error(err, "Cannot set the enabled flag")
+			r.record.Event(profile, util.EventTypeWarning, reasonProfileCreationFailed, err.Error())
+			return fmt.Errorf("format selinuxprofile resource: %w", err)
+		}
+
 		res, err := r.CreateOrUpdate(ctx, r.client, profile,
 			func() error {
 				profile.Spec = profileSpec
@@ -1005,6 +1029,21 @@ func profileLabels(
 	return labels, nil
 }
 
+func (r *RecorderReconciler) setDisabled(
+	ctx context.Context,
+	cli client.Client,
+	profileRecordingName, namespace string,
+	profileSpecBase *profilebase.SpecBase,
+) error {
+	recording, err := r.GetRecording(ctx, cli, types.NamespacedName{Name: profileRecordingName, Namespace: namespace})
+	if err != nil {
+		return fmt.Errorf("get recording: %w", err)
+	}
+
+	profileSpecBase.Disabled = recording.Spec.DisableProfileAfterRecording
+	return nil
+}
+
 func (r *RecorderReconciler) setRecordingFinalizers(
 	ctx context.Context,
 	labels map[string]string,
diff --git a/internal/pkg/daemon/profilerecorder/profilerecorderfakes/fake_impl.go b/internal/pkg/daemon/profilerecorder/profilerecorderfakes/fake_impl.go
index 25c14f073..30de98aa1 100644
--- a/internal/pkg/daemon/profilerecorder/profilerecorderfakes/fake_impl.go
+++ b/internal/pkg/daemon/profilerecorder/profilerecorderfakes/fake_impl.go
@@ -33,7 +33,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 	api_bpfrecorder "sigs.k8s.io/security-profiles-operator/api/grpc/bpfrecorder"
 	api_enricher "sigs.k8s.io/security-profiles-operator/api/grpc/enricher"
-	"sigs.k8s.io/security-profiles-operator/api/spod/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1"
+	v1alpha1a "sigs.k8s.io/security-profiles-operator/api/spod/v1alpha1"
 )
 
 type FakeImpl struct {
@@ -125,18 +126,33 @@ type FakeImpl struct {
 		result1 *v1.Pod
 		result2 error
 	}
-	GetSPODStub        func(context.Context, client.Client) (*v1alpha1.SecurityProfilesOperatorDaemon, error)
+	GetRecordingStub        func(context.Context, client.Client, types.NamespacedName) (*v1alpha1.ProfileRecording, error)
+	getRecordingMutex       sync.RWMutex
+	getRecordingArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Client
+		arg3 types.NamespacedName
+	}
+	getRecordingReturns struct {
+		result1 *v1alpha1.ProfileRecording
+		result2 error
+	}
+	getRecordingReturnsOnCall map[int]struct {
+		result1 *v1alpha1.ProfileRecording
+		result2 error
+	}
+	GetSPODStub        func(context.Context, client.Client) (*v1alpha1a.SecurityProfilesOperatorDaemon, error)
 	getSPODMutex       sync.RWMutex
 	getSPODArgsForCall []struct {
 		arg1 context.Context
 		arg2 client.Client
 	}
 	getSPODReturns struct {
-		result1 *v1alpha1.SecurityProfilesOperatorDaemon
+		result1 *v1alpha1a.SecurityProfilesOperatorDaemon
 		result2 error
 	}
 	getSPODReturnsOnCall map[int]struct {
-		result1 *v1alpha1.SecurityProfilesOperatorDaemon
+		result1 *v1alpha1a.SecurityProfilesOperatorDaemon
 		result2 error
 	}
 	GoArchToSeccompArchStub        func(string) (seccomp.Arch, error)
@@ -668,7 +684,73 @@ func (fake *FakeImpl) GetPodReturnsOnCall(i int, result1 *v1.Pod, result2 error)
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) GetSPOD(arg1 context.Context, arg2 client.Client) (*v1alpha1.SecurityProfilesOperatorDaemon, error) {
+func (fake *FakeImpl) GetRecording(arg1 context.Context, arg2 client.Client, arg3 types.NamespacedName) (*v1alpha1.ProfileRecording, error) {
+	fake.getRecordingMutex.Lock()
+	ret, specificReturn := fake.getRecordingReturnsOnCall[len(fake.getRecordingArgsForCall)]
+	fake.getRecordingArgsForCall = append(fake.getRecordingArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Client
+		arg3 types.NamespacedName
+	}{arg1, arg2, arg3})
+	stub := fake.GetRecordingStub
+	fakeReturns := fake.getRecordingReturns
+	fake.recordInvocation("GetRecording", []interface{}{arg1, arg2, arg3})
+	fake.getRecordingMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeImpl) GetRecordingCallCount() int {
+	fake.getRecordingMutex.RLock()
+	defer fake.getRecordingMutex.RUnlock()
+	return len(fake.getRecordingArgsForCall)
+}
+
+func (fake *FakeImpl) GetRecordingCalls(stub func(context.Context, client.Client, types.NamespacedName) (*v1alpha1.ProfileRecording, error)) {
+	fake.getRecordingMutex.Lock()
+	defer fake.getRecordingMutex.Unlock()
+	fake.GetRecordingStub = stub
+}
+
+func (fake *FakeImpl) GetRecordingArgsForCall(i int) (context.Context, client.Client, types.NamespacedName) {
+	fake.getRecordingMutex.RLock()
+	defer fake.getRecordingMutex.RUnlock()
+	argsForCall := fake.getRecordingArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *FakeImpl) GetRecordingReturns(result1 *v1alpha1.ProfileRecording, result2 error) {
+	fake.getRecordingMutex.Lock()
+	defer fake.getRecordingMutex.Unlock()
+	fake.GetRecordingStub = nil
+	fake.getRecordingReturns = struct {
+		result1 *v1alpha1.ProfileRecording
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) GetRecordingReturnsOnCall(i int, result1 *v1alpha1.ProfileRecording, result2 error) {
+	fake.getRecordingMutex.Lock()
+	defer fake.getRecordingMutex.Unlock()
+	fake.GetRecordingStub = nil
+	if fake.getRecordingReturnsOnCall == nil {
+		fake.getRecordingReturnsOnCall = make(map[int]struct {
+			result1 *v1alpha1.ProfileRecording
+			result2 error
+		})
+	}
+	fake.getRecordingReturnsOnCall[i] = struct {
+		result1 *v1alpha1.ProfileRecording
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) GetSPOD(arg1 context.Context, arg2 client.Client) (*v1alpha1a.SecurityProfilesOperatorDaemon, error) {
 	fake.getSPODMutex.Lock()
 	ret, specificReturn := fake.getSPODReturnsOnCall[len(fake.getSPODArgsForCall)]
 	fake.getSPODArgsForCall = append(fake.getSPODArgsForCall, struct {
@@ -694,7 +776,7 @@ func (fake *FakeImpl) GetSPODCallCount() int {
 	return len(fake.getSPODArgsForCall)
 }
 
-func (fake *FakeImpl) GetSPODCalls(stub func(context.Context, client.Client) (*v1alpha1.SecurityProfilesOperatorDaemon, error)) {
+func (fake *FakeImpl) GetSPODCalls(stub func(context.Context, client.Client) (*v1alpha1a.SecurityProfilesOperatorDaemon, error)) {
 	fake.getSPODMutex.Lock()
 	defer fake.getSPODMutex.Unlock()
 	fake.GetSPODStub = stub
@@ -707,28 +789,28 @@ func (fake *FakeImpl) GetSPODArgsForCall(i int) (context.Context, client.Client)
 	return argsForCall.arg1, argsForCall.arg2
 }
 
-func (fake *FakeImpl) GetSPODReturns(result1 *v1alpha1.SecurityProfilesOperatorDaemon, result2 error) {
+func (fake *FakeImpl) GetSPODReturns(result1 *v1alpha1a.SecurityProfilesOperatorDaemon, result2 error) {
 	fake.getSPODMutex.Lock()
 	defer fake.getSPODMutex.Unlock()
 	fake.GetSPODStub = nil
 	fake.getSPODReturns = struct {
-		result1 *v1alpha1.SecurityProfilesOperatorDaemon
+		result1 *v1alpha1a.SecurityProfilesOperatorDaemon
 		result2 error
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) GetSPODReturnsOnCall(i int, result1 *v1alpha1.SecurityProfilesOperatorDaemon, result2 error) {
+func (fake *FakeImpl) GetSPODReturnsOnCall(i int, result1 *v1alpha1a.SecurityProfilesOperatorDaemon, result2 error) {
 	fake.getSPODMutex.Lock()
 	defer fake.getSPODMutex.Unlock()
 	fake.GetSPODStub = nil
 	if fake.getSPODReturnsOnCall == nil {
 		fake.getSPODReturnsOnCall = make(map[int]struct {
-			result1 *v1alpha1.SecurityProfilesOperatorDaemon
+			result1 *v1alpha1a.SecurityProfilesOperatorDaemon
 			result2 error
 		})
 	}
 	fake.getSPODReturnsOnCall[i] = struct {
-		result1 *v1alpha1.SecurityProfilesOperatorDaemon
+		result1 *v1alpha1a.SecurityProfilesOperatorDaemon
 		result2 error
 	}{result1, result2}
 }
@@ -1446,6 +1528,8 @@ func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	defer fake.dialEnricherMutex.RUnlock()
 	fake.getPodMutex.RLock()
 	defer fake.getPodMutex.RUnlock()
+	fake.getRecordingMutex.RLock()
+	defer fake.getRecordingMutex.RUnlock()
 	fake.getSPODMutex.RLock()
 	defer fake.getSPODMutex.RUnlock()
 	fake.goArchToSeccompArchMutex.RLock()
diff --git a/internal/pkg/daemon/seccompprofile/seccompprofile.go b/internal/pkg/daemon/seccompprofile/seccompprofile.go
index 4a6556f9b..bd7eeac8e 100644
--- a/internal/pkg/daemon/seccompprofile/seccompprofile.go
+++ b/internal/pkg/daemon/seccompprofile/seccompprofile.go
@@ -460,8 +460,8 @@ func (r *Reconciler) reconcileSeccompProfile(
 		return reconcile.Result{RequeueAfter: wait}, nil
 	}
 
-	if sp.IsPartial() {
-		l.Info("Profile is partial, skipping")
+	if !sp.IsReconcilable() {
+		l.Info("Profile is partial or disabled, skipping")
 		return reconcile.Result{}, nil
 	}
 
diff --git a/internal/pkg/daemon/selinuxprofile/common_controller.go b/internal/pkg/daemon/selinuxprofile/common_controller.go
index c732fd35e..d43a29aec 100644
--- a/internal/pkg/daemon/selinuxprofile/common_controller.go
+++ b/internal/pkg/daemon/selinuxprofile/common_controller.go
@@ -252,8 +252,8 @@ func (r *ReconcileSelinux) reconcilePolicy(
 		return reconcile.Result{}, nil
 	}
 
-	if sp.IsPartial() {
-		l.Info("Profile is partial, skipping")
+	if !sp.IsReconcilable() {
+		l.Info("Profile is partial or disabled, skipping")
 		return reconcile.Result{}, nil
 	}
 
diff --git a/internal/pkg/manager/nodestatus/nodestatus.go b/internal/pkg/manager/nodestatus/nodestatus.go
index c865e4e85..829224c10 100644
--- a/internal/pkg/manager/nodestatus/nodestatus.go
+++ b/internal/pkg/manager/nodestatus/nodestatus.go
@@ -306,6 +306,9 @@ func (r *StatusReconciler) reconcileStatus(
 	case statusv1alpha1.ProfileStatePartial:
 		outStatus.Status = statusv1alpha1.ProfileStatePartial
 		outStatus.SetConditions(spodv1alpha1.Unavailable())
+	case statusv1alpha1.ProfileStateDisabled:
+		outStatus.Status = statusv1alpha1.ProfileStateDisabled
+		outStatus.SetConditions(spodv1alpha1.Unavailable())
 	}
 
 	l.V(config.VerboseLevel).Info("Updating status")
diff --git a/internal/pkg/nodestatus/nodestatus.go b/internal/pkg/nodestatus/nodestatus.go
index b4d9570b2..65d985e8a 100644
--- a/internal/pkg/nodestatus/nodestatus.go
+++ b/internal/pkg/nodestatus/nodestatus.go
@@ -128,11 +128,7 @@ func (nsf *StatusClient) statusObj(
 }
 
 func (nsf *StatusClient) createNodeStatus(ctx context.Context) error {
-	state := secprofnodestatusv1alpha1.ProfileStatePending
-	if nsf.pol.IsPartial() {
-		state = secprofnodestatusv1alpha1.ProfileStatePartial
-	}
-	s := nsf.statusObj(state)
+	s := nsf.statusObj(nsf.initialStatus())
 
 	if setCtrlErr := controllerutil.SetControllerReference(nsf.pol, s, nsf.client.Scheme()); setCtrlErr != nil {
 		return fmt.Errorf("cannot set node status owner reference: %s: %w", nsf.pol.GetName(), setCtrlErr)
@@ -145,6 +141,15 @@ func (nsf *StatusClient) createNodeStatus(ctx context.Context) error {
 	return nil
 }
 
+func (nsf *StatusClient) initialStatus() secprofnodestatusv1alpha1.ProfileState {
+	if nsf.pol.IsDisabled() {
+		return secprofnodestatusv1alpha1.ProfileStateDisabled
+	} else if nsf.pol.IsPartial() {
+		return secprofnodestatusv1alpha1.ProfileStatePartial
+	}
+	return secprofnodestatusv1alpha1.ProfileStatePending
+}
+
 func (nsf *StatusClient) Remove(ctx context.Context, c client.Client) error {
 	// if finalizer exists, remove it
 	if nsf.pol.IsPartial() {
diff --git a/vendor/github.com/urfave/cli/v2/app.go b/vendor/github.com/urfave/cli/v2/app.go
index 4b6675a2d..19b76c9b6 100644
--- a/vendor/github.com/urfave/cli/v2/app.go
+++ b/vendor/github.com/urfave/cli/v2/app.go
@@ -332,11 +332,18 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
 	return a.rootCommand.Run(cCtx, arguments...)
 }
 
-// This is a stub function to keep public API unchanged from old code
-//
-// Deprecated: use App.Run or App.RunContext
+// RunAsSubcommand is for legacy/compatibility purposes only. New code should only
+// use App.RunContext. This function is slated to be removed in v3.
 func (a *App) RunAsSubcommand(ctx *Context) (err error) {
-	return a.RunContext(ctx.Context, ctx.Args().Slice())
+	a.Setup()
+
+	cCtx := NewContext(a, nil, ctx)
+	cCtx.shellComplete = ctx.shellComplete
+
+	a.rootCommand = a.newRootCommand()
+	cCtx.Command = a.rootCommand
+
+	return a.rootCommand.Run(cCtx, ctx.Args().Slice()...)
 }
 
 func (a *App) suggestFlagFromError(err error, command string) (string, error) {
diff --git a/vendor/github.com/urfave/cli/v2/context.go b/vendor/github.com/urfave/cli/v2/context.go
index cf5e58fe7..a45c120b5 100644
--- a/vendor/github.com/urfave/cli/v2/context.go
+++ b/vendor/github.com/urfave/cli/v2/context.go
@@ -56,6 +56,7 @@ func (cCtx *Context) Set(name, value string) error {
 
 // IsSet determines if the flag was actually set
 func (cCtx *Context) IsSet(name string) bool {
+
 	if fs := cCtx.lookupFlagSet(name); fs != nil {
 		isSet := false
 		fs.Visit(func(f *flag.Flag) {
@@ -72,7 +73,23 @@ func (cCtx *Context) IsSet(name string) bool {
 			return false
 		}
 
-		return f.IsSet()
+		if f.IsSet() {
+			return true
+		}
+
+		// now redo flagset search on aliases
+		aliases := f.Names()
+		fs.Visit(func(f *flag.Flag) {
+			for _, alias := range aliases {
+				if f.Name == alias {
+					isSet = true
+				}
+			}
+		})
+
+		if isSet {
+			return true
+		}
 	}
 
 	return false
diff --git a/vendor/github.com/urfave/cli/v2/flag_bool.go b/vendor/github.com/urfave/cli/v2/flag_bool.go
index f64d1cd92..369d18b77 100644
--- a/vendor/github.com/urfave/cli/v2/flag_bool.go
+++ b/vendor/github.com/urfave/cli/v2/flag_bool.go
@@ -52,7 +52,7 @@ func (b *boolValue) String() string {
 func (b *boolValue) IsBoolFlag() bool { return true }
 
 func (b *boolValue) Count() int {
-	if b.count != nil {
+	if b.count != nil && *b.count > 0 {
 		return *b.count
 	}
 	return 0
@@ -130,6 +130,11 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
 	if count == nil {
 		count = new(int)
 	}
+
+	// since count will be incremented for each alias as well
+	// subtract number of aliases from overall count
+	*count -= len(f.Aliases)
+
 	if dest == nil {
 		dest = new(bool)
 	}
diff --git a/vendor/github.com/urfave/cli/v2/godoc-current.txt b/vendor/github.com/urfave/cli/v2/godoc-current.txt
index bd5e1defc..6016bd82e 100644
--- a/vendor/github.com/urfave/cli/v2/godoc-current.txt
+++ b/vendor/github.com/urfave/cli/v2/godoc-current.txt
@@ -141,7 +141,7 @@ USAGE:
 DESCRIPTION:
    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
 
-COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
+COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
 
 OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
 
@@ -357,9 +357,8 @@ func (a *App) RunAndExitOnError()
     code in the cli.ExitCoder
 
 func (a *App) RunAsSubcommand(ctx *Context) (err error)
-    This is a stub function to keep public API unchanged from old code
-
-    Deprecated: use App.Run or App.RunContext
+    RunAsSubcommand is for legacy/compatibility purposes only. New code should
+    only use App.RunContext. This function is slated to be removed in v3.
 
 func (a *App) RunContext(ctx context.Context, arguments []string) (err error)
     RunContext is like Run except it takes a Context that will be passed to
diff --git a/vendor/github.com/urfave/cli/v2/help.go b/vendor/github.com/urfave/cli/v2/help.go
index c7b8f55a5..a71fceb70 100644
--- a/vendor/github.com/urfave/cli/v2/help.go
+++ b/vendor/github.com/urfave/cli/v2/help.go
@@ -42,6 +42,7 @@ var helpCommand = &Command{
 		// 3 $ app foo
 		// 4 $ app help foo
 		// 5 $ app foo help
+		// 6 $ app foo -h (with no other sub-commands nor flags defined)
 
 		// Case 4. when executing a help command set the context to parent
 		// to allow resolution of subsequent args. This will transform
@@ -77,6 +78,8 @@ var helpCommand = &Command{
 			HelpPrinter(cCtx.App.Writer, templ, cCtx.Command)
 			return nil
 		}
+
+		// Case 6, handling incorporated in the callee itself
 		return ShowSubcommandHelp(cCtx)
 	},
 }
@@ -292,8 +295,12 @@ func ShowSubcommandHelp(cCtx *Context) error {
 	if cCtx == nil {
 		return nil
 	}
-
-	HelpPrinter(cCtx.App.Writer, SubcommandHelpTemplate, cCtx.Command)
+	// use custom template when provided (fixes #1703)
+	templ := SubcommandHelpTemplate
+	if cCtx.Command != nil && cCtx.Command.CustomHelpTemplate != "" {
+		templ = cCtx.Command.CustomHelpTemplate
+	}
+	HelpPrinter(cCtx.App.Writer, templ, cCtx.Command)
 	return nil
 }
 
diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go
index b565ba61e..da98890eb 100644
--- a/vendor/github.com/urfave/cli/v2/template.go
+++ b/vendor/github.com/urfave/cli/v2/template.go
@@ -88,7 +88,7 @@ USAGE:
 DESCRIPTION:
    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
 
-COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
+COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
 
 OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index e10aba427..5850462b4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1169,7 +1169,7 @@ github.com/transparency-dev/merkle
 github.com/transparency-dev/merkle/compact
 github.com/transparency-dev/merkle/proof
 github.com/transparency-dev/merkle/rfc6962
-# github.com/urfave/cli/v2 v2.25.3
+# github.com/urfave/cli/v2 v2.25.5
 ## explicit; go 1.18
 github.com/urfave/cli/v2
 # github.com/vbatts/tar-split v0.11.3


Test patch:
diff --git a/internal/pkg/daemon/profilerecorder/profilerecorder_test.go b/internal/pkg/daemon/profilerecorder/profilerecorder_test.go
index c71740a54..d8d7fe011 100644
--- a/internal/pkg/daemon/profilerecorder/profilerecorder_test.go
+++ b/internal/pkg/daemon/profilerecorder/profilerecorder_test.go
@@ -234,6 +234,11 @@ func TestReconcile(t *testing.T) {
 					assert.Nil(t, err)
 					return "", nil
 				})
+				mock.GetRecordingReturns(&recordingapi.ProfileRecording{
+					Spec: recordingapi.ProfileRecordingSpec{
+						DisableProfileAfterRecording: false,
+					},
+				}, nil)
 			},
 			assert: func(sut *RecorderReconciler, err error) {
 				assert.Nil(t, err)
@@ -695,6 +700,11 @@ func TestReconcile(t *testing.T) {
 					assert.Nil(t, err)
 					return "", nil
 				})
+				mock.GetRecordingReturns(&recordingapi.ProfileRecording{
+					Spec: recordingapi.ProfileRecordingSpec{
+						DisableProfileAfterRecording: false,
+					},
+				}, nil)
 			},
 			assert: func(sut *RecorderReconciler, err error) {
 				assert.Nil(t, err)
@@ -1037,6 +1047,11 @@ func TestReconcile(t *testing.T) {
 					assert.Nil(t, err)
 					return "", nil
 				})
+				mock.GetRecordingReturns(&recordingapi.ProfileRecording{
+					Spec: recordingapi.ProfileRecordingSpec{
+						DisableProfileAfterRecording: false,
+					},
+				}, nil)
 			},
 			assert: func(sut *RecorderReconciler, err error) {
 				assert.Nil(t, err)
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 8d2c26c09..fa8c7383d 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -160,6 +160,7 @@ func (e *e2e) TestSecurityProfilesOperator() {
 	e.Run("cluster-wide: Selinux: Verify the policy can be marked as permissive", func() {
 		e.testCaseSelinuxIncompletePolicy()
 		e.testCaseSelinuxIncompletePermissivePolicy()
+		e.testCaseSelinuxIncompleteDisabledPolicy()
 	})
 
 	e.Run("cluster-wide: Selinux: Verify SELinux profile recording logs", func() {
@@ -177,6 +178,7 @@ func (e *e2e) TestSecurityProfilesOperator() {
 		e.testSeccompBpfProfileMerging()
 		e.testSeccompLogsProfileMerging()
 		e.testSelinuxLogsProfileMerging()
+		e.testSelinuxLogsDisabledProfileMerging()
 	})
 
 	// Clean up cluster-wide deployment to prepare for namespace deployment
diff --git a/test/tc_profilemerging_test.go b/test/tc_profilemerging_test.go
index 32de3164b..1ec2ccd08 100644
--- a/test/tc_profilemerging_test.go
+++ b/test/tc_profilemerging_test.go
@@ -35,6 +35,7 @@ const (
     metadata:
       name: %s
     spec:
+      disableProfileAfterRecording: %s
       kind: %s
       recorder: %s
       mergeStrategy: %s
@@ -44,6 +45,13 @@ const (
 `
 )
 
+const (
+	policyEnabledAfterRecording = iota
+	policyDisabledAfterRecording
+)
+
+type policyDisableSwitch int
+
 func (e *e2e) testSeccompBpfProfileMerging() {
 	e.bpfRecorderOnlyTestCase()
 	restoreNs := e.switchToRecordingNs(nsRecordingEnabled)
@@ -55,6 +63,7 @@ func (e *e2e) testSeccompBpfProfileMerging() {
 		"/bin/mknod /tmp/foo p",
 		"listen",
 		"mknod\n", // for some reason bpf recording always allows mknodat(), let's explicitly check mknod()
+		policyEnabledAfterRecording,
 		regexp.MustCompile(
 			`(?s)"container"="nginx".*"syscallName"="listen"`+
 				`.*"container"="nginx".*"syscallName"="listen"`))
@@ -70,6 +79,7 @@ func (e *e2e) testSeccompLogsProfileMerging() {
 		"SeccompProfile", "sp",
 		"/bin/mknod /tmp/foo p",
 		"listen", "mknod",
+		policyEnabledAfterRecording,
 		regexp.MustCompile(
 			`(?s)"container"="nginx".*"syscallName"="listen"`+
 				`.*"container"="nginx".*"syscallName"="listen"`))
@@ -86,6 +96,24 @@ func (e *e2e) testSelinuxLogsProfileMerging() {
 		"SelinuxProfile", "selinuxprofile",
 		"curl localhost:8080",
 		"name_bind", "name_connect",
+		policyEnabledAfterRecording,
+		regexp.MustCompile(`(?s)"perm"="listen"`+
+			`.*"perm"="listen"`),
+	)
+}
+
+func (e *e2e) testSelinuxLogsDisabledProfileMerging() {
+	e.logEnricherOnlyTestCase()
+	e.selinuxOnlyTestCase()
+	restoreNs := e.switchToRecordingNs(nsRecordingEnabled)
+	defer restoreNs()
+
+	e.profileMergingTest(
+		"logs",
+		"SelinuxProfile", "selinuxprofile",
+		"curl localhost:8080",
+		"name_bind", "name_connect",
+		policyDisabledAfterRecording,
 		regexp.MustCompile(`(?s)"perm"="listen"`+
 			`.*"perm"="listen"`),
 	)
@@ -93,6 +121,7 @@ func (e *e2e) testSelinuxLogsProfileMerging() {
 
 func (e *e2e) profileMergingTest(
 	recordedMethod, recorderKind, resource, trigger, commonAction, triggeredAction string,
+	isPolicyDisabled policyDisableSwitch,
 	conditions ...*regexp.Regexp,
 ) {
 	const testDeploymentMultiContainer = `
@@ -133,12 +162,13 @@ spec:
 `
 	e.logf("Creating a profile recording with merge strategy 'containers'")
 	deleteManifestFn := createTemplatedProfileRecording(e, &profileRecTmplMetadata{
-		name:          mergeProfileRecordingName,
-		recorderKind:  recorderKind,
-		recorder:      recordedMethod,
-		mergeStrategy: "containers",
-		labelKey:      "app",
-		labelValue:    "alpine",
+		name:           mergeProfileRecordingName,
+		recorderKind:   recorderKind,
+		recorder:       recordedMethod,
+		mergeStrategy:  "containers",
+		labelKey:       "app",
+		labelValue:     "alpine",
+		policyDisabled: isPolicyDisabled,
 	})
 	defer deleteManifestFn()
 
@@ -178,6 +208,8 @@ spec:
 			e.Contains(profile, v1alpha1.ProfilePartialLabel)
 			e.Contains(profile, commonAction)
 
+			retryAssertPrfStatus(e, resource, recordedProfileName, "Pending", isPolicyDisabled)
+
 			if containerName == containerNameNginx {
 				if strings.HasSuffix(onePodName, sfx) {
 					// check the policy from the first container, it should contain the triggered action
@@ -213,6 +245,10 @@ spec:
 	e.Contains(policiesRecorded, mergedProfileNginx)
 	e.Contains(policiesRecorded, mergedProfileRedis)
 
+	// if the recording is supposed to produce disabled policies, check that the merged policy is disabled
+	// otherwise the policy should be installed
+	retryAssertPrfStatus(e, resource, mergedProfileNginx, "Installed", isPolicyDisabled)
+
 	// the result for the nginx container should contain the triggered action
 	mergedProfile := e.retryGetProfile(resource, mergedProfileNginx)
 	e.Contains(mergedProfile, triggeredAction)
@@ -220,13 +256,55 @@ spec:
 	e.kubectl("delete", resource, mergedProfileNginx, mergedProfileRedis)
 }
 
+func retryAssertPrfStatus(e *e2e, kind, name, enabledState string, isPolicyEnabled policyDisableSwitch) {
+	var profileStatus string
+
+	for i := 0; i < 10; i++ {
+		profileStatus = e.kubectl(
+			"get", kind, name, "-o", "jsonpath={.status.status}")
+		if profileStatus != "" {
+			e.logf("The profile %s/%s has a status %s", kind, name, profileStatus)
+			break
+		}
+		// it might take a bit for the nodestatus controller to pick
+		// up the profile, so retry a couple of times
+		time.Sleep(5 * time.Second)
+		e.logf("Waiting for the profile %s/%s to have a status", kind, name)
+		continue
+	}
+
+	if profileStatus == "" {
+		e.Failf("Failed to get a non-empty status of the profile %s/%s", kind, name)
+	}
+
+	switch {
+	case isPolicyEnabled == policyDisabledAfterRecording:
+		e.Equal("Disabled", profileStatus)
+	case enabledState == "Installed":
+		// let's not bother waiting for the profile to be installed, just the fact that it's
+		// being processed is enough
+		expected := []string{"Installed", "Pending", "InProgress"}
+		e.Contains(expected, profileStatus, "Expected the profile to be installed or pending")
+	default:
+		e.Equal("Partial", profileStatus)
+	}
+}
+
 type profileRecTmplMetadata struct {
 	name, recorderKind, recorder, mergeStrategy, labelKey, labelValue string
+	policyDisabled                                                    policyDisableSwitch
 }
 
 func createTemplatedProfileRecording(e *e2e, metadata *profileRecTmplMetadata) func() {
+	policyDisabledStr := "false"
+	if metadata.policyDisabled == policyDisabledAfterRecording {
+		policyDisabledStr = "true"
+	}
+
 	manifest := fmt.Sprintf(profileRecordingTemplate,
-		metadata.name, metadata.recorderKind, metadata.recorder,
+		metadata.name,
+		policyDisabledStr,
+		metadata.recorderKind, metadata.recorder,
 		metadata.mergeStrategy, metadata.labelKey, metadata.labelValue)
 	deleteFn := e.writeAndCreate(manifest, fmt.Sprintf("%s.yml", metadata.name))
 	return deleteFn
diff --git a/test/tc_selinux_base_usage_test.go b/test/tc_selinux_base_usage_test.go
index b09f5256b..5c20ab9f2 100644
--- a/test/tc_selinux_base_usage_test.go
+++ b/test/tc_selinux_base_usage_test.go
@@ -62,15 +62,15 @@ spec:
 `
 
 	// this is the equivalent of errorloggerPolicy but with several calls removed. The idea is to
-	// ensure that the workload will fail if the policy in incomplete. Allows setting the permissive
-	// flag as needed.
+	// ensure that the workload will fail if the policy in incomplete. Allows setting a parameter
+	// as needed.
 	errorloggerIncompletePolFmt = `
 apiVersion: security-profiles-operator.x-k8s.io/v1alpha2
 kind: SelinuxProfile
 metadata:
   name: errorlogger-incomplete-%s
 spec:
-  permissive: %s
+  %s: %s
   allow:
     var_log_t:
       dir:
@@ -189,7 +189,7 @@ func (e *e2e) testCaseSelinuxIncompletePolicy() {
 
 	e.logf("creating incomplete policy")
 	removeFn := e.writeAndCreate(
-		fmt.Sprintf(errorloggerIncompletePolFmt, "enforcing", "false"),
+		fmt.Sprintf(errorloggerIncompletePolFmt, "enforcing", "permissive", "false"),
 		"errorlogger-policy-incomplete-enforcing.yml")
 	defer removeFn()
 
@@ -254,7 +254,7 @@ func (e *e2e) testCaseSelinuxIncompletePermissivePolicy() {
 
 	e.logf("creating incomplete policy")
 	removeFn := e.writeAndCreate(
-		fmt.Sprintf(errorloggerIncompletePolFmt, "permissive", "true"),
+		fmt.Sprintf(errorloggerIncompletePolFmt, "permissive", "permissive", "true"),
 		"errorlogger-policy-incomplete-permissive.yml")
 	defer removeFn()
 
@@ -275,6 +275,48 @@ func (e *e2e) testCaseSelinuxIncompletePermissivePolicy() {
 	e.kubectl("delete", "selinuxprofile", permissiveProfileName)
 }
 
+func (e *e2e) testCaseSelinuxIncompleteDisabledPolicy() {
+	e.selinuxOnlyTestCase()
+	disabledProfileName := "errorlogger-incomplete-disabled"
+
+	e.logf("A disabled policy should be possible to install, but not to use")
+
+	e.logf("creating disabled policy")
+	removeFn := e.writeAndCreate(
+		fmt.Sprintf(errorloggerIncompletePolFmt, "disabled", "disabled", "true"),
+		"errorlogger-policy-incomplete-disabled.yml")
+	defer removeFn()
+
+	// Let's wait for the policy to be processed, it will be ready=false
+	e.kubectl("wait", "--timeout", defaultSelinuxOpTimeout,
+		"--for", "condition=ready=false", "selinuxprofile", disabledProfileName)
+
+	e.logf("creating workload - it should not even become ready")
+	podWithPolicy := fmt.Sprintf(podWithPolicyFmt, e.getSELinuxPolicyUsage(disabledProfileName))
+	e.writeAndCreate(podWithPolicy, "pod-w-incomplete-disabled-policy.yml")
+
+	var exitCode string
+	for i := 0; i < 10; i++ {
+		// loop a few times, because the pod might be in creating state
+		exitCode = e.kubectl("get", "pods", "errorlogger",
+			"-o", "jsonpath={.status.containerStatuses[0].state.waiting.reason}")
+		if exitCode == "CreateContainerError" {
+			break
+		}
+		time.Sleep(2 * time.Second)
+		continue
+	}
+	if exitCode != "CreateContainerError" {
+		e.Fail("The pod should have failed, but it didn't")
+	}
+
+	e.logf("removing workload")
+	e.kubectl("delete", "pod", "errorlogger")
+
+	e.logf("removing policy")
+	e.kubectl("delete", "selinuxprofile", disabledProfileName)
+}
+
 func (e *e2e) assertSelinuxPolicyIsInstalled(nodes []string, policy string, nodeIterations int, sleep time.Duration) {
 	for i := 0; i < nodeIterations; i++ {
 		var missingPolName string