Repository: kubernetes-sigs/security-profiles-operator
Pull request #389 (2021-03-24 10:39:27)

Introduce status controller
#### What type of PR is this?

/kind bug
/kind cleanup
/kind flake

#### What this PR does / why we need it:

This introduces a status controller which aggregates the node statuses generated by the DaemonSet
instances. This addresses the issue of the instances trying to update a single status for the policies
which resulted in races.

#### Which issue(s) this PR fixes:

Fixes https://github.com/kubernetes-sigs/security-profiles-operator/issues/373

#### Does this PR have test?

N/A

#### Does this PR introduce a user-facing change?

```release-note
A new node status controller now runs on the main operator Deployment.
To standardize on a common status model, the SelinuxPolicy `state` was renamed to `status`.
The controller manager now listens on the same namespaces as the DaemonSet does. And thus requires more RBAC permissions.
The SecurityProfilesOperatorDaemon Custom Resource is now Namespaced and not Cluster scoped.
```


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/389#" 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 **saschagrunert** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @saschagrunert` 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":["saschagrunert"]} -->
JAORMX: I'm just pushing this as a PR so it's easier to view the changes this introduces, from here work on completing this feature.
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 | 15ab7381461d092c20ba30e6bd86a7ef26c4dc25 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1374672205450842112) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
cmurphy: Tried this out locally and there's still a race in the e2e tests:

```
time="2021-03-24T11:13:48-07:00" level=info msg="+ /usr/bin/kubectl -n default get seccompprofile profile-allow-unsafe -o json"
{
    "apiVersion": "security-profiles-operator.x-k8s.io/v1alpha1",
    "kind": "SeccompProfile",
    "metadata": {
        "annotations": {
            "description": "UNSAFE: Allows all syscalls. Similar to running as unconfined as it provides no enforcement."
        },
        "creationTimestamp": "2021-03-24T18:13:45Z",
        "finalizers": [
            "spo-e2e-1616609345-worker-delete",
            "spo-e2e-1616609345-control-plane-delete",
            "spo-e2e-1616609345-worker2-delete"
        ],
        "generation": 1,
        "managedFields": [
            {
                "apiVersion": "security-profiles-operator.x-k8s.io/v1alpha1",
                "fieldsType": "FieldsV1",
                "fieldsV1": {
                    "f:metadata": {
                        "f:annotations": {
                            ".": {},
                            "f:description": {}
                        }
                    },
                    "f:spec": {
                        ".": {},
                        "f:defaultAction": {}
                    }
                },
                "manager": "kubectl-create",
                "operation": "Update",
                "time": "2021-03-24T18:13:45Z"
            },
            {
                "apiVersion": "security-profiles-operator.x-k8s.io/v1alpha1",
                "fieldsType": "FieldsV1",
                "fieldsV1": {
                    "f:metadata": {
                        "f:finalizers": {
                            ".": {},
                            "v:\"spo-e2e-1616609345-control-plane-delete\"": {},
                            "v:\"spo-e2e-1616609345-worker-delete\"": {},
                            "v:\"spo-e2e-1616609345-worker2-delete\"": {}
                        }
                    },
                    "f:status": {
                        ".": {},
                        "f:conditions": {},
                        "f:localhostProfile": {},
                        "f:path": {},
                        "f:status": {}
                    }
                },
                "manager": "security-profiles-operator",
                "operation": "Update",
                "time": "2021-03-24T18:13:47Z"
            }
        ],
        "name": "profile-allow-unsafe",
        "namespace": "default",
        "resourceVersion": "1204",
        "uid": "d389e75a-1dd6-4f56-a23c-9cb7626acde3"
    },
    "spec": {
        "defaultAction": "SCMP_ACT_ALLOW"
    },
    "status": {
        "conditions": [
            {
                "lastTransitionTime": "2021-03-24T18:13:46Z",
                "reason": "Available",
                "status": "True",
                "type": "Ready"
            }
        ],
        "localhostProfile": "operator/default/profile-allow-unsafe.json",
        "path": "/var/lib/kubelet/seccomp/operator/default/profile-allow-unsafe.json",
        "status": "Pending"
    }
}
I0324 11:13:48.298778 1097838 suite_test.go:445]  "msg"="Verifying profile-allow-unsafe profile on node spo-e2e-1616609345-worker"  
time="2021-03-24T11:13:48-07:00" level=info msg="+ /usr/bin/docker exec spo-e2e-1616609345-worker cat /var/lib/kubelet/seccomp/operator/default/profile-allow-unsafe.json"
cat: /var/lib/kubelet/seccomp/operator/default/profile-allow-unsafe.json: No such file or directory
```
JAORMX: @cmurphy this is not complete. I just posted it for the diff. As it is now, this still updates the status in the daemonset. I have a local branch where I moved that to the new controller but its still not ready. I'll keep at it tomorrow. 
JAORMX: Still testing this
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | 15ab7381461d092c20ba30e6bd86a7ef26c4dc25 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1374672205450842112) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-unit | d48ec54fc21e8af3ab17c4f74ed3a51595d92d13 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1374989506083557376) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | 15ab7381461d092c20ba30e6bd86a7ef26c4dc25 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1374672205450842112) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-unit | 7fcf8f04529f0ca0eb31db6321e2752cfb71f9de | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1374989806890651648) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-unit | ca1452d5f41d833e6b3124ae3f6d3863f4a2b6d8 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1374991491914534912) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-unit | b3dcbb68450b10562b9b315c8bee1ae4b7152dbb | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1374992914072997888) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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/389?src=pr&el=h1) Report
> Merging [#389](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/389?src=pr&el=desc) (a62501f) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/1bbe0e0d9b300c9bf4c4e53e5c2941da7c9d8909?el=desc) (1bbe0e0) will **increase** coverage by `0.45%`.
> The diff coverage is `2.12%`.

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

```diff
@@            Coverage Diff             @@
##           master     #389      +/-   ##
==========================================
+ Coverage   21.68%   22.14%   +0.45%     
==========================================
  Files          13       13              
  Lines         936      885      -51     
==========================================
- Hits          203      196       -7     
+ Misses        725      681      -44     
  Partials        8        8              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `22.14% <2.12%> (+0.45%)` | :arrow_up: |

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/389?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...ternal/pkg/daemon/seccompprofile/seccompprofile.go](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/389/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGtnL2RhZW1vbi9zZWNjb21wcHJvZmlsZS9zZWNjb21wcHJvZmlsZS5nbw==) | `21.09% <2.12%> (+1.22%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/389?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/389?src=pr&el=footer). Last update [1bbe0e0...a62501f](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/389?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

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-test-e2e | 4f3da1c69396e234fe230c4ccd72139ea67a34a0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1374998359940534272) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 27d3239befea5a5dacbce38efc428ccb752dca9a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375002231698362368) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
saschagrunert: /test pull-security-profiles-operator-test-e2e
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-test-e2e | 17c6af0c4593c43242ea4b4075ab1ac8719a1e65 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375009559541714944) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 17c6af0c4593c43242ea4b4075ab1ac8719a1e65 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375009559541714944) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-verify | 9349afe530344921b24a907a12dcf5392ac9d94a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375012259826241536) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375013222259625984) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375013222259625984) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-build | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build/1375019691944185856) | `/test pull-security-profiles-operator-build`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375013222259625984) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-build | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build/1375019691944185856) | `/test pull-security-profiles-operator-build`
pull-security-profiles-operator-verify | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375019691960963072) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375013222259625984) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-build | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build/1375019691944185856) | `/test pull-security-profiles-operator-build`
pull-security-profiles-operator-verify | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375019691960963072) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-build-image | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build-image/1375019692002906112) | `/test pull-security-profiles-operator-build-image`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-build | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build/1375019691944185856) | `/test pull-security-profiles-operator-build`
pull-security-profiles-operator-verify | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375019691960963072) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-build-image | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-build-image/1375019692002906112) | `/test pull-security-profiles-operator-build-image`
pull-security-profiles-operator-test-e2e | 6b3466fa64a1df4797f03000962eea35a25c0e7c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375019692028071936) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
jhrozek: Shit, sorry, I wanted to push the changes I did *before* I left to my private branch and didn't realize you guys already took over (which is good) so the change overwrote the PR. Sorry. I'll go back to being away now.
JAORMX: @jhrozek I'm currently working on this branch
JAORMX: > Shit, sorry, I wanted to push the changes I did _before_ I left to my private branch and didn't realize you guys already took over (which is good) so the change overwrote the PR. Sorry. I'll go back to being away now.

No worries! Greetings to the family! :smile: talk to you when you're back
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-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375021398774255616) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | d2b440e5e4a5ec39151fef658ea44fe3eee7a177 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375021398774255616) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-verify | 9d84cc9d71044e1d4b93d64d5f05591ab326fc4d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375036319578722304) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 6123c38c2b872d519b4af3d8da8a866f74e0276c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375036983872589824) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | 6123c38c2b872d519b4af3d8da8a866f74e0276c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375039010363150336) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 6123c38c2b872d519b4af3d8da8a866f74e0276c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375039010363150336) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-test-unit | 16b6c627b62b17104bb168d2c81f805e1a904bfb | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1375044956187529216) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-unit
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-test-e2e | 16b6c627b62b17104bb168d2c81f805e1a904bfb | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375044956250443776) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | c5dd151084b06f194d6d51b9de415e864f8ad12c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375048337287286784) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | c5dd151084b06f194d6d51b9de415e864f8ad12c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375051391055171584) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 1fb79fb4113754c2bc67b0aed45578f40d47e73e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375054618756648960) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | 1fb79fb4113754c2bc67b0aed45578f40d47e73e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375057620095733760) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | fcfe329709bf145c118a6b44829295387fe08942 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375061249208881152) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | fcfe329709bf145c118a6b44829295387fe08942 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375066832037220352) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 7a00503d908aa367c43049fa60a2ab4a627ece0e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375079593030979584) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 7a00503d908aa367c43049fa60a2ab4a627ece0e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375079593030979584) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-test-unit | d33fe7a4cdc0ff05e1a4fe35d493454dcc7a5c81 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1375082392347217920) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 7a00503d908aa367c43049fa60a2ab4a627ece0e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375079593030979584) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-test-unit | dd11ac363c15383f6f061aed1f45bedcf553b0ef | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1375083617255952384) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | a656cdfda3441492778a079d5092cb6ef11b3295 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375084681933885440) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | a656cdfda3441492778a079d5092cb6ef11b3295 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375087391441686528) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | c0ef813cef940c10029dc2befdf3e7add09388a6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375096343273410560) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 35e3df150aa00a31d8976eed5f34de5012361fea | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375111689246085120) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | 35e3df150aa00a31d8976eed5f34de5012361fea | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375115428229025792) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 35e3df150aa00a31d8976eed5f34de5012361fea | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375115428229025792) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-test-unit | 53f30d23b35a95b9c2b1c4f6302cc5c0e3b9e550 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-unit/1375117997772902400) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-unit
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-test-e2e | 53f30d23b35a95b9c2b1c4f6302cc5c0e3b9e550 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375117997835816960) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | 886c4c6d86ca6bb52d195d19800eca56283e7992 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375127424504696832) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
cmurphy: The profilebinding test is failing because this change is causing the `SeccompProfile` creation to not be atomic with setting its status, and the profilebinding webhook uses `SeccompProfile.Status.LocalhostProfile` to inject the profile path into the Pod's `SecurityContext`:

https://github.com/kubernetes-sigs/security-profiles-operator/blob/66c5df92f2fa69ff8d4567b4235558a4485f31fe/internal/pkg/webhooks/binding/binding.go#L170

and it ends up injecting an empty string:

```
$ kubectl get pod hello -o yaml | grep -A 3 '^ *securityContext:$'
    securityContext:
      seccompProfile:
        localhostProfile: ""
        type: Localhost
```

That later cause the `PodReconciler` not to find the pod in its index and so it doesn't add it to its `ActiveWorkloads` list which is how the test failure surfaces. But that's less of a problem than the actual profile binding not working.

So for this to work either we'll have to move that `LocalhostProfile` metadata to somewhere out of status (not a bad idea, status is kind of a funny place to put it), or have the webhook do some kind of concurrency-safe check to ensure the SeccompProfile Status isn't out of sync with the Spec.
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 886c4c6d86ca6bb52d195d19800eca56283e7992 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375127424504696832) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-verify | 8cf4d0fe27bf04a3b9e1ae7320305f807d277418 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375333859947712512) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | 8cf4d0fe27bf04a3b9e1ae7320305f807d277418 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375333859947712512) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-e2e | 8cf4d0fe27bf04a3b9e1ae7320305f807d277418 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375333860031598592) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 8cf4d0fe27bf04a3b9e1ae7320305f807d277418 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375333860031598592) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-verify | 3335c3caffcd0695a6b760e1374261049ba20ce9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375338573556879360) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
k8s-ci-robot: @JAORMX: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | 3335c3caffcd0695a6b760e1374261049ba20ce9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-verify/1375338573556879360) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-e2e | 3335c3caffcd0695a6b760e1374261049ba20ce9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375338573724651520) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | 8690ea2e746a15c8cc45a384a3dd65dd917596e8 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375342080141824000) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | aa293da89dec6fcbb0b76873a8fee0e09e11b082 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375390267632259072) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
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-test-e2e | b3f29d0dce2360ab701b2c2db804d796beeec1ff | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375401686482292736) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
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-test-e2e | b3f29d0dce2360ab701b2c2db804d796beeec1ff | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375412630822326272) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e

