diff --git a/cmd/main.go b/cmd/main.go index c7b7d240a..4ef86cc13 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -185,9 +185,10 @@ func main() { } if err = (&controllers.ReconcileGitopsService{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", + DisableDefaultArgoCDConsoleLink: strings.ToLower(os.Getenv(common.DisableDefaultArgoCDConsoleLink)) == "true", }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GitopsService") os.Exit(1) diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 4961a1695..23730a2b9 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -30,6 +30,7 @@ import ( argocdutil "github.com/argoproj-labs/argocd-operator/controllers/argoutil" "github.com/go-logr/logr" version "github.com/hashicorp/go-version" + consolev1 "github.com/openshift/api/console/v1" routev1 "github.com/openshift/api/route/v1" pipelinesv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" "github.com/redhat-developer/gitops-operator/common" @@ -132,6 +133,8 @@ type ReconcileGitopsService struct { // disableDefaultInstall, if true, will ensure that the default ArgoCD instance is not instantiated in the openshift-gitops namespace. DisableDefaultInstall bool + // disableDefaultArgoCDConsoleLink, if true, will ensure that the default Console Plugin is not instantiated in the openshift-gitops namespace. + DisableDefaultArgoCDConsoleLink bool } //+kubebuilder:rbac:groups=pipelines.openshift.io,resources=gitopsservices,verbs=get;list;watch;create;update;patch;delete @@ -265,15 +268,16 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil } else { // If installation of default Argo CD instance is disabled, make sure it doesn't exist, // deleting it if necessary - if err := r.ensureDefaultArgoCDInstanceDoesntExist(instance, reqLogger); err != nil { + if err := r.ensureDefaultArgoCDInstanceDoesntExist(); err != nil { return reconcile.Result{}, fmt.Errorf("unable to ensure non-existence of default Argo CD instance: %v", err) } } - if result, err := r.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger); err != nil { - return result, err + if !r.DisableDefaultArgoCDConsoleLink { + if result, err := r.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger); err != nil { + return result, err + } } - dynamicPluginStartOCPVersion := os.Getenv(dynamicPluginStartOCPVersionEnv) if dynamicPluginStartOCPVersion == "" { dynamicPluginStartOCPVersion = common.DefaultDynamicPluginStartOCPVersion @@ -301,26 +305,148 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil startVersion := v2.Segments() startMajorVersion := startVersion[0] startMinorVersion := startVersion[1] - 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 { + } else if !r.DisableDefaultArgoCDConsoleLink { return r.reconcilePlugin(instance, request) } + if r.DisableDefaultArgoCDConsoleLink { + log.Print("Deleting ConsoleLink resources") + if err := r.ensureConsoleLinkResourcesDoesntExist(gitopsserviceNamespacedName); err != nil { + return reconcile.Result{}, fmt.Errorf("unable to ensure non-existence of ConsoleLink deployments: %v", err) + } + log.Print("Deleting ConsolePlugin resources") + if err := r.ensurePluginResourcesDontExist(); err != nil { + return reconcile.Result{}, fmt.Errorf("unable to ensure non-existence of Console Plugin resources: %v", err) + } + } + return reconcile.Result{}, nil +} + +func (r *ReconcileGitopsService) ensurePluginResourcesDontExist() error { + // check service exist, then delete it + existingServiceRef := &corev1.Service{} + err := r.Client.Get(context.TODO(), types.NamespacedName{Namespace: serviceNamespace, Name: gitopsPluginName}, existingServiceRef) + if err == nil { + log.Print("Deleting Service for Console Plugin") + if err := r.Client.Delete(context.TODO(), existingServiceRef); err != nil { + log.Printf("Error deleting Service for Console Plugin: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting Service for Console Plugin: %v", err) + return err + } + // check deployemt exist, then delete it + existingDeployment := &appsv1.Deployment{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: serviceNamespace, Name: gitopsPluginName}, existingDeployment) + if err == nil { + log.Print("Deleting Deployment for Console Plugin") + if err := r.Client.Delete(context.TODO(), existingDeployment); err != nil { + log.Printf("Error deleting Deployment for Console Plugin: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting Deployment for Console Plugin: %v", err) + return err + } + // check configmap exist, then delete it + existingConfigMap := &corev1.ConfigMap{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: serviceNamespace, Name: httpdConfigMapName}, existingConfigMap) + if err == nil { + log.Print("Deleting ConfigMap for Console Plugin") + if err := r.Client.Delete(context.TODO(), existingConfigMap); err != nil { + log.Printf("Error deleting ConfigMap for Console Plugin: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting ConfigMap for Console Plugin: %v", err) + return err + } + //check consoleplugin exist, then delete it + existingPlugin := &consolev1.ConsolePlugin{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName}, existingPlugin) + if err == nil { + log.Print("Deleting ConsolePlugin") + if err := r.Client.Delete(context.TODO(), existingPlugin); err != nil { + log.Printf("Error deleting ConsolePlugin: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting ConsolePlugin: %v", err) + return err + } + return nil } -func (r *ReconcileGitopsService) ensureDefaultArgoCDInstanceDoesntExist(instance *pipelinesv1alpha1.GitopsService, reqLogger logr.Logger) error { +func (r *ReconcileGitopsService) ensureConsoleLinkResourcesDoesntExist(meta types.NamespacedName) error { + // service account exist, then delete it + existingServiceAccount := &corev1.ServiceAccount{} + err := r.Client.Get(context.TODO(), types.NamespacedName{Namespace: meta.Namespace, Name: gitopsServicePrefix + meta.Name}, existingServiceAccount) + if err == nil { + log.Print("Deleting ServiceAccount for ConsoleLink") + if err := r.Client.Delete(context.TODO(), existingServiceAccount); err != nil { + log.Printf("Error deleting ServiceAccount for ConsoleLink: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting ServiceAccount for ConsoleLink: %v", err) + return err + } + + //cluster role exist, then delete it + existingClusterRole := &rbacv1.ClusterRole{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + meta.Name}, existingClusterRole) + if err == nil { + log.Print("Deleting ClusterRole for ConsoleLink") + if err := r.Client.Delete(context.TODO(), existingClusterRole); err != nil { + log.Printf("Error deleting ClusterRole for ConsoleLink: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting ClusterRole for ConsoleLink: %v", err) + return err + } + + //cluster role binding exist, then delete it + existingClusterRoleBinding := &rbacv1.ClusterRoleBinding{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + meta.Name}, existingClusterRoleBinding) + if err == nil { + log.Print("Deleting ClusterRoleBinding for ConsoleLink") + if err := r.Client.Delete(context.TODO(), existingClusterRoleBinding); err != nil { + log.Printf("Error deleting ClusterRoleBinding for ConsoleLink: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting ClusterRoleBinding for ConsoleLink: %v", err) + return err + } + + // deployment exist, then delete it + existingDeployment := &appsv1.Deployment{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: meta.Namespace, Name: meta.Name}, existingDeployment) + if err == nil { + log.Print("Deleting Deployment for ConsoleLink") + if err := r.Client.Delete(context.TODO(), existingDeployment); err != nil { + log.Printf("Error deleting Deployment for ConsoleLink: %v", err) + return err + } + } else if !errors.IsNotFound(err) { + log.Printf("Error getting Deployment for ConsoleLink: %v", err) + return err + } + return nil +} +func (r *ReconcileGitopsService) ensureDefaultArgoCDInstanceDoesntExist() error { defaultArgoCDInstance, err := argocd.NewCR(common.ArgoCDInstanceName, serviceNamespace) if err != nil { return err } - argocdNS := newRestrictedNamespace(defaultArgoCDInstance.Namespace) err = r.Client.Get(context.TODO(), types.NamespacedName{Name: argocdNS.Name}, &corev1.Namespace{}) if err != nil { - if errors.IsNotFound(err) { // If the namespace doesn't exit, then the instance necessarily doesn't exist, so just return return nil @@ -328,7 +454,6 @@ func (r *ReconcileGitopsService) ensureDefaultArgoCDInstanceDoesntExist(instance return err } } - // Delete the existing Argo CD instance, if it exists existingArgoCD := &argoapp.ArgoCD{} err = r.Client.Get(context.TODO(), types.NamespacedName{Name: defaultArgoCDInstance.Name, Namespace: defaultArgoCDInstance.Namespace}, existingArgoCD) @@ -337,22 +462,18 @@ func (r *ReconcileGitopsService) ensureDefaultArgoCDInstanceDoesntExist(instance if err := r.Client.Delete(context.TODO(), existingArgoCD); err != nil { return err } - } else if !errors.IsNotFound(err) { // If an unexpected error occurred (eg not the 'not found' error, which is expected) then just return it return err } - return nil } func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipelinesv1alpha1.GitopsService, reqLogger logr.Logger) (reconcile.Result, error) { - defaultArgoCDInstance, err := argocd.NewCR(common.ArgoCDInstanceName, serviceNamespace) if err != nil { return reconcile.Result{}, err } - // The operator decides the namespace based on the version of the cluster it is installed in // 4.6 Cluster: Backend in openshift-pipelines-app-delivery namespace and argocd in openshift-gitops namespace // 4.7 Cluster: Both backend and argocd instance in openshift-gitops namespace @@ -389,7 +510,6 @@ func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipeli return reconcile.Result{}, err } } - needsUpdate, updateNameSpace := ensurePodSecurityLabels(argocdNS) if needsUpdate { err = r.Client.Update(context.TODO(), updateNameSpace) diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index e1bbc3005..b30051158 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -128,6 +128,135 @@ func TestReconcileDefaultForArgoCDNodeplacement(t *testing.T) { assert.DeepEqual(t, existingArgoCD.Spec.NodePlacement.NodeSelector, gitopsService.Spec.NodeSelector) } +// If the DISABLE_DEFAULT_ARGOCD_CONSOLE_LINK is set, ensure that the default ArgoCD console link is not created. +func TestDisableDefaultArgoCDConsoleLink(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + var err error + fakeClient := fake.NewFakeClient(util.NewClusterVersion("4.15.1"), newGitopsService()) + reconciler := newReconcileGitOpsService(fakeClient, s) + reconciler.DisableDefaultArgoCDConsoleLink = true + + _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + // backend resources should not be created + + // verify backend resources are deleted + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend deployment should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster", Namespace: serviceNamespace}, &corev1.ServiceAccount{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend service account should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRole{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend cluster role should not exist, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRoleBinding{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend cluster role binding should not exist, error: %v", err) + } + // verify gitopsService exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName}, &pipelinesv1alpha1.GitopsService{}) + assertNoError(t, err) + // verify plugin resources exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName}, &consolev1.ConsolePlugin{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("console plugin should not exist, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin deployment should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &corev1.Service{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin service should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: httpdConfigMapName, Namespace: serviceNamespace}, &corev1.ConfigMap{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin configmap should not exist in namespace, error: %v", err) + } +} + +func TestDisableDefaultArgoCDConsoleLink_DeleteIfAlreadyExists(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + util.SetConsoleAPIFound(true) + var err error + fakeClient := fake.NewFakeClient(util.NewClusterVersion("4.15.1"), newGitopsService()) + reconciler := newReconcileGitOpsService(fakeClient, s) + reconciler.DisableDefaultArgoCDConsoleLink = false + + _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + // verify backend resources are be created + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster", Namespace: serviceNamespace}, &corev1.ServiceAccount{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRole{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRoleBinding{}) + assertNoError(t, err) + // verify gitopsService exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName}, &pipelinesv1alpha1.GitopsService{}) + assertNoError(t, err) + // verify plugin resources exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName}, &consolev1.ConsolePlugin{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &corev1.Service{}) + assertNoError(t, err) + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: httpdConfigMapName, Namespace: serviceNamespace}, &corev1.ConfigMap{}) + assertNoError(t, err) + reconciler.DisableDefaultArgoCDConsoleLink = true + _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + + // verify backend resources are deleted + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend deployment should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster", Namespace: serviceNamespace}, &corev1.ServiceAccount{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend service account should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRole{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend cluster role should not exist, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsServicePrefix + "cluster"}, &rbacv1.ClusterRoleBinding{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("backend cluster role binding should not exist, error: %v", err) + } + // verify gitopsService exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName}, &pipelinesv1alpha1.GitopsService{}) + assertNoError(t, err) + // verify plugin resources exist + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName}, &consolev1.ConsolePlugin{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("console plugin should not exist, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin deployment should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, &corev1.Service{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin service should not exist in namespace, error: %v", err) + } + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: httpdConfigMapName, Namespace: serviceNamespace}, &corev1.ConfigMap{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("plugin configmap should not exist in namespace, error: %v", err) + } +} + // If the DISABLE_DEFAULT_ARGOCD_INSTANCE is set, ensure that the default ArgoCD instance is not created. func TestReconcileDisableDefault(t *testing.T) {