diff --git a/cmd/main.go b/cmd/main.go index 5c2e52707da..d7a15182bd1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -307,12 +307,19 @@ func main() { } } + pluginNamespace, err := util.GetOperatorNamespace() + if err != nil { + setupLog.Error(err, "Error retrieving operator's running namespace") + os.Exit(1) + } + if util.IsOpenShiftCluster() { if err = (&controllers.ReconcileGitopsService{ Client: client, Scheme: mgr.GetScheme(), DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", CentralTLSProfile: profile, + PluginNamespace: pluginNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GitopsService") os.Exit(1) diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index db27fa86f5c..a470fc506e1 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -135,7 +135,7 @@ func getPluginPodSpec(crImagePullPolicy corev1.PullPolicy) corev1.PodSpec { return podSpec } -func pluginDeployment(crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { +func pluginDeployment(namespace string, crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { podSpec := getPluginPodSpec(crImagePullPolicy) template := corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -149,13 +149,13 @@ func pluginDeployment(crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: gitopsPluginName, - Namespace: serviceNamespace, + Namespace: namespace, Labels: map[string]string{ kubeAppLabelApp: gitopsPluginName, kubeAppLabelComponent: gitopsPluginName, kubeAppLabelInstance: gitopsPluginName, kubeAppLabelPartOf: gitopsPluginName, - kubeAppLabelRuntimeNamespace: serviceNamespace, + kubeAppLabelRuntimeNamespace: namespace, }, }, Spec: appsv1.DeploymentSpec{ @@ -170,7 +170,7 @@ func pluginDeployment(crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { } } -func consolePlugin() *consolev1.ConsolePlugin { +func consolePlugin(namespace string) *consolev1.ConsolePlugin { return &consolev1.ConsolePlugin{ ObjectMeta: metav1.ObjectMeta{ Name: gitopsPluginName, @@ -181,7 +181,7 @@ func consolePlugin() *consolev1.ConsolePlugin { Type: consolev1.Service, Service: &consolev1.ConsolePluginService{ Name: gitopsPluginName, - Namespace: serviceNamespace, + Namespace: namespace, Port: servicePort, BasePath: "/", }, @@ -193,7 +193,7 @@ func consolePlugin() *consolev1.ConsolePlugin { } } -func pluginService() *corev1.Service { +func pluginService(namespace string) *corev1.Service { spec := corev1.ServiceSpec{ Selector: map[string]string{ kubeAppLabelApp: gitopsPluginName, @@ -209,7 +209,7 @@ func pluginService() *corev1.Service { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: gitopsPluginName, - Namespace: serviceNamespace, + Namespace: namespace, Labels: map[string]string{ kubeAppLabelApp: gitopsPluginName, kubeAppLabelComponent: gitopsPluginName, @@ -251,11 +251,11 @@ ServerRoot "/etc/httpd" SSLCertificateKeyFile "/etc/httpd-ssl/private/tls.key" `, servicePort, servicePort) -func pluginConfigMap() *corev1.ConfigMap { +func pluginConfigMap(namespace string) *corev1.ConfigMap { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: httpdConfigMapName, - Namespace: serviceNamespace, + Namespace: namespace, Labels: map[string]string{ kubeAppLabelApp: gitopsPluginName, kubeAppLabelPartOf: gitopsPluginName, @@ -339,7 +339,7 @@ func sortTolerations(tolerations []corev1.Toleration) []corev1.Toleration { func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - newPluginDeployment := pluginDeployment(cr.Spec.ImagePullPolicy) + newPluginDeployment := pluginDeployment(r.PluginNamespace, cr.Spec.ImagePullPolicy) if err := controllerutil.SetControllerReference(cr, newPluginDeployment, r.Scheme); err != nil { return reconcile.Result{}, err @@ -412,7 +412,7 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop func (r *ReconcileGitopsService) reconcileService(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - pluginServiceRef := pluginService() + pluginServiceRef := pluginService(r.PluginNamespace) // Set GitopsService instance as the owner and controller if err := controllerutil.SetControllerReference(instance, pluginServiceRef, r.Scheme); err != nil { return reconcile.Result{}, err @@ -444,7 +444,7 @@ func (r *ReconcileGitopsService) reconcileService(instance *pipelinesv1alpha1.Gi existingServiceRef.Labels = pluginServiceRef.Labels existingServiceRef.Spec.Selector = pluginServiceRef.Spec.Selector existingServiceRef.Spec.Ports = pluginServiceRef.Spec.Ports - return reconcile.Result{}, r.Client.Update(context.TODO(), pluginServiceRef) + return reconcile.Result{}, r.Client.Update(context.TODO(), existingServiceRef) } } return reconcile.Result{}, nil @@ -452,7 +452,7 @@ func (r *ReconcileGitopsService) reconcileService(instance *pipelinesv1alpha1.Gi func (r *ReconcileGitopsService) reconcileConsolePlugin(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - newConsolePlugin := consolePlugin() + newConsolePlugin := consolePlugin(r.PluginNamespace) if err := controllerutil.SetControllerReference(instance, newConsolePlugin, r.Scheme); err != nil { return reconcile.Result{}, err @@ -463,7 +463,7 @@ func (r *ReconcileGitopsService) reconcileConsolePlugin(instance *pipelinesv1alp if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName}, existingPlugin); err != nil { if errors.IsNotFound(err) { - reqLogger.Info("Creating a new ConsolePlugin", "Namespace", serviceNamespace, "Name", gitopsPluginName) + reqLogger.Info("Creating a new ConsolePlugin", "Namespace", r.PluginNamespace, "Name", gitopsPluginName) err = r.Client.Create(context.TODO(), newConsolePlugin) if err != nil { reqLogger.Error(err, "Error creating a new console plugin", @@ -481,7 +481,7 @@ func (r *ReconcileGitopsService) reconcileConsolePlugin(instance *pipelinesv1alp reqLogger.Info("Reconciling Console Plugin", "Namespace", existingPlugin.Namespace, "Name", existingPlugin.Name) existingPlugin.Spec.DisplayName = newConsolePlugin.Spec.DisplayName existingPlugin.Spec.Backend.Service = newConsolePlugin.Spec.Backend.Service - return reconcile.Result{}, r.Client.Update(context.TODO(), newConsolePlugin) + return reconcile.Result{}, r.Client.Update(context.TODO(), existingPlugin) } } return reconcile.Result{}, nil @@ -489,7 +489,7 @@ func (r *ReconcileGitopsService) reconcileConsolePlugin(instance *pipelinesv1alp func (r *ReconcileGitopsService) reconcileConfigMap(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - newPluginConfigMap := pluginConfigMap() + newPluginConfigMap := pluginConfigMap(r.PluginNamespace) if err := controllerutil.SetControllerReference(instance, newPluginConfigMap, r.Scheme); err != nil { return reconcile.Result{}, err @@ -515,12 +515,47 @@ func (r *ReconcileGitopsService) reconcileConfigMap(instance *pipelinesv1alpha1. reqLogger.Info("Reconciling plugin configMap", "Namespace", existingPluginConfigMap.Namespace, "Name", existingPluginConfigMap.Name) existingPluginConfigMap.Data = newPluginConfigMap.Data existingPluginConfigMap.Labels = newPluginConfigMap.Labels - return reconcile.Result{}, r.Client.Update(context.TODO(), newPluginConfigMap) + return reconcile.Result{}, r.Client.Update(context.TODO(), existingPluginConfigMap) } } return reconcile.Result{}, nil } +// cleanupOldPluginResources removes plugin resources from the old namespace (openshift-gitops) after they have been moved to the operator namespace, since owner references on the cluster-scoped GitopsService CR won't trigger garbage collection. +func (r *ReconcileGitopsService) cleanupOldPluginResources(ctx context.Context) error { + if r.PluginNamespace == serviceNamespace { + return nil + } + + reqLogger := logs.WithValues() + + oldDeploy := &appsv1.Deployment{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, oldDeploy); err == nil { + reqLogger.Info("Cleaning up old plugin Deployment from previous namespace", "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, oldDeploy); err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete old plugin Deployment from namespace %s: %w", serviceNamespace, err) + } + } + + oldSvc := &corev1.Service{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, oldSvc); err == nil { + reqLogger.Info("Cleaning up old plugin Service from previous namespace", "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, oldSvc); err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete old plugin Service from namespace %s: %w", serviceNamespace, err) + } + } + + oldCM := &corev1.ConfigMap{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: httpdConfigMapName, Namespace: serviceNamespace}, oldCM); err == nil { + reqLogger.Info("Cleaning up old plugin ConfigMap from previous namespace", "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, oldCM); err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete old plugin ConfigMap from namespace %s: %w", serviceNamespace, err) + } + } + + return nil +} + // is this func the reconciler enty point to reconcile the current plugin state? func (r *ReconcileGitopsService) reconcilePlugin(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go index 8624f383d89..88395773c70 100644 --- a/controllers/consoleplugin_test.go +++ b/controllers/consoleplugin_test.go @@ -22,7 +22,7 @@ import ( ) func TestPlugin(t *testing.T) { - testConsolePlugin := consolePlugin() + testConsolePlugin := consolePlugin(serviceNamespace) testDisplayName := displayName assert.Equal(t, testConsolePlugin.Spec.DisplayName, testDisplayName) diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 6c434d93fb0..d8d2820deb8 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -150,6 +150,10 @@ type ReconcileGitopsService struct { DisableDefaultInstall bool //CentralTLSProfile contains MinVersion and CipherSuites CentralTLSProfile configv1.TLSProfileSpec + + // PluginNamespace is the namespace where console plugin resources (Deployment, Service, ConfigMap) are deployed. + // This is typically the operator's own namespace (e.g. "openshift-gitops-operator"). + PluginNamespace string } // +kubebuilder:rbac:groups=config.openshift.io,resources=authentications,verbs=get;list;watch @@ -299,6 +303,25 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil return result, err } + if r.PluginNamespace != namespace { + pluginNS := &corev1.Namespace{} + err = r.Client.Get(ctx, types.NamespacedName{Name: r.PluginNamespace}, pluginNS) + if err != nil { + if errors.IsNotFound(err) { + reqLogger.Info("Creating plugin namespace", "Name", r.PluginNamespace) + pluginNS = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: r.PluginNamespace}, + } + err = r.Client.Create(ctx, pluginNS) + if err != nil { + return reconcile.Result{}, err + } + } else { + return reconcile.Result{}, err + } + } + } + dynamicPluginStartOCPVersion := os.Getenv(dynamicPluginStartOCPVersionEnv) if dynamicPluginStartOCPVersion == "" { dynamicPluginStartOCPVersion = common.DefaultDynamicPluginStartOCPVersion @@ -330,9 +353,18 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil if realMajorVersion < startMajorVersion || (realMajorVersion == startMajorVersion && realMinorVersion < startMinorVersion) { // Skip plugin reconciliation if real OCP version is less than dynamic plugin start OCP version return reconcile.Result{}, nil - } else { - return r.reconcilePlugin(instance, request) } + + result, err := r.reconcilePlugin(instance, request) + if err != nil { + return result, err + } + + if err := r.cleanupOldPluginResources(ctx); err != nil { + return reconcile.Result{}, err + } + + return result, nil } // Detect the unsupported KAM components across Deployments , Routes , Services and deletes them to perform cleanup as KAM is no longer supported since 1.15 diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index 028aca5f7ed..efd9cdb7ffe 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -1133,8 +1133,9 @@ func addKnownTypesToScheme(scheme *runtime.Scheme) { func newReconcileGitOpsService(client client.Client, scheme *runtime.Scheme) *ReconcileGitopsService { return &ReconcileGitopsService{ - Client: client, - Scheme: scheme, + Client: client, + Scheme: scheme, + PluginNamespace: serviceNamespace, } } diff --git a/controllers/util/util.go b/controllers/util/util.go index ebde45f5f6c..6eb7e8105b4 100644 --- a/controllers/util/util.go +++ b/controllers/util/util.go @@ -41,7 +41,9 @@ import ( ) const ( - clusterVersionName = "version" + clusterVersionName = "version" + operatorPodNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + DefaultOperatorNamespace = "openshift-gitops-operator" ) var ( @@ -294,3 +296,17 @@ func AddSeccompProfileForOpenShift(client client.Client, podspec *corev1.PodSpec } } } + +// GetOperatorNamespace returns the namespace the operator is running in by reading +// the serviceaccount namespace file. If the file is not found (e.g. running locally), +// it returns the default operator namespace and a nil error. +func GetOperatorNamespace() (string, error) { + data, err := os.ReadFile(operatorPodNamespacePath) + if err != nil { + if os.IsNotExist(err) { + return DefaultOperatorNamespace, nil + } + return "", fmt.Errorf("error retrieving operator namespace: %w", err) + } + return strings.TrimSpace(string(data)), nil +} diff --git a/test/e2e/suite_test.go b/test/e2e/suite_test.go index b39fcfb2d89..0e72419294a 100644 --- a/test/e2e/suite_test.go +++ b/test/e2e/suite_test.go @@ -146,9 +146,13 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) + pluginNamespace, err := util.GetOperatorNamespace() + Expect(err).NotTo(HaveOccurred(), "Error retrieving operator's running namespace") + err = (&controllers.ReconcileGitopsService{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + PluginNamespace: pluginNamespace, }).SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred()) diff --git a/test/nondefaulte2e/suite_test.go b/test/nondefaulte2e/suite_test.go index a0a4c14b473..e6e6916d36c 100644 --- a/test/nondefaulte2e/suite_test.go +++ b/test/nondefaulte2e/suite_test.go @@ -132,10 +132,14 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) + pluginNamespace, err := util.GetOperatorNamespace() + Expect(err).NotTo(HaveOccurred(), "Error retrieving operator's running namespace") + err = (&controllers.ReconcileGitopsService{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", + PluginNamespace: pluginNamespace, }).SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred()) diff --git a/test/openshift/e2e/ginkgo/sequential/1-085_validate_dynamic_plugin_installation_test.go b/test/openshift/e2e/ginkgo/sequential/1-085_validate_dynamic_plugin_installation_test.go index fa4032f1017..ef0a3a16689 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-085_validate_dynamic_plugin_installation_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-085_validate_dynamic_plugin_installation_test.go @@ -97,21 +97,21 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { By("verifying the plugin's Deployment, ConfigMap, Secret, Service, and other resources have expected values") - depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) - configMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "httpd-cfg", Namespace: "openshift-gitops"}} + configMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "httpd-cfg", Namespace: "openshift-gitops-operator"}} Eventually(configMap).Should(k8sFixture.ExistByName()) Expect(configMap).To( And(k8sFixture.HaveLabelWithValue("app", "gitops-plugin"), k8sFixture.HaveLabelWithValue("app.kubernetes.io/part-of", "gitops-plugin"))) - secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "console-serving-cert", Namespace: "openshift-gitops"}} + secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "console-serving-cert", Namespace: "openshift-gitops-operator"}} Eventually(secret).Should(k8sFixture.ExistByName()) - service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(service).Should(k8sFixture.ExistByName()) match := false diff --git a/test/openshift/e2e/ginkgo/sequential/1-115_validate_imagepullpolicy_console_plugin_test.go b/test/openshift/e2e/ginkgo/sequential/1-115_validate_imagepullpolicy_console_plugin_test.go index 3c47d546a61..394cdbf104a 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-115_validate_imagepullpolicy_console_plugin_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-115_validate_imagepullpolicy_console_plugin_test.go @@ -92,7 +92,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { }) By("verifying console plugin deployment has ImagePullPolicy set to Always") - pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: argoCD.Namespace}} + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(pluginDepl).Should(k8sFixture.ExistByName()) Eventually(func() bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) @@ -243,7 +243,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { }, "3m", "5s").Should(BeTrue()) By("verifying plugin deployment defaults to IfNotPresent") - pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: argoCD.Namespace}} + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(pluginDepl).Should(k8sFixture.ExistByName()) Eventually(func() bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) diff --git a/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go b/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go index d28b82549ef..f4af848be20 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go @@ -43,8 +43,8 @@ func addDynamicPluginEnv(csv *olmv1alpha1.ClusterServiceVersion, ocVersion strin }) } -func verifyResourceConstraints(k8sClient client.Client, deplName string, expectedReq, expectedLim corev1.ResourceList) { - depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: "openshift-gitops"}} +func verifyResourceConstraints(k8sClient client.Client, deplName, namespace string, expectedReq, expectedLim corev1.ResourceList) { + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: namespace}} Eventually(func() corev1.ResourceRequirements { _ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(depl), depl) containers := depl.Spec.Template.Spec.Containers @@ -86,7 +86,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } addDynamicPluginEnv(csv, ocVersion) - depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) @@ -150,8 +150,8 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { corev1.ResourceCPU: resource.MustParse("300m"), corev1.ResourceMemory: resource.MustParse("400Mi"), } - verifyResourceConstraints(k8sClient, "gitops-plugin", expectedReq, expectedLim) - verifyResourceConstraints(k8sClient, "cluster", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "gitops-plugin", "openshift-gitops-operator", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "cluster", "openshift-gitops", expectedReq, expectedLim) }) It("validates that GitOpsService can update resource constraints", func() { @@ -167,7 +167,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } addDynamicPluginEnv(csv, ocVersion) - depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) @@ -221,8 +221,8 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { corev1.ResourceCPU: resource.MustParse("345m"), corev1.ResourceMemory: resource.MustParse("456Mi"), } - verifyResourceConstraints(k8sClient, "gitops-plugin", expectedReq, expectedLim) - verifyResourceConstraints(k8sClient, "cluster", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "gitops-plugin", "openshift-gitops-operator", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "cluster", "openshift-gitops", expectedReq, expectedLim) }) It("validates gitops plugin and backend can have different resource constraints", func() { @@ -238,7 +238,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } addDynamicPluginEnv(csv, ocVersion) - depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops-operator"}} Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) @@ -283,7 +283,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { }) }() k8sClient, _ := utils.GetE2ETestKubeClient() - verifyResourceConstraints(k8sClient, "gitops-plugin", + verifyResourceConstraints(k8sClient, "gitops-plugin", "openshift-gitops-operator", corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("223m"), corev1.ResourceMemory: resource.MustParse("334Mi"), @@ -293,7 +293,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { corev1.ResourceMemory: resource.MustParse("556Mi"), }, ) - verifyResourceConstraints(k8sClient, "cluster", + verifyResourceConstraints(k8sClient, "cluster", "openshift-gitops", corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("123m"), corev1.ResourceMemory: resource.MustParse("234Mi"), diff --git a/test/openshift/e2e/ginkgo/sequential/1-123_validate_list_order_comparison_test.go b/test/openshift/e2e/ginkgo/sequential/1-123_validate_list_order_comparison_test.go index 929001ef6d8..0ae478ddec8 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-123_validate_list_order_comparison_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-123_validate_list_order_comparison_test.go @@ -42,6 +42,7 @@ import ( const testReconciliationTriggerAnnotation = "test-reconciliation-trigger" const gitopsPluginDeploymentName = "gitops-plugin" const openshiftGitopsNamespace = "openshift-gitops" +const openshiftGitopsOperatorNamespace = "openshift-gitops-operator" // ocpVersionLessThanPluginMin returns true when OCP version is below the plugin-reconcile minimum. // Same check as gitopsservice_controller.go (realMajorVersion < startMajorVersion || (realMajorVersion == startMajorVersion && realMinorVersion < startMinorVersion)). @@ -99,7 +100,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { if CurrentSpecReport().Failed() { kubeClient, _, err := fixtureUtils.GetE2ETestKubeClientWithError() if err == nil { - pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: gitopsPluginDeploymentName, Namespace: openshiftGitopsNamespace}} + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: gitopsPluginDeploymentName, Namespace: openshiftGitopsOperatorNamespace}} pluginErr := kubeClient.Get(context.Background(), client.ObjectKeyFromObject(pluginDepl), pluginDepl) deplGen, observedGen := int64(0), int64(0) if pluginErr == nil { @@ -126,13 +127,13 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { pluginDeployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: gitopsPluginDeploymentName, - Namespace: openshiftGitopsNamespace, + Namespace: openshiftGitopsOperatorNamespace, }, } Eventually(pluginDeployment, "5m", "5s").Should(k8sFixture.ExistByName(), - "deployment %s never showed in %s (5m)", gitopsPluginDeploymentName, openshiftGitopsNamespace) + "deployment %s never showed in %s (5m)", gitopsPluginDeploymentName, openshiftGitopsOperatorNamespace) Eventually(pluginDeployment, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1), - "deployment %s in %s not ready after 60s", gitopsPluginDeploymentName, openshiftGitopsNamespace) + "deployment %s in %s not ready after 60s", gitopsPluginDeploymentName, openshiftGitopsOperatorNamespace) By("capturing initial state before simulating etcd order change") Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDeployment), pluginDeployment)).To(Succeed()) @@ -223,13 +224,13 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { pluginDeployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: gitopsPluginDeploymentName, - Namespace: openshiftGitopsNamespace, + Namespace: openshiftGitopsOperatorNamespace, }, } Eventually(pluginDeployment, "5m", "5s").Should(k8sFixture.ExistByName(), - "deployment %s never showed in %s (5m)", gitopsPluginDeploymentName, openshiftGitopsNamespace) + "deployment %s never showed in %s (5m)", gitopsPluginDeploymentName, openshiftGitopsOperatorNamespace) Eventually(pluginDeployment, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1), - "deployment %s in %s not ready after 60s", gitopsPluginDeploymentName, openshiftGitopsNamespace) + "deployment %s in %s not ready after 60s", gitopsPluginDeploymentName, openshiftGitopsOperatorNamespace) By("capturing initial state before making actual change") Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDeployment), pluginDeployment)).To(Succeed()) diff --git a/test/openshift/e2e/ginkgo/sequential/1-124_validate_console_plugin_in_operator_namespace_test.go b/test/openshift/e2e/ginkgo/sequential/1-124_validate_console_plugin_in_operator_namespace_test.go new file mode 100644 index 00000000000..50f0cb69248 --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-124_validate_console_plugin_in_operator_namespace_test.go @@ -0,0 +1,180 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sequential + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + consolev1 "github.com/openshift/api/console/v1" + gitopsoperatorv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + deploymentFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/deployment" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("GitOps Operator Sequential E2E Tests", func() { + + Context("1-124_validate_console_plugin_in_operator_namespace", func() { + + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + }) + + It("verifies that console plugin resources are deployed in the operator namespace, not the default ArgoCD namespace", func() { + + k8sClient, _ := utils.GetE2ETestKubeClient() + + operatorNamespace := "openshift-gitops-operator" + + By("verifying plugin Deployment exists in operator namespace and is ready") + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: operatorNamespace}} + Eventually(pluginDepl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(pluginDepl, "3m", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + By("verifying plugin Service exists in operator namespace") + pluginSvc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: operatorNamespace}} + Eventually(pluginSvc, "60s", "5s").Should(k8sFixture.ExistByName()) + + By("verifying plugin ConfigMap exists in operator namespace") + pluginCM := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "httpd-cfg", Namespace: operatorNamespace}} + Eventually(pluginCM, "60s", "5s").Should(k8sFixture.ExistByName()) + + By("verifying ConsolePlugin CR backend points to operator namespace") + consolePlugin := &consolev1.ConsolePlugin{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin"}} + Eventually(consolePlugin, "60s", "5s").Should(k8sFixture.ExistByName()) + err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(consolePlugin), consolePlugin) + Expect(err).ToNot(HaveOccurred()) + Expect(consolePlugin.Spec.Backend.Service).ToNot(BeNil()) + Expect(consolePlugin.Spec.Backend.Service.Namespace).To(Equal(operatorNamespace)) + + By("verifying plugin resources do NOT exist in openshift-gitops namespace") + oldDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(oldDepl).Should(k8sFixture.NotExistByName()) + + oldSvc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(oldSvc).Should(k8sFixture.NotExistByName()) + + oldCM := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "httpd-cfg", Namespace: "openshift-gitops"}} + Eventually(oldCM).Should(k8sFixture.NotExistByName()) + }) + + It("cleans up plugin resources from the old namespace when they exist (simulating upgrade from older version)", func() { + + k8sClient, _ := utils.GetE2ETestKubeClient() + ctx := context.Background() + oldNamespace := "openshift-gitops" + operatorNamespace := "openshift-gitops-operator" + + By("waiting for plugin deployment to be ready in operator namespace before proceeding") + readyDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: operatorNamespace}} + Eventually(readyDepl, "5m", "5s").Should(k8sFixture.ExistByName()) + Eventually(readyDepl, "3m", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + By("deleting GitopsService CR to prepare for recreation trigger") + Expect(k8sClient.Delete(ctx, &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + })).To(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject( + &gitopsoperatorv1alpha1.GitopsService{ObjectMeta: metav1.ObjectMeta{Name: "cluster"}}), + &gitopsoperatorv1alpha1.GitopsService{}) + return err != nil + }, "60s", "5s").Should(BeTrue()) + + By("creating fake plugin resources in the old namespace to simulate a pre-upgrade installation") + oldDepl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitops-plugin", + Namespace: oldNamespace, + Labels: map[string]string{"app": "gitops-plugin"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "gitops-plugin"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "gitops-plugin"}}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "gitops-plugin", Image: "fake-image:latest"}, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, oldDepl)).To(Succeed()) + + oldSvc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitops-plugin", + Namespace: oldNamespace, + Labels: map[string]string{"app": "gitops-plugin"}, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": "gitops-plugin"}, + Ports: []corev1.ServicePort{{Port: 9001, Protocol: corev1.ProtocolTCP}}, + }, + } + Expect(k8sClient.Create(ctx, oldSvc)).To(Succeed()) + + oldCM := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "httpd-cfg", + Namespace: oldNamespace, + Labels: map[string]string{"app": "gitops-plugin"}, + }, + Data: map[string]string{"test": "data"}, + } + Expect(k8sClient.Create(ctx, oldCM)).To(Succeed()) + + By("verifying the old resources were created successfully") + Eventually(oldDepl, "30s", "5s").Should(k8sFixture.ExistByName()) + Eventually(oldSvc, "30s", "5s").Should(k8sFixture.ExistByName()) + Eventually(oldCM, "30s", "5s").Should(k8sFixture.ExistByName()) + + By("recreating GitopsService CR to trigger reconciliation") + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: oldNamespace}, + } + Expect(k8sClient.Create(ctx, gitopsService)).To(Succeed()) + + By("verifying old plugin resources are cleaned up from openshift-gitops") + Eventually(oldDepl, "5m", "5s").Should(k8sFixture.NotExistByName()) + Eventually(oldSvc, "5m", "5s").Should(k8sFixture.NotExistByName()) + Eventually(oldCM, "5m", "5s").Should(k8sFixture.NotExistByName()) + + By("verifying plugin resources still exist in the operator namespace") + newDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: operatorNamespace}} + Eventually(newDepl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(newDepl, "3m", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + newSvc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: operatorNamespace}} + Eventually(newSvc, "60s", "5s").Should(k8sFixture.ExistByName()) + + newCM := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "httpd-cfg", Namespace: operatorNamespace}} + Eventually(newCM, "60s", "5s").Should(k8sFixture.ExistByName()) + + }) + }) +})