github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/status.go (about)

     1  // Copyright 2020 ArgoCD Operator Developers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // 	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package argocd
    16  
    17  import (
    18  	"context"
    19  	"reflect"
    20  	"strings"
    21  
    22  	oappsv1 "github.com/openshift/api/apps/v1"
    23  	routev1 "github.com/openshift/api/route/v1"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	appsv1 "k8s.io/api/apps/v1"
    30  
    31  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    32  	"github.com/argoproj-labs/argocd-operator/controllers/argoutil"
    33  )
    34  
    35  // reconcileStatus will ensure that all of the Status properties are updated for the given ArgoCD.
    36  func (r *ReconcileArgoCD) reconcileStatus(cr *argoproj.ArgoCD) error {
    37  	if err := r.reconcileStatusApplicationController(cr); err != nil {
    38  		return err
    39  	}
    40  
    41  	if err := r.reconcileStatusSSO(cr); err != nil {
    42  		log.Info(err.Error())
    43  	}
    44  
    45  	if err := r.reconcileStatusPhase(cr); err != nil {
    46  		return err
    47  	}
    48  
    49  	if err := r.reconcileStatusRedis(cr); err != nil {
    50  		return err
    51  	}
    52  
    53  	if err := r.reconcileStatusRepo(cr); err != nil {
    54  		return err
    55  	}
    56  
    57  	if err := r.reconcileStatusServer(cr); err != nil {
    58  		return err
    59  	}
    60  
    61  	if err := r.reconcileStatusHost(cr); err != nil {
    62  		return err
    63  	}
    64  
    65  	if err := r.reconcileStatusNotifications(cr); err != nil {
    66  		return err
    67  	}
    68  
    69  	if err := r.reconcileStatusApplicationSetController(cr); err != nil {
    70  		return err
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  // reconcileStatusApplicationController will ensure that the ApplicationController Status is updated for the given ArgoCD.
    77  func (r *ReconcileArgoCD) reconcileStatusApplicationController(cr *argoproj.ArgoCD) error {
    78  	status := "Unknown"
    79  
    80  	ss := newStatefulSetWithSuffix("application-controller", "application-controller", cr)
    81  	if argoutil.IsObjectFound(r.Client, cr.Namespace, ss.Name, ss) {
    82  		status = "Pending"
    83  
    84  		if ss.Spec.Replicas != nil {
    85  			if ss.Status.ReadyReplicas == *ss.Spec.Replicas {
    86  				status = "Running"
    87  			}
    88  		}
    89  	}
    90  
    91  	if cr.Status.ApplicationController != status {
    92  		cr.Status.ApplicationController = status
    93  		return r.Client.Status().Update(context.TODO(), cr)
    94  	}
    95  	return nil
    96  }
    97  
    98  // reconcileStatusDex will ensure that the Dex status is updated for the given ArgoCD.
    99  func (r *ReconcileArgoCD) reconcileStatusDex(cr *argoproj.ArgoCD) error {
   100  	status := "Unknown"
   101  
   102  	deploy := newDeploymentWithSuffix("dex-server", "dex-server", cr)
   103  	if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   104  		status = "Pending"
   105  
   106  		if deploy.Spec.Replicas != nil {
   107  			if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   108  				status = "Running"
   109  			} else if deploy.Status.Conditions != nil {
   110  				for _, condition := range deploy.Status.Conditions {
   111  					if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   112  						// Deployment has failed
   113  						status = "Failed"
   114  						break
   115  					}
   116  				}
   117  			}
   118  		}
   119  	}
   120  
   121  	if cr.Status.SSO != status {
   122  		cr.Status.SSO = status
   123  		return r.Client.Status().Update(context.TODO(), cr)
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  // reconcileStatusKeycloak will ensure that the Keycloak status is updated for the given ArgoCD.
   130  func (r *ReconcileArgoCD) reconcileStatusKeycloak(cr *argoproj.ArgoCD) error {
   131  	status := "Unknown"
   132  
   133  	if IsTemplateAPIAvailable() {
   134  		// keycloak is installed using OpenShift templates.
   135  		dc := &oappsv1.DeploymentConfig{
   136  			ObjectMeta: metav1.ObjectMeta{
   137  				Name:      defaultKeycloakIdentifier,
   138  				Namespace: cr.Namespace,
   139  			},
   140  		}
   141  		if argoutil.IsObjectFound(r.Client, cr.Namespace, dc.Name, dc) {
   142  			status = "Pending"
   143  
   144  			if dc.Status.ReadyReplicas == dc.Spec.Replicas {
   145  				status = "Running"
   146  			} else if dc.Status.Conditions != nil {
   147  				for _, condition := range dc.Status.Conditions {
   148  					if condition.Type == oappsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   149  						// Deployment has failed
   150  						status = "Failed"
   151  						break
   152  					}
   153  				}
   154  			}
   155  		}
   156  
   157  	} else {
   158  		d := newDeploymentWithName(defaultKeycloakIdentifier, defaultKeycloakIdentifier, cr)
   159  		if argoutil.IsObjectFound(r.Client, cr.Namespace, d.Name, d) {
   160  			status = "Pending"
   161  
   162  			if d.Spec.Replicas != nil {
   163  				if d.Status.ReadyReplicas == *d.Spec.Replicas {
   164  					status = "Running"
   165  				} else if d.Status.Conditions != nil {
   166  					for _, condition := range d.Status.Conditions {
   167  						if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   168  							// Deployment has failed
   169  							status = "Failed"
   170  							break
   171  						}
   172  					}
   173  				}
   174  
   175  			}
   176  		}
   177  	}
   178  
   179  	if cr.Status.SSO != status {
   180  		cr.Status.SSO = status
   181  		return r.Client.Status().Update(context.TODO(), cr)
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // reconcileStatusApplicationSetController will ensure that the ApplicationSet controller status is updated for the given ArgoCD.
   188  func (r *ReconcileArgoCD) reconcileStatusApplicationSetController(cr *argoproj.ArgoCD) error {
   189  	status := "Unknown"
   190  
   191  	deploy := newDeploymentWithSuffix("applicationset-controller", "controller", cr)
   192  	if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   193  		status = "Pending"
   194  
   195  		if deploy.Spec.Replicas != nil {
   196  			if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   197  				status = "Running"
   198  			} else if deploy.Status.Conditions != nil {
   199  				for _, condition := range deploy.Status.Conditions {
   200  					if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   201  						// Deployment has failed
   202  						status = "Failed"
   203  						break
   204  					}
   205  				}
   206  			}
   207  		}
   208  	}
   209  
   210  	if cr.Status.ApplicationSetController != status {
   211  		cr.Status.ApplicationSetController = status
   212  		return r.Client.Status().Update(context.TODO(), cr)
   213  	}
   214  	return nil
   215  }
   216  
   217  // reconcileStatusSSOConfig will ensure that the SSOConfig status is updated for the given ArgoCD.
   218  func (r *ReconcileArgoCD) reconcileStatusSSO(cr *argoproj.ArgoCD) error {
   219  
   220  	// set status to track ssoConfigLegalStatus so it is always up to date with latest sso situation
   221  	status := ssoConfigLegalStatus
   222  
   223  	// perform dex/keycloak status reconciliation only if sso configurations are legal
   224  	if status == ssoLegalSuccess {
   225  		if cr.Spec.SSO != nil && cr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeDex {
   226  			return r.reconcileStatusDex(cr)
   227  		} else if cr.Spec.SSO != nil && cr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeKeycloak {
   228  			return r.reconcileStatusKeycloak(cr)
   229  		}
   230  	} else {
   231  		// illegal/unknown sso configurations
   232  		if cr.Status.SSO != status {
   233  			cr.Status.SSO = status
   234  			return r.Client.Status().Update(context.TODO(), cr)
   235  		}
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  // reconcileStatusPhase will ensure that the Status Phase is updated for the given ArgoCD.
   242  func (r *ReconcileArgoCD) reconcileStatusPhase(cr *argoproj.ArgoCD) error {
   243  	var phase string
   244  
   245  	if ((!cr.Spec.Controller.IsEnabled() && cr.Status.ApplicationController == "Unknown") || cr.Status.ApplicationController == "Running") &&
   246  		((!cr.Spec.Redis.IsEnabled() && cr.Status.Redis == "Unknown") || cr.Status.Redis == "Running") &&
   247  		((!cr.Spec.Repo.IsEnabled() && cr.Status.Repo == "Unknown") || cr.Status.Repo == "Running") &&
   248  		((!cr.Spec.Server.IsEnabled() && cr.Status.Server == "Unknown") || cr.Status.Server == "Running") {
   249  		phase = "Available"
   250  	} else {
   251  		phase = "Pending"
   252  	}
   253  
   254  	if cr.Status.Phase != phase {
   255  		cr.Status.Phase = phase
   256  		return r.Client.Status().Update(context.TODO(), cr)
   257  	}
   258  	return nil
   259  }
   260  
   261  // reconcileStatusRedis will ensure that the Redis status is updated for the given ArgoCD.
   262  func (r *ReconcileArgoCD) reconcileStatusRedis(cr *argoproj.ArgoCD) error {
   263  	status := "Unknown"
   264  
   265  	if !cr.Spec.HA.Enabled {
   266  		deploy := newDeploymentWithSuffix("redis", "redis", cr)
   267  		if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   268  			status = "Pending"
   269  
   270  			if deploy.Spec.Replicas != nil {
   271  				if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   272  					status = "Running"
   273  				} else if deploy.Status.Conditions != nil {
   274  					for _, condition := range deploy.Status.Conditions {
   275  						if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   276  							// Deployment has failed
   277  							status = "Failed"
   278  							break
   279  						}
   280  					}
   281  				}
   282  			}
   283  		}
   284  	} else {
   285  		ss := newStatefulSetWithSuffix("redis-ha-server", "redis-ha-server", cr)
   286  		if argoutil.IsObjectFound(r.Client, cr.Namespace, ss.Name, ss) {
   287  			status = "Pending"
   288  
   289  			if ss.Status.ReadyReplicas == *ss.Spec.Replicas {
   290  				status = "Running"
   291  			}
   292  		}
   293  		// TODO: Add check for HA proxy deployment here as well?
   294  	}
   295  
   296  	if cr.Status.Redis != status {
   297  		cr.Status.Redis = status
   298  		return r.Client.Status().Update(context.TODO(), cr)
   299  	}
   300  	return nil
   301  }
   302  
   303  // reconcileStatusRepo will ensure that the Repo status is updated for the given ArgoCD.
   304  func (r *ReconcileArgoCD) reconcileStatusRepo(cr *argoproj.ArgoCD) error {
   305  	status := "Unknown"
   306  
   307  	deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr)
   308  	if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   309  		status = "Pending"
   310  
   311  		if deploy.Spec.Replicas != nil {
   312  			if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   313  				status = "Running"
   314  			} else if deploy.Status.Conditions != nil {
   315  				for _, condition := range deploy.Status.Conditions {
   316  					if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   317  						// Deployment has failed
   318  						status = "Failed"
   319  						break
   320  					}
   321  				}
   322  			}
   323  		}
   324  	}
   325  
   326  	if cr.Status.Repo != status {
   327  		cr.Status.Repo = status
   328  		return r.Client.Status().Update(context.TODO(), cr)
   329  	}
   330  	return nil
   331  }
   332  
   333  // reconcileStatusServer will ensure that the Server status is updated for the given ArgoCD.
   334  func (r *ReconcileArgoCD) reconcileStatusServer(cr *argoproj.ArgoCD) error {
   335  	status := "Unknown"
   336  
   337  	deploy := newDeploymentWithSuffix("server", "server", cr)
   338  	if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   339  		status = "Pending"
   340  
   341  		// TODO: Refactor these checks.
   342  		if deploy.Spec.Replicas != nil {
   343  			if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   344  				status = "Running"
   345  			} else if deploy.Status.Conditions != nil {
   346  				for _, condition := range deploy.Status.Conditions {
   347  					if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   348  						// Deployment has failed
   349  						status = "Failed"
   350  						break
   351  					}
   352  				}
   353  			}
   354  		}
   355  	}
   356  
   357  	if cr.Status.Server != status {
   358  		cr.Status.Server = status
   359  		return r.Client.Status().Update(context.TODO(), cr)
   360  	}
   361  	return nil
   362  }
   363  
   364  // reconcileStatusNotifications will ensure that the Notifications status is updated for the given ArgoCD.
   365  func (r *ReconcileArgoCD) reconcileStatusNotifications(cr *argoproj.ArgoCD) error {
   366  	status := "Unknown"
   367  
   368  	deploy := newDeploymentWithSuffix("notifications-controller", "controller", cr)
   369  	if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) {
   370  		status = "Pending"
   371  
   372  		if deploy.Spec.Replicas != nil {
   373  			if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
   374  				status = "Running"
   375  			} else if deploy.Status.Conditions != nil {
   376  				for _, condition := range deploy.Status.Conditions {
   377  					if condition.Type == appsv1.DeploymentReplicaFailure && condition.Status == corev1.ConditionTrue {
   378  						// Deployment has failed
   379  						status = "Failed"
   380  						break
   381  					}
   382  				}
   383  			}
   384  		}
   385  	}
   386  
   387  	if cr.Status.NotificationsController != status {
   388  		if !cr.Spec.Notifications.Enabled {
   389  			cr.Status.NotificationsController = ""
   390  		} else {
   391  			cr.Status.NotificationsController = status
   392  		}
   393  		return r.Client.Status().Update(context.TODO(), cr)
   394  	}
   395  	return nil
   396  }
   397  
   398  // reconcileStatusHost will ensure that the host status is updated for the given ArgoCD.
   399  func (r *ReconcileArgoCD) reconcileStatusHost(cr *argoproj.ArgoCD) error {
   400  	cr.Status.Host = ""
   401  
   402  	if (cr.Spec.Server.Route.Enabled || cr.Spec.Server.Ingress.Enabled) && IsRouteAPIAvailable() {
   403  		route := newRouteWithSuffix("server", cr)
   404  
   405  		// The Red Hat OpenShift ingress controller implementation is designed to watch ingress objects and create one or more routes
   406  		// to fulfill the conditions specified.
   407  		// But the names of such created route resources are randomly generated so it is better to identify the routes using Labels
   408  		// instead of Name.
   409  		// 1. If a user creates ingress on openshift, Ingress controller generates a route for the ingress with random name.
   410  		// 2. If a user creates route on openshift, Ingress controller processes the route with provided name.
   411  		routeList := &routev1.RouteList{}
   412  		opts := &client.ListOptions{
   413  			LabelSelector: labels.SelectorFromSet(map[string]string{
   414  				"app.kubernetes.io/name": route.Name,
   415  			}),
   416  			Namespace: cr.Namespace,
   417  		}
   418  
   419  		if err := r.Client.List(context.TODO(), routeList, opts); err != nil {
   420  			return err
   421  		}
   422  
   423  		if len(routeList.Items) == 0 {
   424  			log.Info("argocd-server route requested but not found on cluster")
   425  			return nil
   426  		} else {
   427  			route = &routeList.Items[0]
   428  			// status.ingress not available
   429  			if route.Status.Ingress == nil {
   430  				cr.Status.Host = ""
   431  				cr.Status.Phase = "Pending"
   432  			} else {
   433  				// conditions exist and type is RouteAdmitted
   434  				if len(route.Status.Ingress[0].Conditions) > 0 && route.Status.Ingress[0].Conditions[0].Type == routev1.RouteAdmitted {
   435  					if route.Status.Ingress[0].Conditions[0].Status == corev1.ConditionTrue {
   436  						cr.Status.Host = route.Status.Ingress[0].Host
   437  					} else {
   438  						cr.Status.Host = ""
   439  						cr.Status.Phase = "Pending"
   440  					}
   441  				} else {
   442  					// no conditions are available
   443  					if route.Status.Ingress[0].Host != "" {
   444  						cr.Status.Host = route.Status.Ingress[0].Host
   445  					} else {
   446  						cr.Status.Host = "Unavailable"
   447  						cr.Status.Phase = "Pending"
   448  					}
   449  				}
   450  			}
   451  		}
   452  	} else if cr.Spec.Server.Ingress.Enabled {
   453  		ingress := newIngressWithSuffix("server", cr)
   454  		if !argoutil.IsObjectFound(r.Client, cr.Namespace, ingress.Name, ingress) {
   455  			log.Info("argocd-server ingress requested but not found on cluster")
   456  			cr.Status.Phase = "Pending"
   457  			return nil
   458  		} else {
   459  			if !reflect.DeepEqual(ingress.Status.LoadBalancer, corev1.LoadBalancerStatus{}) && len(ingress.Status.LoadBalancer.Ingress) > 0 {
   460  				var s []string
   461  				var hosts string
   462  				for _, ingressElement := range ingress.Status.LoadBalancer.Ingress {
   463  					if ingressElement.Hostname != "" {
   464  						s = append(s, ingressElement.Hostname)
   465  						continue
   466  					} else if ingressElement.IP != "" {
   467  						s = append(s, ingressElement.IP)
   468  						continue
   469  					}
   470  				}
   471  				hosts = strings.Join(s, ", ")
   472  				cr.Status.Host = hosts
   473  			}
   474  		}
   475  	}
   476  	return r.Client.Status().Update(context.TODO(), cr)
   477  }