You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2026/06/20 10:26:19 UTC

(camel-k) 01/02: feat(install): multi namespace watching

This is an automated email from the ASF dual-hosted git repository.

squakez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 38aa87044f68b16c07d44664dd52e3b4075fc316
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Sat Jun 13 09:15:06 2026 +0200

    feat(install): multi namespace watching
    
    * Enable the possibility to watch another namespace, beside the own namespace
    * Enable the possibility to watch multiple namespaces
    * Deprecate operator.id annotation
    * Change install names to clarify each overlay intent
    
    Ref #6616
---
 docs/modules/ROOT/pages/pipes/bind-cli.adoc        |   2 -
 docs/modules/ROOT/pages/running/running-cli.adoc   |   2 -
 e2e/advanced/operator_id_filtering_test.go         |   2 +-
 e2e/common/misc/pipe_test.go                       |  47 -------
 .../{setup_test.go => all_namespaces_test.go}      |  64 ---------
 e2e/install/kustomize/own_namespace_test.go        |  83 ++++++++++++
 e2e/install/kustomize/single_namespace_test.go     |  87 ++++++++++++
 e2e/support/test_support.go                        |  13 +-
 .../kustomization.yaml                             |   0
 .../patch-log-level.yaml                           |   0
 .../patch-node-selector.yaml                       |   0
 .../patch-operator-id.yaml                         |   0
 .../{namespaced => own-namespace}/patch-ports.yaml |   0
 .../patch-resource-requirements.yaml               |   0
 .../patch-toleration.yaml                          |   0
 .../kustomization.yaml}                            |   8 +-
 .../single-namespace/operator}/kustomization.yaml  |  14 +-
 .../operator/patch-envvars.yaml}                   |  19 +--
 .../operator/remove-watch-ns.yaml}                 |   5 +-
 .../tenant-a-ns-rbac}/kustomization.yaml           |  15 +--
 .../patch-rolebinding-subjects.yaml}               |   9 +-
 pkg/apis/camel/v1/common_types.go                  |   2 +
 pkg/cmd/bind.go                                    |  24 ++--
 pkg/cmd/bind_test.go                               |  18 +--
 pkg/cmd/operator/operator.go                       |  54 ++++----
 pkg/cmd/operator/operator_test.go                  | 150 +++++++++++++++++++++
 pkg/cmd/promote_test.go                            |  12 +-
 pkg/cmd/run.go                                     |  10 +-
 pkg/cmd/run_test.go                                |  14 --
 pkg/controller/integration/build.go                |   1 +
 .../integration/integration_controller.go          |   9 +-
 pkg/controller/integrationkit/build.go             |   1 +
 .../integrationkit/integrationkit_controller.go    |   1 +
 pkg/install/common.go                              |  50 -------
 pkg/install/optional.go                            |   2 +-
 pkg/platform/defaults.go                           |  13 +-
 pkg/platform/operator.go                           |  15 +--
 pkg/resources/config/manager/kustomization.yaml    |  10 ++
 .../config/manager/operator-deployment.yaml        |   6 +
 pkg/util/gitops/gitops.go                          |   4 +
 script/Makefile                                    |   2 +-
 41 files changed, 460 insertions(+), 308 deletions(-)

diff --git a/docs/modules/ROOT/pages/pipes/bind-cli.adoc b/docs/modules/ROOT/pages/pipes/bind-cli.adoc
index 5133b22a6..fd48effbe 100644
--- a/docs/modules/ROOT/pages/pipes/bind-cli.adoc
+++ b/docs/modules/ROOT/pages/pipes/bind-cli.adoc
@@ -65,8 +65,6 @@ As an example, take the option available on the `kamel bind timer-source log-sin
 apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: timer-source-to-log-sink
   namespace: camel-k
 spec:
diff --git a/docs/modules/ROOT/pages/running/running-cli.adoc b/docs/modules/ROOT/pages/running/running-cli.adoc
index 67c8d14d9..d4d871ebe 100644
--- a/docs/modules/ROOT/pages/running/running-cli.adoc
+++ b/docs/modules/ROOT/pages/running/running-cli.adoc
@@ -67,8 +67,6 @@ As an example, take the option available on the `kamel run test.yaml -t promethe
 apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: test
 spec:
   flows:
diff --git a/e2e/advanced/operator_id_filtering_test.go b/e2e/advanced/operator_id_filtering_test.go
index 4e2d71412..184603d91 100644
--- a/e2e/advanced/operator_id_filtering_test.go
+++ b/e2e/advanced/operator_id_filtering_test.go
@@ -91,7 +91,7 @@ func TestOperatorIDFiltering(t *testing.T) {
 								v1.IntegrationKitTypeLabel: v1.IntegrationKitTypeExternal,
 							},
 							Annotations: map[string]string{
-								"camel.apache.org/operator.id": operator2,
+								v1.OperatorIDAnnotation: operator2,
 							},
 						},
 						Spec: v1.IntegrationKitSpec{
diff --git a/e2e/common/misc/pipe_test.go b/e2e/common/misc/pipe_test.go
index 4aa7797bd..2402919b6 100644
--- a/e2e/common/misc/pipe_test.go
+++ b/e2e/common/misc/pipe_test.go
@@ -127,53 +127,6 @@ func TestPipe(t *testing.T) {
 	})
 }
 
