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 <[email protected]> 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"
