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 }