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

     1  /*
     2  Copyright 2019, 2021.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package argocd
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/prometheus/client_golang/prometheus"
    25  
    26  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  
    33  	ctrl "sigs.k8s.io/controller-runtime"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  	logr "sigs.k8s.io/controller-runtime/pkg/log"
    36  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    37  )
    38  
    39  // blank assignment to verify that ReconcileArgoCD implements reconcile.Reconciler
    40  var _ reconcile.Reconciler = &ReconcileArgoCD{}
    41  
    42  // ArgoCDReconciler reconciles a ArgoCD object
    43  // TODO(upgrade): rename to ArgoCDRecoonciler
    44  type ReconcileArgoCD struct {
    45  	client.Client
    46  	Scheme            *runtime.Scheme
    47  	ManagedNamespaces *corev1.NamespaceList
    48  	// Stores a list of ApplicationSourceNamespaces as keys
    49  	ManagedSourceNamespaces map[string]string
    50  	// Stores a list of ApplicationSetSourceNamespaces as keys
    51  	ManagedApplicationSetSourceNamespaces map[string]string
    52  	// Stores label selector used to reconcile a subset of ArgoCD
    53  	LabelSelector string
    54  }
    55  
    56  var log = logr.Log.WithName("controller_argocd")
    57  
    58  // Map to keep track of running Argo CD instances using their namespaces as key and phase as value
    59  // This map will be used for the performance metrics purposes
    60  // Important note: This assumes that each instance only contains one Argo CD instance
    61  // as, having multiple Argo CD instances in the same namespace is considered an anti-pattern
    62  var ActiveInstanceMap = make(map[string]string)
    63  
    64  //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=*
    65  //+kubebuilder:rbac:groups="",resources=configmaps;endpoints;events;persistentvolumeclaims;pods;namespaces;secrets;serviceaccounts;services;services/finalizers,verbs=*
    66  //+kubebuilder:rbac:groups=apps.openshift.io,resources=deploymentconfigs,verbs=*
    67  //+kubebuilder:rbac:groups=apps,resources=deployments;replicasets;daemonsets;statefulsets,verbs=*
    68  //+kubebuilder:rbac:groups=apps,resourceNames=argocd-operator,resources=deployments/finalizers,verbs=update
    69  //+kubebuilder:rbac:groups=argoproj.io,resources=argocds;argocds/finalizers;argocds/status,verbs=*
    70  //+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=*
    71  //+kubebuilder:rbac:groups=batch,resources=cronjobs;jobs,verbs=*
    72  //+kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch
    73  //+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=*
    74  //+kubebuilder:rbac:groups=monitoring.coreos.com,resources=prometheuses;prometheusrules;servicemonitors,verbs=*
    75  //+kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=*
    76  //+kubebuilder:rbac:groups=argoproj.io,resources=applications;appprojects,verbs=*
    77  //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=*,verbs=*
    78  //+kubebuilder:rbac:groups="",resources=pods;pods/log,verbs=get
    79  //+kubebuilder:rbac:groups=template.openshift.io,resources=templates;templateinstances;templateconfigs,verbs=*
    80  //+kubebuilder:rbac:groups="oauth.openshift.io",resources=oauthclients,verbs=get;list;watch;create;delete;patch;update
    81  // +kubebuilder:rbac:groups=argoproj.io,resources=notificationsconfigurations;notificationsconfigurations/finalizers,verbs=*
    82  
    83  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    84  // move the current state of the cluster closer to the desired state.
    85  // the ArgoCD object against the actual cluster state, and then
    86  // perform operations to make the cluster state reflect the state specified by
    87  // the user.
    88  //
    89  // For more details, check Reconcile and its Result here:
    90  // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile
    91  func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
    92  
    93  	reconcileStartTS := time.Now()
    94  	defer func() {
    95  		ReconcileTime.WithLabelValues(request.Namespace).Observe(time.Since(reconcileStartTS).Seconds())
    96  	}()
    97  
    98  	reqLogger := logr.FromContext(ctx, "namespace", request.Namespace, "name", request.Name)
    99  	reqLogger.Info("Reconciling ArgoCD")
   100  
   101  	argocd := &argoproj.ArgoCD{}
   102  	err := r.Client.Get(ctx, request.NamespacedName, argocd)
   103  	if err != nil {
   104  		if errors.IsNotFound(err) {
   105  			// Request object not found, could have been deleted after reconcile request.
   106  			// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
   107  			// Return and don't requeue
   108  			return reconcile.Result{}, nil
   109  		}
   110  		// Error reading the object - requeue the request.
   111  		return reconcile.Result{}, err
   112  	}
   113  
   114  	// Fetch labelSelector from r.LabelSelector (command-line option)
   115  	labelSelector, err := labels.Parse(r.LabelSelector)
   116  	if err != nil {
   117  		reqLogger.Info(fmt.Sprintf("error parsing the labelSelector '%s'.", labelSelector))
   118  		return reconcile.Result{}, err
   119  	}
   120  	// Match the value of labelSelector from ReconcileArgoCD to labels from the argocd instance
   121  	if !labelSelector.Matches(labels.Set(argocd.Labels)) {
   122  		reqLogger.Info(fmt.Sprintf("the ArgoCD instance '%s' does not match the label selector '%s' and skipping for reconciliation", request.NamespacedName, r.LabelSelector))
   123  		return reconcile.Result{}, fmt.Errorf("error: failed to reconcile ArgoCD instance: '%s'", request.NamespacedName)
   124  	}
   125  
   126  	newPhase := argocd.Status.Phase
   127  	// If we discover a new Argo CD instance in a previously un-seen namespace
   128  	// we add it to the map and increment active instance count by phase
   129  	// as well as total active instance count
   130  	if _, ok := ActiveInstanceMap[request.Namespace]; !ok {
   131  		if newPhase != "" {
   132  			ActiveInstanceMap[request.Namespace] = newPhase
   133  			ActiveInstancesByPhase.WithLabelValues(newPhase).Inc()
   134  			ActiveInstancesTotal.Inc()
   135  		}
   136  	} else {
   137  		// If we discover an existing instance's phase has changed since we last saw it
   138  		// increment instance count with new phase and decrement instance count with old phase
   139  		// update the phase in corresponding map entry
   140  		// total instance count remains the same
   141  		if oldPhase := ActiveInstanceMap[argocd.Namespace]; oldPhase != newPhase {
   142  			ActiveInstanceMap[argocd.Namespace] = newPhase
   143  			ActiveInstancesByPhase.WithLabelValues(newPhase).Inc()
   144  			ActiveInstancesByPhase.WithLabelValues(oldPhase).Dec()
   145  		}
   146  	}
   147  
   148  	ActiveInstanceReconciliationCount.WithLabelValues(argocd.Namespace).Inc()
   149  
   150  	if argocd.GetDeletionTimestamp() != nil {
   151  
   152  		// Argo CD instance marked for deletion; remove entry from activeInstances map and decrement active instance count
   153  		// by phase as well as total
   154  		delete(ActiveInstanceMap, argocd.Namespace)
   155  		ActiveInstancesByPhase.WithLabelValues(newPhase).Dec()
   156  		ActiveInstancesTotal.Dec()
   157  		ActiveInstanceReconciliationCount.DeleteLabelValues(argocd.Namespace)
   158  		ReconcileTime.DeletePartialMatch(prometheus.Labels{"namespace": argocd.Namespace})
   159  
   160  		if argocd.IsDeletionFinalizerPresent() {
   161  			if err := r.deleteClusterResources(argocd); err != nil {
   162  				return reconcile.Result{}, fmt.Errorf("failed to delete ClusterResources: %w", err)
   163  			}
   164  
   165  			if isRemoveManagedByLabelOnArgoCDDeletion() {
   166  				if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil {
   167  					return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err)
   168  				}
   169  			}
   170  
   171  			if err := r.removeUnmanagedSourceNamespaceResources(argocd); err != nil {
   172  				return reconcile.Result{}, fmt.Errorf("failed to remove resources from sourceNamespaces, error: %w", err)
   173  			}
   174  
   175  			if err := r.removeUnmanagedApplicationSetSourceNamespaceResources(argocd); err != nil {
   176  				return reconcile.Result{}, fmt.Errorf("failed to remove resources from applicationSetSourceNamespaces, error: %w", err)
   177  			}
   178  
   179  			if err := r.removeDeletionFinalizer(argocd); err != nil {
   180  				return reconcile.Result{}, err
   181  			}
   182  
   183  			// remove namespace of deleted Argo CD instance from deprecationEventEmissionTracker (if exists) so that if another instance
   184  			// is created in the same namespace in the future, that instance is appropriately tracked
   185  			delete(DeprecationEventEmissionTracker, argocd.Namespace)
   186  		}
   187  		return reconcile.Result{}, nil
   188  	}
   189  
   190  	if !argocd.IsDeletionFinalizerPresent() {
   191  		if err := r.addDeletionFinalizer(argocd); err != nil {
   192  			return reconcile.Result{}, err
   193  		}
   194  	}
   195  
   196  	// get the latest version of argocd instance before reconciling
   197  	if err = r.Client.Get(ctx, request.NamespacedName, argocd); err != nil {
   198  		return reconcile.Result{}, err
   199  	}
   200  
   201  	if err = r.setManagedNamespaces(argocd); err != nil {
   202  		return reconcile.Result{}, err
   203  	}
   204  
   205  	if err = r.setManagedSourceNamespaces(argocd); err != nil {
   206  		return reconcile.Result{}, err
   207  	}
   208  
   209  	if err = r.setManagedApplicationSetSourceNamespaces(argocd); err != nil {
   210  		return reconcile.Result{}, err
   211  	}
   212  
   213  	if err := r.reconcileResources(argocd); err != nil {
   214  		// Error reconciling ArgoCD sub-resources - requeue the request.
   215  		return reconcile.Result{}, err
   216  	}
   217  
   218  	// Return and don't requeue
   219  	return reconcile.Result{}, nil
   220  }
   221  
   222  // SetupWithManager sets up the controller with the Manager.
   223  func (r *ReconcileArgoCD) SetupWithManager(mgr ctrl.Manager) error {
   224  	bldr := ctrl.NewControllerManagedBy(mgr)
   225  	r.setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper, r.clusterSecretResourceMapper, r.applicationSetSCMTLSConfigMapMapper)
   226  	return bldr.Complete(r)
   227  }