Failed with:
```
 fatal error: runtime: cannot allocate memory 
```
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-test-e2e | b3f29d0dce2360ab701b2c2db804d796beeec1ff | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375415883849011200) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /retest
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-test-e2e | b3f29d0dce2360ab701b2c2db804d796beeec1ff | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1375424317545779200) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /retest
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-test-e2e | dfc9529e3895c63b5e3d3c25b3fa42ba857f195b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/389/pull-security-profiles-operator-test-e2e/1376417307202424832) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=389). [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 -->
JAORMX: /test pull-security-profiles-operator-test-e2e
k8s-ci-robot: [Keywords](https://help.github.com/articles/closing-issues-using-keywords) which can automatically close issues and at(@) or hashtag(#) mentions are not allowed in commit messages.

**The list of commits with invalid commit messages**:

- [4a06d2e](https://github.com/kubernetes-sigs/security-profiles-operator/commits/4a06d2eaf865309315586ef47ea51bdb6f0ec8bb) Change ErrProfWithoutStatus string's case

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

k8s-ci-robot: [Keywords](https://help.github.com/articles/closing-issues-using-keywords) which can automatically close issues and at(@) or hashtag(#) mentions are not allowed in commit messages.

**The list of commits with invalid commit messages**:

- [4a06d2e](https://github.com/kubernetes-sigs/security-profiles-operator/commits/4a06d2eaf865309315586ef47ea51bdb6f0ec8bb) Change ErrProfWithoutStatus string's case

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

k8s-ci-robot: [Keywords](https://help.github.com/articles/closing-issues-using-keywords) which can automatically close issues and at(@) or hashtag(#) mentions are not allowed in commit messages.

**The list of commits with invalid commit messages**:

- [4a06d2e](https://github.com/kubernetes-sigs/security-profiles-operator/commits/4a06d2eaf865309315586ef47ea51bdb6f0ec8bb) Change ErrProfWithoutStatus string's case

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

JAORMX: > I did an initial pass, left a comment in the tests that will help the e2e stability. Will need to take some more time to understand all the changes. All this cross-controller machinery really increased the complexity of the operator, going back to when we added the profile binding controller, which is a bit of a shame imo.

Yeah, it's not too nice that the operator got more complicated... but I just didn't figure out a better way of doing this. Having everything in the DaemonSet ended up in a ton of races. Having the deployment do this ends up addressing that, even if reconciling is a little slower.

In the end, it kinda resembles the kube architecture more:

* The Kube API == the Deployment: Here is where most of the controller logic actually lives, and it aggregates the results to one consistent one.
* The kubelet == the DaemonSet: It merely listens for objects and makes sure "it happens". So, this just makes sure that the profile is installed and reports its own status. It is then up to the Deployment to gather the status into one cohesive one.
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/389#" title="Author self-approved">JAORMX</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/389#pullrequestreview-623955484" 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/master/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":[]} -->
saschagrunert: /hold

for @cmurphy 
k8s-ci-robot: New changes are detected. LGTM label has been removed.
JAORMX: The  cla/linuxfoundation check seems to have gotten stuck
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/389#pullrequestreview-630276934" title="Approved">cmurphy</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/389#" title="Author self-approved">JAORMX</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/389#pullrequestreview-623955484" 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/master/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":[]} -->
saschagrunert: /hold cancel

Patch:
diff --git a/api/profilebase/v1alpha1/deepcopy.go b/api/profilebase/v1alpha1/deepcopy.go
new file mode 100644
index 000000000..e75c9b75a
--- /dev/null
+++ b/api/profilebase/v1alpha1/deepcopy.go
@@ -0,0 +1,33 @@
+/*
+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 v1alpha1
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StatusBase) DeepCopyInto(out *StatusBase) {
+	*out = *in
+	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusBase.
+func (in *StatusBase) DeepCopy() *StatusBase {
+	if in == nil {
+		return nil
+	}
+	out := new(StatusBase)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/api/profilebase/v1alpha1/profilebase.go b/api/profilebase/v1alpha1/profilebase.go
new file mode 100644
index 000000000..e1e757508
--- /dev/null
+++ b/api/profilebase/v1alpha1/profilebase.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 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 v1alpha1
+
+import (
+	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+)
+
+// StatusBase contains common attributes for a profile's status.
+type StatusBase struct {
+	rcommonv1.ConditionedStatus `json:",inline"`
+	Status                      secprofnodestatusv1alpha1.ProfileState `json:"status,omitempty"`
+}
+
+type StatusBaseUser interface {
+	metav1.Object
+	runtime.Object
+	// GetStatusBase gets an instance of the Base status that the
+	// profiles share
+	GetStatusBase() *StatusBase
+	// DeepCopyToStatusBaseIf exposes an interface to call deep copy
+	// on this StatusBaseUser interface
+	DeepCopyToStatusBaseIf() StatusBaseUser
+	// Sets the needed status that's specific to the profile
+	// implementation
+	SetImplementationStatus()
+}
diff --git a/api/seccompprofile/v1alpha1/seccompprofile_types.go b/api/seccompprofile/v1alpha1/seccompprofile_types.go
index 2562c3967..f9c18382d 100644
--- a/api/seccompprofile/v1alpha1/seccompprofile_types.go
+++ b/api/seccompprofile/v1alpha1/seccompprofile_types.go
@@ -17,11 +17,22 @@ limitations under the License.
 package v1alpha1
 
 import (
+	"path"
+	"path/filepath"
+	"strings"
+
 	"github.com/containers/common/pkg/seccomp"
-	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	profilebasev1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilebase/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
+)
+
+// Ensure SeccompProfile implements the StatusBaseUser interface.
+var _ profilebasev1alpha1.StatusBaseUser = &SeccompProfile{}
+
+const (
+	extJSON = ".json"
 )
 
 // SeccompProfileSpec defines the desired state of SeccompProfile.
@@ -91,10 +102,9 @@ type Arg struct {
 
 // SeccompProfileStatus contains status of the deployed SeccompProfile.
 type SeccompProfileStatus struct {
-	rcommonv1.ConditionedStatus `json:",inline"`
-	Path                        string                                 `json:"path,omitempty"`
-	Status                      secprofnodestatusv1alpha1.ProfileState `json:"status,omitempty"`
-	ActiveWorkloads             []string                               `json:"activeWorkloads,omitempty"`
+	profilebasev1alpha1.StatusBase `json:",inline"`
+	Path                           string   `json:"path,omitempty"`
+	ActiveWorkloads                []string `json:"activeWorkloads,omitempty"`
 	// The path that should be provided to the `securityContext.seccompProfile.localhostProfile`
 	// field of a Pod or container spec
 	LocalhostProfile string `json:"localhostProfile,omitempty"`
@@ -117,6 +127,31 @@ type SeccompProfile struct {
 	Status SeccompProfileStatus `json:"status,omitempty"`
 }
 
+func (sp *SeccompProfile) GetStatusBase() *profilebasev1alpha1.StatusBase {
+	return &sp.Status.StatusBase
+}
+
+func (sp *SeccompProfile) DeepCopyToStatusBaseIf() profilebasev1alpha1.StatusBaseUser {
+	return sp.DeepCopy()
+}
+
+func (sp *SeccompProfile) SetImplementationStatus() {
+	profilePath := sp.GetProfilePath()
+	sp.Status.LocalhostProfile = strings.TrimPrefix(profilePath, config.KubeletSeccompRootPath+"/")
+}
+
+func (sp *SeccompProfile) GetProfilePath() string {
+	pfile := sp.GetName()
+	if !strings.HasSuffix(pfile, extJSON) {
+		pfile = sp.GetName() + extJSON
+	}
+	return path.Join(
+		config.ProfilesRootPath,
+		filepath.Base(sp.GetNamespace()),
+		filepath.Base(pfile),
+	)
+}
+
 // +kubebuilder:object:root=true
 
 // SeccompProfileList contains a list of SeccompProfile.
diff --git a/api/seccompprofile/v1alpha1/zz_generated.deepcopy.go b/api/seccompprofile/v1alpha1/zz_generated.deepcopy.go
index 2f0aa362d..216f9fb5e 100644
--- a/api/seccompprofile/v1alpha1/zz_generated.deepcopy.go
+++ b/api/seccompprofile/v1alpha1/zz_generated.deepcopy.go
@@ -149,7 +149,7 @@ func (in *SeccompProfileSpec) DeepCopy() *SeccompProfileSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SeccompProfileStatus) DeepCopyInto(out *SeccompProfileStatus) {
 	*out = *in
-	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+	in.StatusBase.DeepCopyInto(&out.StatusBase)
 	if in.ActiveWorkloads != nil {
 		in, out := &in.ActiveWorkloads, &out.ActiveWorkloads
 		*out = make([]string, len(*in))
diff --git a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
index 98b4100ac..dcacacde8 100644
--- a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
+++ b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
@@ -51,16 +51,20 @@ const (
 	StatusKindLabel = "spo.x-k8s.io/profile-kind"
 )
 
+// LowestState defines the "lowest" state for the profiles to be at.
+// All of the statuses would need to reach this for us to get here.
+const LowestState ProfileState = ProfileStateInstalled
+
 // LowerOfTwoStates is used to figure out the "lowest common state" and is used to represent
 // the overall status of a profile. The idea is that if, e.g. one in three profiles is already
 // installed, but the two others are pending, the overall state should be pending.
 func LowerOfTwoStates(currentLowest, candidate ProfileState) ProfileState {
 	orderedStates := make(map[ProfileState]int)
-	orderedStates[ProfileStateError] = 0 // error must always have the lowest index
-	orderedStates[ProfileStatePending] = 1
-	orderedStates[ProfileStateInProgress] = 2
-	orderedStates[ProfileStateInstalled] = 3
-	orderedStates[ProfileStateTerminating] = 4
+	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[ProfileStatePending] = 2
+	orderedStates[ProfileStateInProgress] = 3
+	orderedStates[ProfileStateInstalled] = 4
 
 	if orderedStates[currentLowest] > orderedStates[candidate] {
 		return candidate
diff --git a/api/selinuxpolicy/v1alpha1/selinuxpolicy_types.go b/api/selinuxpolicy/v1alpha1/selinuxpolicy_types.go
index f5923e32f..55a1fc3c0 100644
--- a/api/selinuxpolicy/v1alpha1/selinuxpolicy_types.go
+++ b/api/selinuxpolicy/v1alpha1/selinuxpolicy_types.go
@@ -17,12 +17,14 @@ limitations under the License.
 package v1alpha1
 
 import (
-	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	profilebasev1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilebase/v1alpha1"
 )
 
+// Ensure SelinuxPolicy implements the StatusBaseUser interface.
+var _ profilebasev1alpha1.StatusBaseUser = &SelinuxPolicy{}
+
 // SelinuxPolicySpec defines the desired state of SelinuxPolicy.
 type SelinuxPolicySpec struct {
 	Apply  bool   `json:"apply,omitempty"`
@@ -31,12 +33,10 @@ type SelinuxPolicySpec struct {
 
 // SelinuxPolicyStatus defines the observed state of SelinuxPolicy.
 type SelinuxPolicyStatus struct {
-	rcommonv1.ConditionedStatus `json:",inline"`
+	profilebasev1alpha1.StatusBase `json:",inline"`
 	// Represents the string that the SelinuxPolicy object can be
 	// referenced as in a pod seLinuxOptions section.
 	Usage string `json:"usage,omitempty"`
-	// Represents the state that the profile is in.
-	State secprofnodestatusv1alpha1.ProfileState `json:"state,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -46,7 +46,7 @@ type SelinuxPolicyStatus struct {
 // +kubebuilder:resource:path=selinuxpolicies,scope=Namespaced
 // +kubebuilder:printcolumn:name="Usage",type="string",JSONPath=`.status.usage`
 // +kubebuilder:printcolumn:name="Apply",type="boolean",JSONPath=`.spec.apply`
-// +kubebuilder:printcolumn:name="State",type="string",JSONPath=`.status.state`
+// +kubebuilder:printcolumn:name="State",type="string",JSONPath=`.status.status`
 type SelinuxPolicy struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -55,6 +55,30 @@ type SelinuxPolicy struct {
 	Status SelinuxPolicyStatus `json:"status,omitempty"`
 }
 
+func (sp *SelinuxPolicy) GetStatusBase() *profilebasev1alpha1.StatusBase {
+	return &sp.Status.StatusBase
+}
+
+func (sp *SelinuxPolicy) DeepCopyToStatusBaseIf() profilebasev1alpha1.StatusBaseUser {
+	return sp.DeepCopy()
+}
+
+func (sp *SelinuxPolicy) SetImplementationStatus() {
+	sp.Status.Usage = sp.GetPolicyUsage()
+}
+
+// GetPolicyName gets the policy module name in the format that
+// we're expecting for parsing.
+func (sp *SelinuxPolicy) GetPolicyName() string {
+	return sp.GetName() + "_" + sp.GetNamespace()
+}
+
+// GetPolicyUsage is the representation of how a pod will call this
+// SELinux module.
+func (sp *SelinuxPolicy) GetPolicyUsage() string {
+	return sp.GetPolicyName() + ".process"
+}
+
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // SelinuxPolicyList contains a list of SelinuxPolicy.
diff --git a/api/selinuxpolicy/v1alpha1/zz_generated.deepcopy.go b/api/selinuxpolicy/v1alpha1/zz_generated.deepcopy.go
index af4287ced..975ecba1a 100644
--- a/api/selinuxpolicy/v1alpha1/zz_generated.deepcopy.go
+++ b/api/selinuxpolicy/v1alpha1/zz_generated.deepcopy.go
@@ -101,7 +101,7 @@ func (in *SelinuxPolicySpec) DeepCopy() *SelinuxPolicySpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SelinuxPolicyStatus) DeepCopyInto(out *SelinuxPolicyStatus) {
 	*out = *in
-	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+	in.StatusBase.DeepCopyInto(&out.StatusBase)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelinuxPolicyStatus.
diff --git a/api/spod/v1alpha1/spod_types.go b/api/spod/v1alpha1/spod_types.go
index ae83ce5a8..2efef3b85 100644
--- a/api/spod/v1alpha1/spod_types.go
+++ b/api/spod/v1alpha1/spod_types.go
@@ -63,7 +63,7 @@ type SPODStatus struct {
 
 // SecurityProfilesOperatorDaemon is the Schema to configure the spod deployment.
 // +kubebuilder:subresource:status
-// +kubebuilder:resource:path=securityprofilesoperatordaemons,scope=Cluster,shortName=spod
+// +kubebuilder:resource:path=securityprofilesoperatordaemons,shortName=spod
 // +kubebuilder:printcolumn:name="State",type="string",JSONPath=`.status.state`
 type SecurityProfilesOperatorDaemon struct {
 	metav1.TypeMeta   `json:",inline"`
diff --git a/cmd/security-profiles-operator/main.go b/cmd/security-profiles-operator/main.go
index 02d25b6b5..048d93dfd 100644
--- a/cmd/security-profiles-operator/main.go
+++ b/cmd/security-profiles-operator/main.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"strings"
 	"time"
 
 	"github.com/go-logr/logr"
@@ -30,6 +31,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/klog/v2/klogr"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/scheme"
@@ -45,7 +47,9 @@ import (
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/profilerecorder"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/seccompprofile"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/selinuxpolicy"
+	nodestatus "sigs.k8s.io/security-profiles-operator/internal/pkg/manager/nodestatus"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod"
+	wa "sigs.k8s.io/security-profiles-operator/internal/pkg/manager/workloadannotator"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/nonrootenabler"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/version"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/webhooks/binding"
@@ -187,10 +191,7 @@ func runManager(ctx *cli.Context) error {
 		return err
 	}
 
-	// Given that the operator manager only deploys the SPOd and currently
-	// doesn't hold any webhooks, we only listen on the operator's
-	// namespace.
-	ctrlOpts.Namespace = config.GetOperatorNamespace()
+	setControllerOptionsForNamespaces(&ctrlOpts)
 
 	mgr, err := ctrl.NewManager(cfg, ctrlOpts)
 	if err != nil {
@@ -206,6 +207,14 @@ func runManager(ctx *cli.Context) error {
 	if err := seccompprofilev1alpha1.AddToScheme(mgr.GetScheme()); err != nil {
 		return errors.Wrap(err, "add seccompprofile API to scheme")
 	}
+	if err := selinuxpolicyv1alpha1.AddToScheme(mgr.GetScheme()); err != nil {
+		return errors.Wrap(err, "add selinuxpolicy API to scheme")
+	}
+
+	// This API provides status which is used by both seccomp and selinux
+	if err := secprofnodestatusv1alpha1.AddToScheme(mgr.GetScheme()); err != nil {
+		return errors.Wrap(err, "add per-node Status API to scheme")
+	}
 
 	if err := createConfigIfNotExist(ctx.Context, mgr.GetClient()); err != nil {
 		return errors.Wrap(err, "ensure SPOD config")
@@ -215,6 +224,14 @@ func runManager(ctx *cli.Context) error {
 		return errors.Wrap(err, "setup profile controller")
 	}
 
+	if err := nodestatus.Setup(ctx.Context, mgr, ctrl.Log.WithName("nodestatus")); err != nil {
+		return errors.Wrap(err, "setup node status controller")
+	}
+
+	if err := wa.Setup(ctx.Context, mgr, ctrl.Log.WithName("workload-annotator")); err != nil {
+		return errors.Wrap(err, "setup workload annotator controller")
+	}
+
 	setupLog.Info("starting manager")
 	if err := mgr.Start(sigHandler); err != nil {
 		return errors.Wrap(err, "controller manager error")
@@ -259,6 +276,34 @@ func createConfigIfNotExist(ctx context.Context, c client.Client) error {
 	return nil
 }
 
+func setControllerOptionsForNamespaces(opts *ctrl.Options) {
+	namespace := os.Getenv(config.RestrictNamespaceEnvKey)
+
+	// listen globally
+	if namespace == "" {
+		opts.Namespace = namespace
+		return
+	}
+
+	// ensure we listen to our own namespace
+	if !strings.Contains(namespace, config.GetOperatorNamespace()) {
+		namespace = namespace + "," + config.GetOperatorNamespace()
+	}
+
+	namespaceList := strings.Split(namespace, ",")
+	// Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2)
+	// Note that this is not intended to be used for excluding namespaces, this is better done via a Predicate
+	// Also note that you may face performance issues when using this with a high number of namespaces.
+	// More Info: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder
+	// Adding "" adds cluster namespaced resources
+	if strings.Contains(namespace, ",") {
+		opts.NewCache = cache.MultiNamespacedCacheBuilder(namespaceList)
+	} else {
+		// listen to a specific namespace only
+		opts.Namespace = namespace
+	}
+}
+
 func getTunables() (dt spod.DaemonTunables, err error) {
 	dt.WatchNamespace = os.Getenv(config.RestrictNamespaceEnvKey)
 
@@ -293,7 +338,7 @@ func getEnabledControllers(ctx *cli.Context) []*controllerSettings {
 		&controllerSettings{
 			name:          "recorder-spod",
 			setupFn:       profilerecorder.Setup,
-			schemaBuilder: seccompprofilev1alpha1.SchemeBuilder,
+			schemaBuilder: profilerecording1alpha1.SchemeBuilder,
 		},
 	)
 
@@ -328,9 +373,7 @@ func runDaemon(ctx *cli.Context) error {
 		SyncPeriod: &sync,
 	}
 
-	if os.Getenv(config.RestrictNamespaceEnvKey) != "" {
-		ctrlOpts.Namespace = os.Getenv(config.RestrictNamespaceEnvKey)
-	}
+	setControllerOptionsForNamespaces(&ctrlOpts)
 
 	mgr, err := ctrl.NewManager(cfg, ctrlOpts)
 	if err != nil {
diff --git a/deploy/base/crd.yaml b/deploy/base/crd.yaml
index 6f74320e9..3522a04d9 100644
--- a/deploy/base/crd.yaml
+++ b/deploy/base/crd.yaml
@@ -16,7 +16,7 @@ spec:
     shortNames:
     - spod
     singular: securityprofilesoperatordaemon
-  scope: Cluster
+  scope: Namespaced
   versions:
   - additionalPrinterColumns:
     - jsonPath: .status.state
@@ -479,7 +479,7 @@ spec:
     - jsonPath: .spec.apply
       name: Apply
       type: boolean
-    - jsonPath: .status.state
+    - jsonPath: .status.status
       name: State
       type: string
     name: v1alpha1
@@ -543,8 +543,11 @@ spec:
                   - type
                   type: object
                 type: array
-              state:
-                description: Represents the state that the profile is in.
+              status:
+                description: ProfileState defines the state that the profile is in.
+                  A profile in this context refers to a SeccompProfile or a SELinux
+                  profile, the states are shared between them as well as the management
+                  API.
                 type: string
               usage:
                 description: Represents the string that the SelinuxPolicy object can
diff --git a/deploy/base/profiles/selinuxd.cil b/deploy/base/profiles/selinuxd.cil
index 0e69bd504..0e42778dc 100644
--- a/deploy/base/profiles/selinuxd.cil
+++ b/deploy/base/profiles/selinuxd.cil
@@ -13,7 +13,7 @@
     (allow process file_context_t ( sock_file ( append getattr open read write )))
     (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
     (allow process security_t ( security ( load_policy )))
-    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))
     (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
     (allow process selinux_config_t ( sock_file ( append getattr open read write )))
     (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
diff --git a/deploy/base/role.yaml b/deploy/base/role.yaml
index e3ea86dda..e145cab15 100644
--- a/deploy/base/role.yaml
+++ b/deploy/base/role.yaml
@@ -6,6 +6,54 @@ metadata:
   creationTimestamp: null
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  - events
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -17,6 +65,31 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - securityprofilenodestatuses
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -44,19 +117,10 @@ rules:
   - get
   - patch
   - update
-
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  creationTimestamp: null
-  name: security-profiles-operator
-  namespace: security-profiles-operator
-rules:
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets
+  - selinuxpolicies
   verbs:
   - create
   - get
@@ -65,22 +129,31 @@ rules:
   - update
   - watch
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets/finalizers
+  - selinuxpolicies/finalizers
   verbs:
   - delete
   - get
   - patch
   - update
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - deployments
+  - selinuxpolicies/status
   verbs:
   - get
-  - list
-  - watch
+  - patch
+  - update
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  name: security-profiles-operator
+  namespace: security-profiles-operator
+rules:
 - apiGroups:
   - coordination.k8s.io
   resources:
@@ -89,18 +162,6 @@ rules:
   - create
   - get
   - update
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  - events
-  verbs:
-  - create
-  - get
-  - list
-  - patch
-  - update
-  - watch
 - apiGroups:
   - security.openshift.io
   resources:
@@ -115,6 +176,14 @@ metadata:
   creationTimestamp: null
   name: spod
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - ""
   resources:
@@ -215,6 +284,14 @@ metadata:
   name: spod
   namespace: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security.openshift.io
   resources:
diff --git a/deploy/base/webhook/kustomization.yaml b/deploy/base/webhook/kustomization.yaml
index 6ee98efca..602f7f2ef 100644
--- a/deploy/base/webhook/kustomization.yaml
+++ b/deploy/base/webhook/kustomization.yaml
@@ -11,7 +11,7 @@ images:
     # newTag: v0.3.0
 
 commonLabels:
-  app: security-profiles-operator
+  app: security-profiles-operator-webhook
 
 resources:
   - crd-binding.yaml
diff --git a/deploy/namespace-operator.yaml b/deploy/namespace-operator.yaml
index 4d71b7e28..7c73b90af 100644
--- a/deploy/namespace-operator.yaml
+++ b/deploy/namespace-operator.yaml
@@ -16,7 +16,7 @@ spec:
     shortNames:
     - spod
     singular: securityprofilesoperatordaemon
-  scope: Cluster
+  scope: Namespaced
   versions:
   - additionalPrinterColumns:
     - jsonPath: .status.state
@@ -424,7 +424,7 @@ spec:
     - jsonPath: .spec.apply
       name: Apply
       type: boolean
-    - jsonPath: .status.state
+    - jsonPath: .status.status
       name: State
       type: string
     name: v1alpha1
@@ -479,8 +479,8 @@ spec:
                   - type
                   type: object
                 type: array
-              state:
-                description: Represents the state that the profile is in.
+              status:
+                description: ProfileState defines the state that the profile is in. A profile in this context refers to a SeccompProfile or a SELinux profile, the states are shared between them as well as the management API.
                 type: string
               usage:
                 description: Represents the string that the SelinuxPolicy object can be referenced as in a pod seLinuxOptions section.
@@ -529,6 +529,54 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  - events
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -540,6 +588,31 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - securityprofilenodestatuses
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -567,20 +640,10 @@ rules:
   - get
   - patch
   - update
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  creationTimestamp: null
-  labels:
-    app: security-profiles-operator
-  name: security-profiles-operator
-  namespace: security-profiles-operator
-rules:
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets
+  - selinuxpolicies
   verbs:
   - create
   - get
@@ -589,22 +652,32 @@ rules:
   - update
   - watch
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets/finalizers
+  - selinuxpolicies/finalizers
   verbs:
   - delete
   - get
   - patch
   - update
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - deployments
+  - selinuxpolicies/status
   verbs:
   - get
-  - list
-  - watch
+  - patch
+  - update
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  labels:
+    app: security-profiles-operator
+  name: security-profiles-operator
+  namespace: security-profiles-operator
+rules:
 - apiGroups:
   - coordination.k8s.io
   resources:
@@ -613,18 +686,6 @@ rules:
   - create
   - get
   - update
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  - events
-  verbs:
-  - create
-  - get
-  - list
-  - patch
-  - update
-  - watch
 - apiGroups:
   - security.openshift.io
   resources:
@@ -640,6 +701,14 @@ metadata:
     app: security-profiles-operator
   name: spod
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - ""
   resources:
@@ -741,6 +810,14 @@ metadata:
   name: spod
   namespace: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security.openshift.io
   resources:
@@ -943,7 +1020,7 @@ data:
         }
       ]
     }
-  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
+  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
 kind: ConfigMap
 metadata:
   labels:
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
index e05d31ec1..2a48b3348 100644
--- a/deploy/operator.yaml
+++ b/deploy/operator.yaml
@@ -16,7 +16,7 @@ spec:
     shortNames:
     - spod
     singular: securityprofilesoperatordaemon
-  scope: Cluster
+  scope: Namespaced
   versions:
   - additionalPrinterColumns:
     - jsonPath: .status.state
@@ -424,7 +424,7 @@ spec:
     - jsonPath: .spec.apply
       name: Apply
       type: boolean
-    - jsonPath: .status.state
+    - jsonPath: .status.status
       name: State
       type: string
     name: v1alpha1
@@ -479,8 +479,8 @@ spec:
                   - type
                   type: object
                 type: array
-              state:
-                description: Represents the state that the profile is in.
+              status:
+                description: ProfileState defines the state that the profile is in. A profile in this context refers to a SeccompProfile or a SELinux profile, the states are shared between them as well as the management API.
                 type: string
               usage:
                 description: Represents the string that the SelinuxPolicy object can be referenced as in a pod seLinuxOptions section.
@@ -529,6 +529,54 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  - events
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -540,6 +588,31 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/finalizers
+  verbs:
+  - delete
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - seccompprofiles/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - security-profiles-operator.x-k8s.io
+  resources:
+  - securityprofilenodestatuses
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security-profiles-operator.x-k8s.io
   resources:
@@ -567,20 +640,10 @@ rules:
   - get
   - patch
   - update
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  creationTimestamp: null
-  labels:
-    app: security-profiles-operator
-  name: security-profiles-operator
-  namespace: security-profiles-operator
-rules:
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets
+  - selinuxpolicies
   verbs:
   - create
   - get
@@ -589,22 +652,32 @@ rules:
   - update
   - watch
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - daemonsets/finalizers
+  - selinuxpolicies/finalizers
   verbs:
   - delete
   - get
   - patch
   - update
 - apiGroups:
-  - apps
+  - security-profiles-operator.x-k8s.io
   resources:
-  - deployments
+  - selinuxpolicies/status
   verbs:
   - get
-  - list
-  - watch
+  - patch
+  - update
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  labels:
+    app: security-profiles-operator
+  name: security-profiles-operator
+  namespace: security-profiles-operator
+rules:
 - apiGroups:
   - coordination.k8s.io
   resources:
@@ -613,18 +686,6 @@ rules:
   - create
   - get
   - update
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  - events
-  verbs:
-  - create
-  - get
-  - list
-  - patch
-  - update
-  - watch
 - apiGroups:
   - security.openshift.io
   resources:
@@ -640,6 +701,14 @@ metadata:
     app: security-profiles-operator
   name: spod
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - ""
   resources:
@@ -741,6 +810,14 @@ metadata:
   name: spod
   namespace: security-profiles-operator
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - security.openshift.io
   resources:
@@ -941,7 +1018,7 @@ data:
         }
       ]
     }
-  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
+  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
 kind: ConfigMap
 metadata:
   labels:
diff --git a/deploy/webhook.yaml b/deploy/webhook.yaml
index 88c8ebec6..9d2ae0037 100644
--- a/deploy/webhook.yaml
+++ b/deploy/webhook.yaml
@@ -5,7 +5,7 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.5.0
   creationTimestamp: null
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: profilebindings.security-profiles-operator.x-k8s.io
 spec:
   group: security-profiles-operator.x-k8s.io
@@ -81,7 +81,7 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.5.0
   creationTimestamp: null
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: profilerecordings.security-profiles-operator.x-k8s.io
 spec:
   group: security-profiles-operator.x-k8s.io
@@ -176,7 +176,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-webhook
   namespace: security-profiles-operator
 ---
@@ -185,7 +185,7 @@ kind: ClusterRole
 metadata:
   creationTimestamp: null
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-webhook
 rules:
 - apiGroups:
@@ -277,7 +277,7 @@ kind: Role
 metadata:
   creationTimestamp: null
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-webhook
   namespace: security-profiles-operator
 rules:
@@ -305,7 +305,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-webhook
   namespace: security-profiles-operator
 roleRef:
@@ -321,7 +321,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-webhook
   namespace: security-profiles-operator
 roleRef:
@@ -336,21 +336,21 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: security-profiles-operator-webhook
   namespace: security-profiles-operator
 spec:
   replicas: 3
   selector:
     matchLabels:
-      app: security-profiles-operator
+      app: security-profiles-operator-webhook
       name: security-profiles-operator-webhook
   template:
     metadata:
       annotations:
         seccomp.security.alpha.kubernetes.io/pod: runtime/default
       labels:
-        app: security-profiles-operator
+        app: security-profiles-operator-webhook
         name: security-profiles-operator-webhook
     spec:
       containers:
@@ -393,7 +393,7 @@ metadata:
   annotations:
     cert-manager.io/inject-ca-from: security-profiles-operator/webhook-cert
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: spo-mutating-webhook-configuration
 webhooks:
 - admissionReviewVersions:
@@ -459,7 +459,7 @@ apiVersion: cert-manager.io/v1
 kind: Issuer
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: selfsigned-issuer
   namespace: security-profiles-operator
 spec:
@@ -469,7 +469,7 @@ apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: webhook-cert
   namespace: security-profiles-operator
 spec:
@@ -485,7 +485,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
   name: webhook-service
   namespace: security-profiles-operator
 spec:
@@ -493,5 +493,5 @@ spec:
   - port: 443
     targetPort: 9443
   selector:
-    app: security-profiles-operator
+    app: security-profiles-operator-webhook
     name: security-profiles-operator-webhook
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
index 4aa1608bf..46105668b 100644
--- a/internal/pkg/config/config.go
+++ b/internal/pkg/config/config.go
@@ -49,6 +49,10 @@ const (
 	// the name of the current node.
 	NodeNameEnvKey = "NODE_NAME"
 
+	// OperatorNamespaceEnvKey is the default environment variable key for retrieving
+	// the operator's namespace.
+	OperatorNamespaceEnvKey = "OPERATOR_NAMESPACE"
+
 	// RestrictNamespaceEnvKey is the environment variable key for restricting
 	// the operator to work on only a single Kubernetes namespace.
 	RestrictNamespaceEnvKey = "RESTRICT_TO_NAMESPACE"
@@ -79,7 +83,7 @@ func GetOperatorNamespace() string {
 func TryToGetOperatorNamespace() (string, error) {
 	// This is OPERATOR_NAMESPACE should have been set by the downward API to identify
 	// the namespace which this controller is running from
-	operatorNS := util.EnvDefault("OPERATOR_NAMESPACE", "")
+	operatorNS := util.EnvDefault(OperatorNamespaceEnvKey, "")
 	if operatorNS == "" {
 		return "", ErrPodNamespaceEnvNotFound
 	}
diff --git a/internal/pkg/daemon/profilerecorder/profilerecorder.go b/internal/pkg/daemon/profilerecorder/profilerecorder.go
index 4b1999b85..e6c9d0a45 100644
--- a/internal/pkg/daemon/profilerecorder/profilerecorder.go
+++ b/internal/pkg/daemon/profilerecorder/profilerecorder.go
@@ -149,6 +149,7 @@ func (r *RecorderReconciler) isPodWithTraceAnnotation(obj runtime.Object) bool {
 	return false
 }
 
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
 func (r *RecorderReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
 	logger := r.log.WithValues("pod", req.Name, "namespace", req.Namespace)
 
diff --git a/internal/pkg/daemon/seccompprofile/seccompprofile.go b/internal/pkg/daemon/seccompprofile/seccompprofile.go
index 322e02bff..b035e3720 100644
--- a/internal/pkg/daemon/seccompprofile/seccompprofile.go
+++ b/internal/pkg/daemon/seccompprofile/seccompprofile.go
@@ -24,25 +24,18 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
-	"path/filepath"
-	"strings"
 	"time"
 
 	"github.com/containers/common/pkg/seccomp"
-	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"github.com/crossplane/crossplane-runtime/pkg/event"
-	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
-	corev1 "k8s.io/api/core/v1"
-	kerrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	"sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
-	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	statusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/nodestatus"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
@@ -52,8 +45,7 @@ const (
 	// default reconcile timeout.
 	reconcileTimeout = 1 * time.Minute
 
-	shortWait = 2 * time.Second
-	wait      = 30 * time.Second
+	wait = 10 * time.Second
 
 	errGetProfile          = "cannot get profile"
 	errSeccompProfileNil   = "seccomp profile cannot be nil"
@@ -68,65 +60,16 @@ const (
 
 	reasonSeccompNotSupported   event.Reason = "SeccompNotSupportedOnNode"
 	reasonInvalidSeccompProfile event.Reason = "InvalidSeccompProfile"
-	reasonCannotGetProfilePath  event.Reason = "CannotGetSeccompProfilePath"
 	reasonCannotSaveProfile     event.Reason = "CannotSaveSeccompProfile"
 	reasonCannotRemoveProfile   event.Reason = "CannotRemoveSeccompProfile"
 	reasonCannotUpdateProfile   event.Reason = "CannotUpdateSeccompProfile"
+	reasonCannotUpdateStatus    event.Reason = "CannotUpdateNodeStatus"
 
 	reasonSavedProfile event.Reason = "SavedSeccompProfile"
-
-	extJSON = ".json"
-
-	spOwnerKey    = ".metadata.seccompProfileOwner"
-	linkedPodsKey = ".metadata.activeWorkloads"
 )
 
-func hasSeccompProfile(obj runtime.Object) bool {
-	pod, ok := obj.(*corev1.Pod)
-	if !ok {
-		return false
-	}
-
-	return len(getSeccompProfilesFromPod(pod)) > 0
-}
-
 // Setup adds a controller that reconciles seccomp profiles.
 func Setup(ctx context.Context, mgr ctrl.Manager, l logr.Logger) error {
-	// Index Pods using seccomp profiles
-	if err := mgr.GetFieldIndexer().IndexField(ctx, &corev1.Pod{}, spOwnerKey, func(rawObj client.Object) []string {
-		pod, ok := rawObj.(*corev1.Pod)
-		if !ok {
-			return []string{}
-		}
-		return getSeccompProfilesFromPod(pod)
-	}); err != nil {
-		return errors.Wrap(err, "creating pod index")
-	}
-
-	// Index SeccompProfiles with active pods
-	if err := mgr.GetFieldIndexer().IndexField(
-		ctx, &v1alpha1.SeccompProfile{}, linkedPodsKey, func(rawObj client.Object) []string {
-			sp, ok := rawObj.(*v1alpha1.SeccompProfile)
-			if !ok {
-				return []string{}
-			}
-			return sp.Status.ActiveWorkloads
-		}); err != nil {
-		return errors.Wrap(err, "creating seccomp profile index")
-	}
-
-	// Register a special reconciler for pod events
-	if err := ctrl.NewControllerManagedBy(mgr).
-		Named("pods").
-		For(&corev1.Pod{}).
-		WithEventFilter(resource.NewPredicates(hasSeccompProfile)).
-		Complete(&PodReconciler{
-			client: mgr.GetClient(),
-			log:    l,
-			record: event.NewAPIRecorder(mgr.GetEventRecorderFor("pods")),
-		}); err != nil {
-		return errors.Wrap(err, "creating pod controller")
-	}
 	// Register the regular reconciler to manage SeccompProfiles
 	return ctrl.NewControllerManagedBy(mgr).
 		Named("profile").
@@ -149,22 +92,17 @@ type Reconciler struct {
 	save   saver
 }
 
-// A PodReconciler monitors pod changes and links them to SeccompProfiles.
-type PodReconciler struct {
-	client client.Client
-	log    logr.Logger
-	record event.Recorder
-}
-
 // Security Profiles Operator RBAC permissions to manage SeccompProfile
 // nolint:lll
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles,verbs=get;list;watch;create;update;patch
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles/status,verbs=get;update;patch
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles/finalizers,verbs=delete;get;update;patch
 // +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilenodestatuses,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
 // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
 
+// +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=daemonsets,verbs=get;list;watch
+// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch
+
 // OpenShift ... This is ignored in other distros
 // nolint:lll
 // +kubebuilder:rbac:groups=security.openshift.io,namespace="security-profiles-operator",resources=securitycontextconstraints,verbs=use
@@ -195,7 +133,7 @@ func (r *Reconciler) Reconcile(_ context.Context, req reconcile.Request) (reconc
 	seccompProfile := &v1alpha1.SeccompProfile{}
 	if err := r.client.Get(ctx, req.NamespacedName, seccompProfile); err != nil {
 		// Expected to find a SeccompProfile, return an error and requeue
-		return reconcile.Result{}, errors.Wrap(ignoreNotFound(err), errGetProfile)
+		return reconcile.Result{}, errors.Wrap(util.IgnoreNotFound(err), errGetProfile)
 	}
 
 	return r.reconcileSeccompProfile(ctx, seccompProfile, logger)
@@ -267,11 +205,15 @@ func (r *Reconciler) reconcileSeccompProfile(
 	}
 	profileName := sp.Name
 
-	nodeStatus, err := nodestatus.NewForProfile(sp)
+	nodeStatus, err := nodestatus.NewForProfile(sp, r.client)
 	if err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "cannot create nodeStatus")
 	}
 
+	if !sp.GetDeletionTimestamp().IsZero() { // object is being deleted
+		return r.reconcileDeletion(ctx, sp, nodeStatus)
+	}
+
 	outputProfile, err := r.mergeBaseProfile(ctx, sp, l)
 	if err != nil {
 		return reconcile.Result{RequeueAfter: wait}, nil
@@ -280,85 +222,47 @@ func (r *Reconciler) reconcileSeccompProfile(
 	if err != nil {
 		l.Error(err, "cannot validate profile "+profileName)
 		r.record.Event(sp, event.Warning(reasonInvalidSeccompProfile, err))
-		return reconcile.Result{RequeueAfter: wait}, nil
+		return reconcile.Result{}, errors.Wrap(err, "cannot validate profile")
 	}
 
-	profilePath, err := GetProfilePath(profileName, sp.ObjectMeta.Namespace)
-	if err != nil {
-		l.Error(err, "cannot get profile path")
-		r.record.Event(sp, event.Warning(reasonCannotGetProfilePath, err))
-		return reconcile.Result{RequeueAfter: wait}, nil
-	}
+	profilePath := sp.GetProfilePath()
 
-	if !sp.GetDeletionTimestamp().IsZero() { // object is being deleted
-		setTerminatingStatus := func(sp *v1alpha1.SeccompProfile) *v1alpha1.SeccompProfileStatus {
-			status := sp.Status.DeepCopy()
-			status.SetConditions(rcommonv1.Deleting())
-			status.Status = secprofnodestatusv1alpha1.ProfileStateTerminating
-			return status
-		}
+	// The object is not being deleted
+	exists, existErr := nodeStatus.Exists(ctx)
 
-		if err := r.setBothStatuses(ctx, nodeStatus, sp, setTerminatingStatus); err != nil {
-			l.Error(err, "cannot update SeccompProfile status")
-			r.record.Event(sp, event.Warning(reasonCannotUpdateProfile, err))
-			return reconcile.Result{}, errors.Wrap(err, "updating status for deleted SeccompProfile")
-		}
-
-		hasStatus, err := nodeStatus.Exists(ctx, r.client)
-		if err != nil || !hasStatus {
-			return ctrl.Result{}, errors.Wrap(err, "checking if node status exists")
-		}
-
-		if err := handleDeletion(sp, l); err != nil {
-			l.Error(err, "cannot delete profile")
-			r.record.Event(sp, event.Warning(reasonCannotRemoveProfile, err))
-			return ctrl.Result{}, errors.Wrap(err, "handling file deletion for deleted SeccompProfile")
-		}
-
-		if err := nodeStatus.Remove(ctx, r.client); err != nil {
-			l.Error(err, "cannot remove node status/finalizer from seccomp profile")
-			r.record.Event(sp, event.Warning(reasonCannotUpdateProfile, err))
-			return ctrl.Result{}, errors.Wrap(err, "deleting node status/finalizer for deleted SeccompProfile")
-		}
-		return ctrl.Result{}, nil
+	if existErr != nil {
+		return reconcile.Result{}, errors.Wrap(existErr, "checking if node status exists")
 	}
 
-	hasNodeStatus, err := r.ensureNodeStatus(ctx, nodeStatus, sp)
-	if err != nil {
-		l.Error(err, "cannot create SeccompProfile status/finalizers")
-		r.record.Event(sp, event.Warning(reasonCannotUpdateProfile, err))
-		return reconcile.Result{}, errors.Wrap(err, "adding status/finalizer for SeccompProfile")
-	}
-
-	if !hasNodeStatus {
-		l.Info("profile node status created, reconciling")
-		return reconcile.Result{RequeueAfter: shortWait}, nil
+	if !exists {
+		if err := nodeStatus.Create(ctx); err != nil {
+			return reconcile.Result{}, errors.Wrap(err, "cannot ensure node status")
+		}
+		l.Info("Created an initial status for this node")
+		return reconcile.Result{RequeueAfter: wait}, nil
 	}
 
 	updated, err := r.save(profilePath, profileContent)
 	if err != nil {
 		l.Error(err, "cannot save profile into disk")
 		r.record.Event(sp, event.Warning(reasonCannotSaveProfile, err))
-		return reconcile.Result{RequeueAfter: wait}, nil
+		return reconcile.Result{}, errors.Wrap(err, "cannot save profile into disk")
 	}
 
-	// refresh reference
-	if err := r.client.Get(ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-		return reconcile.Result{}, errors.Wrap(ignoreNotFound(err), errGetProfile)
+	isAlreadyInstalled, getErr := nodeStatus.Matches(ctx, statusv1alpha1.ProfileStateInstalled)
+	if getErr != nil {
+		l.Error(err, "couldn't get current status")
+		return reconcile.Result{}, errors.Wrap(err, "getting status for installed SeccompProfile")
 	}
 
-	setInstalledStatus := func(sp *v1alpha1.SeccompProfile) *v1alpha1.SeccompProfileStatus {
-		status := sp.Status.DeepCopy()
-		status.Path = profilePath
-		status.SetConditions(rcommonv1.Available())
-		status.Status = secprofnodestatusv1alpha1.ProfileStateInstalled
-		status.LocalhostProfile = strings.TrimPrefix(profilePath, config.KubeletSeccompRootPath+"/")
-		return status
+	if isAlreadyInstalled {
+		l.Info("Already in the expected Installed state")
+		return reconcile.Result{}, nil
 	}
 
-	if err := r.setBothStatuses(ctx, nodeStatus, sp, setInstalledStatus); err != nil {
-		l.Error(err, "cannot update SeccompProfile status")
-		r.record.Event(sp, event.Warning(reasonCannotUpdateProfile, err))
+	if err := nodeStatus.SetNodeStatus(ctx, statusv1alpha1.ProfileStateInstalled); err != nil {
+		l.Error(err, "cannot update node status")
+		r.record.Event(sp, event.Warning(reasonCannotUpdateStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "updating status in SeccompProfile reconciler")
 	}
 
@@ -374,242 +278,52 @@ func (r *Reconciler) reconcileSeccompProfile(
 	return reconcile.Result{}, nil
 }
 
-// setBothStatuses checks if the node status of a SeccompProfile is in sync with the supplied
-// SeccompProfileStatus and updates the node status if not. Additionally, the status of the
-// SeccompProfile is set to the lowest common denominator as well.
-func (r *Reconciler) setBothStatuses(
-	ctx context.Context, ns *nodestatus.StatusClient,
-	sp *v1alpha1.SeccompProfile, setStatusFn func(sp *v1alpha1.SeccompProfile) *v1alpha1.SeccompProfileStatus,
-) error {
-	if retryErr := util.Retry(func() error {
-		if err := r.client.Get(ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-			return errors.Wrap(err, "retrieving profile")
-		}
-
-		status := setStatusFn(sp)
-
-		profileStatus, err := ns.SetReturnGlobal(ctx, r.client, status.Status)
-		if err != nil {
-			return errors.Wrap(err, "setting per-node status")
-		}
-		status.Status = profileStatus
-		return r.setStatus(ctx, sp, status)
-	}, util.IsNotFoundOrConflict); retryErr != nil {
-		return errors.Wrap(retryErr, "updating profile status")
-	}
-
-	return nil
-}
-
-func (r *Reconciler) ensureNodeStatus(
-	ctx context.Context, nodeStatus *nodestatus.StatusClient, sp *v1alpha1.SeccompProfile) (bool, error) {
-	nodeStatusExists, err := nodeStatus.Exists(ctx, r.client)
+func (r *Reconciler) reconcileDeletion(
+	ctx context.Context,
+	sp *v1alpha1.SeccompProfile,
+	nsc *nodestatus.StatusClient,
+) (reconcile.Result, error) {
+	hasStatus, err := nsc.Exists(ctx)
 	if err != nil {
-		return false, errors.Wrap(err, "Retrieving node status")
-	}
-
-	if !nodeStatusExists {
-		if err := nodeStatus.Create(ctx, r.client); err != nil {
-			return nodeStatusExists, errors.Wrap(err, "Creating node status")
-		}
+		return ctrl.Result{}, errors.Wrap(err, "checking if node status exists")
 	}
 
-	if err := util.Retry(func() error {
-		if sp.Status.Status != "" {
-			return nil
+	// Set the status if it hasn't been deleted already
+	if hasStatus {
+		isTerminating, getErr := nsc.Matches(ctx, statusv1alpha1.ProfileStateTerminating)
+		if getErr != nil {
+			r.log.Error(err, "couldn't get current status")
+			return reconcile.Result{}, errors.Wrap(err, "getting status for deleted SeccompProfile")
 		}
 
-		profileCopy := sp.DeepCopy()
-		profileCopy.Status.Status = secprofnodestatusv1alpha1.ProfileStatePending
-		profileCopy.Status.SetConditions(rcommonv1.Creating())
-
-		updateErr := r.client.Status().Update(context.TODO(), profileCopy)
-		if updateErr != nil {
-			if err := r.client.Get(
-				ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-				return errors.Wrap(err, "retrieving profile")
+		if !isTerminating {
+			r.log.Info("setting status to terminating")
+			if err := nsc.SetNodeStatus(ctx, statusv1alpha1.ProfileStateTerminating); err != nil {
+				r.log.Error(err, "cannot update SeccompProfile status")
+				r.record.Event(sp, event.Warning(reasonCannotUpdateProfile, err))
+				return reconcile.Result{}, errors.Wrap(err, "updating status for deleted SeccompProfile")
 			}
+			return reconcile.Result{Requeue: true, RequeueAfter: wait}, nil
 		}
-		return errors.Wrap(updateErr, "updating to initial state")
-	}, util.IsNotFoundOrConflict); err != nil {
-		return nodeStatusExists, errors.Wrap(err, "Updating seccomp status to PENDING")
 	}
 
-	return nodeStatusExists, nil
-}
-
-// setStatus checks if the Status of a SeccompProfile is in sync with the supplied
-// SeccompProfileStatus and updates the SeccompProfile if not.
-func (r *Reconciler) setStatus(
-	ctx context.Context, sp *v1alpha1.SeccompProfile, status *v1alpha1.SeccompProfileStatus) error {
-	if sp.Status.Status == status.Status &&
-		sp.Status.Path == status.Path &&
-		sp.Status.LocalhostProfile == status.LocalhostProfile {
-		return nil
-	}
-
-	sp.Status = *status
-	if err := r.client.Status().Update(ctx, sp); err != nil {
-		return errors.Wrap(err, "setting SeccompProfile status")
-	}
-	return nil
-}
-
-// Namespace scoped
-// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
-
-// Reconcile reacts to pod events and updates SeccompProfiles if in use or no longer in use by a pod.
-func (r *PodReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
-	logger := r.log.WithValues("pod", req.Name, "namespace", req.Namespace)
-
-	ctx, cancel := context.WithTimeout(context.Background(), reconcileTimeout)
-	defer cancel()
-
-	podID := req.Namespace + "/" + req.Name
-
-	pod := &corev1.Pod{}
-	var err error
-	if err = r.client.Get(ctx, req.NamespacedName, pod); ignoreNotFound(err) != nil {
-		logger.Error(err, "could not get pod")
-		return reconcile.Result{}, errors.Wrap(err, "looking up pod in pod reconciler")
-	}
-	if kerrors.IsNotFound(err) { // this is a pod deletion, so update all seccomp profiles that were using it
-		seccompProfiles := &v1alpha1.SeccompProfileList{}
-		if err = r.client.List(ctx, seccompProfiles, client.MatchingFields{linkedPodsKey: podID}); err != nil {
-			return reconcile.Result{}, errors.Wrap(err, "listing SeccompProfiles for deleted pod")
-		}
-		for i := range seccompProfiles.Items {
-			if err = r.updatePodReferences(ctx, &seccompProfiles.Items[i]); err != nil {
-				return reconcile.Result{}, errors.Wrap(err, "updating SeccompProfile for deleted pod")
-			}
-		}
-		return reconcile.Result{}, nil
-	}
-	// pod is being created or updated so ensure it is linked to a seccomp profile
-	for _, profileIndex := range getSeccompProfilesFromPod(pod) {
-		profileElements := strings.Split(profileIndex, "/")
-		profileNamespace := profileElements[1]
-		profileName := strings.TrimSuffix(profileElements[2], ".json")
-		seccompProfile := &v1alpha1.SeccompProfile{}
-		if err := r.client.Get(ctx, util.NamespacedName(profileName, profileNamespace), seccompProfile); err != nil {
-			logger.Error(err, "could not get seccomp profile for pod")
-			return reconcile.Result{}, errors.Wrap(err, "looking up SeccompProfile for new or updated pod")
-		}
-		if err := r.updatePodReferences(ctx, seccompProfile); err != nil {
-			logger.Error(err, "could not update seccomp profile for pod")
-			return reconcile.Result{}, errors.Wrap(err, "updating SeccompProfile pod references for new or updated pod")
-		}
-	}
-	return reconcile.Result{}, nil
-}
-
-// updatePodReferences updates a SeccompProfile with the identifiers of pods using it and ensures
-// it has a finalizer indicating it is in use to prevent it from being deleted.
-func (r *PodReconciler) updatePodReferences(ctx context.Context, sp *v1alpha1.SeccompProfile) error {
-	linkedPods := &corev1.PodList{}
-	profileReference := fmt.Sprintf("operator/%s/%s.json", sp.GetNamespace(), sp.GetName())
-	err := r.client.List(ctx, linkedPods, client.MatchingFields{spOwnerKey: profileReference})
-	if ignoreNotFound(err) != nil {
-		return errors.Wrap(err, "listing pods to update seccompProfile")
-	}
-	podList := make([]string, len(linkedPods.Items))
-	for i := range linkedPods.Items {
-		pod := linkedPods.Items[i]
-		podList[i] = pod.ObjectMeta.Namespace + "/" + pod.ObjectMeta.Name
-	}
-	if err := util.Retry(func() error {
-		sp.Status.ActiveWorkloads = podList
-
-		updateErr := r.client.Status().Update(ctx, sp)
-		if updateErr != nil {
-			if err := r.client.Get(ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-				return errors.Wrap(err, "retrieving profile")
-			}
-		}
-
-		return errors.Wrap(updateErr, "updating profile")
-	}, util.IsNotFoundOrConflict); err != nil {
-		return errors.Wrap(err, "updating SeccompProfile status")
-	}
-	hasActivePodsFinalizerString := "in-use-by-active-pods"
-	if len(linkedPods.Items) > 0 {
-		if err := util.Retry(func() error {
-			return util.AddFinalizer(ctx, r.client, sp, hasActivePodsFinalizerString)
-		}, util.IsNotFoundOrConflict); err != nil {
-			return errors.Wrap(err, "adding finalizer")
-		}
-	} else {
-		if err := util.Retry(func() error {
-			return util.RemoveFinalizer(ctx, r.client, sp, hasActivePodsFinalizerString)
-		}, util.IsNotFoundOrConflict); err != nil {
-			return errors.Wrap(err, "removing finalizer")
-		}
+	if err := handleDeletion(sp, r.log); err != nil {
+		r.log.Error(err, "cannot delete profile")
+		r.record.Event(sp, event.Warning(reasonCannotRemoveProfile, err))
+		return ctrl.Result{}, errors.Wrap(err, "handling file deletion for deleted SeccompProfile")
 	}
-	return nil
-}
 
-// getSeccompProfilesFromPod returns a slice of strings representing seccomp profiles required by the pod.
-// It looks first at the pod spec level, then in each container and init container, then in the annotations.
-func getSeccompProfilesFromPod(pod *corev1.Pod) []string {
-	profiles := []string{}
-	// try to get profile from pod securityContext
-	sc := pod.Spec.SecurityContext
-	if sc != nil && isOperatorSeccompProfile(sc.SeccompProfile) {
-		profiles = append(profiles, *sc.SeccompProfile.LocalhostProfile)
-	}
-	// try to get profile(s) from securityContext in pods
-	containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
-	contains := func(a []string, b string) bool {
-		for _, s := range a {
-			if s == b {
-				return true
-			}
-		}
-		return false
-	}
-	for i := range containers {
-		sc := containers[i].SecurityContext
-		if sc != nil && isOperatorSeccompProfile(sc.SeccompProfile) {
-			profileString := *containers[i].SecurityContext.SeccompProfile.LocalhostProfile
-			if !contains(profiles, profileString) {
-				profiles = append(profiles, profileString)
-			}
-		}
-	}
-	// try to get profile from annotations
-	annotation, hasAnnotation := pod.GetAnnotations()[corev1.SeccompPodAnnotationKey]
-	if hasAnnotation && strings.HasPrefix(annotation, "localhost/") {
-		profileString := strings.TrimPrefix(annotation, "localhost/")
-		if !contains(profiles, profileString) {
-			profiles = append(profiles, profileString)
-		}
+	if err := nsc.Remove(ctx, r.client); err != nil {
+		r.log.Error(err, "cannot remove node status/finalizer from seccomp profile")
+		r.record.Event(sp, event.Warning(reasonCannotUpdateStatus, err))
+		return ctrl.Result{}, errors.Wrap(err, "deleting node status/finalizer for deleted SeccompProfile")
 	}
-	return profiles
-}
-
-// isOperatorSeccompProfile checks whether a corev1.SeccompProfile object belongs to the operator.
-// SeccompProfiles controlled by the operator are of type "Localhost" and have a path of the form
-// "operator/namespace/profile-name.json".
-func isOperatorSeccompProfile(sp *corev1.SeccompProfile) bool {
-	if sp == nil || sp.Type != "Localhost" {
-		return false
-	}
-	if !strings.HasPrefix(*sp.LocalhostProfile, "operator/") {
-		return false
-	}
-	if !strings.HasSuffix(*sp.LocalhostProfile, ".json") {
-		return false
-	}
-	const pathParts = 3
-	return len(strings.Split(*sp.LocalhostProfile, "/")) == pathParts
+	return ctrl.Result{}, nil
 }
 
 func handleDeletion(sp *v1alpha1.SeccompProfile, l logr.Logger) error {
-	profilePath, err := GetProfilePath(sp.GetName(), sp.GetNamespace())
-	if err != nil {
-		return err
-	}
-	err = os.Remove(profilePath)
+	profilePath := sp.GetProfilePath()
+	err := os.Remove(profilePath)
 	if os.IsNotExist(err) {
 		return nil
 	}
@@ -636,22 +350,3 @@ func saveProfileOnDisk(fileName string, content []byte) (updated bool, err error
 
 	return true, nil
 }
-
-// GetProfilePath returns the full path for the provided profile name and config.
-func GetProfilePath(profileName, namespace string) (string, error) {
-	if filepath.Ext(profileName) != extJSON {
-		profileName += extJSON
-	}
-	return path.Join(
-		config.ProfilesRootPath,
-		filepath.Base(namespace),
-		filepath.Base(profileName),
-	), nil
-}
-
-func ignoreNotFound(err error) error {
-	if kerrors.IsNotFound(err) {
-		return nil
-	}
-	return err
-}
diff --git a/internal/pkg/daemon/selinuxpolicy/selinuxpolicy_controller.go b/internal/pkg/daemon/selinuxpolicy/selinuxpolicy_controller.go
index aa0e985a2..12b343564 100644
--- a/internal/pkg/daemon/selinuxpolicy/selinuxpolicy_controller.go
+++ b/internal/pkg/daemon/selinuxpolicy/selinuxpolicy_controller.go
@@ -30,7 +30,6 @@ import (
 	"text/template"
 	"time"
 
-	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"github.com/crossplane/crossplane-runtime/pkg/event"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
@@ -39,11 +38,10 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
-	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	statusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
 	selinuxpolicyv1alpha1 "sigs.k8s.io/security-profiles-operator/api/selinuxpolicy/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod/bindata"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/nodestatus"
-	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
 )
 
 // The underscore is not a valid character in a pod, so we can
@@ -106,36 +104,30 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 		return reconcile.Result{}, IgnoreNotFound(err)
 	}
 
-	nodeStatus, err := nodestatus.NewForProfile(instance)
+	nodeStatus, err := nodestatus.NewForProfile(instance, r.client)
 	if err != nil {
-		return reconcile.Result{}, errors.Wrap(err, "cannot create nodeStatus")
-	}
-
-	if instance.Status.Usage != GetPolicyUsage(instance.Name, instance.Namespace) {
-		if err := r.addUsageStatus(instance); err != nil {
-			return reconcile.Result{}, err
-		}
-		return reconcile.Result{Requeue: true}, nil
+		return reconcile.Result{}, errors.Wrap(err, "cannot create nodeStatus instance")
 	}
 
 	if instance.ObjectMeta.DeletionTimestamp.IsZero() {
+		ctx := context.Background()
 		// The object is not being deleted
-		if err := r.ensureNodeStatus(context.TODO(), nodeStatus, instance); err != nil {
-			return reconcile.Result{}, errors.Wrap(err, "ensuring node status exists")
+		exists, existErr := nodeStatus.Exists(ctx)
+
+		if existErr != nil {
+			return reconcile.Result{}, errors.Wrap(existErr, "checking if node status exists")
 		}
 
-		return r.reconcilePolicy(instance, nodeStatus, reqLogger)
-	}
+		if !exists {
+			if err := nodeStatus.Create(ctx); err != nil {
+				return reconcile.Result{}, errors.Wrap(err, "cannot ensure node status")
+			}
+		}
 
-	// The object is being deleted
-	setTerminatingStatus := func(sp *selinuxpolicyv1alpha1.SelinuxPolicy) *selinuxpolicyv1alpha1.SelinuxPolicyStatus {
-		statusCopy := instance.Status.DeepCopy()
-		statusCopy.SetConditions(rcommonv1.Deleting())
-		statusCopy.State = secprofnodestatusv1alpha1.ProfileStateTerminating
-		return statusCopy
+		return r.reconcilePolicy(instance, nodeStatus, reqLogger)
 	}
 
-	if err := r.setBothStatuses(context.TODO(), nodeStatus, instance, setTerminatingStatus); err != nil {
+	if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateTerminating); err != nil {
 		reqLogger.Error(err, "cannot update SELinux profile status")
 		return reconcile.Result{}, errors.Wrap(err, "updating status for deleted SELinux profile")
 	}
@@ -143,7 +135,7 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 	// since the nodeStatus API always removes both the node status and the node's finalizer in sync,
 	// this condition will only be true after both are gone and therefore when the profile is really
 	// gone from the node
-	hasStatus, err := nodeStatus.Exists(context.TODO(), r.client)
+	hasStatus, err := nodeStatus.Exists(context.TODO())
 	if err != nil || !hasStatus {
 		return reconcile.Result{}, errors.Wrap(err, "asserting if node status exists")
 	}
@@ -161,109 +153,6 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 	return reconcile.Result{}, nil
 }
 
-func (r *ReconcileSP) ensureNodeStatus(
-	ctx context.Context, nodeStatus *nodestatus.StatusClient, sp *selinuxpolicyv1alpha1.SelinuxPolicy,
-) error {
-	if err := nodeStatus.Create(ctx, r.client); err != nil {
-		return errors.Wrap(err, "cannot ensure node status")
-	}
-
-	// Retry makes sense here as we're updating the shared state directly from multiple nodes
-	if err := util.Retry(func() error {
-		if sp.Status.State != "" && sp.Spec.Apply {
-			return nil
-		}
-
-		policyCopy := sp.DeepCopy()
-		if sp.Status.State == "" {
-			// Set up an initial state
-			policyCopy.Status.State = secprofnodestatusv1alpha1.ProfileStatePending
-			policyCopy.Status.SetConditions(rcommonv1.Creating())
-		} else if !sp.Spec.Apply {
-			// If "apply" is false, no need to do anything, let the deployer
-			// review it.
-			policyCopy.Status.State = secprofnodestatusv1alpha1.ProfileStatePending
-			policyCopy.Status.SetConditions(rcommonv1.Unavailable())
-		}
-
-		updateErr := r.client.Status().Update(context.TODO(), policyCopy)
-		if updateErr != nil {
-			if err := r.client.Get(
-				ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-				return errors.Wrap(err, "retrieving profile")
-			}
-		}
-		return errors.Wrap(updateErr, "updating to initial state")
-	}, util.IsNotFoundOrConflict); err != nil {
-		return errors.Wrap(err, "Updating SelinuxPolicy status to PENDING")
-	}
-
-	return nil
-}
-
-// setBothStatuses checks if the node status of a SELinux profile is in sync with the supplied
-// SELinux profile and updates the node status if not. Additionally, the status of the
-// SELinux profile is set to the lowest common denominator as well.
-func (r *ReconcileSP) setBothStatuses(
-	ctx context.Context, ns *nodestatus.StatusClient,
-	sp *selinuxpolicyv1alpha1.SelinuxPolicy,
-	setStatusFn func(sp *selinuxpolicyv1alpha1.SelinuxPolicy) *selinuxpolicyv1alpha1.SelinuxPolicyStatus,
-) error {
-	if retryErr := util.Retry(func() error {
-		if err := r.client.Get(ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
-			return errors.Wrap(err, "retrieving profile")
-		}
-
-		status := setStatusFn(sp)
-
-		policyState, err := ns.SetReturnGlobal(ctx, r.client, status.State)
-		if err != nil {
-			return errors.Wrap(err, "setting per-node status")
-		}
-
-		if sp.Status.State == policyState {
-			// avoid needless writes
-			return nil
-		}
-
-		// this should already be a deep-copy and the only thing we're changing is a scalar..
-		sp.Status = *status
-		if err := r.client.Status().Update(ctx, sp); err != nil {
-			return errors.Wrap(err, "setting SELinux profile status")
-		}
-		return nil
-	}, util.IsNotFoundOrConflict); retryErr != nil {
-		return errors.Wrap(retryErr, "updating policy status")
-	}
-
-	return nil
-}
-
-func (r *ReconcileSP) reconcileInProgressPolicy(
-	nodeStatus *nodestatus.StatusClient, sp *selinuxpolicyv1alpha1.SelinuxPolicy,
-) error {
-	setInProgressStatus := func(sp *selinuxpolicyv1alpha1.SelinuxPolicy) *selinuxpolicyv1alpha1.SelinuxPolicyStatus {
-		statusCopy := sp.Status.DeepCopy()
-		statusCopy.SetConditions(rcommonv1.Creating())
-		statusCopy.State = secprofnodestatusv1alpha1.ProfileStateInProgress
-		return statusCopy
-	}
-
-	if err := r.setBothStatuses(context.TODO(), nodeStatus, sp, setInProgressStatus); err != nil {
-		return errors.Wrap(err, "setting both statuses to in progress")
-	}
-	return nil
-}
-
-func (r *ReconcileSP) addUsageStatus(sp *selinuxpolicyv1alpha1.SelinuxPolicy) error {
-	spcopy := sp.DeepCopy()
-	spcopy.Status.Usage = GetPolicyUsage(spcopy.Name, spcopy.Namespace)
-	if err := r.client.Status().Update(context.Background(), spcopy); err != nil {
-		return errors.Wrap(err, "Updating SelinuxPolicy usage status")
-	}
-	return nil
-}
-
 func (r *ReconcileSP) reconcilePolicy(
 	sp *selinuxpolicyv1alpha1.SelinuxPolicy, nodeStatus *nodestatus.StatusClient, l logr.Logger,
 ) (reconcile.Result, error) {
@@ -281,43 +170,39 @@ func (r *ReconcileSP) reconcilePolicy(
 		return reconcile.Result{}, errors.Wrap(err, "Creating policy file")
 	}
 
-	l.Info("Checking if policy is installed", "policyName", sp.Name)
+	l.Info("Checking if policy deployed", "policyName", sp.Name)
 	polStatus, err := getPolicyStatus(sp)
 	if err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "Looking up policy status")
 	}
 
 	if polStatus == nil {
-		if err := r.reconcileInProgressPolicy(nodeStatus, sp); err != nil {
-			return reconcile.Result{}, errors.Wrap(err, "updating in progress status")
+		if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateInProgress); err != nil {
+			return reconcile.Result{}, errors.Wrap(err, "setting node status to in progress")
 		}
 		return reconcile.Result{Requeue: true}, nil
 	}
 
-	l.Info("Policy installed")
-	setInstallationStatus := func(sp *selinuxpolicyv1alpha1.SelinuxPolicy) *selinuxpolicyv1alpha1.SelinuxPolicyStatus {
-		statusCopy := sp.Status.DeepCopy()
+	var polState statusv1alpha1.ProfileState
 
-		switch polStatus.Status {
-		case installedStatus:
-			statusCopy.State = secprofnodestatusv1alpha1.ProfileStateInstalled
-			statusCopy.SetConditions(rcommonv1.Available())
-		case failedStatus:
-			statusCopy.State = secprofnodestatusv1alpha1.ProfileStateError
-			statusCopy.SetConditions(rcommonv1.Unavailable())
-		}
-		return statusCopy
+	switch polStatus.Status {
+	case installedStatus:
+		polState = statusv1alpha1.ProfileStateInstalled
+	case failedStatus:
+		polState = statusv1alpha1.ProfileStateError
 	}
 
-	if err := r.setBothStatuses(context.TODO(), nodeStatus, sp, setInstallationStatus); err != nil {
-		return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation success")
+	l.Info("Policy deployed", "status", polState)
+
+	if err := nodeStatus.SetNodeStatus(context.TODO(), polState); err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "setting profile status")
 	}
 
 	return reconcile.Result{}, nil
 }
 
 func (r *ReconcileSP) reconcilePolicyFile(sp *selinuxpolicyv1alpha1.SelinuxPolicy, l logr.Logger) error {
-	policyPath := path.Join(bindata.SelinuxDropDirectory, GetPolicyName(sp.Name, sp.Namespace)+".cil")
+	policyPath := path.Join(bindata.SelinuxDropDirectory, sp.GetPolicyName()+".cil")
 	policyContent := []byte(r.wrapPolicy(sp))
 
 	l.Info("Writing to policy file", "policyPath", policyPath)
@@ -386,13 +271,7 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 			l.Info("Policy still installed, requeue")
 			return reconcile.Result{Requeue: true}, nil
 		case failedStatus:
-			setFailedStatus := func(sp *selinuxpolicyv1alpha1.SelinuxPolicy) *selinuxpolicyv1alpha1.SelinuxPolicyStatus {
-				statusCopy := sp.Status.DeepCopy()
-				statusCopy.State = secprofnodestatusv1alpha1.ProfileStateError
-				statusCopy.SetConditions(rcommonv1.Unavailable())
-				return statusCopy
-			}
-			if err := r.setBothStatuses(context.TODO(), nodeStatus, sp, setFailedStatus); err != nil {
+			if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateError); err != nil {
 				return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation success")
 			}
 
@@ -406,7 +285,7 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 
 func (r *ReconcileSP) reconcileDeletePolicyFile(sp *selinuxpolicyv1alpha1.SelinuxPolicy,
 	l logr.Logger) (reconcile.Result, error) {
-	policyPath := path.Join(bindata.SelinuxDropDirectory, GetPolicyName(sp.Name, sp.Namespace)+".cil")
+	policyPath := path.Join(bindata.SelinuxDropDirectory, sp.GetPolicyName()+".cil")
 
 	l.Info("Removing policy file", "policyPath", policyPath)
 	err := os.Remove(policyPath)
@@ -486,7 +365,7 @@ func isSelinuxdReady() (bool, error) {
 }
 
 func getPolicyStatus(sp *selinuxpolicyv1alpha1.SelinuxPolicy) (*sePolStatus, error) {
-	polURL := selinuxdPoliciesBaseURL + GetPolicyName(sp.Name, sp.Namespace)
+	polURL := selinuxdPoliciesBaseURL + sp.GetPolicyName()
 	response, err := selinuxdGetRequest(polURL)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to send a request to selinuxd")
diff --git a/internal/pkg/daemon/selinuxpolicy/utils.go b/internal/pkg/daemon/selinuxpolicy/utils.go
index 4637436c0..82f52104d 100644
--- a/internal/pkg/daemon/selinuxpolicy/utils.go
+++ b/internal/pkg/daemon/selinuxpolicy/utils.go
@@ -30,18 +30,6 @@ import (
 	"k8s.io/apimachinery/pkg/api/errors"
 )
 
-// GetPolicyName gets the policy module name in the format that
-// we're expecting for parsing.
-func GetPolicyName(name, ns string) string {
-	return name + "_" + ns
-}
-
-// GetPolicyUsage is the representation of how a pod will call this
-// SELinux module.
-func GetPolicyUsage(name, ns string) string {
-	return GetPolicyName(name, ns) + ".process"
-}
-
 // GetPolicyK8sName gets the policy name in a format that's OK for k8s names.
 func GetPolicyK8sName(name, ns string) string {
 	return name + "-" + ns
diff --git a/internal/pkg/manager/nodestatus/nodestatus.go b/internal/pkg/manager/nodestatus/nodestatus.go
new file mode 100644
index 000000000..3309a35d8
--- /dev/null
+++ b/internal/pkg/manager/nodestatus/nodestatus.go
@@ -0,0 +1,282 @@
+/*
+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 nodestatus
+
+import (
+	"context"
+	"time"
+
+	rcommonv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
+	appsv1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/selection"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	pbv1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilebase/v1alpha1"
+	seccompv1alpha1 "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
+	statusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	selinuxv1alpha1 "sigs.k8s.io/security-profiles-operator/api/selinuxpolicy/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
+)
+
+const (
+	reconcileTimeout = 1 * time.Minute
+	dsWait           = 30 * time.Second
+)
+
+var (
+	ErrNoOwnerProfile  = errors.New("No owner profile defined for this status")
+	ErrUnkownOwnerKind = errors.New("the node status owner is of an unknown kind")
+)
+
+// A StatusReconciler monitors node changes and updates the profile status.
+type StatusReconciler struct {
+	client client.Client
+	log    logr.Logger
+	record event.Recorder
+}
+
+func NewStatusReconciler(cli client.Client, log logr.Logger, record event.Recorder) *StatusReconciler {
+	return &StatusReconciler{
+		client: cli,
+		log:    log,
+		record: record,
+	}
+}
+
+// Security Profiles Operator RBAC permissions to manage SelinuxPolicy
+// nolint:lll
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=selinuxpolicies,verbs=get;list;watch;create;update;patch
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=selinuxpolicies/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=selinuxpolicies/finalizers,verbs=delete;get;update;patch
+
+// Security Profiles Operator RBAC permissions to manage SeccompProfile
+// nolint:lll
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles,verbs=get;list;watch;create;update;patch
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=seccompprofiles/finalizers,verbs=delete;get;update;patch
+
+// Security Profiles Operator RBAC permissions to manage Node Statuses
+// nolint:lll
+// +kubebuilder:rbac:groups=security-profiles-operator.x-k8s.io,resources=securityprofilenodestatuses,verbs=get;list;watch
+
+// Reconcile reconciles a NodeStatus.
+func (r *StatusReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), reconcileTimeout)
+	defer cancel()
+
+	logger := r.log.WithValues("nodeStatus", req.Name, "namespace", req.Namespace)
+	logger.Info("Reconciling node status")
+
+	// get the status to be reconciled
+	instance := &statusv1alpha1.SecurityProfileNodeStatus{}
+	if err := r.client.Get(ctx, req.NamespacedName, instance); err != nil {
+		// Expected to find a node profile, return an error and requeue
+		// TODO(jhrozek): ignore not found?
+		return reconcile.Result{}, util.IgnoreNotFound(err)
+	}
+
+	prof, getProfErr := r.getProfileFromStatus(ctx, instance)
+	if getProfErr != nil {
+		return reconcile.Result{}, getProfErr
+	}
+
+	lprof := logger.WithValues(
+		"Profile.Name", prof.GetName(),
+		"Profile.Namespace", prof.GetNamespace(),
+		"Profile.Kind", prof.GetObjectKind().GroupVersionKind(),
+	)
+
+	// get all the other statuses
+	profName := instance.Labels[statusv1alpha1.StatusToProfLabel]
+	if profName == "" {
+		return reconcile.Result{}, errors.New("Unlabeled node status")
+	}
+
+	if prof.GetName() != profName {
+		return reconcile.Result{}, errors.New("status doesn't match owner")
+	}
+
+	// Initialize status if it hasn't happened already
+	if prof.GetStatusBase().Status == "" {
+		lprof.Info("Initializing Profile status")
+
+		targetStatus := statusv1alpha1.ProfileStatePending
+		if instance.Status != "" {
+			targetStatus = instance.Status
+		}
+		return r.reconcileStatus(ctx, prof, targetStatus, lprof)
+	}
+
+	nodeStatusList, err := listStatusesForProfile(ctx, r.client, profName)
+	if err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "cannot list the node statuses")
+	}
+
+	// get the DS
+	spodDS, err := r.getDS(ctx, config.GetOperatorNamespace(), lprof)
+	if err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "cannot get the DS")
+	}
+
+	if !daemonSetIsReady(spodDS) || daemonSetIsUpdating(spodDS) {
+		// If the DS is not ready or updating, don't bother updating the status
+		logger.Info("Not updating policy because the SPOd is not ready")
+		return reconcile.Result{RequeueAfter: dsWait}, nil
+	}
+
+	// make sure we have all the statuses already
+	hasStatuses := len(nodeStatusList.Items)
+	wantsStatuses := spodDS.Status.DesiredNumberScheduled
+	if wantsStatuses != int32(hasStatuses) {
+		logger.Info("Not updating policy: not all statuses are ready",
+			"has", hasStatuses, "wants", wantsStatuses)
+		// Don't reconcile again, let's just wait for another update
+		return reconcile.Result{}, nil
+	}
+
+	var lowestCommonState statusv1alpha1.ProfileState = statusv1alpha1.LowestState
+	for i := range nodeStatusList.Items {
+		lowestCommonState = statusv1alpha1.LowerOfTwoStates(lowestCommonState, nodeStatusList.Items[i].Status)
+	}
+	logger.Info("Setting the status to", "Status", lowestCommonState)
+
+	return r.reconcileStatus(ctx, prof, lowestCommonState, lprof)
+}
+
+func (r *StatusReconciler) getDS(ctx context.Context, namespace string, l logr.Logger) (*appsv1.DaemonSet, error) {
+	dsSelect := labels.NewSelector()
+	spodDSFilter, err := labels.NewRequirement("spod", selection.Exists, []string{})
+	if err != nil {
+		return nil, errors.Wrap(err, "cannot create DS list label")
+	}
+	dsSelect.Add(*spodDSFilter)
+	dsListOpts := client.ListOptions{
+		LabelSelector: dsSelect,
+		Namespace:     namespace,
+	}
+
+	spodDSList := appsv1.DaemonSetList{}
+	if err := r.client.List(ctx, &spodDSList, &dsListOpts); err != nil {
+		return nil, errors.Wrap(err, "cannot list DS")
+	}
+
+	if len(spodDSList.Items) != 1 {
+		retErr := errors.New("Did not find exactly one DS")
+		l.Error(retErr, "Expected to find 1 DS", "len(dsList.Items)", len(spodDSList.Items))
+		return nil, errors.Wrap(retErr, "listing DS")
+	}
+
+	return &spodDSList.Items[0], nil
+}
+
+func (r *StatusReconciler) getProfileFromStatus(
+	ctx context.Context,
+	s *statusv1alpha1.SecurityProfileNodeStatus,
+) (pbv1alpha1.StatusBaseUser, error) {
+	ctrl := metav1.GetControllerOf(s)
+	if ctrl == nil {
+		return nil, errors.Wrapf(ErrNoOwnerProfile, "getting owner profile")
+	}
+
+	key := types.NamespacedName{
+		Name:      ctrl.Name,
+		Namespace: s.GetNamespace(),
+	}
+	var prof pbv1alpha1.StatusBaseUser
+	switch ctrl.Kind {
+	case "SeccompProfile":
+		prof = &seccompv1alpha1.SeccompProfile{}
+	case "SelinuxPolicy":
+		prof = &selinuxv1alpha1.SelinuxPolicy{}
+	default:
+		return nil, errors.Wrapf(ErrUnkownOwnerKind, "getting owner profile")
+	}
+	if err := r.client.Get(ctx, key, prof); err != nil {
+		return nil, errors.Wrapf(err, "getting owner profile: %s/%s", s.GetNamespace(), ctrl.Name)
+	}
+	return prof, nil
+}
+
+func (r *StatusReconciler) reconcileStatus(
+	ctx context.Context,
+	prof pbv1alpha1.StatusBaseUser,
+	state statusv1alpha1.ProfileState,
+	l logr.Logger,
+) (reconcile.Result, error) {
+	pCopy := prof.DeepCopyToStatusBaseIf()
+
+	// We always set this status
+	pCopy.SetImplementationStatus()
+
+	outStatus := pCopy.GetStatusBase()
+	switch state {
+	case statusv1alpha1.ProfileStatePending, "":
+		outStatus.Status = statusv1alpha1.ProfileStatePending
+		outStatus.SetConditions(rcommonv1.Creating())
+	case statusv1alpha1.ProfileStateInProgress:
+		outStatus.SetConditions(rcommonv1.Creating())
+		outStatus.Status = statusv1alpha1.ProfileStateInProgress
+	case statusv1alpha1.ProfileStateInstalled:
+		outStatus.Status = statusv1alpha1.ProfileStateInstalled
+		outStatus.SetConditions(rcommonv1.Available())
+	case statusv1alpha1.ProfileStateTerminating:
+		outStatus.Status = statusv1alpha1.ProfileStateTerminating
+		outStatus.SetConditions(rcommonv1.Deleting())
+	case statusv1alpha1.ProfileStateError:
+		outStatus.Status = statusv1alpha1.ProfileStateError
+		outStatus.SetConditions(rcommonv1.Unavailable())
+	}
+
+	l.Info("Updating status")
+	if updateErr := r.client.Status().Update(ctx, pCopy); updateErr != nil {
+		return reconcile.Result{}, errors.Wrapf(updateErr, "Updating policy status")
+	}
+
+	return reconcile.Result{}, nil
+}
+
+func daemonSetIsReady(ds *appsv1.DaemonSet) bool {
+	return ds.Status.DesiredNumberScheduled > 0 && ds.Status.DesiredNumberScheduled == ds.Status.NumberAvailable
+}
+
+func daemonSetIsUpdating(ds *appsv1.DaemonSet) bool {
+	return ds.Status.UpdatedNumberScheduled > 0 &&
+		(ds.Status.UpdatedNumberScheduled < ds.Status.DesiredNumberScheduled || ds.Status.NumberUnavailable > 0)
+}
+
+func listStatusesForProfile(
+	ctx context.Context, c client.Client, polName string,
+) (*statusv1alpha1.SecurityProfileNodeStatusList, error) {
+	statusList := statusv1alpha1.SecurityProfileNodeStatusList{}
+	allStatusesForProf := client.MatchingLabels{
+		statusv1alpha1.StatusToProfLabel: polName,
+	}
+
+	if err := c.List(ctx, &statusList, allStatusesForProf); err != nil {
+		return nil, errors.Wrap(err, "listing statuses")
+	}
+
+	return &statusList, nil
+}
diff --git a/internal/pkg/manager/nodestatus/setup.go b/internal/pkg/manager/nodestatus/setup.go
new file mode 100644
index 000000000..2a7d9c3ec
--- /dev/null
+++ b/internal/pkg/manager/nodestatus/setup.go
@@ -0,0 +1,39 @@
+/*
+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 nodestatus
+
+import (
+	"context"
+
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/go-logr/logr"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+)
+
+// Setup adds a controller that reconciles the SPOd DaemonSet.
+func Setup(ctx context.Context, mgr ctrl.Manager, l logr.Logger) error {
+	// Register a special reconciler for status events
+	return ctrl.NewControllerManagedBy(mgr).
+		Named("nodestatus").
+		For(&secprofnodestatusv1alpha1.SecurityProfileNodeStatus{}).
+		Complete(NewStatusReconciler(mgr.GetClient(),
+			l,
+			event.NewAPIRecorder(mgr.GetEventRecorderFor("nodestatus"))),
+		)
+}
diff --git a/internal/pkg/manager/spod/bindata/spod.go b/internal/pkg/manager/spod/bindata/spod.go
index 9b0b2b7ba..fc7c39c60 100644
--- a/internal/pkg/manager/spod/bindata/spod.go
+++ b/internal/pkg/manager/spod/bindata/spod.go
@@ -241,6 +241,14 @@ semodule -i /opt/spo-profiles/selinuxd.cil
 									},
 								},
 							},
+							{
+								Name: "OPERATOR_NAMESPACE",
+								ValueFrom: &v1.EnvVarSource{
+									FieldRef: &v1.ObjectFieldSelector{
+										FieldPath: "metadata.namespace",
+									},
+								},
+							},
 						},
 					},
 					{
diff --git a/internal/pkg/manager/spod/setup.go b/internal/pkg/manager/spod/setup.go
index c3285dc23..2b9509730 100644
--- a/internal/pkg/manager/spod/setup.go
+++ b/internal/pkg/manager/spod/setup.go
@@ -20,9 +20,11 @@ import (
 	"context"
 
 	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/go-logr/logr"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 
 	spodv1alpha1 "sigs.k8s.io/security-profiles-operator/api/spod/v1alpha1"
@@ -44,6 +46,7 @@ func Setup(ctx context.Context, mgr ctrl.Manager, dt *DaemonTunables, l logr.Log
 		Named("spod-config").
 		For(&spodv1alpha1.SecurityProfilesOperatorDaemon{}).
 		Owns(&appsv1.DaemonSet{}).
+		WithEventFilter(resource.NewPredicates(isInOperatorNamespace)).
 		Complete(&ReconcileSPOd{
 			baseSPOd:       getEffectiveSPOd(dt),
 			client:         mgr.GetClient(),
@@ -56,6 +59,7 @@ func Setup(ctx context.Context, mgr ctrl.Manager, dt *DaemonTunables, l logr.Log
 
 func getEffectiveSPOd(dt *DaemonTunables) *appsv1.DaemonSet {
 	refSPOd := bindata.Manifest.DeepCopy()
+	refSPOd.SetNamespace(config.GetOperatorNamespace())
 
 	daemon := &refSPOd.Spec.Template.Spec.Containers[0]
 	if dt.WatchNamespace != "" {
@@ -75,3 +79,12 @@ func getEffectiveSPOd(dt *DaemonTunables) *appsv1.DaemonSet {
 	sepolImage.Image = dt.SelinuxdImage // selinuxd ships the policies as well
 	return refSPOd
 }
+
+func isInOperatorNamespace(obj runtime.Object) bool {
+	spod, ok := obj.(*spodv1alpha1.SecurityProfilesOperatorDaemon)
+	if !ok {
+		return false
+	}
+
+	return spod.GetNamespace() == config.GetOperatorNamespace()
+}
diff --git a/internal/pkg/manager/spod/spod_controller.go b/internal/pkg/manager/spod/spod_controller.go
index e2d5c3978..ea4fe1899 100644
--- a/internal/pkg/manager/spod/spod_controller.go
+++ b/internal/pkg/manager/spod/spod_controller.go
@@ -63,16 +63,16 @@ type ReconcileSPOd struct {
 // nolint:lll
 //
 // Used for leader election:
-// +kubebuilder:rbac:groups=core,namespace="security-profiles-operator",resources=configmaps;events,verbs=get;list;watch;create;update;patch
+// +kubebuilder:rbac:groups=core,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;patch
-// +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=daemonsets/finalizers,verbs=delete;get;update;patch
+// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch
+// +kubebuilder:rbac:groups=apps,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
 // +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:
-// +kubebuilder:rbac:groups=apps,namespace="security-profiles-operator",resources=deployments,verbs=get;list;watch;
+// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;
 // +kubebuilder:rbac:groups=coordination.k8s.io,namespace="security-profiles-operator",resources=leases,verbs=create;get;update;
 //
 // Needed for default profiles:
diff --git a/internal/pkg/manager/workloadannotator/setup.go b/internal/pkg/manager/workloadannotator/setup.go
new file mode 100644
index 000000000..06f0f9803
--- /dev/null
+++ b/internal/pkg/manager/workloadannotator/setup.go
@@ -0,0 +1,78 @@
+/*
+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 workloadannotator
+
+import (
+	"context"
+
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/crossplane/crossplane-runtime/pkg/resource"
+	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
+)
+
+// Setup adds a controller that reconciles the SPOd DaemonSet.
+func Setup(ctx context.Context, mgr ctrl.Manager, l logr.Logger) error {
+	// Index Pods using seccomp profiles
+	if err := mgr.GetFieldIndexer().IndexField(ctx, &corev1.Pod{}, spOwnerKey, func(rawObj client.Object) []string {
+		pod, ok := rawObj.(*corev1.Pod)
+		if !ok {
+			return []string{}
+		}
+		return getSeccompProfilesFromPod(pod)
+	}); err != nil {
+		return errors.Wrap(err, "creating pod index")
+	}
+
+	// Index SeccompProfiles with active pods
+	if err := mgr.GetFieldIndexer().IndexField(
+		ctx, &v1alpha1.SeccompProfile{}, linkedPodsKey, func(rawObj client.Object) []string {
+			sp, ok := rawObj.(*v1alpha1.SeccompProfile)
+			if !ok {
+				return []string{}
+			}
+			return sp.Status.ActiveWorkloads
+		}); err != nil {
+		return errors.Wrap(err, "creating seccomp profile index")
+	}
+
+	// Register a special reconciler for pod events
+	return ctrl.NewControllerManagedBy(mgr).
+		Named("pods").
+		For(&corev1.Pod{}).
+		WithEventFilter(resource.NewPredicates(hasSeccompProfile)).
+		Complete(&PodReconciler{
+			client: mgr.GetClient(),
+			log:    l,
+			record: event.NewAPIRecorder(mgr.GetEventRecorderFor("pods")),
+		})
+}
+
+func hasSeccompProfile(obj runtime.Object) bool {
+	pod, ok := obj.(*corev1.Pod)
+	if !ok {
+		return false
+	}
+
+	return len(getSeccompProfilesFromPod(pod)) > 0
+}
diff --git a/internal/pkg/manager/workloadannotator/workloadannotator.go b/internal/pkg/manager/workloadannotator/workloadannotator.go
new file mode 100644
index 000000000..4116b078f
--- /dev/null
+++ b/internal/pkg/manager/workloadannotator/workloadannotator.go
@@ -0,0 +1,197 @@
+/*
+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 workloadannotator
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/crossplane/crossplane-runtime/pkg/event"
+	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
+)
+
+const (
+	spOwnerKey       = ".metadata.seccompProfileOwner"
+	linkedPodsKey    = ".metadata.activeWorkloads"
+	reconcileTimeout = 1 * time.Minute
+)
+
+// A PodReconciler monitors pod changes and links them to SeccompProfiles.
+type PodReconciler struct {
+	client client.Client
+	log    logr.Logger
+	record event.Recorder
+}
+
+// Namespace scoped
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
+
+// Reconcile reacts to pod events and updates SeccompProfiles if in use or no longer in use by a pod.
+func (r *PodReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
+	logger := r.log.WithValues("pod", req.Name, "namespace", req.Namespace)
+
+	ctx, cancel := context.WithTimeout(context.Background(), reconcileTimeout)
+	defer cancel()
+
+	podID := req.Namespace + "/" + req.Name
+
+	pod := &corev1.Pod{}
+	var err error
+	if err = r.client.Get(ctx, req.NamespacedName, pod); util.IgnoreNotFound(err) != nil {
+		logger.Error(err, "could not get pod")
+		return reconcile.Result{}, errors.Wrap(err, "looking up pod in pod reconciler")
+	}
+	if kerrors.IsNotFound(err) { // this is a pod deletion, so update all seccomp profiles that were using it
+		seccompProfiles := &v1alpha1.SeccompProfileList{}
+		if err = r.client.List(ctx, seccompProfiles, client.MatchingFields{linkedPodsKey: podID}); err != nil {
+			return reconcile.Result{}, errors.Wrap(err, "listing SeccompProfiles for deleted pod")
+		}
+		for i := range seccompProfiles.Items {
+			if err = r.updatePodReferences(ctx, &seccompProfiles.Items[i]); err != nil {
+				return reconcile.Result{}, errors.Wrap(err, "updating SeccompProfile for deleted pod")
+			}
+		}
+		return reconcile.Result{}, nil
+	}
+	// pod is being created or updated so ensure it is linked to a seccomp profile
+	for _, profileIndex := range getSeccompProfilesFromPod(pod) {
+		profileElements := strings.Split(profileIndex, "/")
+		profileNamespace := profileElements[1]
+		profileName := strings.TrimSuffix(profileElements[2], ".json")
+		seccompProfile := &v1alpha1.SeccompProfile{}
+		if err := r.client.Get(ctx, util.NamespacedName(profileName, profileNamespace), seccompProfile); err != nil {
+			logger.Error(err, "could not get seccomp profile for pod")
+			return reconcile.Result{}, errors.Wrap(err, "looking up SeccompProfile for new or updated pod")
+		}
+		if err := r.updatePodReferences(ctx, seccompProfile); err != nil {
+			logger.Error(err, "could not update seccomp profile for pod")
+			return reconcile.Result{}, errors.Wrap(err, "updating SeccompProfile pod references for new or updated pod")
+		}
+	}
+	return reconcile.Result{}, nil
+}
+
+// updatePodReferences updates a SeccompProfile with the identifiers of pods using it and ensures
+// it has a finalizer indicating it is in use to prevent it from being deleted.
+func (r *PodReconciler) updatePodReferences(ctx context.Context, sp *v1alpha1.SeccompProfile) error {
+	linkedPods := &corev1.PodList{}
+	profileReference := fmt.Sprintf("operator/%s/%s.json", sp.GetNamespace(), sp.GetName())
+	err := r.client.List(ctx, linkedPods, client.MatchingFields{spOwnerKey: profileReference})
+	if util.IgnoreNotFound(err) != nil {
+		return errors.Wrap(err, "listing pods to update seccompProfile")
+	}
+	podList := make([]string, len(linkedPods.Items))
+	for i := range linkedPods.Items {
+		pod := linkedPods.Items[i]
+		podList[i] = pod.ObjectMeta.Namespace + "/" + pod.ObjectMeta.Name
+	}
+	if err := util.Retry(func() error {
+		sp.Status.ActiveWorkloads = podList
+
+		updateErr := r.client.Status().Update(ctx, sp)
+		if updateErr != nil {
+			if err := r.client.Get(ctx, util.NamespacedName(sp.GetName(), sp.GetNamespace()), sp); err != nil {
+				return errors.Wrap(err, "retrieving profile")
+			}
+		}
+
+		return errors.Wrap(updateErr, "updating profile")
+	}, util.IsNotFoundOrConflict); err != nil {
+		return errors.Wrap(err, "updating SeccompProfile status")
+	}
+	hasActivePodsFinalizerString := "in-use-by-active-pods"
+	if len(linkedPods.Items) > 0 {
+		if err := util.Retry(func() error {
+			return util.AddFinalizer(ctx, r.client, sp, hasActivePodsFinalizerString)
+		}, util.IsNotFoundOrConflict); err != nil {
+			return errors.Wrap(err, "adding finalizer")
+		}
+	} else {
+		if err := util.Retry(func() error {
+			return util.RemoveFinalizer(ctx, r.client, sp, hasActivePodsFinalizerString)
+		}, util.IsNotFoundOrConflict); err != nil {
+			return errors.Wrap(err, "removing finalizer")
+		}
+	}
+	return nil
+}
+
+// getSeccompProfilesFromPod returns a slice of strings representing seccomp profiles required by the pod.
+// It looks first at the pod spec level, then in each container and init container, then in the annotations.
+func getSeccompProfilesFromPod(pod *corev1.Pod) []string {
+	profiles := []string{}
+	// try to get profile from pod securityContext
+	sc := pod.Spec.SecurityContext
+	if sc != nil && isOperatorSeccompProfile(sc.SeccompProfile) {
+		profiles = append(profiles, *sc.SeccompProfile.LocalhostProfile)
+	}
+	// try to get profile(s) from securityContext in pods
+	containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
+	contains := func(a []string, b string) bool {
+		for _, s := range a {
+			if s == b {
+				return true
+			}
+		}
+		return false
+	}
+	for i := range containers {
+		sc := containers[i].SecurityContext
+		if sc != nil && isOperatorSeccompProfile(sc.SeccompProfile) {
+			profileString := *containers[i].SecurityContext.SeccompProfile.LocalhostProfile
+			if !contains(profiles, profileString) {
+				profiles = append(profiles, profileString)
+			}
+		}
+	}
+	// try to get profile from annotations
+	annotation, hasAnnotation := pod.GetAnnotations()[corev1.SeccompPodAnnotationKey]
+	if hasAnnotation && strings.HasPrefix(annotation, "localhost/") {
+		profileString := strings.TrimPrefix(annotation, "localhost/")
+		if !contains(profiles, profileString) {
+			profiles = append(profiles, profileString)
+		}
+	}
+	return profiles
+}
+
+// isOperatorSeccompProfile checks whether a corev1.SeccompProfile object belongs to the operator.
+// SeccompProfiles controlled by the operator are of type "Localhost" and have a path of the form
+// "operator/namespace/profile-name.json".
+func isOperatorSeccompProfile(sp *corev1.SeccompProfile) bool {
+	if sp == nil || sp.Type != "Localhost" {
+		return false
+	}
+	if !strings.HasPrefix(*sp.LocalhostProfile, "operator/") {
+		return false
+	}
+	if !strings.HasSuffix(*sp.LocalhostProfile, ".json") {
+		return false
+	}
+	const pathParts = 3
+	return len(strings.Split(*sp.LocalhostProfile, "/")) == pathParts
+}
diff --git a/internal/pkg/nodestatus/nodestatus.go b/internal/pkg/nodestatus/nodestatus.go
index 370b14d81..85161f008 100644
--- a/internal/pkg/nodestatus/nodestatus.go
+++ b/internal/pkg/nodestatus/nodestatus.go
@@ -36,9 +36,10 @@ type StatusClient struct {
 	pol             client.Object
 	nodeName        string
 	finalizerString string
+	client          client.Client
 }
 
-func NewForProfile(pol client.Object) (*StatusClient, error) {
+func NewForProfile(pol client.Object, c client.Client) (*StatusClient, error) {
 	nodeName, ok := os.LookupEnv(config.NodeNameEnvKey)
 	if !ok {
 		return nil, errors.New("cannot determine node name")
@@ -47,6 +48,7 @@ func NewForProfile(pol client.Object) (*StatusClient, error) {
 		pol:             pol,
 		nodeName:        nodeName,
 		finalizerString: nodeName + "-delete",
+		client:          c,
 	}, nil
 }
 
@@ -58,21 +60,21 @@ func (nsf *StatusClient) perNodeStatusNamespacedName() types.NamespacedName {
 	return util.NamespacedName(nsf.perNodeStatusName(), nsf.pol.GetNamespace())
 }
 
-func (nsf *StatusClient) Create(ctx context.Context, c client.Client) error {
-	if err := nsf.createFinalizer(ctx, c); err != nil {
+func (nsf *StatusClient) Create(ctx context.Context) error {
+	if err := nsf.createFinalizer(ctx); err != nil {
 		return errors.Wrapf(err, "cannot create finalizer for %s", nsf.pol.GetName())
 	}
 
 	// if object does not exist, add it
-	if err := nsf.createNodeStatus(ctx, c); err != nil {
+	if err := nsf.createNodeStatus(ctx); err != nil {
 		return errors.Wrapf(err, "cannot create node status for %s", nsf.pol.GetName())
 	}
 	return nil
 }
 
-func (nsf *StatusClient) createFinalizer(ctx context.Context, c client.Client) error {
+func (nsf *StatusClient) createFinalizer(ctx context.Context) error {
 	return util.Retry(func() error {
-		return util.AddFinalizer(ctx, c, nsf.pol, nsf.finalizerString)
+		return util.AddFinalizer(ctx, nsf.client, nsf.pol, nsf.finalizerString)
 	}, util.IsNotFoundOrConflict)
 }
 
@@ -95,8 +97,12 @@ func (nsf *StatusClient) statusObj(
 	}
 }
 
-func (nsf *StatusClient) createNodeStatus(ctx context.Context, c client.Client) error {
-	err := c.Create(ctx, nsf.statusObj(secprofnodestatusv1alpha1.ProfileStatePending))
+func (nsf *StatusClient) createNodeStatus(ctx context.Context) error {
+	s := nsf.statusObj(secprofnodestatusv1alpha1.ProfileStatePending)
+	if setCtrlErr := controllerutil.SetControllerReference(nsf.pol, s, nsf.client.Scheme()); setCtrlErr != nil {
+		return errors.Wrapf(setCtrlErr, "cannot set node status owner reference: %s", nsf.pol.GetName())
+	}
+	err := nsf.client.Create(ctx, s)
 	if err != nil && !kerrors.IsAlreadyExists(err) {
 		return errors.Wrap(err, "creating node status")
 	}
@@ -105,22 +111,22 @@ func (nsf *StatusClient) createNodeStatus(ctx context.Context, c client.Client)
 }
 
 func (nsf *StatusClient) Remove(ctx context.Context, c client.Client) error {
+	// if finalizer exists, remove it
+	if err := nsf.removeFinalizer(ctx); err != nil {
+		return errors.Wrapf(err, "cannot create finalizer for %s", nsf.pol.GetName())
+	}
+
 	// if object exists, remove it
 	if err := nsf.removeNodeStatus(ctx, c); err != nil {
 		return errors.Wrapf(err, "cannot create nodeStatus for %s", nsf.pol.GetName())
 	}
 
-	// if finalizer exists, remove it
-	if err := nsf.removeFinalizer(ctx, c); err != nil {
-		return errors.Wrapf(err, "cannot create finalizer for %s", nsf.pol.GetName())
-	}
-
 	return nil
 }
 
-func (nsf *StatusClient) removeFinalizer(ctx context.Context, c client.Client) error {
+func (nsf *StatusClient) removeFinalizer(ctx context.Context) error {
 	return util.Retry(func() error {
-		return util.RemoveFinalizer(ctx, c, nsf.pol, nsf.finalizerString)
+		return util.RemoveFinalizer(ctx, nsf.client, nsf.pol, nsf.finalizerString)
 	}, util.IsNotFoundOrConflict)
 }
 
@@ -134,9 +140,9 @@ func (nsf *StatusClient) removeNodeStatus(ctx context.Context, c client.Client)
 	return nil
 }
 
-func (nsf *StatusClient) Exists(ctx context.Context, c client.Client) (bool, error) {
+func (nsf *StatusClient) Exists(ctx context.Context) (bool, error) {
 	f := nsf.finalizerExists()
-	s, err := nsf.nodeStatusExists(ctx, c)
+	s, err := nsf.nodeStatusExists(ctx)
 
 	return s || f, err
 }
@@ -145,9 +151,9 @@ func (nsf *StatusClient) finalizerExists() bool {
 	return controllerutil.ContainsFinalizer(nsf.pol, nsf.finalizerString)
 }
 
-func (nsf *StatusClient) nodeStatusExists(ctx context.Context, c client.Client) (bool, error) {
+func (nsf *StatusClient) nodeStatusExists(ctx context.Context) (bool, error) {
 	status := secprofnodestatusv1alpha1.SecurityProfileNodeStatus{}
-	err := c.Get(ctx, nsf.perNodeStatusNamespacedName(), &status)
+	err := nsf.client.Get(ctx, nsf.perNodeStatusNamespacedName(), &status)
 	if kerrors.IsNotFound(err) {
 		return false, nil
 	} else if err != nil {
@@ -157,40 +163,11 @@ func (nsf *StatusClient) nodeStatusExists(ctx context.Context, c client.Client)
 	return true, nil
 }
 
-func (nsf *StatusClient) SetReturnGlobal(
-	ctx context.Context, c client.Client, polState secprofnodestatusv1alpha1.ProfileState,
-) (secprofnodestatusv1alpha1.ProfileState, error) {
-	if err := nsf.setNodeStatus(ctx, c, polState); err != nil {
-		return secprofnodestatusv1alpha1.ProfileStateError, errors.Wrap(err, "setting node status")
-	}
-
-	// search all statuses
-	statusList := secprofnodestatusv1alpha1.SecurityProfileNodeStatusList{}
-	allStatusesForProf := client.MatchingLabels{
-		secprofnodestatusv1alpha1.StatusToProfLabel: nsf.pol.GetName(),
-	}
-	if err := c.List(ctx, &statusList, allStatusesForProf); err != nil {
-		return secprofnodestatusv1alpha1.ProfileStateError, errors.Wrap(err, "listing statuses")
-	}
-
-	// figure out the 'lowest common one'
-	lowestCommonState := secprofnodestatusv1alpha1.ProfileStateTerminating
-	for i := range statusList.Items {
-		lowestCommonState = secprofnodestatusv1alpha1.LowerOfTwoStates(lowestCommonState, statusList.Items[i].Status)
-	}
-
-	// TODO(jhrozek): It would be nicer to come up with an interface the consumer would assign so that
-	// instead of returning a state we could just call that method to do the work..
-
-	// return the global status to the caller so that they can set it
-	return lowestCommonState, nil
-}
-
-func (nsf *StatusClient) setNodeStatus(
-	ctx context.Context, c client.Client, polState secprofnodestatusv1alpha1.ProfileState,
+func (nsf *StatusClient) SetNodeStatus(
+	ctx context.Context, polState secprofnodestatusv1alpha1.ProfileState,
 ) error {
 	status := secprofnodestatusv1alpha1.SecurityProfileNodeStatus{}
-	err := c.Get(ctx, nsf.perNodeStatusNamespacedName(), &status)
+	err := nsf.client.Get(ctx, nsf.perNodeStatusNamespacedName(), &status)
 	if kerrors.IsNotFound(err) && polState == secprofnodestatusv1alpha1.ProfileStateTerminating {
 		// it's OK if we're about to terminate a profile but it was already gone
 		return nil
@@ -200,9 +177,23 @@ func (nsf *StatusClient) setNodeStatus(
 
 	status.Status = polState
 	status.Labels[secprofnodestatusv1alpha1.StatusStateLabel] = string(polState)
-	if err := c.Update(ctx, &status); err != nil {
+	if err := nsf.client.Update(ctx, &status); err != nil {
 		return errors.Wrap(err, "updating node status")
 	}
 
 	return nil
 }
+
+func (nsf *StatusClient) Matches(
+	ctx context.Context, polState secprofnodestatusv1alpha1.ProfileState,
+) (bool, error) {
+	status := secprofnodestatusv1alpha1.SecurityProfileNodeStatus{}
+	if err := nsf.client.Get(ctx, nsf.perNodeStatusNamespacedName(), &status); err != nil {
+		if kerrors.IsNotFound(err) && polState == secprofnodestatusv1alpha1.ProfileStateTerminating {
+			// it's OK if we're about to terminate a profile but it was already gone
+			return true, nil
+		}
+		return false, errors.Wrapf(err, "getting node status for matching")
+	}
+	return status.Status == polState, nil
+}
diff --git a/internal/pkg/util/reconcileutils.go b/internal/pkg/util/reconcileutils.go
new file mode 100644
index 000000000..2f32d1160
--- /dev/null
+++ b/internal/pkg/util/reconcileutils.go
@@ -0,0 +1,28 @@
+/*
+Copyright 2020 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 util
+
+import (
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+)
+
+func IgnoreNotFound(err error) error {
+	if kerrors.IsNotFound(err) {
+		return nil
+	}
+	return err
+}
diff --git a/internal/pkg/webhooks/binding/binding.go b/internal/pkg/webhooks/binding/binding.go
index 311a89cdf..9547f53db 100644
--- a/internal/pkg/webhooks/binding/binding.go
+++ b/internal/pkg/webhooks/binding/binding.go
@@ -26,6 +26,7 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -35,11 +36,14 @@ import (
 
 	profilebindingv1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilebinding/v1alpha1"
 	seccompprofilev1alpha1 "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/webhooks/utils"
 )
 
 const finalizer = "active-workload-lock"
 
+var ErrProfWithoutStatus = errors.New("profile hasn't been initialized with status")
+
 type podSeccompBinder struct {
 	client  client.Client
 	log     logr.Logger
@@ -136,13 +140,13 @@ func (p *podSeccompBinder) Handle(ctx context.Context, req admission.Request) ad
 			continue
 		}
 
-		seccompProfile := &seccompprofilev1alpha1.SeccompProfile{}
 		namespacedName := types.NamespacedName{Namespace: req.Namespace, Name: profileName}
-		err = p.client.Get(ctx, namespacedName, seccompProfile)
+		seccompProfile, err := p.getSeccompProfile(ctx, namespacedName)
 		if err != nil {
 			p.log.Error(err, fmt.Sprintf("failed to get SeccompProfile %#v", namespacedName))
 			return admission.Errored(http.StatusInternalServerError, err)
 		}
+
 		for j := range containers {
 			podChanged = p.addSecurityContext(containers[j], seccompProfile)
 		}
@@ -164,6 +168,29 @@ func (p *podSeccompBinder) Handle(ctx context.Context, req admission.Request) ad
 	return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod)
 }
 
+func (p *podSeccompBinder) getSeccompProfile(
+	ctx context.Context,
+	key types.NamespacedName,
+) (*seccompprofilev1alpha1.SeccompProfile, error) {
+	seccompProfile := &seccompprofilev1alpha1.SeccompProfile{}
+	err := util.Retry(
+		func() error {
+			getErr := p.client.Get(ctx, key, seccompProfile)
+			if getErr != nil {
+				return errors.Wrapf(getErr, "getting profile")
+			}
+			if seccompProfile.Status.Status == "" {
+				return errors.Wrapf(ErrProfWithoutStatus, "getting profile")
+			}
+			return nil
+		}, func(inErr error) bool {
+			return errors.Is(inErr, ErrProfWithoutStatus) || kerrors.IsNotFound(inErr)
+		})
+	// nolint:wrapcheck
+	// error is already wrapped
+	return seccompProfile, err
+}
+
 func (p *podSeccompBinder) addSecurityContext(
 	c *corev1.Container, seccompProfile *seccompprofilev1alpha1.SeccompProfile) bool {
 	podChanged := false


Test patch:
diff --git a/internal/pkg/daemon/seccompprofile/seccompprofile_test.go b/internal/pkg/daemon/seccompprofile/seccompprofile_test.go
index d589017ef..2d5e9e213 100644
--- a/internal/pkg/daemon/seccompprofile/seccompprofile_test.go
+++ b/internal/pkg/daemon/seccompprofile/seccompprofile_test.go
@@ -29,7 +29,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
-	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -202,10 +201,9 @@ func TestGetProfilePath(t *testing.T) {
 	t.Parallel()
 
 	cases := []struct {
-		name    string
-		want    string
-		wantErr string
-		sp      *v1alpha1.SeccompProfile
+		name string
+		want string
+		sp   *v1alpha1.SeccompProfile
 	}{
 		{
 			name: "AppendNamespaceAndProfile",
@@ -264,46 +262,7 @@ func TestGetProfilePath(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			got, gotErr := GetProfilePath(tc.sp.ObjectMeta.Name, tc.sp.ObjectMeta.Namespace)
-			if tc.wantErr == "" {
-				require.NoError(t, gotErr)
-			} else {
-				require.Equal(t, tc.wantErr, gotErr.Error())
-			}
-			require.Equal(t, tc.want, got)
-		})
-	}
-}
-
-func TestIgnoreNotFound(t *testing.T) {
-	t.Parallel()
-
-	profileName := "cool-profile"
-	errOops := errors.New("oops")
-
-	cases := []struct {
-		name string
-		err  error
-		want error
-	}{
-		{
-			name: "IsErrorNotFound",
-			err:  kerrors.NewNotFound(schema.GroupResource{}, profileName),
-			want: nil,
-		},
-		{
-			name: "OtherError",
-			err:  errOops,
-			want: errOops,
-		},
-	}
-
-	for _, tc := range cases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			got := ignoreNotFound(tc.err)
+			got := tc.sp.GetProfilePath()
 			require.Equal(t, tc.want, got)
 		})
 	}
@@ -453,177 +412,3 @@ func TestUnionSyscalls(t *testing.T) {
 		})
 	}
 }
-
-func TestGetSeccompProfilesFromPod(t *testing.T) {
-	t.Parallel()
-
-	profilePath := "operator/default/test.json"
-	profilePath2 := "operator/default/test2.json"
-	cases := []struct {
-		name string
-		pod  corev1.Pod
-		want []string
-	}{
-		{
-			name: "SeccompProfileForPod",
-			pod: corev1.Pod{
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
-					SecurityContext: &corev1.PodSecurityContext{
-						SeccompProfile: &corev1.SeccompProfile{
-							Type:             "Localhost",
-							LocalhostProfile: &profilePath,
-						},
-					},
-				},
-			},
-			want: []string{profilePath},
-		},
-		{
-			name: "SeccompProfileForOneContainer",
-			pod: corev1.Pod{
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{{
-						Name:  "container1",
-						Image: "testimage",
-						SecurityContext: &corev1.SecurityContext{
-							SeccompProfile: &corev1.SeccompProfile{
-								Type:             "Localhost",
-								LocalhostProfile: &profilePath,
-							},
-						},
-					}},
-				},
-			},
-			want: []string{profilePath},
-		},
-		{
-			name: "SeccompProfileForMultipleContainers",
-			pod: corev1.Pod{
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{
-						{
-							Name:  "container1",
-							Image: "testimage",
-							SecurityContext: &corev1.SecurityContext{
-								SeccompProfile: &corev1.SeccompProfile{
-									Type:             "Localhost",
-									LocalhostProfile: &profilePath,
-								},
-							},
-						},
-						{
-							Name:  "container2",
-							Image: "testimage2",
-							SecurityContext: &corev1.SecurityContext{
-								SeccompProfile: &corev1.SeccompProfile{
-									Type:             "Localhost",
-									LocalhostProfile: &profilePath2,
-								},
-							},
-						},
-					},
-				},
-			},
-			want: []string{profilePath, profilePath2},
-		},
-		{
-			name: "SeccompProfileInAnnotation",
-			pod: corev1.Pod{
-				ObjectMeta: metav1.ObjectMeta{
-					Annotations: map[string]string{corev1.SeccompPodAnnotationKey: "localhost/" + profilePath},
-				},
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
-				},
-			},
-			want: []string{profilePath},
-		},
-		{
-			name: "SeccompProfileInPodAndContainerAndAnnotation",
-			pod: corev1.Pod{
-				ObjectMeta: metav1.ObjectMeta{
-					Annotations: map[string]string{corev1.SeccompPodAnnotationKey: "localhost/" + profilePath},
-				},
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{
-						{
-							Name:  "container1",
-							Image: "testimage",
-							SecurityContext: &corev1.SecurityContext{
-								SeccompProfile: &corev1.SeccompProfile{
-									Type:             "Localhost",
-									LocalhostProfile: &profilePath2,
-								},
-							},
-						},
-						{
-							Name:  "container2",
-							Image: "testimage2",
-						},
-					},
-					SecurityContext: &corev1.PodSecurityContext{
-						SeccompProfile: &corev1.SeccompProfile{
-							Type:             "Localhost",
-							LocalhostProfile: &profilePath,
-						},
-					},
-				},
-			},
-			want: []string{profilePath, profilePath2},
-		},
-	}
-
-	for _, tc := range cases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			got := getSeccompProfilesFromPod(&tc.pod)
-			require.Equal(t, tc.want, got)
-		})
-	}
-
-	badPod := corev1.Pod{
-		Spec: corev1.PodSpec{
-			Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
-			SecurityContext: &corev1.PodSecurityContext{
-				SeccompProfile: &corev1.SeccompProfile{
-					Type:             "Localhost",
-					LocalhostProfile: nil,
-				},
-			},
-		},
-	}
-	badCases := []struct {
-		name    string
-		profile string
-	}{
-		{
-			name:    "NoSuffix",
-			profile: "operator/default/test",
-		},
-		{
-			name:    "BadSuffix",
-			profile: "operator/default/test.js",
-		},
-		{
-			name:    "WrongPath",
-			profile: "foo/bar/baz",
-		},
-		{
-			name:    "NotLocalhostPath",
-			profile: "runtime/default",
-		},
-	}
-	for _, tc := range badCases {
-		tc := tc
-		badPod.Spec.SecurityContext.SeccompProfile.LocalhostProfile = &tc.profile
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			got := getSeccompProfilesFromPod(&badPod)
-			require.Equal(t, []string{}, got)
-		})
-	}
-}
diff --git a/internal/pkg/manager/spod/setup_test.go b/internal/pkg/manager/spod/setup_test.go
index ba69372be..1390894a5 100644
--- a/internal/pkg/manager/spod/setup_test.go
+++ b/internal/pkg/manager/spod/setup_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package spod
 
 import (
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -49,6 +50,7 @@ func Test_getEffectiveSPOd(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
+			os.Setenv("OPERATOR_NAMESPACE", "default")
 			got := getEffectiveSPOd(&tt.dt)
 			require.Equal(t, tt.dt.SelinuxdImage, got.Spec.Template.Spec.Containers[1].Image)
 			require.Equal(t, tt.dt.LogEnricherImage, got.Spec.Template.Spec.Containers[2].Image)
diff --git a/internal/pkg/manager/workloadannotator/workloadannotator_test.go b/internal/pkg/manager/workloadannotator/workloadannotator_test.go
new file mode 100644
index 000000000..aaadbce14
--- /dev/null
+++ b/internal/pkg/manager/workloadannotator/workloadannotator_test.go
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 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 workloadannotator
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestGetSeccompProfilesFromPod(t *testing.T) {
+	t.Parallel()
+
+	profilePath := "operator/default/test.json"
+	profilePath2 := "operator/default/test2.json"
+	cases := []struct {
+		name string
+		pod  corev1.Pod
+		want []string
+	}{
+		{
+			name: "SeccompProfileForPod",
+			pod: corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
+					SecurityContext: &corev1.PodSecurityContext{
+						SeccompProfile: &corev1.SeccompProfile{
+							Type:             "Localhost",
+							LocalhostProfile: &profilePath,
+						},
+					},
+				},
+			},
+			want: []string{profilePath},
+		},
+		{
+			name: "SeccompProfileForOneContainer",
+			pod: corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{{
+						Name:  "container1",
+						Image: "testimage",
+						SecurityContext: &corev1.SecurityContext{
+							SeccompProfile: &corev1.SeccompProfile{
+								Type:             "Localhost",
+								LocalhostProfile: &profilePath,
+							},
+						},
+					}},
+				},
+			},
+			want: []string{profilePath},
+		},
+		{
+			name: "SeccompProfileForMultipleContainers",
+			pod: corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name:  "container1",
+							Image: "testimage",
+							SecurityContext: &corev1.SecurityContext{
+								SeccompProfile: &corev1.SeccompProfile{
+									Type:             "Localhost",
+									LocalhostProfile: &profilePath,
+								},
+							},
+						},
+						{
+							Name:  "container2",
+							Image: "testimage2",
+							SecurityContext: &corev1.SecurityContext{
+								SeccompProfile: &corev1.SeccompProfile{
+									Type:             "Localhost",
+									LocalhostProfile: &profilePath2,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: []string{profilePath, profilePath2},
+		},
+		{
+			name: "SeccompProfileInAnnotation",
+			pod: corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{corev1.SeccompPodAnnotationKey: "localhost/" + profilePath},
+				},
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
+				},
+			},
+			want: []string{profilePath},
+		},
+		{
+			name: "SeccompProfileInPodAndContainerAndAnnotation",
+			pod: corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{corev1.SeccompPodAnnotationKey: "localhost/" + profilePath},
+				},
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name:  "container1",
+							Image: "testimage",
+							SecurityContext: &corev1.SecurityContext{
+								SeccompProfile: &corev1.SeccompProfile{
+									Type:             "Localhost",
+									LocalhostProfile: &profilePath2,
+								},
+							},
+						},
+						{
+							Name:  "container2",
+							Image: "testimage2",
+						},
+					},
+					SecurityContext: &corev1.PodSecurityContext{
+						SeccompProfile: &corev1.SeccompProfile{
+							Type:             "Localhost",
+							LocalhostProfile: &profilePath,
+						},
+					},
+				},
+			},
+			want: []string{profilePath, profilePath2},
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			got := getSeccompProfilesFromPod(&tc.pod)
+			require.Equal(t, tc.want, got)
+		})
+	}
+
+	badPod := corev1.Pod{
+		Spec: corev1.PodSpec{
+			Containers: []corev1.Container{{Name: "container1", Image: "testimage"}},
+			SecurityContext: &corev1.PodSecurityContext{
+				SeccompProfile: &corev1.SeccompProfile{
+					Type:             "Localhost",
+					LocalhostProfile: nil,
+				},
+			},
+		},
+	}
+	badCases := []struct {
+		name    string
+		profile string
+	}{
+		{
+			name:    "NoSuffix",
+			profile: "operator/default/test",
+		},
+		{
+			name:    "BadSuffix",
+			profile: "operator/default/test.js",
+		},
+		{
+			name:    "WrongPath",
+			profile: "foo/bar/baz",
+		},
+		{
+			name:    "NotLocalhostPath",
+			profile: "runtime/default",
+		},
+	}
+	for _, tc := range badCases {
+		tc := tc
+		badPod.Spec.SecurityContext.SeccompProfile.LocalhostProfile = &tc.profile
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			got := getSeccompProfilesFromPod(&badPod)
+			require.Equal(t, []string{}, got)
+		})
+	}
+}
diff --git a/internal/pkg/util/reconcileutils_test.go b/internal/pkg/util/reconcileutils_test.go
new file mode 100644
index 000000000..9c6b7a34c
--- /dev/null
+++ b/internal/pkg/util/reconcileutils_test.go
@@ -0,0 +1,60 @@
+/*
+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 util
+
+import (
+	"testing"
+
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/require"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+func TestIgnoreNotFound(t *testing.T) {
+	t.Parallel()
+
+	profileName := "cool-profile"
+	errOops := errors.New("oops")
+
+	cases := []struct {
+		name string
+		err  error
+		want error
+	}{
+		{
+			name: "IsErrorNotFound",
+			err:  kerrors.NewNotFound(schema.GroupResource{}, profileName),
+			want: nil,
+		},
+		{
+			name: "OtherError",
+			err:  errOops,
+			want: errOops,
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			got := IgnoreNotFound(tc.err)
+			require.Equal(t, tc.want, got)
+		})
+	}
+}
diff --git a/test/e2e_test.go b/test/e2e_test.go
index de1fc4e58..f642e8c91 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -39,8 +39,8 @@ const (
 	// migrate to a single daemonset-based implementation for the
 	// SELinux pieces.
 	defaultSelinuxOpTimeout = "360s"
-	defaultWaitTimeout      = "90s"
-	defaultWaitTime         = 5 * time.Second
+	defaultWaitTimeout      = "120s"
+	defaultWaitTime         = 10 * time.Second
 )
 
 func (e *e2e) TestSecurityProfilesOperator() {
@@ -188,6 +188,7 @@ func (e *e2e) deployOperator(manifest string) {
 	e.waitInOperatorNSFor("condition=ready", "pod", "-l", "app=security-profiles-operator")
 	// Wait for all pods in DaemonSet
 	time.Sleep(defaultWaitTime)
+	e.waitInOperatorNSFor("condition=initialized", "pod", "-l", "app=spod")
 	e.waitInOperatorNSFor("condition=ready", "pod", "-l", "app=spod")
 }
 
diff --git a/test/suite_test.go b/test/suite_test.go
index 25f3f1e3f..7fa4b9241 100644
--- a/test/suite_test.go
+++ b/test/suite_test.go
@@ -457,7 +457,7 @@ func (e *e2e) enableSelinuxInSpod() {
 	selinuxEnabledInSPODDS := e.kubectlOperatorNS("get", "ds", "spod", "-o", "yaml")
 	if !strings.Contains(selinuxEnabledInSPODDS, "--with-selinux=true") {
 		e.logf("Enable selinux in SPOD")
-		e.kubectl("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": true}}`, "--type=merge")
+		e.kubectlOperatorNS("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": true}}`, "--type=merge")
 
 		time.Sleep(defaultWaitTime)
 		e.waitInOperatorNSFor("condition=ready", "spod", "spod")
diff --git a/test/tc_base_profiles_test.go b/test/tc_base_profiles_test.go
index 377b5f205..c61a2f761 100644
--- a/test/tc_base_profiles_test.go
+++ b/test/tc_base_profiles_test.go
@@ -64,7 +64,7 @@ spec:
     seccompProfile:
       type: Localhost
       localhostProfile: operator/%s/hello.json
-  restartPolicy: Never
+  restartPolicy: OnFailure
 `
 	e.kubectl("create", "-f", baseProfilePath)
 	defer e.kubectl("delete", "-f", baseProfilePath)
diff --git a/test/tc_default_profiles_test.go b/test/tc_default_profiles_test.go
index 01189569d..c7c206cb3 100644
--- a/test/tc_default_profiles_test.go
+++ b/test/tc_default_profiles_test.go
@@ -54,6 +54,11 @@ func (e *e2e) testCaseDefaultAndExampleProfiles(nodes []string) {
 		// Default profile verification
 		namespace := e.getCurrentContextNamespace("security-profiles-operator")
 		for _, name := range defaultProfileNames {
+			e.waitFor(
+				"condition=ready",
+				"--namespace", namespace,
+				"seccompprofile", name,
+			)
 			sp := e.getSeccompProfile(name, namespace)
 			e.verifyCRDProfileContent(node, sp)
 
@@ -64,6 +69,11 @@ func (e *e2e) testCaseDefaultAndExampleProfiles(nodes []string) {
 		// Example profile verification
 		namespace = e.getCurrentContextNamespace(defaultNamespace)
 		for _, name := range exampleProfileNames {
+			e.waitFor(
+				"condition=ready",
+				"--namespace", namespace,
+				"seccompprofile", name,
+			)
 			sp := e.getSeccompProfile(name, namespace)
 			e.verifyCRDProfileContent(node, sp)
 
@@ -93,12 +103,10 @@ func (e *e2e) verifyBaseProfileContent(node string, cm *v1.ConfigMap) {
 
 func (e *e2e) verifyCRDProfileContent(node string, sp *v1alpha1.SeccompProfile) {
 	e.logf("Verifying %s profile on node %s", sp.Name, node)
-	name := sp.Name
-	profilePath, err := seccompprofile.GetProfilePath(name, sp.ObjectMeta.Namespace)
-	e.Nil(err)
+	profilePath := sp.GetProfilePath()
 	catOutput := e.execNode(node, "cat", profilePath)
 	output := seccompprofile.OutputProfile{}
-	err = json.Unmarshal([]byte(catOutput), &output)
+	err := json.Unmarshal([]byte(catOutput), &output)
 	e.Nil(err)
 	expected := seccompprofile.OutputProfile{}
 	spec, err := json.Marshal(sp.Spec)
diff --git a/test/tc_delete_profiles_test.go b/test/tc_delete_profiles_test.go
index a550cb781..8f9f4fd8e 100644
--- a/test/tc_delete_profiles_test.go
+++ b/test/tc_delete_profiles_test.go
@@ -23,7 +23,6 @@ import (
 	v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 
 	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
-	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/seccompprofile"
 )
 
 func (e *e2e) testCaseDeleteProfiles(nodes []string) {
@@ -103,8 +102,7 @@ spec:
 
 	namespace := e.getCurrentContextNamespace(defaultNamespace)
 	sp := e.getSeccompProfile(deleteProfileName, namespace)
-	path, err := seccompprofile.GetProfilePath(deleteProfileName, sp.ObjectMeta.Namespace)
-	e.Nil(err)
+	path := sp.GetProfilePath()
 
 	e.logf("Waiting for profile to be reconciled")
 	e.waitFor("condition=ready", "seccompprofile", deleteProfileName)
diff --git a/test/tc_spod_update_selinux_test.go b/test/tc_spod_update_selinux_test.go
index 079c48138..3ef9292c1 100644
--- a/test/tc_spod_update_selinux_test.go
+++ b/test/tc_spod_update_selinux_test.go
@@ -22,7 +22,7 @@ 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}")
+	selinuxEnabledInSPODObj := e.kubectlOperatorNS("get", "spod", "spod", "-o", "jsonpath={.spec.enableSelinux}")
 	e.Equal(selinuxEnabledInSPODObj, "true")
 
 	e.logf("assert selinux is enabled in the spod DS")
@@ -30,7 +30,7 @@ func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
 	e.Contains(selinuxEnabledInSPODDS, "--with-selinux=true")
 
 	e.logf("Disable selinux from SPOD")
-	e.kubectl("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": false}}`, "--type=merge")
+	e.kubectlOperatorNS("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": false}}`, "--type=merge")
 
 	time.Sleep(defaultWaitTime)
 	e.waitInOperatorNSFor("condition=ready", "spod", "spod")
@@ -40,7 +40,7 @@ func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
 	e.NotContains(selinuxDisabledInSPODDS, "--with-selinux=true")
 
 	e.logf("Re-enable selinux in SPOD")
-	e.kubectl("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": true}}`, "--type=merge")
+	e.kubectlOperatorNS("patch", "spod", "spod", "-p", `{"spec":{"enableSelinux": true}}`, "--type=merge")
 
 	time.Sleep(defaultWaitTime)
 	e.waitInOperatorNSFor("condition=ready", "spod", "spod")
@@ -48,4 +48,7 @@ func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
 	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("waiting for final rollout")
+	e.kubectlOperatorNS("rollout", "status", "ds", "spod", "--timeout", defaultSelinuxOpTimeout)
 }