github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/service.go (about) 1 // Copyright 2019 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 "fmt" 20 21 corev1 "k8s.io/api/core/v1" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/util/intstr" 24 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 25 26 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 27 "github.com/argoproj-labs/argocd-operator/common" 28 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 29 ) 30 31 // getArgoServerServiceType will return the server Service type for the ArgoCD. 32 func getArgoServerServiceType(cr *argoproj.ArgoCD) corev1.ServiceType { 33 if len(cr.Spec.Server.Service.Type) > 0 { 34 return cr.Spec.Server.Service.Type 35 } 36 return corev1.ServiceTypeClusterIP 37 } 38 39 // newService returns a new Service for the given ArgoCD instance. 40 func newService(cr *argoproj.ArgoCD) *corev1.Service { 41 return &corev1.Service{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: cr.Name, 44 Namespace: cr.Namespace, 45 Labels: argoutil.LabelsForCluster(cr), 46 }, 47 } 48 } 49 50 // newServiceWithName returns a new Service instance for the given ArgoCD using the given name. 51 func newServiceWithName(name string, component string, cr *argoproj.ArgoCD) *corev1.Service { 52 svc := newService(cr) 53 svc.ObjectMeta.Name = name 54 55 lbls := svc.ObjectMeta.Labels 56 lbls[common.ArgoCDKeyName] = name 57 lbls[common.ArgoCDKeyComponent] = component 58 svc.ObjectMeta.Labels = lbls 59 60 return svc 61 } 62 63 // newServiceWithSuffix returns a new Service instance for the given ArgoCD using the given suffix. 64 func newServiceWithSuffix(suffix string, component string, cr *argoproj.ArgoCD) *corev1.Service { 65 return newServiceWithName(fmt.Sprintf("%s-%s", cr.Name, suffix), component, cr) 66 } 67 68 // reconcileGrafanaService will ensure that the Service for Grafana is present. 69 func (r *ReconcileArgoCD) reconcileGrafanaService(cr *argoproj.ArgoCD) error { 70 svc := newServiceWithSuffix("grafana", "grafana", cr) 71 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 72 if !cr.Spec.Grafana.Enabled { 73 // Service exists but enabled flag has been set to false, delete the Service 74 return r.Client.Delete(context.TODO(), svc) 75 } 76 log.Info(grafanaDeprecatedWarning) 77 return nil // Service found, do nothing 78 } 79 80 if !cr.Spec.Grafana.Enabled { 81 return nil // Grafana not enabled, do nothing. 82 } 83 84 log.Info(grafanaDeprecatedWarning) 85 return nil 86 } 87 88 // reconcileMetricsService will ensure that the Service for the Argo CD application controller metrics is present. 89 func (r *ReconcileArgoCD) reconcileMetricsService(cr *argoproj.ArgoCD) error { 90 svc := newServiceWithSuffix("metrics", "metrics", cr) 91 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 92 // Service found, do nothing 93 return nil 94 } 95 96 svc.Spec.Selector = map[string]string{ 97 common.ArgoCDKeyName: nameWithSuffix("application-controller", cr), 98 } 99 100 svc.Spec.Ports = []corev1.ServicePort{ 101 { 102 Name: "metrics", 103 Port: 8082, 104 Protocol: corev1.ProtocolTCP, 105 TargetPort: intstr.FromInt(8082), 106 }, 107 } 108 109 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 110 return err 111 } 112 return r.Client.Create(context.TODO(), svc) 113 } 114 115 // reconcileRedisHAAnnounceServices will ensure that the announce Services are present for Redis when running in HA mode. 116 func (r *ReconcileArgoCD) reconcileRedisHAAnnounceServices(cr *argoproj.ArgoCD) error { 117 for i := int32(0); i < common.ArgoCDDefaultRedisHAReplicas; i++ { 118 svc := newServiceWithSuffix(fmt.Sprintf("redis-ha-announce-%d", i), "redis", cr) 119 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 120 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 121 return r.Client.Delete(context.TODO(), svc) 122 } 123 return nil // Service found, do nothing 124 } 125 126 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 127 return nil //return as Ha is not enabled do nothing 128 } 129 130 svc.ObjectMeta.Annotations = map[string]string{ 131 common.ArgoCDKeyTolerateUnreadyEndpounts: "true", 132 } 133 134 svc.Spec.PublishNotReadyAddresses = true 135 136 svc.Spec.Selector = map[string]string{ 137 common.ArgoCDKeyName: nameWithSuffix("redis-ha", cr), 138 common.ArgoCDKeyStatefulSetPodName: nameWithSuffix(fmt.Sprintf("redis-ha-server-%d", i), cr), 139 } 140 141 svc.Spec.Ports = []corev1.ServicePort{ 142 { 143 Name: "server", 144 Port: common.ArgoCDDefaultRedisPort, 145 Protocol: corev1.ProtocolTCP, 146 TargetPort: intstr.FromString("redis"), 147 }, { 148 Name: "sentinel", 149 Port: common.ArgoCDDefaultRedisSentinelPort, 150 Protocol: corev1.ProtocolTCP, 151 TargetPort: intstr.FromString("sentinel"), 152 }, 153 } 154 155 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 156 return err 157 } 158 159 if err := r.Client.Create(context.TODO(), svc); err != nil { 160 return err 161 } 162 } 163 return nil 164 } 165 166 // reconcileRedisHAMasterService will ensure that the "master" Service is present for Redis when running in HA mode. 167 func (r *ReconcileArgoCD) reconcileRedisHAMasterService(cr *argoproj.ArgoCD) error { 168 svc := newServiceWithSuffix("redis-ha", "redis", cr) 169 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 170 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 171 return r.Client.Delete(context.TODO(), svc) 172 } 173 return nil // Service found, do nothing 174 } 175 176 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 177 return nil //return as Ha is not enabled do nothing 178 } 179 180 svc.Spec.Selector = map[string]string{ 181 common.ArgoCDKeyName: nameWithSuffix("redis-ha", cr), 182 } 183 184 svc.Spec.Ports = []corev1.ServicePort{ 185 { 186 Name: "server", 187 Port: common.ArgoCDDefaultRedisPort, 188 Protocol: corev1.ProtocolTCP, 189 TargetPort: intstr.FromString("redis"), 190 }, { 191 Name: "sentinel", 192 Port: common.ArgoCDDefaultRedisSentinelPort, 193 Protocol: corev1.ProtocolTCP, 194 TargetPort: intstr.FromString("sentinel"), 195 }, 196 } 197 198 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 199 return err 200 } 201 return r.Client.Create(context.TODO(), svc) 202 } 203 204 // reconcileRedisHAProxyService will ensure that the HA Proxy Service is present for Redis when running in HA mode. 205 func (r *ReconcileArgoCD) reconcileRedisHAProxyService(cr *argoproj.ArgoCD) error { 206 svc := newServiceWithSuffix("redis-ha-haproxy", "redis", cr) 207 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 208 209 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 210 return r.Client.Delete(context.TODO(), svc) 211 } 212 213 if ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS()) { 214 return r.Client.Update(context.TODO(), svc) 215 } 216 return nil // Service found, do nothing 217 } 218 219 if !cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 220 return nil //return as Ha is not enabled do nothing 221 } 222 223 ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS()) 224 225 svc.Spec.Selector = map[string]string{ 226 common.ArgoCDKeyName: nameWithSuffix("redis-ha-haproxy", cr), 227 } 228 229 svc.Spec.Ports = []corev1.ServicePort{ 230 { 231 Name: "haproxy", 232 Port: common.ArgoCDDefaultRedisPort, 233 Protocol: corev1.ProtocolTCP, 234 TargetPort: intstr.FromString("redis"), 235 }, 236 } 237 238 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 239 return err 240 } 241 return r.Client.Create(context.TODO(), svc) 242 } 243 244 // reconcileRedisHAServices will ensure that all required Services are present for Redis when running in HA mode. 245 func (r *ReconcileArgoCD) reconcileRedisHAServices(cr *argoproj.ArgoCD) error { 246 247 if err := r.reconcileRedisHAAnnounceServices(cr); err != nil { 248 return err 249 } 250 251 if err := r.reconcileRedisHAMasterService(cr); err != nil { 252 return err 253 } 254 255 if err := r.reconcileRedisHAProxyService(cr); err != nil { 256 return err 257 } 258 return nil 259 } 260 261 // reconcileRedisService will ensure that the Service for Redis is present. 262 func (r *ReconcileArgoCD) reconcileRedisService(cr *argoproj.ArgoCD) error { 263 svc := newServiceWithSuffix("redis", "redis", cr) 264 265 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 266 if !cr.Spec.Redis.IsEnabled() { 267 return r.Client.Delete(context.TODO(), svc) 268 } 269 if ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS()) { 270 return r.Client.Update(context.TODO(), svc) 271 } 272 if cr.Spec.HA.Enabled { 273 return r.Client.Delete(context.TODO(), svc) 274 } 275 return nil // Service found, do nothing 276 } 277 278 if cr.Spec.HA.Enabled || !cr.Spec.Redis.IsEnabled() { 279 return nil //return as Ha is enabled do nothing 280 } 281 282 ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS()) 283 284 svc.Spec.Selector = map[string]string{ 285 common.ArgoCDKeyName: nameWithSuffix("redis", cr), 286 } 287 288 svc.Spec.Ports = []corev1.ServicePort{ 289 { 290 Name: "tcp-redis", 291 Port: common.ArgoCDDefaultRedisPort, 292 Protocol: corev1.ProtocolTCP, 293 TargetPort: intstr.FromInt(common.ArgoCDDefaultRedisPort), 294 }, 295 } 296 297 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 298 return err 299 } 300 return r.Client.Create(context.TODO(), svc) 301 } 302 303 // ensureAutoTLSAnnotation ensures that the service svc has the desired state 304 // of the auto TLS annotation set, which is either set (when enabled is true) 305 // or unset (when enabled is false). 306 // 307 // Returns true when annotations have been updated, otherwise returns false. 308 // 309 // When this method returns true, the svc resource will need to be updated on 310 // the cluster. 311 func ensureAutoTLSAnnotation(svc *corev1.Service, secretName string, enabled bool) bool { 312 var autoTLSAnnotationName, autoTLSAnnotationValue string 313 314 // We currently only support OpenShift for automatic TLS 315 if IsRouteAPIAvailable() { 316 autoTLSAnnotationName = common.AnnotationOpenShiftServiceCA 317 if svc.Annotations == nil { 318 svc.Annotations = make(map[string]string) 319 } 320 autoTLSAnnotationValue = secretName 321 } 322 323 if autoTLSAnnotationName != "" { 324 val, ok := svc.Annotations[autoTLSAnnotationName] 325 if enabled { 326 if !ok || val != secretName { 327 log.Info(fmt.Sprintf("requesting AutoTLS on service %s", svc.ObjectMeta.Name)) 328 svc.Annotations[autoTLSAnnotationName] = autoTLSAnnotationValue 329 return true 330 } 331 } else { 332 if ok { 333 log.Info(fmt.Sprintf("removing AutoTLS from service %s", svc.ObjectMeta.Name)) 334 delete(svc.Annotations, autoTLSAnnotationName) 335 return true 336 } 337 } 338 } 339 340 return false 341 } 342 343 // reconcileRepoService will ensure that the Service for the Argo CD repo server is present. 344 func (r *ReconcileArgoCD) reconcileRepoService(cr *argoproj.ArgoCD) error { 345 svc := newServiceWithSuffix("repo-server", "repo-server", cr) 346 347 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 348 if !cr.Spec.Repo.IsEnabled() { 349 return r.Client.Delete(context.TODO(), svc) 350 } 351 if ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) { 352 return r.Client.Update(context.TODO(), svc) 353 } 354 return nil // Service found, do nothing 355 } 356 357 if !cr.Spec.Repo.IsEnabled() { 358 return nil 359 } 360 361 ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) 362 363 svc.Spec.Selector = map[string]string{ 364 common.ArgoCDKeyName: nameWithSuffix("repo-server", cr), 365 } 366 367 svc.Spec.Ports = []corev1.ServicePort{ 368 { 369 Name: "server", 370 Port: common.ArgoCDDefaultRepoServerPort, 371 Protocol: corev1.ProtocolTCP, 372 TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), 373 }, { 374 Name: "metrics", 375 Port: common.ArgoCDDefaultRepoMetricsPort, 376 Protocol: corev1.ProtocolTCP, 377 TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoMetricsPort), 378 }, 379 } 380 381 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 382 return err 383 } 384 return r.Client.Create(context.TODO(), svc) 385 } 386 387 // reconcileServerMetricsService will ensure that the Service for the Argo CD server metrics is present. 388 func (r *ReconcileArgoCD) reconcileServerMetricsService(cr *argoproj.ArgoCD) error { 389 svc := newServiceWithSuffix("server-metrics", "server", cr) 390 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 391 return nil // Service found, do nothing 392 } 393 394 svc.Spec.Selector = map[string]string{ 395 common.ArgoCDKeyName: nameWithSuffix("server", cr), 396 } 397 398 svc.Spec.Ports = []corev1.ServicePort{ 399 { 400 Name: "metrics", 401 Port: 8083, 402 Protocol: corev1.ProtocolTCP, 403 TargetPort: intstr.FromInt(8083), 404 }, 405 } 406 407 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 408 return err 409 } 410 return r.Client.Create(context.TODO(), svc) 411 } 412 413 // reconcileServerService will ensure that the Service is present for the Argo CD server component. 414 func (r *ReconcileArgoCD) reconcileServerService(cr *argoproj.ArgoCD) error { 415 svc := newServiceWithSuffix("server", "server", cr) 416 if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { 417 if !cr.Spec.Server.IsEnabled() { 418 return r.Client.Delete(context.TODO(), svc) 419 } 420 if ensureAutoTLSAnnotation(svc, common.ArgoCDServerTLSSecretName, cr.Spec.Server.WantsAutoTLS()) { 421 return r.Client.Update(context.TODO(), svc) 422 } 423 return nil // Service found, do nothing 424 } 425 426 if !cr.Spec.Repo.IsEnabled() { 427 return nil 428 } 429 430 ensureAutoTLSAnnotation(svc, common.ArgoCDServerTLSSecretName, cr.Spec.Server.WantsAutoTLS()) 431 432 svc.Spec.Ports = []corev1.ServicePort{ 433 { 434 Name: "http", 435 Port: 80, 436 Protocol: corev1.ProtocolTCP, 437 TargetPort: intstr.FromInt(8080), 438 }, { 439 Name: "https", 440 Port: 443, 441 Protocol: corev1.ProtocolTCP, 442 TargetPort: intstr.FromInt(8080), 443 }, 444 } 445 446 svc.Spec.Selector = map[string]string{ 447 common.ArgoCDKeyName: nameWithSuffix("server", cr), 448 } 449 450 svc.Spec.Type = getArgoServerServiceType(cr) 451 452 if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { 453 return err 454 } 455 return r.Client.Create(context.TODO(), svc) 456 } 457 458 // reconcileServices will ensure that all Services are present for the given ArgoCD. 459 func (r *ReconcileArgoCD) reconcileServices(cr *argoproj.ArgoCD) error { 460 461 if err := r.reconcileDexService(cr); err != nil { 462 log.Error(err, "error reconciling dex service") 463 } 464 465 err := r.reconcileGrafanaService(cr) 466 if err != nil { 467 return err 468 } 469 470 err = r.reconcileMetricsService(cr) 471 if err != nil { 472 return err 473 } 474 475 err = r.reconcileRedisHAServices(cr) 476 if err != nil { 477 return err 478 } 479 480 err = r.reconcileRedisService(cr) 481 if err != nil { 482 return err 483 } 484 485 err = r.reconcileRepoService(cr) 486 if err != nil { 487 return err 488 } 489 490 err = r.reconcileServerMetricsService(cr) 491 if err != nil { 492 return err 493 } 494 495 err = r.reconcileServerService(cr) 496 if err != nil { 497 return err 498 } 499 return nil 500 }