-func TestPipeWithImage(t *testing.T) {
-	WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
-		bindingID := "with-image-binding"
-
-		t.Run("run with initial image", func(t *testing.T) {
-			expectedImage := "quay.io/fuse_qe/echo-server:0.3.2"
-
-			g.Expect(KamelBind(t, ctx, ns, "my-own-timer-source", "my-own-log-sink",
-				"--trait", "container.image="+expectedImage, "--trait", "jvm.enabled=false",
-				"--trait", "kamelets.enabled=false", "--trait", "dependencies.enabled=false",
-				"--annotation", "test=1", "--name", bindingID).Execute()).To(Succeed())
-
-			g.Eventually(IntegrationGeneration(t, ctx, ns, bindingID)).
-				Should(gstruct.PointTo(BeNumerically("==", 1)))
-			g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations,
-				HaveKeyWithValue("test", "1"),
-			))
-			g.Eventually(IntegrationStatusImage(t, ctx, ns, bindingID)).
-				Should(Equal(expectedImage))
-			g.Eventually(IntegrationPodPhase(t, ctx, ns, bindingID), TestTimeoutShort).
-				Should(Equal(corev1.PodRunning))
-			g.Eventually(IntegrationPodImage(t, ctx, ns, bindingID)).
-				Should(Equal(expectedImage))
-		})
-
-		t.Run("run with new image", func(t *testing.T) {
-			expectedImage := "quay.io/fuse_qe/echo-server:0.3.3"
-
-			g.Expect(KamelBind(t, ctx, ns, "my-own-timer-source", "my-own-log-sink",
-				"--trait", "container.image="+expectedImage, "--trait", "jvm.enabled=false",
-				"--trait", "kamelets.enabled=false", "--trait", "dependencies.enabled=false",
-				"--annotation", "test=2", "--name", bindingID).Execute()).To(Succeed())
-			g.Eventually(IntegrationGeneration(t, ctx, ns, bindingID)).
-				Should(gstruct.PointTo(BeNumerically("==", 1)))
-			g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations,
-				HaveKeyWithValue("test", "2"),
-			))
-			g.Eventually(IntegrationStatusImage(t, ctx, ns, bindingID)).
-				Should(Equal(expectedImage))
-			g.Eventually(IntegrationPodPhase(t, ctx, ns, bindingID), TestTimeoutShort).
-				Should(Equal(corev1.PodRunning))
-			g.Eventually(IntegrationPodImage(t, ctx, ns, bindingID)).
-				Should(Equal(expectedImage))
-		})
-	})
-}
-
 func TestPipeScale(t *testing.T) {
 	WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
 		name := RandomizedSuffixName("timer2log")
diff --git a/e2e/install/kustomize/setup_test.go b/e2e/install/kustomize/all_namespaces_test.go
similarity index 61%
rename from e2e/install/kustomize/setup_test.go
rename to e2e/install/kustomize/all_namespaces_test.go
index e5740b9d3..9df5e5e8f 100644
--- a/e2e/install/kustomize/setup_test.go
+++ b/e2e/install/kustomize/all_namespaces_test.go
@@ -37,67 +37,6 @@ import (
 	. "github.com/onsi/gomega"
 )
 
-func TestKustomizeNamespaced(t *testing.T) {
-	kustomizeDir := testutil.MakeTempCopyDir(t, "../../../install")
-	WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
-		// Let's make sure no CRD is yet available in the cluster
-		// as we must make the procedure to install them accordingly
-		g.Eventually(CRDs(t)).Should(BeNil(), "No Camel K CRDs should be previously installed for this test")
-		// We must change a few values in the Kustomize config
-		ExpectExecSucceed(t, g,
-			exec.Command(
-				"sed",
-				"-i",
-				fmt.Sprintf("s/namespace: .*/namespace: %s/", ns),
-				fmt.Sprintf("%s/overlays/kubernetes/namespaced/kustomization.yaml", kustomizeDir),
-			))
-		ExpectExecSucceed(t, g, Kubectl(
-			"apply",
-			"-k",
-			fmt.Sprintf("%s/overlays/kubernetes/namespaced", kustomizeDir),
-			"--server-side",
-		))
-
-		// Refresh the test client to account for the newly installed CRDs
-		RefreshClient(t)
-		g.Eventually(OperatorPod(t, ctx, ns)).ShouldNot(BeNil())
-		g.Eventually(OperatorPodPhase(t, ctx, ns)).Should(Equal(corev1.PodRunning))
-		// Check if restricted security context has been applied
-		operatorPod := OperatorPod(t, ctx, ns)()
-		g.Expect(operatorPod.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(
-			Equal(DefaultOperatorSecurityContext().RunAsNonRoot),
-		)
-		g.Expect(operatorPod.Spec.Containers[0].SecurityContext.Capabilities).To(
-			Equal(DefaultOperatorSecurityContext().Capabilities),
-		)
-		g.Expect(operatorPod.Spec.Containers[0].SecurityContext.SeccompProfile).To(
-			Equal(DefaultOperatorSecurityContext().SeccompProfile),
-		)
-		g.Expect(operatorPod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(
-			Equal(DefaultOperatorSecurityContext().AllowPrivilegeEscalation),
-		)
-
-		// Test a simple integration is running
-		g.Expect(KamelRun(t, ctx, ns, "files/yaml.yaml").Execute()).To(Succeed())
-		g.Eventually(IntegrationPodPhase(t, ctx, ns, "yaml"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
-		g.Eventually(IntegrationConditionStatus(t, ctx, ns, "yaml", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
-		g.Eventually(IntegrationLogs(t, ctx, ns, "yaml"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
-
-		// Test operator only uninstall
-		UninstallOperator(t, ctx, g, ns, "../../../")
-
-		g.Eventually(OperatorPod(t, ctx, ns)).Should(BeNil())
-		g.Eventually(Integration(t, ctx, ns, "yaml"), TestTimeoutShort).ShouldNot(BeNil())
-		g.Eventually(IntegrationConditionStatus(t, ctx, ns, "yaml", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
-
-		// Test CRD uninstall (will remove Integrations as well)
-		UninstallCRDs(t, ctx, g, "../../../")
-
-		g.Eventually(OperatorPod(t, ctx, ns)).Should(BeNil())
-		g.Eventually(CRDs(t)).Should(BeNil())
-	})
-}
-
 func TestKustomizeDescoped(t *testing.T) {
 	kustomizeDir := testutil.MakeTempCopyDir(t, "../../../install")
 	WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
@@ -119,9 +58,6 @@ func TestKustomizeDescoped(t *testing.T) {
 			"--server-side",
 		))
 
-		// Refresh the test client to account for the newly installed CRDs
-		RefreshClient(t)
-
 		podFunc := OperatorPod(t, ctx, ns)
 		g.Eventually(podFunc).ShouldNot(BeNil())
 		g.Eventually(OperatorPodPhase(t, ctx, ns)).Should(Equal(corev1.PodRunning))
diff --git a/e2e/install/kustomize/own_namespace_test.go b/e2e/install/kustomize/own_namespace_test.go
new file mode 100644
index 000000000..82f0a7161
--- /dev/null
+++ b/e2e/install/kustomize/own_namespace_test.go
@@ -0,0 +1,83 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kustomize
+
+import (
+	"context"
+	"fmt"
+	"os/exec"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/v2/e2e/support"
+	testutil "github.com/apache/camel-k/v2/e2e/support/util"
+	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+
+	. "github.com/onsi/gomega"
+)
+
+func TestKustomizeOwnNamespace(t *testing.T) {
+	kustomizeDir := testutil.MakeTempCopyDir(t, "../../../install")
+	WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
+		// Let's make sure no CRD is yet available in the cluster
+		// as we must make the procedure to install them accordingly
+		g.Eventually(CRDs(t)).Should(BeNil(), "No Camel K CRDs should be previously installed for this test")
+		// We must change a few values in the Kustomize config
+		ExpectExecSucceed(t, g,
+			exec.Command(
+				"sed",
+				"-i",
+				fmt.Sprintf("s/namespace: .*/namespace: %s/", ns),
+				fmt.Sprintf("%s/overlays/kubernetes/own-namespace/kustomization.yaml", kustomizeDir),
+			))
+		ExpectExecSucceed(t, g, Kubectl(
+			"apply",
+			"-k",
+			fmt.Sprintf("%s/overlays/kubernetes/own-namespace", kustomizeDir),
+			"--server-side",
+		))
+
+		g.Eventually(OperatorPod(t, ctx, ns)).ShouldNot(BeNil())
+		g.Eventually(OperatorPodPhase(t, ctx, ns)).Should(Equal(corev1.PodRunning))
+
+		// Test a simple integration is running
+		g.Expect(KamelRun(t, ctx, ns, "files/yaml.yaml").Execute()).To(Succeed())
+		g.Eventually(IntegrationPodPhase(t, ctx, ns, "yaml"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
+		g.Eventually(IntegrationConditionStatus(t, ctx, ns, "yaml", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+		g.Eventually(IntegrationLogs(t, ctx, ns, "yaml"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+		// Test operator only uninstall
+		UninstallOperator(t, ctx, g, ns, "../../../")
+
+		g.Eventually(OperatorPod(t, ctx, ns)).Should(BeNil())
+		g.Eventually(Integration(t, ctx, ns, "yaml"), TestTimeoutShort).ShouldNot(BeNil())
+		g.Eventually(IntegrationConditionStatus(t, ctx, ns, "yaml", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+
+		// Test CRD uninstall (will remove Integrations as well)
+		UninstallCRDs(t, ctx, g, "../../../")
+
+		g.Eventually(OperatorPod(t, ctx, ns)).Should(BeNil())
+		g.Eventually(CRDs(t)).Should(BeNil())
+	})
+}
diff --git a/e2e/install/kustomize/single_namespace_test.go b/e2e/install/kustomize/single_namespace_test.go
new file mode 100644
index 000000000..e0229e49a
--- /dev/null
+++ b/e2e/install/kustomize/single_namespace_test.go
@@ -0,0 +1,87 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kustomize
+
+import (
+	"context"
+	"fmt"
+	"testing"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/v2/e2e/support"
+	testutil "github.com/apache/camel-k/v2/e2e/support/util"
+	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+
+	. "github.com/onsi/gomega"
+)
+
+func TestKustomizeSingleNamespace(t *testing.T) {
+	kustomizeDir := testutil.MakeTempCopyDir(t, "../../../install")
+
+	// The operator is expected to be installed in "operators" namespace
+	// it also expects to reconcile correctly an Integration in namespace "tenant-a"
+	// but it won't reconcile in any other namespaces, for example, "tenan-b"
+	WithNamedTestNamespace(t, func(ctx context.Context, g *WithT, operatorNs string) {
+		WithNamedTestNamespace(t, func(ctx context.Context, g *WithT, tenantNs string) {
+			// Let's make sure no CRD is yet available in the cluster
+			// as we must make the procedure to install them accordingly
+			g.Eventually(CRDs(t)).Should(BeNil(), "No Camel K CRDs should be previously installed for this test")
+			ExpectExecSucceed(t, g, Kubectl(
+				"apply",
+				"-k",
+				fmt.Sprintf("%s/overlays/kubernetes/single-namespace", kustomizeDir),
+				"--server-side",
+			))
+			g.Eventually(OperatorPod(t, ctx, operatorNs)).ShouldNot(BeNil())
+			g.Eventually(OperatorPodPhase(t, ctx, operatorNs)).Should(Equal(corev1.PodRunning))
+
+			WithNamedTestNamespace(t, func(ctx context.Context, g *WithT, tenantNs string) {
+				// Test a simple integration in "tenant-b" is not reconciled
+				g.Expect(KamelRun(t, ctx, tenantNs, "files/yaml.yaml").Execute()).To(Succeed())
+				g.Consistently(IntegrationPhase(t, ctx, tenantNs, "yaml"), 10*time.Second).Should(BeEmpty())
+			}, "tenant-b")
+
+			// Test a simple integration in "tenant-a" is reconciled and runs correctly
+			g.Expect(KamelRun(t, ctx, tenantNs, "files/yaml.yaml").Execute()).To(Succeed())
+			g.Eventually(IntegrationConditionStatus(t, ctx, tenantNs, "yaml", v1.IntegrationConditionReady), TestTimeoutMedium).
+				Should(Equal(corev1.ConditionTrue))
+			g.Eventually(IntegrationLogs(t, ctx, tenantNs, "yaml"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+			// Test operator only uninstall
+			UninstallOperator(t, ctx, g, operatorNs, "../../../")
+
+			g.Eventually(OperatorPod(t, ctx, operatorNs)).Should(BeNil())
+			g.Eventually(Integration(t, ctx, "tenant-a", "yaml"), TestTimeoutShort).ShouldNot(BeNil())
+			g.Eventually(IntegrationConditionStatus(t, ctx, "tenant-a", "yaml", v1.IntegrationConditionReady), TestTimeoutShort).
+				Should(Equal(corev1.ConditionTrue))
+
+			// Test CRD uninstall (will remove Integrations as well)
+			UninstallCRDs(t, ctx, g, "../../../")
+
+			g.Eventually(OperatorPod(t, ctx, operatorNs)).Should(BeNil())
+			g.Eventually(CRDs(t)).Should(BeNil())
+		}, "tenant-a")
+	}, "operators")
+}
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 156f7cde9..e61bbca34 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -292,7 +292,10 @@ func kamelCommandWithContext(t *testing.T, ctx context.Context, command string,
 	cmdMutex.Lock()
 	defer cmdMutex.Unlock()
 
-	cmdArgs := []string{command, "-n", namespace, "--operator-id", operatorID}
+	cmdArgs := []string{command, "-n", namespace}
+	if operatorID != platform.DefaultPlatformName {
+		cmdArgs = append(cmdArgs, "--operator-id", operatorID)
+	}
 	cmdArgs = append(cmdArgs, args...)
 	return KamelWithContext(t, ctx, cmdArgs...)
 }
@@ -2539,6 +2542,14 @@ func WithNewTestNamespace(t *testing.T, doRun func(context.Context, *gomega.With
 	invokeUserTestCode(t, testContext, ns.GetName(), doRun)
 }
 
+func WithNamedTestNamespace(t *testing.T, doRun func(context.Context, *gomega.WithT, string), namespace string) {
+	ns := NewNamedTestNamespace(t, testContext, namespace, false)
+	defer deleteTestNamespace(t, testContext, ns)
+	defer userCleanup(t)
+
+	invokeUserTestCode(t, testContext, ns.GetName(), doRun)
+}
+
 func WithNewTestNamespaceWithKnativeBroker(t *testing.T, doRun func(context.Context, *gomega.WithT, string)) {
 	ns := NewTestNamespace(t, testContext, true)
 	defer deleteTestNamespace(t, testContext, ns)
diff --git a/install/overlays/kubernetes/namespaced/kustomization.yaml b/install/overlays/kubernetes/own-namespace/kustomization.yaml
similarity index 100%
rename from install/overlays/kubernetes/namespaced/kustomization.yaml
rename to install/overlays/kubernetes/own-namespace/kustomization.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-log-level.yaml b/install/overlays/kubernetes/own-namespace/patch-log-level.yaml
similarity index 100%
rename from install/overlays/kubernetes/namespaced/patch-log-level.yaml
rename to install/overlays/kubernetes/own-namespace/patch-log-level.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-node-selector.yaml b/install/overlays/kubernetes/own-namespace/patch-node-selector.yaml
similarity index 100%
rename from install/overlays/kubernetes/namespaced/patch-node-selector.yaml
rename to install/overlays/kubernetes/own-namespace/patch-node-selector.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-operator-id.yaml b/install/overlays/kubernetes/own-namespace/patch-operator-id.yaml
similarity index 100%
copy from install/overlays/kubernetes/namespaced/patch-operator-id.yaml
copy to install/overlays/kubernetes/own-namespace/patch-operator-id.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-ports.yaml b/install/overlays/kubernetes/own-namespace/patch-ports.yaml
similarity index 100%
rename from install/overlays/kubernetes/namespaced/patch-ports.yaml
rename to install/overlays/kubernetes/own-namespace/patch-ports.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-resource-requirements.yaml b/install/overlays/kubernetes/own-namespace/patch-resource-requirements.yaml
similarity index 100%
rename from install/overlays/kubernetes/namespaced/patch-resource-requirements.yaml
rename to install/overlays/kubernetes/own-namespace/patch-resource-requirements.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-toleration.yaml b/install/overlays/kubernetes/own-namespace/patch-toleration.yaml
similarity index 100%
copy from install/overlays/kubernetes/namespaced/patch-toleration.yaml
copy to install/overlays/kubernetes/own-namespace/patch-toleration.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-operator-id.yaml b/install/overlays/kubernetes/single-namespace/kustomization.yaml
similarity index 89%
copy from install/overlays/kubernetes/namespaced/patch-operator-id.yaml
copy to install/overlays/kubernetes/single-namespace/kustomization.yaml
index 25c150420..bd6a00ee6 100644
--- a/install/overlays/kubernetes/namespaced/patch-operator-id.yaml
+++ b/install/overlays/kubernetes/single-namespace/kustomization.yaml
@@ -14,7 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ---------------------------------------------------------------------------
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
 
-- op: add
-  path: /spec/template/spec/containers/0/env/2/value
-  value: camel-k
+resources:
+- operator
+- tenant-a-ns-rbac
diff --git a/pkg/resources/config/manager/kustomization.yaml b/install/overlays/kubernetes/single-namespace/operator/kustomization.yaml
similarity index 86%
copy from pkg/resources/config/manager/kustomization.yaml
copy to install/overlays/kubernetes/single-namespace/operator/kustomization.yaml
index a4543333a..62fa2c867 100644
--- a/pkg/resources/config/manager/kustomization.yaml
+++ b/install/overlays/kubernetes/single-namespace/operator/kustomization.yaml
@@ -14,17 +14,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ---------------------------------------------------------------------------
-
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 
+namespace: operators
+nameSuffix: -tenant-a
+
 resources:
-- operator-deployment.yaml
-- operator-service-account.yaml
-- builder-service-account.yaml
+- ../../own-namespace
 
 patches:
-  - path: add-registry-envvars.yaml
-    target:
+ - target:
       kind: Deployment
-      name: camel-k-operator
+   path: remove-watch-ns.yaml
+ - path: patch-envvars.yaml
diff --git a/install/overlays/kubernetes/namespaced/patch-toleration.yaml b/install/overlays/kubernetes/single-namespace/operator/patch-envvars.yaml
similarity index 78%
rename from install/overlays/kubernetes/namespaced/patch-toleration.yaml
rename to install/overlays/kubernetes/single-namespace/operator/patch-envvars.yaml
index 71b7f1228..b5ceec673 100644
--- a/install/overlays/kubernetes/namespaced/patch-toleration.yaml
+++ b/install/overlays/kubernetes/single-namespace/operator/patch-envvars.yaml
@@ -22,11 +22,14 @@ metadata:
 spec:
   template:
     spec:
-      tolerations:
-#
-# Add tolerations for configuring the deployment
-# eg.
-#        - key: "key1"
-#          operator: "Equal"
-#          value: "value1"
-#          effect: "NoSchedule"
+      containers:
+        - name: camel-k-operator
+          env:
+            - name: BUILDER_SA
+              value: camel-k-builder-tenant-a
+
+            - name: WATCH_NAMESPACE
+              value: tenant-a
+
+            - name: OPERATOR_ID
+              value: camel-k-tenant-a
\ No newline at end of file
diff --git a/install/overlays/kubernetes/namespaced/patch-operator-id.yaml b/install/overlays/kubernetes/single-namespace/operator/remove-watch-ns.yaml
similarity index 92%
rename from install/overlays/kubernetes/namespaced/patch-operator-id.yaml
rename to install/overlays/kubernetes/single-namespace/operator/remove-watch-ns.yaml
index 25c150420..6a9dc9b98 100644
--- a/install/overlays/kubernetes/namespaced/patch-operator-id.yaml
+++ b/install/overlays/kubernetes/single-namespace/operator/remove-watch-ns.yaml
@@ -15,6 +15,5 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
-- op: add
-  path: /spec/template/spec/containers/0/env/2/value
-  value: camel-k
+- op: remove
+  path: /spec/template/spec/containers/0/env/0
\ No newline at end of file
diff --git a/pkg/resources/config/manager/kustomization.yaml b/install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/kustomization.yaml
similarity index 84%
copy from pkg/resources/config/manager/kustomization.yaml
copy to install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/kustomization.yaml
index a4543333a..0b1aafa9b 100644
--- a/pkg/resources/config/manager/kustomization.yaml
+++ b/install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/kustomization.yaml
@@ -14,17 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ---------------------------------------------------------------------------
-
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 
+namespace: tenant-a
+nameSuffix: -tenant-a
+
 resources:
-- operator-deployment.yaml
-- operator-service-account.yaml
-- builder-service-account.yaml
+- ../../../../base/config/rbac/namespaced
 
 patches:
-  - path: add-registry-envvars.yaml
-    target:
-      kind: Deployment
-      name: camel-k-operator
+ - target:
+      kind: RoleBinding
+   path: patch-rolebinding-subjects.yaml
\ No newline at end of file
diff --git a/install/overlays/kubernetes/namespaced/patch-install-default-kamelets.yaml b/install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/patch-rolebinding-subjects.yaml
similarity index 88%
rename from install/overlays/kubernetes/namespaced/patch-install-default-kamelets.yaml
rename to install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/patch-rolebinding-subjects.yaml
index fa3012076..d60bd2220 100644
--- a/install/overlays/kubernetes/namespaced/patch-install-default-kamelets.yaml
+++ b/install/overlays/kubernetes/single-namespace/tenant-a-ns-rbac/patch-rolebinding-subjects.yaml
@@ -16,7 +16,8 @@
 # ---------------------------------------------------------------------------
 
 - op: add
-  path: /spec/template/spec/containers/0/env/-
-  value:
-    name: KAMEL_INSTALL_DEFAULT_KAMELETS
-    value: "false"
+  path: /subjects/0/namespace
+  value: "operators"
+- op: replace
+  path: /subjects/0/name
+  value: camel-k-operator-tenant-a
\ No newline at end of file
diff --git a/pkg/apis/camel/v1/common_types.go b/pkg/apis/camel/v1/common_types.go
index 1a7cf947a..826a6099b 100644
--- a/pkg/apis/camel/v1/common_types.go
+++ b/pkg/apis/camel/v1/common_types.go
@@ -29,6 +29,8 @@ const (
 	// Deprecated: use .spec.traits instead.
 	TraitAnnotationPrefix = "trait.camel.apache.org/"
 	// OperatorIDAnnotation operator id annotation label.
+	//
+	// Deprecated: will be removed in the future.
 	OperatorIDAnnotation = "camel.apache.org/operator.id"
 	// PlatformSelectorAnnotation platform id annotation label.
 	PlatformSelectorAnnotation = "camel.apache.org/platform.id"
diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go
index e5b991196..1e775567d 100644
--- a/pkg/cmd/bind.go
+++ b/pkg/cmd/bind.go
@@ -62,7 +62,7 @@ func newCmdBind(rootCmdOptions *RootCmdOptions) (*cobra.Command, *bindCmdOptions
 	cmd.Flags().Bool("skip-checks", false, "Do not verify the binding for compliance with Kamelets and other Kubernetes resources")
 	cmd.Flags().StringArray("step", nil, `Add binding steps as Kubernetes resources. Endpoints are expected in the format "[[apigroup/]version:]kind:[namespace/]name", plain Camel URIs or Kamelet name.`)
 	cmd.Flags().StringArrayP("trait", "t", nil, `Add a trait to the corresponding Integration.`)
-	cmd.Flags().StringP("operator-id", "x", "camel-k", "Operator id selected to manage this Pipe.")
+	cmd.Flags().StringP("operator-id", "x", "", "Deprecated. Operator id selected to manage this Pipe.")
 	cmd.Flags().StringArray("annotation", nil, "Add an annotation to the Pipe. E.g. \"--annotation my.company=hello\"")
 	cmd.Flags().String("service-account", "", "The SA to use to run this binding")
 	cmd.Flags().StringArrayP("dependency", "d", nil, `A dependency that should be included, e.g., "camel:mail" for a Camel component, "mvn:org.my:app:1.0" for a Maven dependency`)
@@ -120,10 +120,6 @@ func (o *bindCmdOptions) validate(cmd *cobra.Command, args []string) error {
 		return errors.New("source or sink arguments are missing")
 	}
 
-	if o.OperatorID == "" {
-		return errors.New("cannot use empty operator id")
-	}
-
 	for _, annotation := range o.Annotations {
 		parts := strings.SplitN(annotation, "=", 2)
 		if len(parts) != 2 {
@@ -245,12 +241,20 @@ func (o *bindCmdOptions) run(cmd *cobra.Command, args []string) error {
 	}
 
 	// --operator-id={id} is a syntax sugar for '--annotation camel.apache.org/operator.id={id}'
-	pipe.SetOperatorID(strings.TrimSpace(o.OperatorID))
+	if o.OperatorID != "" {
+		pipe.SetOperatorID(strings.TrimSpace(o.OperatorID))
+	}
 
-	for _, annotation := range o.Annotations {
-		parts := strings.SplitN(annotation, "=", 2)
-		if len(parts) == 2 {
-			pipe.Annotations[parts[0]] = parts[1]
+	if o.Annotations != nil {
+		if pipe.Annotations == nil {
+			pipe.Annotations = map[string]string{}
+		}
+
+		for _, annotation := range o.Annotations {
+			parts := strings.SplitN(annotation, "=", 2)
+			if len(parts) == 2 {
+				pipe.Annotations[parts[0]] = parts[1]
+			}
 		}
 	}
 
diff --git a/pkg/cmd/bind_test.go b/pkg/cmd/bind_test.go
index 1e0396e9d..a6627d666 100644
--- a/pkg/cmd/bind_test.go
+++ b/pkg/cmd/bind_test.go
@@ -55,7 +55,7 @@ func TestBindOutputJSON(t *testing.T) {
 	assert.Equal(t, "json", buildCmdOptions.OutputFormat)
 
 	require.NoError(t, err)
-	assert.Equal(t, `{"kind":"Pipe","apiVersion":"camel.apache.org/v1","metadata":{"name":"my-to-my","annotations":{"camel.apache.org/operator.id":"camel-k"}},"spec":{"source":{"uri":"my:src"},"sink":{"uri":"my:dst"}},"status":{}}`, output)
+	assert.Equal(t, `{"kind":"Pipe","apiVersion":"camel.apache.org/v1","metadata":{"name":"my-to-my"},"spec":{"source":{"uri":"my:src"},"sink":{"uri":"my:dst"}},"status":{}}`, output)
 }
 
 func TestBindOutputYAML(t *testing.T) {
@@ -67,8 +67,6 @@ func TestBindOutputYAML(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   sink:
@@ -97,8 +95,6 @@ func TestBindErrorHandlerDLCKamelet(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   errorHandler:
@@ -128,8 +124,6 @@ func TestBindErrorHandlerNone(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   errorHandler:
@@ -152,8 +146,6 @@ func TestBindErrorHandlerLog(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   errorHandler:
@@ -176,8 +168,6 @@ func TestBindTraits(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   sink:
@@ -202,8 +192,6 @@ func TestBindTraitsArray(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   sink:
@@ -231,8 +219,6 @@ func TestBindSteps(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   sink:
@@ -283,8 +269,6 @@ func TestBindOutputWithDependencies(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Pipe
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-to-my
 spec:
   dependencies:
diff --git a/pkg/cmd/operator/operator.go b/pkg/cmd/operator/operator.go
index 9255c727c..58d11ebbc 100644
--- a/pkg/cmd/operator/operator.go
+++ b/pkg/cmd/operator/operator.go
@@ -19,7 +19,6 @@ package operator
 
 import (
 	"context"
-	"errors"
 	"flag"
 	"fmt"
 	"os"
@@ -38,7 +37,6 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/client-go/tools/leaderelection/resourcelock"
@@ -158,8 +156,7 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 	}
 
 	// Set the operator container image if it runs in-container
-	platform.OperatorImage, err = getOperatorImage(ctx, bootstrapClient)
-	exitOnError(err, "cannot get operator container image")
+	platform.OperatorImage = getOperatorImage()
 
 	if !leaderElection {
 		log.Info("Leader election is disabled!")
@@ -173,11 +170,15 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 		Label: labelsSelector,
 	}
 
+	cacheConfigs := getNamespacesSelector(operatorNamespace, watchNamespace)
 	if !platform.IsCurrentOperatorGlobal() {
+		log.Infof("This operator is configured to watch only %s namespace(s)", watchNamespace)
 		selector = cache.ByObject{
 			Label:      labelsSelector,
-			Namespaces: getNamespacesSelector(operatorNamespace, watchNamespace),
+			Namespaces: cacheConfigs,
 		}
+	} else {
+		log.Info("This operator is global and will watch all namespaces!")
 	}
 
 	selectors := map[ctrl.Object]cache.ByObject{
@@ -186,11 +187,13 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 		&batchv1.Job{}:       selector,
 	}
 
-	if ok, err := kubernetes.IsAPIResourceInstalled(bootstrapClient, servingv1.SchemeGroupVersion.String(), reflect.TypeFor[servingv1.Service]().Name()); ok && err == nil {
+	if ok, err := kubernetes.IsAPIResourceInstalled(bootstrapClient, servingv1.SchemeGroupVersion.String(),
+		reflect.TypeFor[servingv1.Service]().Name()); ok && err == nil {
 		selectors[&servingv1.Service{}] = selector
 	}
 
-	if ok, err := kubernetes.IsAPIResourceInstalled(bootstrapClient, batchv1.SchemeGroupVersion.String(), reflect.TypeFor[batchv1.CronJob]().Name()); ok && err == nil {
+	if ok, err := kubernetes.IsAPIResourceInstalled(bootstrapClient, batchv1.SchemeGroupVersion.String(),
+		reflect.TypeFor[batchv1.CronJob]().Name()); ok && err == nil {
 		selectors[&batchv1.CronJob{}] = selector
 	}
 
@@ -199,7 +202,7 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 	}
 
 	if !platform.IsCurrentOperatorGlobal() {
-		options.DefaultNamespaces = getNamespacesSelector(operatorNamespace, watchNamespace)
+		options.DefaultNamespaces = cacheConfigs
 	}
 
 	mgr, err := manager.New(cfg, manager.Options{
@@ -224,7 +227,7 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 	log.Info("Installing operator resources")
 	installCtx, installCancel := context.WithTimeout(ctx, 1*time.Minute)
 	defer installCancel()
-	install.OperatorStartupOptionalTools(installCtx, bootstrapClient, watchNamespace, operatorNamespace, log)
+	install.OperatorStartupOptionalTools(installCtx, bootstrapClient, log)
 
 	synthEnvVal, synth := os.LookupEnv("CAMEL_K_SYNTHETIC_INTEGRATIONS")
 	if synth && synthEnvVal == "true" {
@@ -238,10 +241,19 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID
 
 func getNamespacesSelector(operatorNamespace string, watchNamespace string) map[string]cache.Config {
 	namespacesSelector := map[string]cache.Config{
+		// The same operator namespace is needed while the operator stores
+		// Builds and IntegrationKits and CamelCatalogs in its namespace
+		// TODO: remove this when we either remove those CR or we move them in the tenant namespace only
 		operatorNamespace: {},
 	}
-	if operatorNamespace != watchNamespace {
-		namespacesSelector[watchNamespace] = cache.Config{}
+
+	for ns := range strings.SplitSeq(watchNamespace, ",") {
+		ns = strings.TrimSpace(ns)
+		if ns == "" || ns == operatorNamespace {
+			continue
+		}
+
+		namespacesSelector[ns] = cache.Config{}
 	}
 
 	return namespacesSelector
@@ -258,24 +270,8 @@ func getWatchNamespace() (string, error) {
 }
 
 // getOperatorImage returns the image currently used by the running operator if present (when running out of cluster, it may be absent).
-func getOperatorImage(ctx context.Context, c ctrl.Reader) (string, error) {
-	ns := platform.GetOperatorNamespace()
-	name := platform.GetOperatorPodName()
-	if ns == "" || name == "" {
-		return "", nil
-	}
-
-	pod := corev1.Pod{}
-	if err := c.Get(ctx, ctrl.ObjectKey{Namespace: ns, Name: name}, &pod); err != nil && k8serrors.IsNotFound(err) {
-		return "", nil
-	} else if err != nil {
-		return "", err
-	}
-	if len(pod.Spec.Containers) == 0 {
-		return "", errors.New("no containers found in operator pod")
-	}
-
-	return pod.Spec.Containers[0].Image, nil
+func getOperatorImage() string {
+	return os.Getenv("CONTAINER_IMAGE")
 }
 
 func exitOnError(err error, msg string) {
diff --git a/pkg/cmd/operator/operator_test.go b/pkg/cmd/operator/operator_test.go
new file mode 100644
index 000000000..00eac0784
--- /dev/null
+++ b/pkg/cmd/operator/operator_test.go
@@ -0,0 +1,150 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package operator
+
+import (
+	"testing"
+
+	"github.com/apache/camel-k/v2/pkg/platform"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+)
+
+func TestGetNamespacesSelector(t *testing.T) {
+	tests := []struct {
+		name              string
+		operatorNamespace string
+		watchNamespace    string
+		expected          map[string]cache.Config
+	}{
+		{
+			name:              "same namespace",
+			operatorNamespace: "operator",
+			watchNamespace:    "operator",
+			expected: map[string]cache.Config{
+				"operator": {},
+			},
+		},
+		{
+			name:              "different namespace",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant":   {},
+			},
+		},
+		{
+			name:              "csv namespaces",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant-a,tenant-b,tenant-c",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant-a": {},
+				"tenant-b": {},
+				"tenant-c": {},
+			},
+		},
+		{
+			name:              "trim spaces",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant-a, tenant-b , tenant-c",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant-a": {},
+				"tenant-b": {},
+				"tenant-c": {},
+			},
+		},
+		{
+			name:              "ignore duplicates",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant-a,tenant-a,tenant-b",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant-a": {},
+				"tenant-b": {},
+			},
+		},
+		{
+			name:              "ignore empty entries",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant-a,,tenant-b,",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant-a": {},
+				"tenant-b": {},
+			},
+		},
+		{
+			name:              "ignore operator namespace in csv",
+			operatorNamespace: "operator",
+			watchNamespace:    "tenant-a,operator,tenant-b",
+			expected: map[string]cache.Config{
+				"operator": {},
+				"tenant-a": {},
+				"tenant-b": {},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := getNamespacesSelector(tt.operatorNamespace, tt.watchNamespace)
+			assert.Equal(t, tt.expected, actual)
+		})
+	}
+}
+
+func TestGetWatchNamespace(t *testing.T) {
+	t.Run("env variable set", func(t *testing.T) {
+		t.Setenv(platform.OperatorWatchNamespaceEnvVariable, "tenant-a,tenant-b")
+
+		ns, err := getWatchNamespace()
+
+		require.NoError(t, err)
+		assert.Equal(t, "tenant-a,tenant-b", ns)
+	})
+
+	t.Run("env variable not set", func(t *testing.T) {
+		ns, err := getWatchNamespace()
+
+		require.Error(t, err)
+		assert.Empty(t, ns)
+		assert.Contains(t, err.Error(), platform.OperatorWatchNamespaceEnvVariable)
+	})
+}
+
+func TestGetOperatorImage(t *testing.T) {
+	t.Run("env variable set", func(t *testing.T) {
+		t.Setenv("CONTAINER_IMAGE", "quay.io/example/operator:latest")
+
+		image := getOperatorImage()
+
+		assert.Equal(t, "quay.io/example/operator:latest", image)
+	})
+
+	t.Run("env variable not set", func(t *testing.T) {
+		t.Setenv("CONTAINER_IMAGE", "")
+
+		image := getOperatorImage()
+
+		assert.Empty(t, image)
+	})
+}
diff --git a/pkg/cmd/promote_test.go b/pkg/cmd/promote_test.go
index 7a128395e..522ec5ab1 100644
--- a/pkg/cmd/promote_test.go
+++ b/pkg/cmd/promote_test.go
@@ -145,8 +145,7 @@ func createTestCamelCatalog(ns string, runtimeProvider v1.RuntimeProvider, versi
 func TestIntegrationWithMetadataDryRun(t *testing.T) {
 	defaultIntegration, defaultKit := nominalIntegration("my-it-test")
 	defaultIntegration.Annotations = map[string]string{
-		"camel.apache.org/operator.id": "camel-k",
-		"my-annotation":                "my-value",
+		"my-annotation": "my-value",
 	}
 	defaultIntegration.Labels = map[string]string{
 		"my-label": "my-value",
@@ -182,8 +181,7 @@ status: {}
 func TestPipeWithMetadataDryRun(t *testing.T) {
 	defaultKB := nominalPipe("my-pipe-test")
 	defaultKB.Annotations = map[string]string{
-		"camel.apache.org/operator.id": "camel-k",
-		"my-annotation":                "my-value",
+		"my-annotation": "my-value",
 	}
 	defaultKB.Labels = map[string]string{
 		"my-label": "my-value",
@@ -334,8 +332,7 @@ status: {}
 func TestPipeWithSavedTraitsDryRun(t *testing.T) {
 	defaultKB := nominalPipe("my-pipe-test")
 	defaultKB.Annotations = map[string]string{
-		"camel.apache.org/operator.id": "camel-k",
-		"my-annotation":                "my-value",
+		"my-annotation": "my-value",
 	}
 	defaultKB.Labels = map[string]string{
 		"my-label": "my-value",
@@ -614,8 +611,7 @@ resources:
 func TestPipeGitOps(t *testing.T) {
 	defaultPipe := nominalPipe("my-pipe-test")
 	defaultPipe.Annotations = map[string]string{
-		"camel.apache.org/operator.id": "camel-k",
-		"my-annotation":                "my-value",
+		"my-annotation": "my-value",
 	}
 	defaultPipe.Labels = map[string]string{
 		"my-label": "my-value",
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 87ef58382..4a008edf4 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -97,7 +97,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions)
 	cmd.Flags().Bool("sync", false, "[Deprecated] Synchronize the local source file with the cluster, republishing at each change")
 	cmd.Flags().Bool("dev", false, "[Deprecated] Enable Dev mode (equivalent to \"-w --logs --sync\")")
 	cmd.Flags().Bool("use-flows", true, "Write yaml sources as Flow objects in the integration custom resource")
-	cmd.Flags().StringP("operator-id", "x", "camel-k", "Operator id selected to manage this integration.")
+	cmd.Flags().StringP("operator-id", "x", "", "Operator id selected to manage this integration.")
 	cmd.Flags().String("profile", "", "Trait profile used for deployment")
 	cmd.Flags().String("integration-profile", "", "Integration profile used for deployment")
 	cmd.Flags().StringArrayP("trait", "t", nil, "Configure a trait. E.g. \"-t service.enabled=false\"")
@@ -246,10 +246,6 @@ func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
 }
 
 func (o *runCmdOptions) validate(cmd *cobra.Command) error {
-	if o.OperatorID == "" {
-		return errors.New("cannot use empty operator id")
-	}
-
 	for _, volume := range o.Volumes {
 		volumeConfig := strings.Split(volume, ":")
 		if len(volumeConfig) != 2 || len(strings.TrimSpace(volumeConfig[0])) == 0 || len(strings.TrimSpace(volumeConfig[1])) == 0 {
@@ -719,7 +715,9 @@ func (o *runCmdOptions) applyAnnotations(it *v1.Integration) {
 	}
 
 	// --operator-id={id} is a syntax sugar for '--annotation camel.apache.org/operator.id={id}'
-	it.SetOperatorID(strings.TrimSpace(o.OperatorID))
+	if o.OperatorID != "" {
+		it.SetOperatorID(strings.TrimSpace(o.OperatorID))
+	}
 
 	// --integration-profile={id} is a syntax sugar for '--annotation camel.apache.org/integration-profile.id={id}'
 	if o.IntegrationProfile != "" {
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 39dc64c5d..73873a7de 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -578,8 +578,6 @@ func TestOutputYaml(t *testing.T) {
 	assert.Equal(t, fmt.Sprintf(`apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: %s
 spec:
   sources:
@@ -612,8 +610,6 @@ func TestTrait(t *testing.T) {
 	assert.Equal(t, fmt.Sprintf(`apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: %s
 spec:
   sources:
@@ -919,8 +915,6 @@ func TestSelfManagedBuildIntegration(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-app-v1
 spec:
   traits:
@@ -942,8 +936,6 @@ func TestGitRepoIntegration(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-it
 spec:
   git:
@@ -962,8 +954,6 @@ func TestGitTagIntegration(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-it
 spec:
   git:
@@ -983,8 +973,6 @@ func TestGitBranchIntegration(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-it
 spec:
   git:
@@ -1004,8 +992,6 @@ func TestGitCommitIntegration(t *testing.T) {
 	assert.Equal(t, `apiVersion: camel.apache.org/v1
 kind: Integration
 metadata:
-  annotations:
-    camel.apache.org/operator.id: camel-k
   name: my-it
 spec:
   git:
diff --git a/pkg/controller/integration/build.go b/pkg/controller/integration/build.go
index 64cf10133..6c57004e9 100644
--- a/pkg/controller/integration/build.go
+++ b/pkg/controller/integration/build.go
@@ -100,6 +100,7 @@ func (action *buildAction) createBuild(ctx context.Context, it *v1.Integration)
 
 	operatorID := defaults.OperatorID()
 	if operatorID != "" {
+		//nolint:staticcheck
 		annotations[v1.OperatorIDAnnotation] = operatorID
 	}
 
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index 612d30675..10a8c7c35 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -149,12 +149,7 @@ func integrationKitEnqueueRequestsFromMapFunc(ctx context.Context, c client.Clie
 	}
 
 	list := &v1.IntegrationList{}
-	// Do global search in case of global operator (it may be using a global platform)
-	var opts []ctrl.ListOption
-	if !platform.IsCurrentOperatorGlobal() {
-		opts = append(opts, ctrl.InNamespace(kit.Namespace))
-	}
-	if err := c.List(ctx, list, opts...); err != nil {
+	if err := c.List(ctx, list); err != nil {
 		log.Error(err, "Failed to retrieve integration list")
 
 		return requests
@@ -452,7 +447,7 @@ func watchKnativeResources(ctx context.Context, c client.Client, b *builder.Buil
 		b.Owns(&servingv1.Service{}, builder.WithPredicates(StatusChangedPredicate{}))
 	} else {
 		log.Info("KnativeService resources installed in the cluster. However Camel K operator has not the required RBAC privileges. " +
-			"You can't use Knative features.Make sure to apply the required RBAC privileges and restart the Camel K Operator Pod to be able " +
+			"You can't use Knative features. Make sure to apply the required RBAC privileges and restart the Camel K Operator Pod to be able " +
 			"to watch for Camel K managed Knative Services.")
 	}
 
diff --git a/pkg/controller/integrationkit/build.go b/pkg/controller/integrationkit/build.go
index b7f41c83e..ab830f8a9 100644
--- a/pkg/controller/integrationkit/build.go
+++ b/pkg/controller/integrationkit/build.go
@@ -128,6 +128,7 @@ func (action *buildAction) createBuild(ctx context.Context, kit *v1.IntegrationK
 
 	operatorID := defaults.OperatorID()
 	if operatorID != "" {
+		//nolint:staticcheck
 		annotations[v1.OperatorIDAnnotation] = operatorID
 	}
 
diff --git a/pkg/controller/integrationkit/integrationkit_controller.go b/pkg/controller/integrationkit/integrationkit_controller.go
index 5fb5f2fd3..12c9c171d 100644
--- a/pkg/controller/integrationkit/integrationkit_controller.go
+++ b/pkg/controller/integrationkit/integrationkit_controller.go
@@ -153,6 +153,7 @@ func add(_ context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 
 							continue
 						}
+						//nolint:staticcheck
 						if v, ok := kit.Annotations[v1.OperatorIDAnnotation]; ok && v != itp.Name {
 							// kit waiting for another platform to become ready - skip here
 							log.Debugf("Integration kit %s is waiting for another integration platform '%s' - skip it now", kit.Name, v)
diff --git a/pkg/install/common.go b/pkg/install/common.go
deleted file mode 100644
index a2f620be4..000000000
--- a/pkg/install/common.go
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package install
-
-import (
-	networking "k8s.io/api/networking/v1"
-	rbacv1 "k8s.io/api/rbac/v1"
-
-	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-// ResourceCustomizer can be used to inject code that changes the objects before they are created.
-type ResourceCustomizer func(object ctrl.Object) ctrl.Object
-
-// IdentityResourceCustomizer is a ResourceCustomizer that does nothing.
-var IdentityResourceCustomizer = func(object ctrl.Object) ctrl.Object {
-	return object
-}
-
-var RemoveIngressRoleCustomizer = func(object ctrl.Object) ctrl.Object {
-	if role, ok := object.(*rbacv1.Role); ok && role.Name == "camel-k-operator" {
-	rules:
-		for i, rule := range role.Rules {
-			for _, group := range rule.APIGroups {
-				if group == networking.GroupName {
-					role.Rules = append(role.Rules[:i], role.Rules[i+1:]...)
-
-					break rules
-				}
-			}
-		}
-	}
-
-	return object
-}
diff --git a/pkg/install/optional.go b/pkg/install/optional.go
index 11b23d97c..764c5ece3 100644
--- a/pkg/install/optional.go
+++ b/pkg/install/optional.go
@@ -27,7 +27,7 @@ import (
 )
 
 // OperatorStartupOptionalTools tries to install optional tools at operator startup and warns if something goes wrong.
-func OperatorStartupOptionalTools(ctx context.Context, c client.Client, namespace string, operatorNamespace string, log logutil.Logger) {
+func OperatorStartupOptionalTools(ctx context.Context, c client.Client, log logutil.Logger) {
 	// Try to register the OpenShift CLI Download link if possible
 	if err := OpenShiftConsoleDownloadLink(ctx, c); err != nil {
 		log.Info("Cannot install OpenShift CLI download link: skipping.")
diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go
index 28175c98a..22538128e 100644
--- a/pkg/platform/defaults.go
+++ b/pkg/platform/defaults.go
@@ -19,6 +19,7 @@ package platform
 
 import (
 	"context"
+	"os"
 	"runtime"
 	"strings"
 	"time"
@@ -39,7 +40,6 @@ import (
 
 const (
 	DefaultPlatformName                    = "camel-k"
-	BuilderServiceAccount                  = "camel-k-builder"
 	DefaultBuildTimeout                    = 5 * time.Minute
 	DefaultBuildStrategy                   = v1.BuildStrategyRoutine
 	DefaultBuildOrderStrategy              = v1.BuildOrderStrategyDependencies
@@ -49,6 +49,17 @@ const (
 	DefaultMaxRunningBuildsRoutineStrategy = 3
 )
 
+var BuilderServiceAccount = getBuilderServiceAccount()
+
+func getBuilderServiceAccount() string {
+	bsa := os.Getenv("BUILDER_SA")
+	if bsa == "" {
+		bsa = "camel-k-builder"
+	}
+
+	return bsa
+}
+
 // ConfigureDefaults fills with default values all missing details about the integration platform.
 // Defaults are set in the status fields, not in the spec.
 //
diff --git a/pkg/platform/operator.go b/pkg/platform/operator.go
index ad9275a67..16aa0c863 100644
--- a/pkg/platform/operator.go
+++ b/pkg/platform/operator.go
@@ -150,20 +150,7 @@ func IsOperatorHandler(object ctrl.Object) bool {
 		return true
 	}
 
-	// check if we are dealing with resource that is missing a proper operator id annotation
-	if resourceID == "" {
-		// allow default global operator to handle legacy resources (missing proper operator id annotations)
-		if operatorID == DefaultPlatformName {
-			return true
-		}
-
-		// allow local operators to handle legacy resources (missing proper operator id annotations)
-		if !IsCurrentOperatorGlobal() {
-			return true
-		}
-	}
-
-	return false
+	return resourceID == ""
 }
 
 // IsOperatorHandlerConsideringLock uses normal IsOperatorHandler checks and adds additional check for legacy resources
diff --git a/pkg/resources/config/manager/kustomization.yaml b/pkg/resources/config/manager/kustomization.yaml
index a4543333a..b067e2c33 100644
--- a/pkg/resources/config/manager/kustomization.yaml
+++ b/pkg/resources/config/manager/kustomization.yaml
@@ -28,3 +28,13 @@ patches:
     target:
       kind: Deployment
       name: camel-k-operator
+
+replacements:
+  - source:
+      kind: Deployment
+      fieldPath: spec.template.spec.containers.[name=camel-k-operator].image
+    targets:
+      - select:
+          kind: Deployment
+        fieldPaths:
+          - spec.template.spec.containers.[name=camel-k-operator].env.[name=CONTAINER_IMAGE].value
diff --git a/pkg/resources/config/manager/operator-deployment.yaml b/pkg/resources/config/manager/operator-deployment.yaml
index d395d64ab..1331ea35d 100644
--- a/pkg/resources/config/manager/operator-deployment.yaml
+++ b/pkg/resources/config/manager/operator-deployment.yaml
@@ -77,6 +77,12 @@ spec:
             # Note: remove the variable to disable the feature.
             - name: CAMEL_MONITOR_OPERATOR_LABEL
               value: "camel.apache.org/monitor"
+            # Used to query which is the image this operator is running
+            - name: CONTAINER_IMAGE
+              value: ""
+            # You can provide a different SA for builder Pods
+            - name: BUILDER_SA
+              value: "camel-k-builder"
           # Attempt to read bootstrap configuration from configmap or secret
           envFrom:
             - configMapRef:
diff --git a/pkg/util/gitops/gitops.go b/pkg/util/gitops/gitops.go
index 16b4f40d1..c16214216 100644
--- a/pkg/util/gitops/gitops.go
+++ b/pkg/util/gitops/gitops.go
@@ -123,6 +123,7 @@ func cloneAnnotations(ann map[string]string, operatorID string) map[string]strin
 		if k == "kubectl.kubernetes.io/last-applied-configuration" {
 			continue
 		}
+		//nolint:staticcheck
 		if k == v1.OperatorIDAnnotation {
 			if operatorID != "" {
 				newMap[v1.OperatorIDAnnotation] = operatorID
@@ -132,6 +133,7 @@ func cloneAnnotations(ann map[string]string, operatorID string) map[string]strin
 			newMap[k] = v
 		}
 	}
+	//nolint:staticcheck
 	if !operatorIDAnnotationSet && operatorID != "" {
 		newMap[v1.OperatorIDAnnotation] = operatorID
 	}
@@ -213,6 +215,7 @@ func AppendKustomizeIntegration(dstIt *v1.Integration, destinationDir string, ov
 	baseIt := dstIt.DeepCopy()
 	baseIt.Namespace = ""
 	if baseIt.Annotations != nil {
+		//nolint:staticcheck
 		delete(baseIt.Annotations, v1.OperatorIDAnnotation)
 	}
 	appFolderName := strings.ToLower(baseIt.Name)
@@ -417,6 +420,7 @@ func AppendKustomizePipe(dstPipe *v1.Pipe, destinationDir string, overwrite bool
 	basePipe := dstPipe.DeepCopy()
 	basePipe.Namespace = ""
 	if basePipe.Annotations != nil {
+		//nolint:staticcheck
 		delete(basePipe.Annotations, v1.OperatorIDAnnotation)
 	}
 	appFolderName := strings.ToLower(basePipe.Name)
diff --git a/script/Makefile b/script/Makefile
index 8753ad6bf..f1acf5ed8 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -804,7 +804,7 @@ install-k8s-global: KUSTOMIZE_DIR="install/overlays/kubernetes/descoped"
 install-k8s-global: clone-kustomize-dir set-operator-id set-operator-env install-operator
 
 install-k8s-ns: DEFAULT_NS="default"
-install-k8s-ns: KUSTOMIZE_DIR="install/overlays/kubernetes/namespaced"
+install-k8s-ns: KUSTOMIZE_DIR="install/overlays/kubernetes/own-namespace"
 install-k8s-ns: clone-kustomize-dir set-operator-id set-operator-env install-operator
 
 install-registry: NAMESPACE="camel-k"