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  }