Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
150 changes: 135 additions & 15 deletions controllers/gitopsservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -301,34 +305,155 @@ 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
} else {
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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
129 changes: 129 additions & 0 deletions controllers/gitopsservice_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down