Repository: rook/rook
Pull request #15648 (2025-04-04 09:13:27)

ci: fix rgw flaky ci test
s3cmd can take more time to put the data
of 1M to the bucket as it waits for
connection to get established

currently ci fails with, Retrying failed
request: /test1-1mib-test.dat ([Errno 111]
Connection refused)

also increase the timeout for creating objectstore


<!-- Thank you for contributing to Rook! -->

<!-- STEPS TO FOLLOW:
  1. Add a description of the changes (frequently the same as the commit description)
  2. Enter the issue number next to "Resolves #" below (if there is no tracking issue resolved, **remove that section**)
  3. Review our Contributing documentation at https://rook.io/docs/rook/latest/Contributing/development-flow/
  4. Follow the steps in the checklist below, starting with the **Commit Message Formatting**.
-->

<!-- Uncomment this section with the issue number if an issue is being resolved
**Issue resolved by this Pull Request:**
Resolves #
--->

**Checklist:**

- [ ] **Commit Message Formatting**: Commit titles and messages follow guidelines in the [developer guide](https://rook.io/docs/rook/latest/Contributing/development-flow/#commit-structure).
- [ ] Reviewed the developer guide on [Submitting a Pull Request](https://rook.io/docs/rook/latest/Contributing/development-flow/#submitting-a-pull-request)
- [ ] [Pending release notes](https://github.com/rook/rook/blob/master/PendingReleaseNotes.md) updated with breaking and/or notable changes for the next minor release.
- [ ] Documentation has been updated, if necessary.
- [ ] Unit tests have been added, if necessary.
- [ ] Integration tests have been added, if necessary.


parth-gr: @BlaineEXE it still failed https://github.com/rook/rook/actions/runs/14314601939/job/40117716302?pr=15648
jhoblitt: It looks like s3cmd has a `socket_timeout` config value which can be set in a config file: https://s3tools.org/kb/item14.htm
jhoblitt: Another option might be to replace s3cmd with rclone as it has a `--timeout` flag.

Patch:
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 4d2cfd168..d22fe4034 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -64,6 +64,6 @@ jobs:
       # Upload the results to GitHub's code scanning dashboard (optional).
       # Commenting out will disable upload of results to your repo's Code Scanning dashboard
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@fc7e4a0fa01c3cca5fd6a1fddec5c0740c977aa2 # v3.28.14
+        uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
         with:
           sarif_file: results.sarif
diff --git a/.mergify.yml b/.mergify.yml
index d913cf835..6468d1e58 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -141,7 +141,6 @@ pull_request_rules:
       - label!=do-not-merge
       - "status-success=DCO"
       - "check-success=linux-build-all (1.22)"
-      - "check-success=linux-build-all (1.23)"
       - "check-success=unittests"
       - "check-success=golangci-lint"
       - "check-success=codegen"
@@ -198,50 +197,46 @@ pull_request_rules:
       - base=release-1.17
       - label!=do-not-merge
       - "status-success=DCO"
+      - "check-success=linux-build-all (1.22)"
+      - "check-success=unittests"
+      - "check-success=golangci-lint"
       - "check-success=codegen"
       - "check-success=codespell"
-      - "check-success=crds-gen"
-      - "check-success=docs-check"
-      - "check-success=gen-rbac"
-      - "check-success=golangci-lint"
-      - "check-success=govulncheck"
       - "check-success=lint"
-      - "check-success=lint-test"
-      - "check-success=linux-build-all (1.23)"
-      - "check-success=linux-build-all (1.24)"
-      - "check-success=misspell"
       - "check-success=modcheck"
-      - "check-success=pylint"
       - "check-success=Shellcheck"
-      - "check-success=unittests"
       - "check-success=yaml-linter"
+      - "check-success=lint-test"
+      - "check-success=gen-rbac"
+      - "check-success=crds-gen"
+      - "check-success=docs-check"
+      - "check-success=pylint"
       - "check-success=canary-tests / canary (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / raw-disk-with-object (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / two-osds-in-device (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / osd-with-metadata-partition-device (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / osd-with-metadata-device (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / encryption (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / lvm (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / pvc (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / pvc-db (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / pvc-db-wal (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / encryption-pvc (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / encryption-pvc-db (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / encryption-pvc-db-wal (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / encryption-pvc-kms-ibm-kp (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / encryption-pvc-kms-vault-k8s-auth (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / encryption-pvc-kms-vault-token-auth (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / lvm (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / encryption-pvc-kms-vault-k8s-auth (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / lvm-pvc (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / multi-cluster-mirroring (quay.io/ceph/ceph:v19)"
+      - "check-success=canary-tests / encryption-pvc-kms-ibm-kp (quay.io/ceph/ceph:v19)"
       - "check-success=canary-tests / multus-public-and-cluster (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / osd-with-metadata-device (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / osd-with-metadata-partition-device (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / pvc (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / pvc-db (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / pvc-db-wal (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / raw-disk-with-object (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / two-object-one-zone (quay.io/ceph/ceph:v19)"
-      - "check-success=canary-tests / two-osds-in-device (quay.io/ceph/ceph:v19)"
+      - "check-success=TestCephSmokeSuite (v1.27.16)"
+      - "check-success=TestCephSmokeSuite (v1.32.0)"
       - "check-success=TestCephHelmSuite (v1.27.16)"
       - "check-success=TestCephHelmSuite (v1.32.0)"
       - "check-success=TestCephMultiClusterDeploySuite (v1.32.0)"
       - "check-success=TestCephObjectSuite (v1.27.16)"
       - "check-success=TestCephObjectSuite (v1.32.0)"
-      - "check-success=TestCephSmokeSuite (v1.27.16)"
-      - "check-success=TestCephSmokeSuite (v1.32.0)"
       - "check-success=TestCephUpgradeSuite (v1.27.16)"
       - "check-success=TestCephUpgradeSuite (v1.32.0)"
       - "check-success=TestHelmUpgradeSuite (v1.27.16)"
diff --git a/Documentation/Upgrade/rook-upgrade.md b/Documentation/Upgrade/rook-upgrade.md
index d86d53a47..700eac7cc 100644
--- a/Documentation/Upgrade/rook-upgrade.md
+++ b/Documentation/Upgrade/rook-upgrade.md
@@ -14,7 +14,7 @@ We welcome feedback and opening issues!
 
 ## Supported Versions
 
-This guide is for upgrading from **Rook v1.16.x to Rook v1.17.x**.
+This guide is for upgrading from **Rook v1.15.x to Rook v1.16.x**.
 
 Please refer to the upgrade guides from previous releases for supported upgrade paths.
 Rook upgrades are only supported between official releases.
@@ -22,7 +22,6 @@ Rook upgrades are only supported between official releases.
 For a guide to upgrade previous versions of Rook, please refer to the version of documentation for
 those releases.
 
-* [Upgrade 1.15 to 1.16](https://rook.io/docs/rook/v1.16/Upgrade/rook-upgrade/)
 * [Upgrade 1.14 to 1.15](https://rook.io/docs/rook/v1.15/Upgrade/rook-upgrade/)
 * [Upgrade 1.13 to 1.14](https://rook.io/docs/rook/v1.14/Upgrade/rook-upgrade/)
 * [Upgrade 1.12 to 1.13](https://rook.io/docs/rook/v1.13/Upgrade/rook-upgrade/)
@@ -51,25 +50,18 @@ those releases.
     official releases. Builds from the master branch can have functionality changed or removed at any
     time without compatibility support and without prior notice.
 
-## Breaking changes in v1.17
+## Breaking changes in v1.16
 
-* The minimum supported Kubernetes version is v1.28.
+* The minimum supported Kubernetes version is v1.27.
 
-* S3 users provisioned via CephObjectStoreUser resources no longer allow multiple credentials to
-    exist on underlying S3 users, unless explicitly managed by Rook. Rook will purge all but one of
-    the undeclared credentials. This could be a user observable regression for administrators who
-    manually edited/rotated S3 user credentials for CephObjectStoreUsers. For affected users, Rook
-    now provides first-class
-    [user credential management support](../Storage-Configuration/Object-Storage-RGW/object-storage.md#managing-user-s3-credentials).
-    For more details, see [#15359](https://github.com/rook/rook/issues/15359).
+* Rook no longer supports Ceph v17 (Quincy) as it is end of life. Rook v1.15 clusters should
+    [upgrade Ceph](./ceph-upgrade.md) to at least Ceph v18 (Reef) before upgrading Rook to v1.16.
+    Be aware of known [Ceph v18.2.4 ARM issues](https://github.com/rook/rook/issues/14502).
 
-* Some ObjectBucketClaim options were added in Rook v1.16 that allowed more control over buckets.
-    These controls allow users to self-serve their own S3 policies, which many administrators might
-    consider a risk, depending on their environment. Rook has taken steps to ensure potentially risky
-    configurations are disabled by default to ensure the safest off-the-shelf configurations.
-    Administrators who wish to allow users to use the full range of OBC configurations must use the
-    new `ROOK_OBC_ALLOW_ADDITIONAL_CONFIG_FIELDS` to enable users to set potentially risky options.
-    See [#15376](https://github.com/rook/rook/pull/15376) for more information.
+* Rook fully deprecated CSI "holder" pods in Rook v1.16. Any Rook v1.15 CephCluster with
+    `csi-*plugin-holder-*` pods present in the Rook operator namespace must follow holder pod
+    removal migration steps outlined in
+    [v1.15 documentation](https://rook.io/docs/rook/v1.15/CRDs/Cluster/network-providers/?h=depre#holder-pod-deprecation).
 
 ## Considerations
 
@@ -85,11 +77,11 @@ With this upgrade guide, there are a few notes to consider:
 
 Unless otherwise noted due to extenuating requirements, upgrades from one patch release of Rook to
 another are as simple as updating the common resources and the image of the Rook operator. For
-example, when Rook v1.17.1 is released, the process of updating from v1.17.0 is as simple as running
+example, when Rook v1.16.1 is released, the process of updating from v1.16.0 is as simple as running
 the following:
 
 ```console
-git clone --single-branch --depth=1 --branch v1.17.1 https://github.com/rook/rook.git
+git clone --single-branch --depth=1 --branch v1.16.1 https://github.com/rook/rook.git
 cd rook/deploy/examples
 ```
 
@@ -97,11 +89,11 @@ If the Rook Operator or CephCluster are deployed into a different namespace than
 `rook-ceph`, see the [Update common resources and CRDs](#1-update-common-resources-and-crds)
 section for instructions on how to change the default namespaces in `common.yaml`.
 
-Then, apply the latest changes from v1.17, and update the Rook Operator image.
+Then, apply the latest changes from v1.16, and update the Rook Operator image.
 
 ```console
 kubectl apply -f common.yaml -f crds.yaml
-kubectl -n rook-ceph set image deploy/rook-ceph-operator rook-ceph-operator=rook/ceph:v1.17.1
+kubectl -n rook-ceph set image deploy/rook-ceph-operator rook-ceph-operator=rook/ceph:v1.16.1
 ```
 
 As exemplified above, it is a good practice to update Rook common resources from the example
@@ -135,9 +127,9 @@ In order to successfully upgrade a Rook cluster, the following prerequisites mus
 
 ## Rook Operator Upgrade
 
-The examples given in this guide upgrade a live Rook cluster running `v1.16.6` to
-the version `v1.17.0`. This upgrade should work from any official patch release of Rook v1.16 to any
-official patch release of v1.17.
+The examples given in this guide upgrade a live Rook cluster running `v1.15.6` to
+the version `v1.16.0`. This upgrade should work from any official patch release of Rook v1.15 to any
+official patch release of v1.16.
 
 Let's get started!
 
@@ -198,7 +190,7 @@ kubectl apply -f deploy/examples/monitoring/rbac.yaml
 !!! hint
     The operator is automatically updated when using Helm charts.
 
-The largest portion of the upgrade is triggered when the operator's image is updated to `v1.17.x`.
+The largest portion of the upgrade is triggered when the operator's image is updated to `v1.16.x`.
 When the operator is updated, it will proceed to update all of the Ceph daemons.
 
 ```console
@@ -229,18 +221,18 @@ watch --exec kubectl -n $ROOK_CLUSTER_NAMESPACE get deployments -l rook_cluster=
 ```
 
 As an example, this cluster is midway through updating the OSDs. When all deployments report `1/1/1`
-availability and `rook-version=v1.17.0`, the Ceph cluster's core components are fully updated.
+availability and `rook-version=v1.16.0`, the Ceph cluster's core components are fully updated.
 
 ```console
 Every 2.0s: kubectl -n rook-ceph get deployment -o j...
 
-rook-ceph-mgr-a         req/upd/avl: 1/1/1      rook-version=v1.17.0
-rook-ceph-mon-a         req/upd/avl: 1/1/1      rook-version=v1.17.0
-rook-ceph-mon-b         req/upd/avl: 1/1/1      rook-version=v1.17.0
-rook-ceph-mon-c         req/upd/avl: 1/1/1      rook-version=v1.17.0
-rook-ceph-osd-0         req/upd/avl: 1//        rook-version=v1.17.0
-rook-ceph-osd-1         req/upd/avl: 1/1/1      rook-version=v1.16.6
-rook-ceph-osd-2         req/upd/avl: 1/1/1      rook-version=v1.16.6
+rook-ceph-mgr-a         req/upd/avl: 1/1/1      rook-version=v1.16.0
+rook-ceph-mon-a         req/upd/avl: 1/1/1      rook-version=v1.16.0
+rook-ceph-mon-b         req/upd/avl: 1/1/1      rook-version=v1.16.0
+rook-ceph-mon-c         req/upd/avl: 1/1/1      rook-version=v1.16.0
+rook-ceph-osd-0         req/upd/avl: 1//        rook-version=v1.16.0
+rook-ceph-osd-1         req/upd/avl: 1/1/1      rook-version=v1.15.6
+rook-ceph-osd-2         req/upd/avl: 1/1/1      rook-version=v1.15.6
 ```
 
 An easy check to see if the upgrade is totally finished is to check that there is only one
@@ -249,14 +241,14 @@ An easy check to see if the upgrade is totally finished is to check that there i
 ```console
 # kubectl -n $ROOK_CLUSTER_NAMESPACE get deployment -l rook_cluster=$ROOK_CLUSTER_NAMESPACE -o jsonpath='{range .items[*]}{"rook-version="}{.metadata.labels.rook-version}{"\n"}{end}' | sort | uniq
 This cluster is not yet finished:
-  rook-version=v1.16.6
-  rook-version=v1.17.0
+  rook-version=v1.15.6
+  rook-version=v1.16.0
 This cluster is finished:
-  rook-version=v1.17.0
+  rook-version=v1.16.0
 ```
 
 ### **5. Verify the updated cluster**
 
-At this point, the Rook operator should be running version `rook/ceph:v1.17.0`.
+At this point, the Rook operator should be running version `rook/ceph:v1.16.0`.
 
 Verify the CephCluster health using the [health verification doc](health-verification.md).
diff --git a/PendingReleaseNotes.md b/PendingReleaseNotes.md
index aa323ecf0..11608157c 100644
--- a/PendingReleaseNotes.md
+++ b/PendingReleaseNotes.md
@@ -12,13 +12,11 @@ Object:
     new `ROOK_OBC_ALLOW_ADDITIONAL_CONFIG_FIELDS` to enable users to set potentially risky options.
     See https://github.com/rook/rook/pull/15376 for more information.
 
-- Add first-class credential management to CephObjectStoreUser. Existing S3 users provisioned via
-    CephObjectStoreUser resources no longer allow multiple credentials to exist on underlying S3
-    users, unless explicitly managed by Rook. Rook will purge all but one of the undeclared
-    credentials. This could be a user observable regression for administrators who manually
-    edited/rotated S3 user credentials for CephObjectStoreUsers. Affected users should make use of
-    the new credential management feature instead.
-    For more details, see [#15359](https://github.com/rook/rook/issues/15359).
+- Add credentials/keys management to CephObjectStoreUser. Note that this will
+    cause manually created credentials/keys on an rgw user to be purged. This
+    behavior is necessary to ensure that keys that were explicitly declared and
+    then undeclared are purged. This could be a user observable regression.
+    (see [#15359](https://github.com/rook/rook/issues/15359)
 
 ## Features
 
diff --git a/go.mod b/go.mod
index 7bb46def5..36a2a9ca1 100644
--- a/go.mod
+++ b/go.mod
@@ -42,7 +42,7 @@ require (
 	github.com/sykesm/zap-logfmt v0.0.4
 	go.uber.org/automaxprocs v1.6.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/sync v0.13.0
+	golang.org/x/sync v0.12.0
 	gopkg.in/ini.v1 v1.67.0
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.32.3
@@ -146,12 +146,12 @@ require (
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/crypto v0.35.0 // indirect
+	golang.org/x/net v0.36.0 // indirect
 	golang.org/x/oauth2 v0.25.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/term v0.30.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/term v0.29.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
diff --git a/go.sum b/go.sum
index 0cc106c71..8d8943300 100644
--- a/go.sum
+++ b/go.sum
@@ -1021,8 +1021,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1133,8 +1133,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1171,8 +1171,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1277,8 +1277,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1291,8 +1291,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1310,8 +1310,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/pkg/apis/go.mod b/pkg/apis/go.mod
index 97441dafc..2f9ca6d7f 100644
--- a/pkg/apis/go.mod
+++ b/pkg/apis/go.mod
@@ -70,12 +70,12 @@ require (
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/crypto v0.35.0 // indirect
+	golang.org/x/net v0.36.0 // indirect
 	golang.org/x/oauth2 v0.25.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/term v0.30.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/term v0.29.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/pkg/apis/go.sum b/pkg/apis/go.sum
index 66d66bc0a..dd2981aa1 100644
--- a/pkg/apis/go.sum
+++ b/pkg/apis/go.sum
@@ -806,8 +806,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -917,8 +917,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1054,8 +1054,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1068,8 +1068,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1087,8 +1087,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/pkg/operator/ceph/client/controller.go b/pkg/operator/ceph/client/controller.go
index fcf54bf3a..2543764f8 100644
--- a/pkg/operator/ceph/client/controller.go
+++ b/pkg/operator/ceph/client/controller.go
@@ -98,31 +98,25 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephClient CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephClient{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephClient]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephClient](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephClient{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch secrets
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&v1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: v1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestForOwner[*v1.Secret](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephClient{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[*v1.Secret](&cephv1.CephClient{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
+
+	ownerRequest := handler.EnqueueRequestForOwner(
+		mgr.GetScheme(),
+		mgr.GetRESTMapper(),
+		&cephv1.CephClient{},
+	)
+	secretSource := source.Kind[client.Object](
+		mgr.GetCache(),
+		&v1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: v1.SchemeGroupVersion.String()}},
+		ownerRequest,
+		opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephClient{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
 	)
+	err = c.Watch(secretSource)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/cluster/controller.go b/pkg/operator/ceph/cluster/controller.go
index 75b310a58..4d3797aaa 100644
--- a/pkg/operator/ceph/cluster/controller.go
+++ b/pkg/operator/ceph/cluster/controller.go
@@ -127,21 +127,6 @@ func newReconciler(mgr manager.Manager, ctx *clusterd.Context, clusterController
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephCluster{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephCluster{TypeMeta: ControllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reconciler, context *clusterd.Context, opConfig opcontroller.OperatorConfig) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -161,21 +146,22 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 	}
 
 	// Watch for changes on the CephCluster CR object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephCluster{TypeMeta: ControllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephCluster]{},
-			watchControllerPredicate(opManagerContext, mgr.GetClient()),
-		),
+	s := source.Kind[client.Object](
+		mgr.GetCache(), &cephv1.CephCluster{TypeMeta: ControllerTypeMeta},
+		&handler.EnqueueRequestForObject{}, watchControllerPredicate(opManagerContext, mgr.GetClient()),
 	)
+	err = c.Watch(s)
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources of the Ceph Cluster
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		err = c.Watch(
+			source.Kind[client.Object](
+				mgr.GetCache(), t, handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &cephv1.CephCluster{}),
+				opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephCluster{TypeMeta: ControllerTypeMeta}, mgr.GetScheme())),
+		)
 		if err != nil {
 			return err
 		}
@@ -183,35 +169,18 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 
 	// Build Handler function to return the list of ceph clusters
 	// This is used by the watchers below
-	nodeHandler, err := opcontroller.ObjectToCRMapper[*cephv1.CephClusterList, *corev1.Node](
-		opManagerContext,
-		mgr.GetClient(),
-		&cephv1.CephClusterList{},
-		mgr.GetScheme(),
-	)
+	handlerFunc, err := opcontroller.ObjectToCRMapper(opManagerContext, mgr.GetClient(), &cephv1.CephClusterList{}, mgr.GetScheme())
 	if err != nil {
 		return err
 	}
 
 	// Watch for nodes additions and updates
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.Node{TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: corev1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestsFromMapFunc(nodeHandler),
-			predicateForNodeWatcher(opManagerContext, mgr.GetClient(), context, opConfig.OperatorNamespace),
-		),
-	)
-	if err != nil {
-		return err
-	}
-
-	cmHandler, err := opcontroller.ObjectToCRMapper[*cephv1.CephClusterList, *corev1.ConfigMap](
-		opManagerContext,
-		mgr.GetClient(),
-		&cephv1.CephClusterList{},
-		mgr.GetScheme(),
-	)
+	nodeKind := source.Kind[client.Object](
+		mgr.GetCache(),
+		&corev1.Node{TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: corev1.SchemeGroupVersion.String()}},
+		handler.EnqueueRequestsFromMapFunc(handlerFunc),
+		predicateForNodeWatcher(opManagerContext, mgr.GetClient(), context, opConfig.OperatorNamespace))
+	err = c.Watch(nodeKind)
 	if err != nil {
 		return err
 	}
@@ -221,14 +190,11 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 	disableVal := k8sutil.GetOperatorSetting(disableHotplugEnv, "false")
 	if disableVal != "true" {
 		logger.Info("enabling hotplug orchestration")
-		err = c.Watch(
-			source.Kind(
-				mgr.GetCache(),
-				&corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
-				handler.TypedEnqueueRequestsFromMapFunc(cmHandler),
-				predicateForHotPlugCMWatcher(mgr.GetClient()),
-			),
-		)
+		s := source.Kind[client.Object](
+			mgr.GetCache(),
+			&corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
+			handler.EnqueueRequestsFromMapFunc(handlerFunc), predicateForHotPlugCMWatcher(mgr.GetClient()))
+		err = c.Watch(s)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/operator/ceph/cluster/mon/predicate.go b/pkg/operator/ceph/cluster/mon/predicate.go
index c3438f0ce..023e92ec9 100644
--- a/pkg/operator/ceph/cluster/mon/predicate.go
+++ b/pkg/operator/ceph/cluster/mon/predicate.go
@@ -27,20 +27,26 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
 
-func PredicateMonEndpointChanges[T *corev1.ConfigMap]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
+func PredicateMonEndpointChanges() predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
 			return false
 		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			return false
 		},
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			cmOld := (*corev1.ConfigMap)(e.ObjectOld)
-			cmNew := (*corev1.ConfigMap)(e.ObjectNew)
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			cmNew, ok := e.ObjectNew.(*corev1.ConfigMap)
+			if !ok {
+				return false
+			}
+			cmOld, ok := e.ObjectOld.(*corev1.ConfigMap)
+			if !ok {
+				return false
+			}
 			if cmNew.GetName() == EndpointConfigMapName {
 				if wereMonEndpointsUpdated(cmOld.Data, cmNew.Data) {
 					logger.Info("monitor endpoints changed, updating the bootstrap peer token")
diff --git a/pkg/operator/ceph/cluster/nodedaemon/add.go b/pkg/operator/ceph/cluster/nodedaemon/add.go
index b2b8c8886..325114cb9 100644
--- a/pkg/operator/ceph/cluster/nodedaemon/add.go
+++ b/pkg/operator/ceph/cluster/nodedaemon/add.go
@@ -29,6 +29,7 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -73,23 +74,21 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes to the nodes
-	specChangePredicate := predicate.TypedFuncs[*corev1.Node]{
-		UpdateFunc: func(e event.TypedUpdateEvent[*corev1.Node]) bool {
-			nodeOld := (*corev1.Node)(e.ObjectOld)
-			nodeNew := (*corev1.Node)(e.ObjectNew)
-
+	specChangePredicate := predicate.Funcs{
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			nodeOld, ok := e.ObjectOld.DeepCopyObject().(*corev1.Node)
+			if !ok {
+				return false
+			}
+			nodeNew, ok := e.ObjectNew.DeepCopyObject().(*corev1.Node)
+			if !ok {
+				return false
+			}
 			return !reflect.DeepEqual(nodeOld.Spec, nodeNew.Spec)
 		},
 	}
 	logger.Debugf("watch for changes to the nodes")
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.Node{},
-			&handler.TypedEnqueueRequestForObject[*corev1.Node]{},
-			specChangePredicate,
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &corev1.Node{}, &handler.EnqueueRequestForObject{}, specChangePredicate))
 	if err != nil {
 		return errors.Wrap(err, "failed to watch for node changes")
 	}
@@ -97,23 +96,24 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Watch for changes to the ceph-crash deployments
 	logger.Debugf("watch for changes to the ceph-crash deployments")
 	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&appsv1.Deployment{},
-			handler.TypedEnqueueRequestsFromMapFunc(
-				func(context context.Context, deployment *appsv1.Deployment) []reconcile.Request {
-					labels := deployment.GetLabels()
-					appName, ok := labels[k8sutil.AppAttr]
-					if !ok || appName != CrashCollectorAppName {
-						return []reconcile.Request{}
-					}
-					nodeName, ok := deployment.Spec.Template.ObjectMeta.Labels[NodeNameLabel]
-					if !ok {
-						return []reconcile.Request{}
-					}
-					req := reconcile.Request{NamespacedName: types.NamespacedName{Name: nodeName}}
-					return []reconcile.Request{req}
-				},
+		source.Kind[client.Object](mgr.GetCache(), &appsv1.Deployment{},
+			handler.EnqueueRequestsFromMapFunc(handler.MapFunc(func(context context.Context, obj client.Object) []reconcile.Request {
+				deployment, ok := obj.(*appsv1.Deployment)
+				if !ok {
+					return []reconcile.Request{}
+				}
+				labels := deployment.GetLabels()
+				appName, ok := labels[k8sutil.AppAttr]
+				if !ok || appName != CrashCollectorAppName {
+					return []reconcile.Request{}
+				}
+				nodeName, ok := deployment.Spec.Template.ObjectMeta.Labels[NodeNameLabel]
+				if !ok {
+					return []reconcile.Request{}
+				}
+				req := reconcile.Request{NamespacedName: types.NamespacedName{Name: nodeName}}
+				return []reconcile.Request{req}
+			}),
 			),
 		),
 	)
@@ -124,30 +124,39 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Watch for changes to the ceph pods and enqueue their nodes
 	logger.Debugf("watch for changes to the ceph pods and enqueue their nodes")
 	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.Pod{},
-			handler.TypedEnqueueRequestsFromMapFunc[*corev1.Pod](
-				func(context context.Context, pod *corev1.Pod) []reconcile.Request {
-					nodeName := pod.Spec.NodeName
-					if nodeName == "" {
-						return []reconcile.Request{}
-					}
-					if isCephPod(pod.Labels, pod.Name) {
-						req := reconcile.Request{NamespacedName: types.NamespacedName{Name: nodeName}}
-						return []reconcile.Request{req}
-					}
+		source.Kind[client.Object](mgr.GetCache(), &corev1.Pod{},
+			handler.EnqueueRequestsFromMapFunc(handler.MapFunc(func(context context.Context, obj client.Object) []reconcile.Request {
+				pod, ok := obj.(*corev1.Pod)
+				if !ok {
 					return []reconcile.Request{}
-				},
+				}
+				nodeName := pod.Spec.NodeName
+				if nodeName == "" {
+					return []reconcile.Request{}
+				}
+				if isCephPod(pod.Labels, pod.Name) {
+					req := reconcile.Request{NamespacedName: types.NamespacedName{Name: nodeName}}
+					return []reconcile.Request{req}
+				}
+				return []reconcile.Request{}
+			}),
 			),
 			// only enqueue the update event if the pod moved nodes
-			predicate.TypedFuncs[*corev1.Pod]{
-				UpdateFunc: func(event event.TypedUpdateEvent[*corev1.Pod]) bool {
-					oldPod := (*corev1.Pod)(event.ObjectOld)
-					newPod := (*corev1.Pod)(event.ObjectNew)
-
+			predicate.Funcs{
+				UpdateFunc: func(event event.UpdateEvent) bool {
+					oldPod, ok := event.ObjectOld.(*corev1.Pod)
+					if !ok {
+						return false
+					}
+					newPod, ok := event.ObjectNew.(*corev1.Pod)
+					if !ok {
+						return false
+					}
 					// only enqueue if the nodename has changed
-					return oldPod.Spec.NodeName != newPod.Spec.NodeName
+					if oldPod.Spec.NodeName == newPod.Spec.NodeName {
+						return false
+					}
+					return true
 				},
 			},
 		),
diff --git a/pkg/operator/ceph/cluster/predicate.go b/pkg/operator/ceph/cluster/predicate.go
index 2da2439df..709b5b6bf 100644
--- a/pkg/operator/ceph/cluster/predicate.go
+++ b/pkg/operator/ceph/cluster/predicate.go
@@ -30,6 +30,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -51,71 +52,75 @@ func shouldReconcileChangedNode(objOld, objNew *corev1.Node) bool {
 }
 
 // predicateForNodeWatcher is the predicate function to trigger reconcile on Node events
-func predicateForNodeWatcher[T *corev1.Node](ctx context.Context, client client.Client, context *clusterd.Context, opNamespace string) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			obj := (*corev1.Node)(e.Object)
-
-			clientCluster := newClientCluster(client, obj.GetNamespace(), context)
-			return clientCluster.onK8sNode(ctx, obj, opNamespace)
+func predicateForNodeWatcher(ctx context.Context, client client.Client, context *clusterd.Context, opNamespace string) predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			clientCluster := newClientCluster(client, e.Object.GetNamespace(), context)
+			return clientCluster.onK8sNode(ctx, e.Object, opNamespace)
 		},
 
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			objOld := (*corev1.Node)(e.ObjectOld)
-			objNew := (*corev1.Node)(e.ObjectNew)
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			if objOld, ok := e.ObjectOld.(*corev1.Node); ok {
+				if objNew, ok := e.ObjectNew.(*corev1.Node); ok {
+					if !shouldReconcileChangedNode(objOld, objNew) {
+						return false
+					}
 
-			if !shouldReconcileChangedNode(objOld, objNew) {
-				return false
+					clientCluster := newClientCluster(client, e.ObjectNew.GetNamespace(), context)
+					return clientCluster.onK8sNode(ctx, e.ObjectNew, opNamespace)
+				}
 			}
-
-			clientCluster := newClientCluster(client, objNew.GetNamespace(), context)
-			return clientCluster.onK8sNode(ctx, objNew, opNamespace)
+			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			return false
 		},
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
 }
 
 // predicateForHotPlugCMWatcher is the predicate function to trigger reconcile on ConfigMap events (hot-plug)
-func predicateForHotPlugCMWatcher[T *corev1.ConfigMap](client client.Client) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			objOld := (*corev1.ConfigMap)(e.ObjectOld)
-			objNew := (*corev1.ConfigMap)(e.ObjectNew)
-
-			if !isHotPlugCM(objNew) {
+func predicateForHotPlugCMWatcher(client client.Client) predicate.Funcs {
+	return predicate.Funcs{
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			isHotPlugCM := isHotPlugCM(e.ObjectNew)
+			if !isHotPlugCM {
 				return false
 			}
 
-			clientCluster := newClientCluster(client, objNew.GetNamespace(), &clusterd.Context{})
-			return clientCluster.onDeviceCMUpdate(objOld, objNew)
+			clientCluster := newClientCluster(client, e.ObjectNew.GetNamespace(), &clusterd.Context{})
+			return clientCluster.onDeviceCMUpdate(e.ObjectOld, e.ObjectNew)
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			// TODO: if the configmap goes away we could retrigger rook-discover DS
 			// However at this point the returned bool can only trigger a reconcile of the CephCluster object
 			// Definitely non-trivial but nice to have in the future
 			return false
 		},
 
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
+		CreateFunc: func(e event.CreateEvent) bool {
 			return false
 		},
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
 }
 
 // isHotPlugCM informs whether the object is the cm for hot-plug disk
-func isHotPlugCM(cm *corev1.ConfigMap) bool {
+func isHotPlugCM(obj runtime.Object) bool {
+	// If not a ConfigMap, let's not reconcile
+	cm, ok := obj.(*corev1.ConfigMap)
+	if !ok {
+		return false
+	}
+
 	// Get the labels
 	labels := cm.GetLabels()
 
@@ -127,20 +132,20 @@ func isHotPlugCM(cm *corev1.ConfigMap) bool {
 	return false
 }
 
-func watchControllerPredicate[T *cephv1.CephCluster](ctx context.Context, c client.Client) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
+func watchControllerPredicate(ctx context.Context, c client.Client) predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
 			if controller.DuplicateCephClusters(ctx, c, e.Object, true) {
 				return false
 			}
 			logger.Debug("create event from a CR")
 			return true
 		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			logger.Debug("delete event from a CR")
 			return true
 		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
+		UpdateFunc: func(e event.UpdateEvent) bool {
 			// We still need to check on update event since the user must delete the additional CR
 			// Until this is done, the user can still update the CR and the operator will reconcile
 			// This should not happen
@@ -151,44 +156,46 @@ func watchControllerPredicate[T *cephv1.CephCluster](ctx context.Context, c clie
 			// resource.Quantity has non-exportable fields, so we use its comparator method
 			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })
 
-			objOld := (*cephv1.CephCluster)(e.ObjectOld)
-			objNew := (*cephv1.CephCluster)(e.ObjectNew)
-
-			logger.Debug("update event on CephCluster CR")
-			// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
-			if controller.IsDoNotReconcile(objNew.GetLabels()) {
-				logger.Debugf("object %q matched on update but %q label is set, doing nothing", controller.DoNotReconcileLabelName, objNew.Name)
-				return false
-			}
-			diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
-			if diff != "" {
-				logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
-
-				if objNew.Spec.CleanupPolicy.HasDataDirCleanPolicy() {
-					logger.Infof("skipping orchestration for cluster object %q in namespace %q because its cleanup policy is set. not reloading the manager", objNew.GetName(), objNew.GetNamespace())
+			switch objOld := e.ObjectOld.(type) {
+			case *cephv1.CephCluster:
+				objNew := e.ObjectNew.(*cephv1.CephCluster)
+				logger.Debug("update event on CephCluster CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				isDoNotReconcile := controller.IsDoNotReconcile(objNew.GetLabels())
+				if isDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", controller.DoNotReconcileLabelName, objNew.Name)
 					return false
 				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
 
-				// Stop any ongoing orchestration
-				controller.ReloadManager()
+					if objNew.Spec.CleanupPolicy.HasDataDirCleanPolicy() {
+						logger.Infof("skipping orchestration for cluster object %q in namespace %q because its cleanup policy is set. not reloading the manager", objNew.GetName(), objNew.GetNamespace())
+						return false
+					}
 
-				return false
+					// Stop any ongoing orchestration
+					controller.ReloadManager()
 
-			} else if !objOld.GetDeletionTimestamp().Equal(objNew.GetDeletionTimestamp()) {
-				logger.Infof("CR %q is going be deleted, cancelling any ongoing orchestration", objNew.Name)
+					return false
 
-				// Stop any ongoing orchestration
-				controller.ReloadManager()
+				} else if !objOld.GetDeletionTimestamp().Equal(objNew.GetDeletionTimestamp()) {
+					logger.Infof("CR %q is going be deleted, cancelling any ongoing orchestration", objNew.Name)
 
-				return false
+					// Stop any ongoing orchestration
+					controller.ReloadManager()
+
+					return false
 
-			} else if objOld.GetGeneration() != objNew.GetGeneration() {
-				logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
 			}
 
 			return false
 		},
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
diff --git a/pkg/operator/ceph/cluster/rbd/controller.go b/pkg/operator/ceph/cluster/rbd/controller.go
index 051b223bb..5e2d14d85 100644
--- a/pkg/operator/ceph/cluster/rbd/controller.go
+++ b/pkg/operator/ceph/cluster/rbd/controller.go
@@ -107,21 +107,6 @@ func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerCont
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephRBDMirror{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephRBDMirror{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -131,21 +116,26 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the cephRBDMirror CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephRBDMirror{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephRBDMirror]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephRBDMirror](mgr.GetScheme()),
-		),
-	)
+	s := source.Kind[client.Object](
+		mgr.GetCache(), &cephv1.CephRBDMirror{TypeMeta: controllerTypeMeta},
+		&handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate())
+	err = c.Watch(s)
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		ownerRequest := handler.EnqueueRequestForOwner(
+			mgr.GetScheme(),
+			mgr.GetRESTMapper(),
+			&cephv1.CephRBDMirror{},
+		)
+		err = c.Watch(
+			source.Kind[client.Object](
+				mgr.GetCache(), t, ownerRequest,
+				opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephRBDMirror{TypeMeta: controllerTypeMeta}, mgr.GetScheme())),
+		)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/operator/ceph/controller.go b/pkg/operator/ceph/controller.go
index 19d1f206d..5aa86f336 100644
--- a/pkg/operator/ceph/controller.go
+++ b/pkg/operator/ceph/controller.go
@@ -73,14 +73,12 @@ func add(ctx context.Context, context *clusterd.Context, mgr manager.Manager, r
 	logger.Infof("%s successfully started", controllerName)
 
 	// Watch for ConfigMap (operator config)
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&v1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: v1.SchemeGroupVersion.String()}},
-			&handler.TypedEnqueueRequestForObject[*v1.ConfigMap]{},
-			operatorSettingConfigMapPredicate(),
-		),
+	s := source.Kind[client.Object](
+		mgr.GetCache(),
+		&v1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: v1.SchemeGroupVersion.String()}},
+		&handler.EnqueueRequestForObject{}, predicateController(ctx, mgr.GetClient()),
 	)
+	err = c.Watch(s)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/controller/handler.go b/pkg/operator/ceph/controller/handler.go
index 462bd4177..17eff08c0 100644
--- a/pkg/operator/ceph/controller/handler.go
+++ b/pkg/operator/ceph/controller/handler.go
@@ -19,25 +19,32 @@ package controller
 import (
 	"context"
 
+	"github.com/pkg/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
 // ObjectToCRMapper returns the list of a given object type metadata
 // It is used to trigger a reconcile object Kind A when watching object Kind B
 // So we reconcile Kind A instead of Kind B
 // For instance, we watch for CephCluster CR changes but want to reconcile CephFilesystem based on a Spec change
-func ObjectToCRMapper[List client.ObjectList, T runtime.Object](ctx context.Context, c client.Client, list List, scheme *runtime.Scheme) (handler.TypedMapFunc[T, reconcile.Request], error) {
-	gvk, err := apiutil.GVKForObject(list, scheme)
+func ObjectToCRMapper(ctx context.Context, c client.Client, ro runtime.Object, scheme *runtime.Scheme) (handler.MapFunc, error) {
+	if _, ok := ro.(metav1.ListInterface); !ok {
+		return nil, errors.Errorf("expected a metav1.ListInterface, got %T instead", ro)
+	}
+
+	gvk, err := apiutil.GVKForObject(ro, scheme)
 	if err != nil {
 		return nil, err
 	}
 
-	return func(ctx context.Context, obj T) []reconcile.Request {
+	// return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []ctrl.Request {
+	return handler.MapFunc(func(ctx context.Context, o client.Object) []ctrl.Request {
 		list := &unstructured.UnstructuredList{}
 		list.SetGroupVersionKind(gvk)
 		err := c.List(ctx, list)
@@ -45,9 +52,9 @@ func ObjectToCRMapper[List client.ObjectList, T runtime.Object](ctx context.Cont
 			return nil
 		}
 
-		results := []reconcile.Request{}
+		results := []ctrl.Request{}
 		for _, obj := range list.Items {
-			results = append(results, reconcile.Request{
+			results = append(results, ctrl.Request{
 				NamespacedName: client.ObjectKey{
 					Namespace: obj.GetNamespace(),
 					Name:      obj.GetName(),
@@ -55,5 +62,5 @@ func ObjectToCRMapper[List client.ObjectList, T runtime.Object](ctx context.Cont
 			})
 		}
 		return results
-	}, nil
+	}), nil
 }
diff --git a/pkg/operator/ceph/controller/predicate.go b/pkg/operator/ceph/controller/predicate.go
index cf51f2332..3ee7ac115 100644
--- a/pkg/operator/ceph/controller/predicate.go
+++ b/pkg/operator/ceph/controller/predicate.go
@@ -19,6 +19,7 @@ package controller
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"os"
 	"reflect"
 	"strings"
@@ -26,6 +27,7 @@ import (
 
 	"github.com/banzaicloud/k8s-objectmatcher/patch"
 	"github.com/google/go-cmp/cmp"
+	bktv1alpha1 "github.com/kube-object-storage/lib-bucket-provisioner/pkg/apis/objectbucket.io/v1alpha1"
 	"github.com/pkg/errors"
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
 	"github.com/rook/rook/pkg/operator/ceph/config"
@@ -35,9 +37,7 @@ import (
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/api/resource"
 	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
@@ -46,71 +46,372 @@ const (
 	DoNotReconcileLabelName = "do_not_reconcile"
 )
 
-// Retrieve the GVK (GroupVersionKind) and NamespacedName for the given object.
-// Primarily used for logging.
-func objectInfo(scheme *runtime.Scheme, obj client.Object) (string, types.NamespacedName) {
-	gvk, err := apiutil.GVKForObject(obj, scheme)
-	if err != nil {
-		logger.Debugf("Unable to get GVK for object %+v", obj)
-	}
-
-	nsName := types.NamespacedName{
-		Namespace: obj.GetNamespace(),
-		Name:      obj.GetName(),
-	}
-
-	return gvk.Kind, nsName
-}
-
 // WatchControllerPredicate is a special update filter for update events
 // do not reconcile if the status changes, this avoids a reconcile storm loop
 //
 // returning 'true' means triggering a reconciliation
 // returning 'false' means do NOT trigger a reconciliation
-func WatchControllerPredicate[T client.Object](scheme *runtime.Scheme) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			kind, nsName := objectInfo(scheme, e.Object)
-			logger.Debugf("create event for %q: %q", kind, nsName)
+func WatchControllerPredicate() predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			logger.Debugf("create event from a CR: %q", e.Object.GetName())
 			return true
 		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			kind, nsName := objectInfo(scheme, e.Object)
-			logger.Debugf("delete event for %q: %q", kind, nsName)
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			logger.Debugf("delete event from a CR: %q", e.Object.GetName())
 			return true
 		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			objOld := e.ObjectOld
-			objNew := e.ObjectNew
-			kind, nsName := objectInfo(scheme, objNew)
-
-			logger.Debugf("update event for %q: %q", kind, nsName)
-
-			// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
-			IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
-			if IsDoNotReconcile {
-				logger.Debugf("resource %q: %q had update event but %q label is set, doing nothing", kind, nsName, DoNotReconcileLabelName)
-				return false
-			}
-
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			logger.Debugf("update event from a CR: %q", e.ObjectOld.GetName())
 			// resource.Quantity has non-exportable fields, so we use its comparator method
 			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })
 
-			diff := cmp.Diff(GetSpec(objOld), GetSpec(objNew), resourceQtyComparer)
-			if diff != "" {
-				logger.Infof("resource %q: %q spec has changed. diff=%s", kind, nsName, diff)
-				return true
-			} else if objectToBeDeleted(objOld, objNew) {
-				logger.Debugf("resource %q: %q is going to be deleted", kind, nsName)
-				return true
-			} else if objOld.GetGeneration() != objNew.GetGeneration() {
-				logger.Debugf("skipping resource %q: %q, update event with unchanged spec", kind, nsName)
+			switch objOld := e.ObjectOld.(type) {
+			case *cephv1.CephObjectStore:
+				objNew := e.ObjectNew.(*cephv1.CephObjectStore)
+				logger.Debug("update event on CephObjectStore CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephObjectStoreUser:
+				objNew := e.ObjectNew.(*cephv1.CephObjectStoreUser)
+				logger.Debug("update event on CephObjectStoreUser CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephObjectRealm:
+				objNew := e.ObjectNew.(*cephv1.CephObjectRealm)
+				logger.Debug("update event on CephObjectRealm")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephObjectZoneGroup:
+				objNew := e.ObjectNew.(*cephv1.CephObjectZoneGroup)
+				logger.Debug("update event on CephObjectZoneGroup")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephObjectZone:
+				objNew := e.ObjectNew.(*cephv1.CephObjectZone)
+				logger.Debug("update event on CephObjectZone")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephBlockPool:
+				objNew := e.ObjectNew.(*cephv1.CephBlockPool)
+				logger.Debug("update event on CephBlockPool CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephFilesystem:
+				objNew := e.ObjectNew.(*cephv1.CephFilesystem)
+				logger.Debug("update event on CephFilesystem CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephNFS:
+				objNew := e.ObjectNew.(*cephv1.CephNFS)
+				logger.Debug("update event on CephNFS CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephRBDMirror:
+				objNew := e.ObjectNew.(*cephv1.CephRBDMirror)
+				logger.Debug("update event on CephRBDMirror CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephClient:
+				objNew := e.ObjectNew.(*cephv1.CephClient)
+				logger.Debug("update event on CephClient CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephFilesystemMirror:
+				objNew := e.ObjectNew.(*cephv1.CephFilesystemMirror)
+				logger.Debug("update event on CephFilesystemMirror CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", DoNotReconcileLabelName, objNew.Name)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephBucketTopic:
+				objNew := e.ObjectNew.(*cephv1.CephBucketTopic)
+				logger.Debug("update event on CephBucketTopic CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", objNew.Name, DoNotReconcileLabelName)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephBucketNotification:
+				objNew := e.ObjectNew.(*cephv1.CephBucketNotification)
+				logger.Debug("update event on CephBucketNotification CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", objNew.Name, DoNotReconcileLabelName)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *cephv1.CephFilesystemSubVolumeGroup:
+				objNew := e.ObjectNew.(*cephv1.CephFilesystemSubVolumeGroup)
+				logger.Debug("update event on CephFilesystemSubVolumeGroup CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", objNew.Name, DoNotReconcileLabelName)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CR %q is going be deleted", objNew.Name)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping resource %q update with unchanged spec", objNew.Name)
+				}
+
+			case *bktv1alpha1.ObjectBucketClaim:
+				objNew := e.ObjectNew.(*bktv1alpha1.ObjectBucketClaim)
+				logger.Debug("update event on ObjectBucketClaim CR")
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", objNew.Name, DoNotReconcileLabelName)
+					return false
+				}
+				if !reflect.DeepEqual(objOld.Labels, objNew.Labels) {
+					logger.Infof("CR labels has changed for %q", objNew.Name)
+					return true
+				} else if objOld.Spec.ObjectBucketName != objNew.Spec.ObjectBucketName {
+					logger.Infof("CR %q bucket name changed from %q to %q", objNew.Name, objOld.Spec.ObjectBucketName, objNew.Spec.ObjectBucketName)
+					return true
+				}
+				logger.Debugf("no change in CR %q", objNew.Name)
+
+			case *cephv1.CephBlockPoolRadosNamespace:
+				objNew := e.ObjectNew.(*cephv1.CephBlockPoolRadosNamespace)
+				namespacedName := fmt.Sprintf("%s/%s", objNew.Namespace, objNew.Name)
+				logger.Debugf("update event on CephBlockPoolRadosNamespace %q CR", namespacedName)
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing", namespacedName, DoNotReconcileLabelName)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
+				if diff != "" {
+					logger.Infof("CephBlockPoolRadosNamespace CR has changed for %q. diff=%s", namespacedName, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CephBlockPoolRadosNamespace CR %q is going be deleted", namespacedName)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping CephBlockPoolRadosNamespace resource %q update with unchanged spec", namespacedName)
+				}
+
+			case *cephv1.CephCOSIDriver:
+				objNew := e.ObjectNew.(*cephv1.CephCOSIDriver)
+				namespacedName := fmt.Sprintf("%s/%s", objNew.Namespace, objNew.Name)
+				logger.Debugf("update event on CephCOSIDriver %q CR", namespacedName)
+				// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
+				IsDoNotReconcile := IsDoNotReconcile(objNew.GetLabels())
+				if IsDoNotReconcile {
+					logger.Debugf("object %q matched on update but %q label is set, doing nothing",
+						namespacedName, DoNotReconcileLabelName)
+					return false
+				}
+				diff := cmp.Diff(objOld.Spec, objNew.Spec)
+				if diff != "" {
+					logger.Infof("CephCOSIDriver CR has changed for %q. diff=%s", namespacedName, diff)
+					return true
+				} else if objectToBeDeleted(objOld, objNew) {
+					logger.Debugf("CephCOSIDriver CR %q is going be deleted", namespacedName)
+					return true
+				} else if objOld.GetGeneration() != objNew.GetGeneration() {
+					logger.Debugf("skipping CephCOSIDriver resource %q update with unchanged spec", namespacedName)
+				}
+
 			}
 			return false
 		},
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
-			kind, nsName := objectInfo(scheme, e.Object)
-			logger.Debugf("generic event for %q: %q", kind, nsName)
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
@@ -167,19 +468,19 @@ func objectChanged(oldObj, newObj runtime.Object, objectName string) (bool, erro
 //
 // We return 'false' on a create event so we don't overstep with the main watcher on cephv1.CephBlockPool{}
 // This avoids a double reconcile when the secret gets deleted.
-func WatchPredicateForNonCRDObject[T client.Object](owner runtime.Object, scheme *runtime.Scheme) predicate.TypedFuncs[T] {
+func WatchPredicateForNonCRDObject(owner runtime.Object, scheme *runtime.Scheme) predicate.Funcs {
 	// Initialize the Owner Matcher, which is the main controller object: e.g. cephv1.CephBlockPool{}
 	ownerMatcher, err := NewOwnerReferenceMatcher(owner, scheme)
 	if err != nil {
 		logger.Errorf("failed to initialize owner matcher. %v", err)
 	}
 
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
 			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			match, object, err := ownerMatcher.Match(e.Object)
 			if err != nil {
 				logger.Errorf("failed to check if object kind %q matched. %v", e.Object.GetObjectKind(), err)
@@ -205,7 +506,7 @@ func WatchPredicateForNonCRDObject[T client.Object](owner runtime.Object, scheme
 			return false
 		},
 
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
+		UpdateFunc: func(e event.UpdateEvent) bool {
 			match, object, err := ownerMatcher.Match(e.ObjectNew)
 			if err != nil {
 				logger.Errorf("failed to check if object matched. %v", err)
@@ -229,22 +530,24 @@ func WatchPredicateForNonCRDObject[T client.Object](owner runtime.Object, scheme
 					return true
 				}
 
-				switch newObjCopy := any(e.ObjectNew).(type) {
-				case *corev1.ConfigMap:
-					// If the resource is a ConfigMap we don't reconcile
+				// If the resource is a ConfigMap we don't reconcile
+				_, ok := e.ObjectNew.(*corev1.ConfigMap)
+				if ok {
 					logger.Debugf("do not reconcile on configmap %q", objectName)
 					return false
+				}
 
-				case *corev1.Secret:
-					// SECRETS BLACKLIST
-					// If the resource is a Secret, we might want to ignore it
-					// We want to reconcile Secrets in case their content gets altered
-					if isSecretToIgnoreOnUpdate(newObjCopy) {
-						return false
-					}
+				// SECRETS BLACKLIST
+				// If the resource is a Secret, we might want to ignore it
+				// We want to reconcile Secrets in case their content gets altered
+				isSecretToIgnoreOnUpdate := isSecretToIgnoreOnUpdate(e.ObjectNew)
+				if isSecretToIgnoreOnUpdate {
+					return false
+				}
 
-				case *appsv1.Deployment:
-					// If the resource is a deployment we don't reconcile
+				// If the resource is a deployment we don't reconcile
+				_, ok = e.ObjectNew.(*appsv1.Deployment)
+				if ok {
 					logger.Debug("do not reconcile deployments updates")
 					return false
 				}
@@ -261,7 +564,7 @@ func WatchPredicateForNonCRDObject[T client.Object](owner runtime.Object, scheme
 			return false
 		},
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
@@ -406,10 +709,16 @@ func isCMToIgnoreOnDelete(obj runtime.Object) bool {
 	return false
 }
 
-func isSecretToIgnoreOnUpdate(secret *corev1.Secret) bool {
-	switch secret.GetName() {
+func isSecretToIgnoreOnUpdate(obj runtime.Object) bool {
+	s, ok := obj.(*corev1.Secret)
+	if !ok {
+		return false
+	}
+
+	objectName := s.GetName()
+	switch objectName {
 	case config.StoreName:
-		logger.Debugf("do not reconcile on %q secret changes", secret.GetName())
+		logger.Debugf("do not reconcile on %q secret changes", objectName)
 		return true
 	}
 
@@ -434,7 +743,13 @@ func ReloadManager() {
 
 // DuplicateCephClusters determine whether a similar object exists in the same namespace
 // mainly used for the CephCluster which we only support a single instance per namespace
-func DuplicateCephClusters(ctx context.Context, c client.Client, object *cephv1.CephCluster, log bool) bool {
+func DuplicateCephClusters(ctx context.Context, c client.Client, object client.Object, log bool) bool {
+	objectType, ok := object.(*cephv1.CephCluster)
+	if !ok {
+		logger.Errorf("expected type CephCluster but found %T", objectType)
+		return false
+	}
+
 	cephClusterList := &cephv1.CephClusterList{}
 	listOpts := []client.ListOption{
 		client.InNamespace(object.GetNamespace()),
@@ -462,20 +777,3 @@ func DuplicateCephClusters(ctx context.Context, c client.Client, object *cephv1.
 
 	return false
 }
-
-func GetSpec(obj client.Object) interface{} {
-	val := reflect.ValueOf(obj)
-
-	// If obj is a pointer, get the element
-	if val.Kind() == reflect.Ptr {
-		val = val.Elem()
-	}
-
-	spec := val.FieldByName("Spec")
-	if !spec.IsValid() {
-		logger.Warningf("No Spec field found for object %+v. This should not happen.", obj)
-		return nil
-	}
-
-	return spec.Interface()
-}
diff --git a/pkg/operator/ceph/csi/controller.go b/pkg/operator/ceph/csi/controller.go
index ffebd3464..809b2da4d 100644
--- a/pkg/operator/ceph/csi/controller.go
+++ b/pkg/operator/ceph/csi/controller.go
@@ -38,7 +38,7 @@ import (
 	opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
 	"github.com/rook/rook/pkg/operator/ceph/csi/peermap"
 	"github.com/rook/rook/pkg/operator/k8sutil"
-	corev1 "k8s.io/api/core/v1"
+	v1 "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"
@@ -91,27 +91,23 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler, opCon
 	}
 
 	// Watch for ConfigMap (operator config)
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
-			&handler.TypedEnqueueRequestForObject[*corev1.ConfigMap]{},
-			cmPredicate(),
-		),
+	configmapKind := source.Kind[client.Object](
+		mgr.GetCache(),
+		&v1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: v1.SchemeGroupVersion.String()}},
+		&handler.EnqueueRequestForObject{}, predicateController(ctx, mgr.GetClient(), opConfig.OperatorNamespace),
 	)
+	err = c.Watch(configmapKind)
 	if err != nil {
 		return err
 	}
 
 	// Watch for CephCluster
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephCluster{TypeMeta: metav1.TypeMeta{Kind: "CephCluster", APIVersion: corev1.SchemeGroupVersion.String()}},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephCluster]{},
-			cephClusterPredicate(ctx, mgr.GetClient(), opConfig.OperatorNamespace),
-		),
+	clusterKind := source.Kind[client.Object](
+		mgr.GetCache(),
+		&cephv1.CephCluster{TypeMeta: metav1.TypeMeta{Kind: "CephCluster", APIVersion: v1.SchemeGroupVersion.String()}},
+		&handler.EnqueueRequestForObject{}, predicateController(ctx, mgr.GetClient(), opConfig.OperatorNamespace),
 	)
+	err = c.Watch(clusterKind)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/csi/csi.go b/pkg/operator/ceph/csi/csi.go
index 231c5c041..445b4ebbb 100644
--- a/pkg/operator/ceph/csi/csi.go
+++ b/pkg/operator/ceph/csi/csi.go
@@ -135,6 +135,8 @@ func (r *ReconcileCSI) setParams() error {
 		CSIParam.EnableOMAPGenerator = true
 	}
 
+	CSIParam.EnableCSIDriverSeLinuxMount = true
+
 	CSIParam.EnableRBDSnapshotter = true
 	if strings.EqualFold(k8sutil.GetOperatorSetting("CSI_ENABLE_RBD_SNAPSHOTTER", "true"), "false") {
 		CSIParam.EnableRBDSnapshotter = false
diff --git a/pkg/operator/ceph/csi/csidriver.go b/pkg/operator/ceph/csi/csidriver.go
index 7948dddfd..ca12a0cbd 100644
--- a/pkg/operator/ceph/csi/csidriver.go
+++ b/pkg/operator/ceph/csi/csidriver.go
@@ -37,7 +37,7 @@ func (d v1CsiDriver) createCSIDriverInfo(
 	ctx context.Context,
 	clientset kubernetes.Interface,
 	name, fsGroupPolicy string,
-	attachRequired bool,
+	attachRequired, seLinuxMountRequired bool,
 ) error {
 	mountInfo := false
 	// Create CSIDriver object
@@ -50,10 +50,10 @@ func (d v1CsiDriver) createCSIDriverInfo(
 			PodInfoOnMount: &mountInfo,
 		},
 	}
-
-	selinuxMount := true
-	csiDriver.Spec.SELinuxMount = &selinuxMount
-
+	if seLinuxMountRequired {
+		selinuxMount := true
+		csiDriver.Spec.SELinuxMount = &selinuxMount
+	}
 	if fsGroupPolicy != "" {
 		policy := v1k8scsi.FSGroupPolicy(fsGroupPolicy)
 		csiDriver.Spec.FSGroupPolicy = &policy
diff --git a/pkg/operator/ceph/csi/predicate.go b/pkg/operator/ceph/csi/predicate.go
index ebe6fa9e2..d04a8ac5f 100644
--- a/pkg/operator/ceph/csi/predicate.go
+++ b/pkg/operator/ceph/csi/predicate.go
@@ -22,7 +22,7 @@ import (
 	"github.com/google/go-cmp/cmp"
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
 	opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
-	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/resource"
 	"k8s.io/apimachinery/pkg/types"
@@ -31,90 +31,81 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
 
-// reconcile on operator settings cm change
-func cmPredicate[T *corev1.ConfigMap]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			cm := (*corev1.ConfigMap)(e.Object)
-
-			// We don't want to use cm.Generation here, it case the operator was stopped and the
-			// ConfigMap was created
-			return cm.Name == opcontroller.OperatorSettingConfigMapName
-		},
+// predicateController is the predicate function to trigger reconcile on operator configuration cm change
+func predicateController(ctx context.Context, c client.Client, opNamespace string) predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			// if the operator configuration file is created we want to reconcile
+			if cm, ok := e.Object.(*v1.ConfigMap); ok {
+				// We don't want to use cm.Generation here, it case the operator was stopped and the
+				// ConfigMap was created
+				return cm.Name == opcontroller.OperatorSettingConfigMapName
+			}
 
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			cmOld := (*corev1.ConfigMap)(e.ObjectOld)
-			cmNew := (*corev1.ConfigMap)(e.ObjectNew)
+			// If a Ceph Cluster is created we want to reconcile the csi driver
+			if cephCluster, ok := e.Object.(*cephv1.CephCluster); ok {
+				// If there are more than one ceph cluster in the same namespace do not reconcile
+				if opcontroller.DuplicateCephClusters(ctx, c, e.Object, false) {
+					return false
+				}
 
-			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })
-			if cmOld.GetName() == opcontroller.OperatorSettingConfigMapName && cmNew.GetName() == opcontroller.OperatorSettingConfigMapName {
-				diff := cmp.Diff(cmOld.Data, cmNew.Data, resourceQtyComparer)
-				logger.Debugf("operator configmap diff:\n %s", diff)
-				if diff != "" {
+				// We still have users that don't use the ConfigMap to configure the
+				// operator/csi and still rely on environment variables from the operator pod's
+				// spec. So we still want to reconcile the controller if the ConfigMap cannot be found.
+				err := c.Get(ctx, types.NamespacedName{Name: opcontroller.OperatorSettingConfigMapName, Namespace: opNamespace}, &v1.ConfigMap{})
+				if err != nil && kerrors.IsNotFound(err) {
+					logger.Debugf("could not find operator configuration ConfigMap, will reconcile the csi controller")
 					return true
 				}
+
+				// This allows us to avoid a double reconcile of the CSI controller if this is not
+				// the first generation of the CephCluster. So only return true if this is the very
+				// first instance of the CephCluster
+				// Corner case is when the cluster is created but the operator is down AND the cm
+				// does not exist... However, these days the operator config map is part of the
+				// operator.yaml so it's probably acceptable?
+				// This does not account for the case where the cephcluster is already deployed and
+				// the upgrade the operator or restart it. However, the CM check above should catch that
+				return cephCluster.Generation == 1
 			}
 
 			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			cm := (*corev1.ConfigMap)(e.Object)
-
-			// if the operator configuration file is deleted we want to reconcile to apply the
-			// configuration based on environment variables present in the operator's pod spec
-			return cm.Name == opcontroller.OperatorSettingConfigMapName
-		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })
+			if old, ok := e.ObjectOld.(*v1.ConfigMap); ok {
+				if new, ok := e.ObjectNew.(*v1.ConfigMap); ok {
+					if old.Name == opcontroller.OperatorSettingConfigMapName && new.Name == opcontroller.OperatorSettingConfigMapName {
+						diff := cmp.Diff(old.Data, new.Data, resourceQtyComparer)
+						logger.Debugf("operator configmap diff:\n %s", diff)
+						if diff != "" {
+							return true
+						}
+					}
+				}
+			}
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
 			return false
 		},
-	}
-}
-
-// predicateController is the predicate function to trigger reconcile on operator configuration cm change
-func cephClusterPredicate[T *cephv1.CephCluster](ctx context.Context, c client.Client, opNamespace string) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			cephCluster := (*cephv1.CephCluster)(e.Object)
 
-			// If a Ceph Cluster is created we want to reconcile the csi driver
-			// If there are more than one ceph cluster in the same namespace do not reconcile
-			if opcontroller.DuplicateCephClusters(ctx, c, cephCluster, false) {
-				return false
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			// if the operator configuration file is deleted we want to reconcile to apply the
+			// configuration based on environment variables present in the operator's pod spec
+			if cm, ok := e.Object.(*v1.ConfigMap); ok {
+				return cm.Name == opcontroller.OperatorSettingConfigMapName
 			}
 
-			// We still have users that don't use the ConfigMap to configure the
-			// operator/csi and still rely on environment variables from the operator pod's
-			// spec. So we still want to reconcile the controller if the ConfigMap cannot be found.
-			err := c.Get(ctx, types.NamespacedName{Name: opcontroller.OperatorSettingConfigMapName, Namespace: opNamespace}, &corev1.ConfigMap{})
-			if err != nil && kerrors.IsNotFound(err) {
-				logger.Debugf("could not find operator configuration ConfigMap, will reconcile the csi controller")
+			// if cephCluster is deleted, trigger reconcile to cleanup the csi driver resources
+			// if zero cephClusters exist.
+			if _, ok := e.Object.(*cephv1.CephCluster); ok {
 				return true
 			}
 
-			// This allows us to avoid a double reconcile of the CSI controller if this is not
-			// the first generation of the CephCluster. So only return true if this is the very
-			// first instance of the CephCluster
-			// Corner case is when the cluster is created but the operator is down AND the cm
-			// does not exist... However, these days the operator config map is part of the
-			// operator.yaml so it's probably acceptable?
-			// This does not account for the case where the cephcluster is already deployed and
-			// the upgrade the operator or restart it. However, the CM check above should catch that
-			return cephCluster.Generation == 1
-		},
-
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
 			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			// if cephCluster is deleted, trigger reconcile to cleanup the csi driver resources
-			// if zero cephClusters exist.
-			return true
-		},
-
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
diff --git a/pkg/operator/ceph/csi/spec.go b/pkg/operator/ceph/csi/spec.go
index c889d858d..59e4b31ae 100644
--- a/pkg/operator/ceph/csi/spec.go
+++ b/pkg/operator/ceph/csi/spec.go
@@ -70,6 +70,7 @@ type Param struct {
 	EnableNFSSnapshotter                     bool
 	EnableCSIAddonsSideCar                   bool
 	MountCustomCephConf                      bool
+	EnableCSIDriverSeLinuxMount              bool
 	EnableCSIEncryption                      bool
 	EnableCSITopology                        bool
 	EnableLiveness                           bool
@@ -634,7 +635,7 @@ func (r *ReconcileCSI) startDrivers(ownerInfo *k8sutil.OwnerInfo) error {
 		err = csiDriverobj.createCSIDriverInfo(
 			r.opManagerContext, r.context.Clientset,
 			RBDDriverName, k8sutil.GetOperatorSetting("CSI_RBD_FSGROUPPOLICY", string(k8scsi.FileFSGroupPolicy)),
-			tp.Param.RBDAttachRequired)
+			tp.Param.RBDAttachRequired, CSIParam.EnableCSIDriverSeLinuxMount)
 		if err != nil {
 			return errors.Wrapf(err, "failed to create CSI driver object for %q", RBDDriverName)
 		}
@@ -643,7 +644,7 @@ func (r *ReconcileCSI) startDrivers(ownerInfo *k8sutil.OwnerInfo) error {
 		err = csiDriverobj.createCSIDriverInfo(
 			r.opManagerContext, r.context.Clientset,
 			CephFSDriverName, k8sutil.GetOperatorSetting("CSI_CEPHFS_FSGROUPPOLICY", string(k8scsi.FileFSGroupPolicy)),
-			tp.Param.CephFSAttachRequired)
+			tp.Param.CephFSAttachRequired, CSIParam.EnableCSIDriverSeLinuxMount)
 		if err != nil {
 			return errors.Wrapf(err, "failed to create CSI driver object for %q", CephFSDriverName)
 		}
@@ -651,7 +652,7 @@ func (r *ReconcileCSI) startDrivers(ownerInfo *k8sutil.OwnerInfo) error {
 	if EnableNFS {
 		err = csiDriverobj.createCSIDriverInfo(r.opManagerContext, r.context.Clientset,
 			NFSDriverName, k8sutil.GetOperatorSetting("CSI_NFS_FSGROUPPOLICY", string(k8scsi.FileFSGroupPolicy)),
-			tp.Param.NFSAttachRequired)
+			tp.Param.NFSAttachRequired, CSIParam.EnableCSIDriverSeLinuxMount)
 		if err != nil {
 			return errors.Wrapf(err, "failed to create CSI driver object for %q", NFSDriverName)
 		}
diff --git a/pkg/operator/ceph/disruption/clusterdisruption/add.go b/pkg/operator/ceph/disruption/clusterdisruption/add.go
index 0c2cb270f..0faf21e8e 100644
--- a/pkg/operator/ceph/disruption/clusterdisruption/add.go
+++ b/pkg/operator/ceph/disruption/clusterdisruption/add.go
@@ -36,72 +36,6 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 )
 
-// Watch for CephBlockPools and enqueue the CephCluster in the namespace
-// Watch for CephFileSystems and enqueue the CephCluster in the namespace
-// Watch for CephObjectStores and enqueue the CephCluster in the namespace
-var objectsToWatch = []client.Object{
-	&cephv1.CephBlockPool{},
-	&cephv1.CephFilesystem{},
-	&cephv1.CephObjectStore{},
-}
-
-func cephClusterPredicate[T *cephv1.CephCluster]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			logger.Debug("create event from ceph cluster CR")
-			return true
-		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			objOld := (*cephv1.CephCluster)(e.ObjectOld)
-			objNew := (*cephv1.CephCluster)(e.ObjectNew)
-
-			return !reflect.DeepEqual(objOld.Spec, objNew.Spec)
-		},
-	}
-}
-
-func pdbPredicate[T *policyv1.PodDisruptionBudget]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			// Do not reconcile when PDB is created
-			return false
-		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			pdb := (*policyv1.PodDisruptionBudget)(e.ObjectNew)
-
-			// reconcile for the main PDB update event when first OSD goes down, that is, when `DisruptionsAllowed` gets updated to 0.
-			return pdb.Name == osdPDBAppName && pdb.Spec.MaxUnavailable.IntVal == 1 && pdb.Status.DisruptionsAllowed == 0
-		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			// Do not reconcile when PDB is deleted
-			return false
-		},
-	}
-}
-
-func watchNamespacedObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	// enqueues with an empty name that is populated by the reconciler.
-	// There is a one-per-namespace limit on CephClusters
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestsFromMapFunc(
-				func(context ctx.Context, obj T) []reconcile.Request {
-					// The name will be populated in the reconcile
-					namespace := obj.GetNamespace()
-					if len(namespace) == 0 {
-						logger.Errorf("enqueueByNamespace received an obj without a namespace. %+v", obj)
-						return []reconcile.Request{}
-					}
-					req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
-					return []reconcile.Request{req}
-				},
-			),
-		),
-	)
-}
-
 // Add adds a new Controller to the Manager based on clusterdisruption.ReconcileClusterDisruption and registers the relevant watches and handlers.
 // Read more about how Managers, Controllers, and their Watches, Handlers, Predicates, etc work here:
 // https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg
@@ -122,43 +56,100 @@ func Add(mgr manager.Manager, context *controllerconfig.Context) error {
 		return err
 	}
 
+	cephClusterPredicate := predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			logger.Debug("create event from ceph cluster CR")
+			return true
+		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			oldCluster, ok := e.ObjectOld.DeepCopyObject().(*cephv1.CephCluster)
+			if !ok {
+				return false
+			}
+			newCluster, ok := e.ObjectNew.DeepCopyObject().(*cephv1.CephCluster)
+			if !ok {
+				return false
+			}
+			return !reflect.DeepEqual(oldCluster.Spec, newCluster.Spec)
+		},
+	}
+
 	// Watch for CephClusters
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephCluster{},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephCluster]{},
-			cephClusterPredicate(),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephCluster{}, &handler.EnqueueRequestForObject{}, cephClusterPredicate))
 	if err != nil {
 		return err
 	}
 
+	pdbPredicate := predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			// Do not reconcile when PDB is created
+			return false
+		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			pdb, ok := e.ObjectNew.DeepCopyObject().(*policyv1.PodDisruptionBudget)
+			if !ok {
+				return false
+			}
+			// reconcile for the main PDB update event when first OSD goes down, that is,  when `DisruptionsAllowed` gets updated to 0.
+			return pdb.Name == osdPDBAppName && pdb.Spec.MaxUnavailable.IntVal == 1 && pdb.Status.DisruptionsAllowed == 0
+		},
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			// Do not reconcile when PDB is deleted
+			return false
+		},
+	}
+
 	// Watch for main PodDisruptionBudget and enqueue the CephCluster in the namespace
 	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&policyv1.PodDisruptionBudget{},
-			handler.TypedEnqueueRequestsFromMapFunc(
-				func(context ctx.Context, pdb *policyv1.PodDisruptionBudget) []reconcile.Request {
-					namespace := pdb.GetNamespace()
-					req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
-					return []reconcile.Request{req}
-				},
+		source.Kind[client.Object](mgr.GetCache(), &policyv1.PodDisruptionBudget{},
+			handler.EnqueueRequestsFromMapFunc(handler.MapFunc(func(context ctx.Context, obj client.Object) []reconcile.Request {
+				pdb, ok := obj.(*policyv1.PodDisruptionBudget)
+				if !ok {
+					// Not a pdb, returning empty
+					logger.Error("PDB handler received non-PDB")
+					return []reconcile.Request{}
+				}
+				namespace := pdb.GetNamespace()
+				req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
+				return []reconcile.Request{req}
+			}),
 			),
-			pdbPredicate(),
-		),
-	)
+			pdbPredicate,
+		))
 	if err != nil {
 		return err
 	}
 
-	for _, t := range objectsToWatch {
-		err = watchNamespacedObject(c, mgr, t)
-		if err != nil {
-			return err
+	// enqueues with an empty name that is populated by the reconciler.
+	// There is a one-per-namespace limit on CephClusters
+	enqueueByNamespace := handler.EnqueueRequestsFromMapFunc(handler.MapFunc(func(context ctx.Context, obj client.Object) []reconcile.Request {
+		// The name will be populated in the reconcile
+		namespace := obj.GetNamespace()
+		if len(namespace) == 0 {
+			logger.Errorf("enqueueByNamespace received an obj without a namespace. %+v", obj)
+			return []reconcile.Request{}
 		}
+		req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
+		return []reconcile.Request{req}
+	}),
+	)
+
+	// Watch for CephBlockPools and enqueue the CephCluster in the namespace
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephBlockPool{}, enqueueByNamespace))
+	if err != nil {
+		return err
+	}
+
+	// Watch for CephFileSystems and enqueue the CephCluster in the namespace
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephFilesystem{}, enqueueByNamespace))
+	if err != nil {
+		return err
+	}
+
+	// Watch for CephObjectStores and enqueue the CephCluster in the namespace
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectStore{}, enqueueByNamespace))
+	if err != nil {
+		return err
 	}
 
 	return nil
diff --git a/pkg/operator/ceph/disruption/clusterdisruption/osd.go b/pkg/operator/ceph/disruption/clusterdisruption/osd.go
index 5a4979e47..bb3aa2d32 100644
--- a/pkg/operator/ceph/disruption/clusterdisruption/osd.go
+++ b/pkg/operator/ceph/disruption/clusterdisruption/osd.go
@@ -268,6 +268,7 @@ func (r *ReconcileClusterDisruption) reconcilePDBsForOSDs(
 				maxUnavailableOSDCount = 1
 			} else {
 				maxUnavailableOSDCount = len(downOSDs) + 1
+				resetPDBConfig(pdbStateMap)
 			}
 		} else {
 			maxUnavailableOSDCount = len(downOSDs) + 1
diff --git a/pkg/operator/ceph/file/controller.go b/pkg/operator/ceph/file/controller.go
index 51ffecee5..fa2a80e14 100644
--- a/pkg/operator/ceph/file/controller.go
+++ b/pkg/operator/ceph/file/controller.go
@@ -108,21 +108,6 @@ func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerCont
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephFilesystem{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephFilesystem{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -132,21 +117,20 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephFilesystem CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephFilesystem{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephFilesystem]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephFilesystem](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephFilesystem{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		ownerRequest := handler.EnqueueRequestForOwner(
+			mgr.GetScheme(),
+			mgr.GetRESTMapper(),
+			&cephv1.CephFilesystem{},
+		)
+		err = c.Watch(source.Kind[client.Object](mgr.GetCache(), t, ownerRequest,
+			opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephFilesystem{TypeMeta: controllerTypeMeta}, mgr.GetScheme())))
 		if err != nil {
 			return err
 		}
@@ -154,20 +138,15 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 
 	// Build Handler function to return the list of ceph filesystems
 	// This is used by the watchers below
-	handlerFunc, err := opcontroller.ObjectToCRMapper[*cephv1.CephFilesystemList, *corev1.ConfigMap](opManagerContext, mgr.GetClient(), &cephv1.CephFilesystemList{}, mgr.GetScheme())
+	handlerFunc, err := opcontroller.ObjectToCRMapper(opManagerContext, mgr.GetClient(), &cephv1.CephFilesystemList{}, mgr.GetScheme())
 	if err != nil {
 		return err
 	}
 
 	// Watch for ConfigMap "rook-ceph-mon-endpoints" update and reconcile, which will reconcile update the bootstrap peer token
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestsFromMapFunc(handlerFunc),
-			mon.PredicateMonEndpointChanges(),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](
+		mgr.GetCache(), &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
+		handler.EnqueueRequestsFromMapFunc(handlerFunc), mon.PredicateMonEndpointChanges()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/file/mirror/controller.go b/pkg/operator/ceph/file/mirror/controller.go
index 2afc008ef..2857d39ce 100644
--- a/pkg/operator/ceph/file/mirror/controller.go
+++ b/pkg/operator/ceph/file/mirror/controller.go
@@ -98,21 +98,6 @@ func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerCont
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephFilesystemMirror{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephFilesystemMirror{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -122,21 +107,19 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephFilesystemMirror CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephFilesystemMirror{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephFilesystemMirror]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephFilesystemMirror](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephFilesystemMirror{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		ownerRequest := handler.EnqueueRequestForOwner(
+			mgr.GetScheme(),
+			mgr.GetRESTMapper(),
+			&cephv1.CephFilesystemMirror{},
+		)
+		err = c.Watch(source.Kind[client.Object](mgr.GetCache(), t, ownerRequest, opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephFilesystemMirror{TypeMeta: controllerTypeMeta}, mgr.GetScheme())))
 		if err != nil {
 			return err
 		}
diff --git a/pkg/operator/ceph/file/subvolumegroup/controller.go b/pkg/operator/ceph/file/subvolumegroup/controller.go
index ae387dc31..4ccbd2635 100644
--- a/pkg/operator/ceph/file/subvolumegroup/controller.go
+++ b/pkg/operator/ceph/file/subvolumegroup/controller.go
@@ -102,14 +102,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephFilesystemSubVolumeGroup CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephFilesystemSubVolumeGroup{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephFilesystemSubVolumeGroup]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephFilesystemSubVolumeGroup](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephFilesystemSubVolumeGroup{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/nfs/config.go b/pkg/operator/ceph/nfs/config.go
index a252b63b7..7d75200fa 100644
--- a/pkg/operator/ceph/nfs/config.go
+++ b/pkg/operator/ceph/nfs/config.go
@@ -101,7 +101,6 @@ NFS_CORE_PARAM {
 	Enable_NLM = false;
 	Enable_RQUOTA = false;
 	Protocols = 4;
-	allow_set_io_flusher_fail = true;
 }
 
 MDCACHE {
diff --git a/pkg/operator/ceph/nfs/controller.go b/pkg/operator/ceph/nfs/controller.go
index 27c4be1e6..487724150 100644
--- a/pkg/operator/ceph/nfs/controller.go
+++ b/pkg/operator/ceph/nfs/controller.go
@@ -100,21 +100,6 @@ func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerCont
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephNFS{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephNFS{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -124,21 +109,20 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the cephNFS CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephNFS{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephNFS]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephNFS](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephNFS{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		ownerRequest := handler.EnqueueRequestForOwner(
+			mgr.GetScheme(),
+			mgr.GetRESTMapper(),
+			&cephv1.CephNFS{},
+		)
+		err = c.Watch(source.Kind[client.Object](mgr.GetCache(), t, ownerRequest,
+			opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephNFS{TypeMeta: controllerTypeMeta}, mgr.GetScheme())))
 		if err != nil {
 			return err
 		}
diff --git a/pkg/operator/ceph/object/bucket/controller.go b/pkg/operator/ceph/object/bucket/controller.go
index 6cdd3365e..b9a4007f7 100644
--- a/pkg/operator/ceph/object/bucket/controller.go
+++ b/pkg/operator/ceph/object/bucket/controller.go
@@ -80,27 +80,22 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error
 	logger.Infof("%s successfully started", controllerName)
 
 	// Watch for ConfigMap (operator config)
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&v1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: v1.SchemeGroupVersion.String()}},
-			&handler.TypedEnqueueRequestForObject[*v1.ConfigMap]{},
-			cmPredicate(),
-		),
+	cmKind := source.Kind[client.Object](
+		mgr.GetCache(),
+		&v1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: v1.SchemeGroupVersion.String()}},
+		&handler.EnqueueRequestForObject{}, predicateController(ctx, mgr.GetClient()),
 	)
+	err = c.Watch(cmKind)
 	if err != nil {
 		return err
 	}
 
 	// Watch for CephCluster
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephCluster{TypeMeta: metav1.TypeMeta{Kind: "CephCluster", APIVersion: v1.SchemeGroupVersion.String()}},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephCluster]{},
-			cephClusterPredicate(ctx, mgr.GetClient()),
-		),
+	clusterKind := source.Kind[client.Object](mgr.GetCache(),
+		&cephv1.CephCluster{TypeMeta: metav1.TypeMeta{Kind: "CephCluster", APIVersion: v1.SchemeGroupVersion.String()}},
+		&handler.EnqueueRequestForObject{}, predicateController(ctx, mgr.GetClient()),
 	)
+	err = c.Watch(clusterKind)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/bucket/predicate.go b/pkg/operator/ceph/object/bucket/predicate.go
index 686a2816a..4e481055e 100644
--- a/pkg/operator/ceph/object/bucket/predicate.go
+++ b/pkg/operator/ceph/object/bucket/predicate.go
@@ -21,7 +21,7 @@ import (
 
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
 	"github.com/rook/rook/pkg/operator/ceph/controller"
-	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -29,60 +29,49 @@ import (
 
 const rookOBCWatchOperatorNamespace = "ROOK_OBC_WATCH_OPERATOR_NAMESPACE"
 
-// reconcile on ConfigMap change
-func cmPredicate[T *corev1.ConfigMap]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			cm := (*corev1.ConfigMap)(e.Object)
-
-			return cm.Name == controller.OperatorSettingConfigMapName && cm.Generation == 1
-		},
-
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			cmOld := (*corev1.ConfigMap)(e.ObjectOld)
-			cmNew := (*corev1.ConfigMap)(e.ObjectNew)
-
-			if cmOld.GetName() == controller.OperatorSettingConfigMapName && cmNew.GetName() == controller.OperatorSettingConfigMapName {
-				if cmOld.Data[rookOBCWatchOperatorNamespace] != cmNew.Data[rookOBCWatchOperatorNamespace] {
-					logger.Infof("%s changed. reconciling bucket controller", rookOBCWatchOperatorNamespace)
-
-					// We reload the manager so that the controller restarts and goes
-					// through the CreateFunc again. Then the CephCluster watcher will be triggered
-					controller.ReloadManager()
-				}
+// predicateController is the predicate function to trigger reconcile on operator configuration cm change
+func predicateController(ctx context.Context, c client.Client) predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			// if the operator configuration file is created we want to reconcile
+			if cm, ok := e.Object.(*v1.ConfigMap); ok {
+				// It's probably fine to use the Generation value here. The case where the operator was stopped and the
+				// ConfigMap was created is low since the cm is always present these days
+				return cm.Name == controller.OperatorSettingConfigMapName && cm.Generation == 1
 			}
 
-			return false
-		},
-
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			return false
-		},
+			// If a Ceph Cluster is created we want to reconcile the bucket provisioner
+			if _, ok := e.Object.(*cephv1.CephCluster); ok {
+				// If there are more than one ceph cluster in the same namespace do not reconcile
+				return !controller.DuplicateCephClusters(ctx, c, e.Object, false)
+			}
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
 			return false
 		},
-	}
-}
 
-// trigger reconcile on CephCluster change
-func cephClusterPredicate[T *cephv1.CephCluster](ctx context.Context, c client.Client) predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			// If a Ceph Cluster is created we want to reconcile the bucket provisioner
-			// If there are more than one ceph cluster in the same namespace do not reconcile
-			return !controller.DuplicateCephClusters(ctx, c, e.Object, false)
-		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			if old, ok := e.ObjectOld.(*v1.ConfigMap); ok {
+				if new, ok := e.ObjectNew.(*v1.ConfigMap); ok {
+					if old.Name == controller.OperatorSettingConfigMapName && new.Name == controller.OperatorSettingConfigMapName {
+						if old.Data[rookOBCWatchOperatorNamespace] != new.Data[rookOBCWatchOperatorNamespace] {
+							logger.Infof("%s changed. reconciling bucket controller", rookOBCWatchOperatorNamespace)
+
+							// We reload the manager so that the controller restarts and goes
+							// through the CreateFunc again. Then the CephCluster watcher will be triggered
+							controller.ReloadManager()
+						}
+					}
+				}
+			}
 
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
 			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
+		DeleteFunc: func(e event.DeleteEvent) bool {
 			return false
 		},
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
diff --git a/pkg/operator/ceph/object/controller.go b/pkg/operator/ceph/object/controller.go
index e1b010dc4..59a11f315 100644
--- a/pkg/operator/ceph/object/controller.go
+++ b/pkg/operator/ceph/object/controller.go
@@ -116,21 +116,6 @@ func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerCont
 	}
 }
 
-func watchOwnedCoreObject[T client.Object](c controller.Controller, mgr manager.Manager, obj T) error {
-	return c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			obj,
-			handler.TypedEnqueueRequestForOwner[T](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephObjectStore{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[T](&cephv1.CephObjectStore{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
-	)
-}
-
 func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -140,35 +125,30 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the cephObjectStore CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectStore{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectStore]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectStore](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectStore{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch all other resources
 	for _, t := range objectsToWatch {
-		err = watchOwnedCoreObject(c, mgr, t)
+		ownerRequest := handler.EnqueueRequestForOwner(
+			mgr.GetScheme(),
+			mgr.GetRESTMapper(),
+			&cephv1.CephObjectStore{},
+		)
+		err = c.Watch(source.Kind[client.Object](mgr.GetCache(), t, ownerRequest,
+			opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephObjectStore{TypeMeta: controllerTypeMeta}, mgr.GetScheme())))
 		if err != nil {
 			return err
 		}
 	}
 
 	// Watch Secrets secrets annotated for the object store
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestsFromMapFunc(mapSecretToCR(mgr.GetClient())),
-			secretPredicate(),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(),
+		&corev1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}},
+		handler.EnqueueRequestsFromMapFunc(mapSecretToCR(mgr.GetClient())),
+		secretPredicate()))
 	if err != nil {
 		return err
 	}
@@ -177,12 +157,15 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 }
 
 // Watch all secrets not owned by Rook
-func secretPredicate[T *corev1.Secret]() predicate.TypedPredicate[T] {
+func secretPredicate() predicate.Predicate {
 	rookGV := cephv1.SchemeGroupVersion.String()
-	return predicate.TypedFuncs[T]{
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			secret := (*corev1.Secret)(e.ObjectNew)
-
+	return predicate.Funcs{
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			secret, ok := e.ObjectNew.(*corev1.Secret)
+			if !ok {
+				// not a secret
+				return false
+			}
 			// check if secret already owned by Rook:
 			for _, owner := range secret.OwnerReferences {
 				if owner.APIVersion == rookGV {
@@ -192,9 +175,12 @@ func secretPredicate[T *corev1.Secret]() predicate.TypedPredicate[T] {
 			}
 			return true
 		},
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			secret := (*corev1.Secret)(e.Object)
-
+		CreateFunc: func(e event.CreateEvent) bool {
+			secret, ok := e.Object.(*corev1.Secret)
+			if !ok {
+				// not a secret
+				return false
+			}
 			// check if secret already owned by Rook:
 			for _, owner := range secret.OwnerReferences {
 				if owner.APIVersion == rookGV {
@@ -204,8 +190,12 @@ func secretPredicate[T *corev1.Secret]() predicate.TypedPredicate[T] {
 			}
 			return true
 		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			secret := (*corev1.Secret)(e.Object)
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			secret, ok := e.Object.(*corev1.Secret)
+			if !ok {
+				// not a secret
+				return false
+			}
 			// check if secret already owned by Rook:
 			for _, owner := range secret.OwnerReferences {
 				if owner.APIVersion == rookGV {
@@ -219,8 +209,13 @@ func secretPredicate[T *corev1.Secret]() predicate.TypedPredicate[T] {
 }
 
 // Maps secret referenced by object store to the object store CR
-func mapSecretToCR(k8sClient client.Client) handler.TypedMapFunc[*corev1.Secret, reconcile.Request] {
-	return func(ctx context.Context, secret *corev1.Secret) []reconcile.Request {
+func mapSecretToCR(k8sClient client.Client) func(context.Context, client.Object) []reconcile.Request {
+	return func(ctx context.Context, obj client.Object) []reconcile.Request {
+		secret, ok := obj.(*corev1.Secret)
+		if !ok {
+			return nil
+		}
+
 		// lookup object store CRs by name
 		objStores := cephv1.CephObjectStoreList{}
 		err := k8sClient.List(ctx, &objStores, client.InNamespace(secret.Namespace))
diff --git a/pkg/operator/ceph/object/cosi/controller.go b/pkg/operator/ceph/object/cosi/controller.go
index 175fd4254..22a400e44 100644
--- a/pkg/operator/ceph/object/cosi/controller.go
+++ b/pkg/operator/ceph/object/cosi/controller.go
@@ -94,27 +94,13 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 
 	logger.Info("successfully started")
 	// Watch for changes to CephCOSIDriver
-	err = controller.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephCOSIDriver{},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephCOSIDriver]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephCOSIDriver](mgr.GetScheme()),
-		),
-	)
+	err = controller.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephCOSIDriver{}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return errors.Wrap(err, "failed to watch for CephCOSIDriver object changes")
 	}
 
 	// Watch for changes to CephObjectStore as arbitrary resource and predicate functions
-	err = controller.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectStore{},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectStore]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectStore](mgr.GetScheme()),
-		),
-	)
+	err = controller.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectStore{}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return errors.Wrap(err, "failed to watch for CephObjectStore object changes")
 	}
diff --git a/pkg/operator/ceph/object/notification/controller.go b/pkg/operator/ceph/object/notification/controller.go
index ec398d366..722cf2a64 100644
--- a/pkg/operator/ceph/object/notification/controller.go
+++ b/pkg/operator/ceph/object/notification/controller.go
@@ -102,14 +102,7 @@ func addNotificationReconciler(mgr manager.Manager, r reconcile.Reconciler) erro
 	logger.Info("successfully started")
 
 	// Watch for changes on the OBC CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephBucketNotification{},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephBucketNotification]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephBucketNotification](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephBucketNotification{}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/notification/obc_label_controller.go b/pkg/operator/ceph/object/notification/obc_label_controller.go
index 4b849c3e1..226144d63 100644
--- a/pkg/operator/ceph/object/notification/obc_label_controller.go
+++ b/pkg/operator/ceph/object/notification/obc_label_controller.go
@@ -19,7 +19,6 @@ package notification
 
 import (
 	"context"
-	"reflect"
 	"strings"
 
 	bktv1alpha1 "github.com/kube-object-storage/lib-bucket-provisioner/pkg/apis/objectbucket.io/v1alpha1"
@@ -38,10 +37,8 @@ import (
 	"k8s.io/client-go/tools/record"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
-	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
-	"sigs.k8s.io/controller-runtime/pkg/predicate"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 	"sigs.k8s.io/controller-runtime/pkg/source"
 )
@@ -60,47 +57,6 @@ type ReconcileOBCLabels struct {
 	recorder         record.EventRecorder
 }
 
-func obcPredicate[T *bktv1alpha1.ObjectBucketClaim]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			obj := (*bktv1alpha1.ObjectBucketClaim)(e.Object)
-
-			logger.Debugf("create event from a CR: %q", obj.GetName())
-			return true
-		},
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			obj := (*bktv1alpha1.ObjectBucketClaim)(e.Object)
-
-			logger.Debugf("delete event from a CR: %q", obj.GetName())
-			return true
-		},
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			// break generic-ness in order to access .Spec.ObjectBucketName
-			objOld := (*bktv1alpha1.ObjectBucketClaim)(e.ObjectOld)
-			objNew := (*bktv1alpha1.ObjectBucketClaim)(e.ObjectNew)
-
-			logger.Debug("update event on ObjectBucketClaim CR")
-			// If the labels "do_not_reconcile" is set on the object, let's not reconcile that request
-			if opcontroller.IsDoNotReconcile(objNew.GetLabels()) {
-				logger.Debugf("object %q matched on update but %q label is set, doing nothing", objNew.GetName(), opcontroller.DoNotReconcileLabelName)
-				return false
-			}
-			if !reflect.DeepEqual(objOld.GetLabels(), objNew.GetLabels()) {
-				logger.Infof("CR labels has changed for %q", objNew.GetName())
-				return true
-			} else if objOld.Spec.ObjectBucketName != objNew.Spec.ObjectBucketName {
-				logger.Infof("CR %q bucket name changed from %q to %q", objNew.GetName(), objOld.Spec.ObjectBucketName, objNew.Spec.ObjectBucketName)
-				return true
-			}
-			logger.Debugf("no change in CR %q", objNew.GetName())
-			return false
-		},
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
-			return false
-		},
-	}
-}
-
 func addOBCLabelReconciler(mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
 	c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
@@ -110,14 +66,7 @@ func addOBCLabelReconciler(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the OBC CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&bktv1alpha1.ObjectBucketClaim{},
-			&handler.TypedEnqueueRequestForObject[*bktv1alpha1.ObjectBucketClaim]{},
-			obcPredicate(),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &bktv1alpha1.ObjectBucketClaim{}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/realm/controller.go b/pkg/operator/ceph/object/realm/controller.go
index 4e59c99b9..4f8b3fd1f 100644
--- a/pkg/operator/ceph/object/realm/controller.go
+++ b/pkg/operator/ceph/object/realm/controller.go
@@ -106,14 +106,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephObjectRealm CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectRealm{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectRealm]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectRealm](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectRealm{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/topic/controller.go b/pkg/operator/ceph/object/topic/controller.go
index 0250baf15..71734a675 100644
--- a/pkg/operator/ceph/object/topic/controller.go
+++ b/pkg/operator/ceph/object/topic/controller.go
@@ -73,14 +73,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephBucketTopic CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephBucketTopic{},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephBucketTopic]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephBucketTopic](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephBucketTopic{}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/user/controller.go b/pkg/operator/ceph/object/user/controller.go
index 8488d47f2..bf3aef339 100644
--- a/pkg/operator/ceph/object/user/controller.go
+++ b/pkg/operator/ceph/object/user/controller.go
@@ -110,31 +110,21 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephObjectStoreUser CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectStoreUser{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectStoreUser]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectStoreUser](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectStoreUser{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Watch secrets
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestForOwner[*corev1.Secret](
-				mgr.GetScheme(),
-				mgr.GetRESTMapper(),
-				&cephv1.CephObjectStoreUser{},
-			),
-			opcontroller.WatchPredicateForNonCRDObject[*corev1.Secret](&cephv1.CephObjectStoreUser{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
-		),
+	ownerRequest := handler.EnqueueRequestForOwner(
+		mgr.GetScheme(),
+		mgr.GetRESTMapper(),
+		&cephv1.CephObjectStoreUser{},
+	)
+	secretSource := source.Kind[client.Object](mgr.GetCache(), &corev1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}}, ownerRequest,
+		opcontroller.WatchPredicateForNonCRDObject(&cephv1.CephObjectStoreUser{TypeMeta: controllerTypeMeta}, mgr.GetScheme()),
 	)
+	err = c.Watch(secretSource)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/zone/controller.go b/pkg/operator/ceph/object/zone/controller.go
index 105265130..ef51bd712 100644
--- a/pkg/operator/ceph/object/zone/controller.go
+++ b/pkg/operator/ceph/object/zone/controller.go
@@ -114,14 +114,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephObjectZone CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectZone{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectZone]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectZone](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectZone{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/object/zonegroup/controller.go b/pkg/operator/ceph/object/zonegroup/controller.go
index 60420c956..111d52138 100644
--- a/pkg/operator/ceph/object/zonegroup/controller.go
+++ b/pkg/operator/ceph/object/zonegroup/controller.go
@@ -97,14 +97,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephObjectZoneGroup CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephObjectZoneGroup{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectZoneGroup]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephObjectZoneGroup](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephObjectZoneGroup{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/pool/controller.go b/pkg/operator/ceph/pool/controller.go
index b04af10dc..e1ccf71e2 100644
--- a/pkg/operator/ceph/pool/controller.go
+++ b/pkg/operator/ceph/pool/controller.go
@@ -114,39 +114,23 @@ func add(opManagerContext context.Context, mgr manager.Manager, r reconcile.Reco
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephBlockPool CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephBlockPool{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephBlockPool]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephBlockPool](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephBlockPool{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
 
 	// Build Handler function to return the list of ceph block pool
 	// This is used by the watchers below
-	handlerFunc, err := opcontroller.ObjectToCRMapper[*cephv1.CephBlockPoolList, *corev1.ConfigMap](
-		opManagerContext,
-		mgr.GetClient(),
-		&cephv1.CephBlockPoolList{},
-		mgr.GetScheme(),
-	)
+	handlerFunc, err := opcontroller.ObjectToCRMapper(opManagerContext, mgr.GetClient(), &cephv1.CephBlockPoolList{}, mgr.GetScheme())
 	if err != nil {
 		return err
 	}
 
 	// Watch for ConfigMap "rook-ceph-mon-endpoints" update and reconcile, which will reconcile update the bootstrap peer token
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
-			handler.TypedEnqueueRequestsFromMapFunc(handlerFunc),
-			mon.PredicateMonEndpointChanges(),
-		),
+	cmSource := source.Kind[client.Object](mgr.GetCache(), &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String()}},
+		handler.EnqueueRequestsFromMapFunc(handlerFunc), mon.PredicateMonEndpointChanges(),
 	)
+	err = c.Watch(cmSource)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/pool/radosnamespace/controller.go b/pkg/operator/ceph/pool/radosnamespace/controller.go
index 18a4d5de7..d03f35638 100644
--- a/pkg/operator/ceph/pool/radosnamespace/controller.go
+++ b/pkg/operator/ceph/pool/radosnamespace/controller.go
@@ -108,14 +108,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	logger.Info("successfully started")
 
 	// Watch for changes on the CephBlockPoolRadosNamespace CRD object
-	err = c.Watch(
-		source.Kind(
-			mgr.GetCache(),
-			&cephv1.CephBlockPoolRadosNamespace{TypeMeta: controllerTypeMeta},
-			&handler.TypedEnqueueRequestForObject[*cephv1.CephBlockPoolRadosNamespace]{},
-			opcontroller.WatchControllerPredicate[*cephv1.CephBlockPoolRadosNamespace](mgr.GetScheme()),
-		),
-	)
+	err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &cephv1.CephBlockPoolRadosNamespace{TypeMeta: controllerTypeMeta}, &handler.EnqueueRequestForObject{}, opcontroller.WatchControllerPredicate()))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/ceph/predicate.go b/pkg/operator/ceph/predicate.go
index 2b353ef21..578ba264b 100644
--- a/pkg/operator/ceph/predicate.go
+++ b/pkg/operator/ceph/predicate.go
@@ -18,54 +18,59 @@ limitations under the License.
 package operator
 
 import (
+	"context"
+
 	"github.com/rook/rook/pkg/operator/ceph/controller"
-	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
 
 // predicateController is the predicate function to trigger reconcile on operator configuration cm change
-func operatorSettingConfigMapPredicate[T *corev1.ConfigMap]() predicate.TypedFuncs[T] {
-	return predicate.TypedFuncs[T]{
-		CreateFunc: func(e event.TypedCreateEvent[T]) bool {
-			obj := (*corev1.ConfigMap)(e.Object)
-
-			return obj.GetName() == controller.OperatorSettingConfigMapName
+func predicateController(ctx context.Context, client client.Client) predicate.Funcs {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			if cm, ok := e.Object.(*v1.ConfigMap); ok {
+				return cm.Name == controller.OperatorSettingConfigMapName
+			}
+			return false
 		},
 
-		UpdateFunc: func(e event.TypedUpdateEvent[T]) bool {
-			objOld := (*corev1.ConfigMap)(e.ObjectOld)
-			objNew := (*corev1.ConfigMap)(e.ObjectNew)
-
-			if objOld.GetName() == controller.OperatorSettingConfigMapName && objNew.GetName() == controller.OperatorSettingConfigMapName {
-				if objOld.Data["ROOK_CURRENT_NAMESPACE_ONLY"] != objNew.Data["ROOK_CURRENT_NAMESPACE_ONLY"] {
-					logger.Debug("ROOK_CURRENT_NAMESPACE_ONLY config updated, reloading the manager")
-					controller.ReloadManager()
-
-					// No need to ask for reconciliation since the context is going to be terminated when
-					// the signal is caught and the reconcile will run when the controller starts.
-					return false
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			if old, ok := e.ObjectOld.(*v1.ConfigMap); ok {
+				if new, ok := e.ObjectNew.(*v1.ConfigMap); ok {
+					if old.Name == controller.OperatorSettingConfigMapName && new.Name == controller.OperatorSettingConfigMapName {
+						if old.Data["ROOK_CURRENT_NAMESPACE_ONLY"] != new.Data["ROOK_CURRENT_NAMESPACE_ONLY"] {
+							logger.Debug("ROOK_CURRENT_NAMESPACE_ONLY config updated, reloading the manager")
+							controller.ReloadManager()
+
+							// No need to ask for reconciliation since the context is going to be terminated when
+							// the signal is caught and the reconcile will run when the controller starts.
+							return false
+						}
+
+						// We still want to reconcile the operator manager if the configmap is updated
+						return true
+					}
 				}
-
-				// We still want to reconcile the operator manager if the configmap is updated
-				return true
 			}
 
 			return false
 		},
 
-		DeleteFunc: func(e event.TypedDeleteEvent[T]) bool {
-			obj := (*corev1.ConfigMap)(e.Object)
-
-			if obj.GetName() == controller.OperatorSettingConfigMapName {
-				logger.Debug("operator configmap deleted, not reconciling")
-				return false
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			if cm, ok := e.Object.(*v1.ConfigMap); ok {
+				if cm.Name == controller.OperatorSettingConfigMapName {
+					logger.Debug("operator configmap deleted, not reconciling")
+					return false
+				}
 			}
 
 			return false
 		},
 
-		GenericFunc: func(e event.TypedGenericEvent[T]) bool {
+		GenericFunc: func(e event.GenericEvent) bool {
 			return false
 		},
 	}
diff --git a/pkg/operator/discover/discover.go b/pkg/operator/discover/discover.go
index 1981fc8f1..630ecc3e6 100644
--- a/pkg/operator/discover/discover.go
+++ b/pkg/operator/discover/discover.go
@@ -32,7 +32,7 @@ import (
 	discoverDaemon "github.com/rook/rook/pkg/daemon/discover"
 	"github.com/rook/rook/pkg/operator/ceph/controller"
 	opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
-	"github.com/rook/rook/pkg/operator/k8sutil"
+	k8sutil "github.com/rook/rook/pkg/operator/k8sutil"
 	"github.com/rook/rook/pkg/util/sys"
 
 	apps "k8s.io/api/apps/v1"
@@ -424,7 +424,7 @@ func GetAvailableDevices(ctx context.Context, clusterdContext *clusterd.Context,
 				}
 			}
 		}
-	} else if len(filter) > 0 {
+	} else if len(filter) >= 0 {
 		for i := range nodeDevices {
 			// TODO support filter based on other keys
 			matched, err := regexp.Match(filter, []byte(nodeDevices[i].Name))
diff --git a/pkg/operator/k8sutil/pod.go b/pkg/operator/k8sutil/pod.go
index bec194178..1b2952458 100644
--- a/pkg/operator/k8sutil/pod.go
+++ b/pkg/operator/k8sutil/pod.go
@@ -165,7 +165,7 @@ func GetRunningPod(ctx context.Context, clientset kubernetes.Interface) (*v1.Pod
 		return nil, fmt.Errorf("cannot detect the pod name. Please provide it using the downward API in the manifest file")
 	}
 	podNamespace := os.Getenv(PodNamespaceEnvVar)
-	if podNamespace == "" {
+	if podName == "" {
 		return nil, fmt.Errorf("cannot detect the pod namespace. Please provide it using the downward API in the manifest file")
 	}
 


Test patch:
diff --git a/.github/workflows/canary-integration-test.yml b/.github/workflows/canary-integration-test.yml
index 4bc77b1d8..42b60d136 100644
--- a/.github/workflows/canary-integration-test.yml
+++ b/.github/workflows/canary-integration-test.yml
@@ -1591,7 +1591,7 @@ jobs:
           tests/scripts/github-action-helper.sh deploy_first_rook_cluster
           kubectl create -f deploy/examples/object-multisite-test.yaml
           # wait for multisite-store to be created
-          tests/scripts/github-action-helper.sh wait_for cephobjectstore multisite-store rook-ceph 480
+          tests/scripts/github-action-helper.sh wait_for cephobjectstore multisite-store rook-ceph 600
 
       - name: prep second cluster pull realm config
         shell: bash --noprofile --norc -eo pipefail -x {0}
@@ -1610,7 +1610,7 @@ jobs:
           tests/scripts/github-action-helper.sh deploy_second_rook_cluster
           kubectl create -f deploy/examples/object-multisite-pull-realm-test.yaml
           # wait for realms to be pulled and zone-b-multisite-store to be created
-          tests/scripts/github-action-helper.sh wait_for cephobjectstore zone-b-multisite-store rook-ceph-secondary 480
+          tests/scripts/github-action-helper.sh wait_for cephobjectstore zone-b-multisite-store rook-ceph-secondary 600
 
       - name: wait for both ceph clusters to be ready
         shell: bash --noprofile --norc -eo pipefail -x {0}
diff --git a/pkg/operator/ceph/cluster/predicate_test.go b/pkg/operator/ceph/cluster/predicate_test.go
index dfbbf8c7d..ce760d42a 100644
--- a/pkg/operator/ceph/cluster/predicate_test.go
+++ b/pkg/operator/ceph/cluster/predicate_test.go
@@ -20,12 +20,17 @@ import (
 	"testing"
 	"time"
 
+	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestIsHotPlugCM(t *testing.T) {
+	blockPool := &cephv1.CephBlockPool{}
+
+	assert.False(t, isHotPlugCM(blockPool))
+
 	cm := &corev1.ConfigMap{}
 	assert.False(t, isHotPlugCM(cm))
 
diff --git a/pkg/operator/ceph/controller/handler_test.go b/pkg/operator/ceph/controller/handler_test.go
index 0701b9e6b..830201914 100644
--- a/pkg/operator/ceph/controller/handler_test.go
+++ b/pkg/operator/ceph/controller/handler_test.go
@@ -62,7 +62,7 @@ func TestObjectToCRMapper(t *testing.T) {
 		{NamespacedName: client.ObjectKey{Name: "my-pool", Namespace: "rook-ceph"}},
 	}
 
-	handlerFunc, err := ObjectToCRMapper[*cephv1.CephFilesystemList, runtime.Object](context.TODO(), cl, &cephv1.CephFilesystemList{}, s)
+	handlerFunc, err := ObjectToCRMapper(context.TODO(), cl, objects[0], s)
 	assert.NoError(t, err)
 	assert.ElementsMatch(t, fakeRequest, handlerFunc(context.TODO(), fs))
 }
diff --git a/pkg/operator/ceph/controller/predicate_test.go b/pkg/operator/ceph/controller/predicate_test.go
index 509e206a1..5e20900ce 100644
--- a/pkg/operator/ceph/controller/predicate_test.go
+++ b/pkg/operator/ceph/controller/predicate_test.go
@@ -185,6 +185,9 @@ func TestIsCMToIgnoreOnDelete(t *testing.T) {
 }
 
 func TestIsSecretToIgnoreOnUpdate(t *testing.T) {
+	blockPool := &cephv1.CephBlockPool{}
+	assert.False(t, isSecretToIgnoreOnUpdate(blockPool))
+
 	s := &corev1.Secret{}
 	assert.False(t, isSecretToIgnoreOnUpdate(s))
 
diff --git a/pkg/operator/ceph/csi/predicate_test.go b/pkg/operator/ceph/csi/predicate_test.go
index d16f3689b..ae3ff720b 100644
--- a/pkg/operator/ceph/csi/predicate_test.go
+++ b/pkg/operator/ceph/csi/predicate_test.go
@@ -24,118 +24,116 @@ import (
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
 	"github.com/rook/rook/pkg/client/clientset/versioned/scheme"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
 
-func Test_cmPredicate(t *testing.T) {
+func Test_predicateController(t *testing.T) {
 	capnslog.SetGlobalLogLevel(capnslog.DEBUG)
+	var (
+		client    client.WithWatch
+		namespace v1.Namespace
+		p         predicate.Funcs
+		c         event.CreateEvent
+		d         event.DeleteEvent
+		u         event.UpdateEvent
+		cm        v1.ConfigMap
+		cm2       v1.ConfigMap
+		cluster   cephv1.CephCluster
+		cluster2  cephv1.CephCluster
+	)
+
+	s := scheme.Scheme
+	s.AddKnownTypes(cephv1.SchemeGroupVersion, &v1.ConfigMap{}, &v1.ConfigMapList{}, &cephv1.CephCluster{}, &cephv1.CephClusterList{})
+	client = fake.NewClientBuilder().WithScheme(s).Build()
+
+	t.Run("create event is not the one we are looking for", func(t *testing.T) {
+		c = event.CreateEvent{Object: &namespace}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Create(c))
+	})
 
 	t.Run("create event is a CM but not the operator config", func(t *testing.T) {
-		cm := &corev1.ConfigMap{}
-		e := event.TypedCreateEvent[*corev1.ConfigMap]{Object: cm}
-		p := cmPredicate()
-		assert.False(t, p.Create(e))
+		c = event.CreateEvent{Object: &cm}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Create(c))
 	})
 
 	t.Run("delete event is a CM and it's not the operator config", func(t *testing.T) {
-		cm := &corev1.ConfigMap{}
-		e := event.TypedDeleteEvent[*corev1.ConfigMap]{Object: cm}
-		p := cmPredicate()
-		assert.False(t, p.Delete(e))
+		d = event.DeleteEvent{Object: &cm}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Delete(d))
 	})
 
 	t.Run("create event is a CM and it's the operator config", func(t *testing.T) {
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config"}}
-		e := event.TypedCreateEvent[*corev1.ConfigMap]{Object: cm}
-		p := cmPredicate()
-		assert.True(t, p.Create(e))
+		cm.Name = "rook-ceph-operator-config"
+		c = event.CreateEvent{Object: &cm}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.True(t, p.Create(c))
 	})
 
 	t.Run("delete event is a CM and it's the operator config", func(t *testing.T) {
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config"}}
-		e := event.TypedDeleteEvent[*corev1.ConfigMap]{Object: cm}
-		p := cmPredicate()
-		assert.True(t, p.Delete(e))
+		d = event.DeleteEvent{Object: &cm}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.True(t, p.Delete(d))
 	})
 
 	t.Run("update event is a CM and it's the operator config but nothing changed", func(t *testing.T) {
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config"}}
-		e := event.TypedUpdateEvent[*corev1.ConfigMap]{ObjectOld: cm, ObjectNew: cm}
-		p := cmPredicate()
-		assert.False(t, p.Update(e))
+		u = event.UpdateEvent{ObjectOld: &cm, ObjectNew: &cm}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Update(u))
 	})
 
 	t.Run("update event is a CM and the content changed with correct setting", func(t *testing.T) {
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config"}}
-		cm2 := cm.DeepCopy()
 		cm.Data = map[string]string{"foo": "bar"}
+		cm2.Name = "rook-ceph-operator-config"
 		cm2.Data = map[string]string{"CSI_PROVISIONER_REPLICAS": "2"}
-		e := event.TypedUpdateEvent[*corev1.ConfigMap]{ObjectOld: cm, ObjectNew: cm2}
-		p := cmPredicate()
-		assert.True(t, p.Update(e))
+		u = event.UpdateEvent{ObjectOld: &cm, ObjectNew: &cm2}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.True(t, p.Update(u))
 	})
-}
-
-func Test_cephClusterPredicate(t *testing.T) {
-	capnslog.SetGlobalLogLevel(capnslog.DEBUG)
-
-	s := scheme.Scheme
-	s.AddKnownTypes(cephv1.SchemeGroupVersion, &corev1.ConfigMap{}, &corev1.ConfigMapList{}, &cephv1.CephCluster{}, &cephv1.CephClusterList{})
 
 	t.Run("create event is a CephCluster and it's the first instance and a cm is present", func(t *testing.T) {
-		client := fake.NewClientBuilder().WithScheme(s).Build()
-
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config", Namespace: "rook-ceph"}}
-		err := client.Create(context.TODO(), cm)
-		require.NoError(t, err)
-
-		cluster := &cephv1.CephCluster{ObjectMeta: metav1.ObjectMeta{Namespace: "rook-ceph", Generation: 1}}
-		e := event.TypedCreateEvent[*cephv1.CephCluster]{Object: cluster}
-		p := cephClusterPredicate(context.TODO(), client, "rook-ceph")
-		assert.True(t, p.Create(e))
+		cm.Namespace = "rook-ceph"
+		err := client.Create(context.TODO(), &cm)
+		assert.NoError(t, err)
+		cluster.Generation = 1
+		c = event.CreateEvent{Object: &cluster}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.True(t, p.Create(c))
 	})
 
 	t.Run("create event is a CephCluster and it's not the first instance", func(t *testing.T) {
-		client := fake.NewClientBuilder().WithScheme(s).Build()
-
-		cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "rook-ceph-operator-config", Namespace: "rook-ceph"}}
-		err := client.Create(context.TODO(), cm)
-		require.NoError(t, err)
-
-		cluster := &cephv1.CephCluster{ObjectMeta: metav1.ObjectMeta{Namespace: "rook-ceph", Generation: 2}}
-		e := event.TypedCreateEvent[*cephv1.CephCluster]{Object: cluster}
-		p := cephClusterPredicate(context.TODO(), client, "rook-ceph")
-		assert.False(t, p.Create(e))
+		cluster.Generation = 2
+		c = event.CreateEvent{Object: &cluster}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Create(c))
 	})
 
 	t.Run("create event is a CephCluster and it's not the first instance but no cm so we reconcile", func(t *testing.T) {
-		client := fake.NewClientBuilder().WithScheme(s).Build()
-
-		cluster := &cephv1.CephCluster{ObjectMeta: metav1.ObjectMeta{Namespace: "rook-ceph", Generation: 2}}
-		e := event.TypedCreateEvent[*cephv1.CephCluster]{Object: cluster}
-		p := cephClusterPredicate(context.TODO(), client, "rook-ceph")
-		assert.True(t, p.Create(e))
+		err := client.Delete(context.TODO(), &cm)
+		assert.NoError(t, err)
+		cluster.Generation = 2
+		c = event.CreateEvent{Object: &cluster}
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.True(t, p.Create(c))
 	})
 
 	t.Run("create event is a CephCluster and a cluster already exists - not reconciling", func(t *testing.T) {
-		client := fake.NewClientBuilder().WithScheme(s).Build()
-
-		{
-			cluster := &cephv1.CephCluster{ObjectMeta: metav1.ObjectMeta{Name: "ceph1", Namespace: "rook-ceph", Generation: 2}}
-			err := client.Create(context.TODO(), cluster)
-			require.NoError(t, err)
-		}
-
-		cluster := &cephv1.CephCluster{ObjectMeta: metav1.ObjectMeta{Name: "ceph2", Namespace: "rook-ceph"}}
-		err := client.Create(context.TODO(), cluster)
-		require.NoError(t, err)
-
-		e := event.TypedCreateEvent[*cephv1.CephCluster]{Object: cluster}
-		p := cephClusterPredicate(context.TODO(), client, "rook-ceph")
-		assert.False(t, p.Create(e))
+		cluster.Name = "ceph1"
+		cluster2.Name = "ceph2"
+		cluster.Namespace = "rook-ceph"
+		cluster2.Namespace = "rook-ceph"
+		err := client.Create(context.TODO(), &cluster)
+		assert.NoError(t, err)
+		err = client.Create(context.TODO(), &cluster2)
+		assert.NoError(t, err)
+		c = event.CreateEvent{Object: &cluster2}
+		assert.Equal(t, "rook-ceph", c.Object.GetNamespace())
+		p = predicateController(context.TODO(), client, "rook-ceph")
+		assert.False(t, p.Create(c))
 	})
 }
diff --git a/tests/scripts/github-action-helper.sh b/tests/scripts/github-action-helper.sh
index a9f2f4759..da27b709a 100755
--- a/tests/scripts/github-action-helper.sh
+++ b/tests/scripts/github-action-helper.sh
@@ -562,7 +562,7 @@ function get_secret_key() {
 }
 
 function s3cmd() {
-  command timeout 150 s3cmd -v --config=s3cfg --access_key="${S3CMD_ACCESS_KEY}" --secret_key="${S3CMD_SECRET_KEY}" "$@"
+  command timeout 200 s3cmd -v --config=s3cfg --access_key="${S3CMD_ACCESS_KEY}" --secret_key="${S3CMD_SECRET_KEY}" "$@"
 }
 
 function write_object_read_from_replica_cluster() {