github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/clusters/multiclusterconfigmap/controller.go (about)

     1  // Copyright (c) 2021, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package multiclusterconfigmap
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    11  	"github.com/verrazzano/verrazzano/application-operator/constants"
    12  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    13  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    14  	vzlogInit "github.com/verrazzano/verrazzano/pkg/log"
    15  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    16  	"go.uber.org/zap"
    17  	corev1 "k8s.io/api/core/v1"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	ctrl "sigs.k8s.io/controller-runtime"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    23  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    24  )
    25  
    26  // Reconciler reconciles a MultiClusterConfigMap object
    27  type Reconciler struct {
    28  	client.Client
    29  	Log          *zap.SugaredLogger
    30  	Scheme       *runtime.Scheme
    31  	AgentChannel chan clusters.StatusUpdateMessage
    32  }
    33  
    34  const (
    35  	finalizerName  = "multiclusterconfigmap.verrazzano.io"
    36  	controllerName = "multiclusterconfigmap"
    37  )
    38  
    39  // Reconcile reconciles a MultiClusterConfigMap resource. It fetches the embedded ConfigMap,
    40  // mutates it based on the MultiClusterConfigMap, and updates the status of the
    41  // MultiClusterConfigMap to reflect the success or failure of the changes to the embedded resource
    42  // Currently it does NOT support Immutable ConfigMap resources
    43  func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    44  	if ctx == nil {
    45  		return ctrl.Result{}, errors.New("context cannot be nil")
    46  	}
    47  
    48  	// We do not want any resource to get reconciled if it is in namespace kube-system
    49  	// This is due to a bug found in OKE, it should not affect functionality of any vz operators
    50  	// If this is the case then return success
    51  	if req.Namespace == vzconst.KubeSystem {
    52  		log := zap.S().With(vzlogInit.FieldResourceNamespace, req.Namespace, vzlogInit.FieldResourceName, req.Name, vzlogInit.FieldController, controllerName)
    53  		log.Infof("Multi-cluster application configuration resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName)
    54  		return reconcile.Result{}, nil
    55  	}
    56  
    57  	var mcConfigMap clustersv1alpha1.MultiClusterConfigMap
    58  	err := r.fetchMultiClusterConfigMap(ctx, req.NamespacedName, &mcConfigMap)
    59  	if err != nil {
    60  		return clusters.IgnoreNotFoundWithLog(err, zap.S())
    61  	}
    62  	log, err := clusters.GetResourceLogger("mcconfigmap", req.NamespacedName, &mcConfigMap)
    63  	if err != nil {
    64  		zap.S().Error("Failed to create controller logger for multi-cluster config map resource: %v", err)
    65  		return clusters.NewRequeueWithDelay(), nil
    66  	}
    67  	log.Oncef("Reconciling multi-cluster config map resource %v, generation %v", req.NamespacedName, mcConfigMap.Generation)
    68  
    69  	res, err := r.doReconcile(ctx, mcConfigMap, log)
    70  	if clusters.ShouldRequeue(res) {
    71  		return res, nil
    72  	}
    73  	// Never return an error since it has already been logged and we don't want the
    74  	// controller runtime to log again (with stack trace).  Just re-queue if there is an error.
    75  	if err != nil {
    76  		return clusters.NewRequeueWithDelay(), nil
    77  	}
    78  
    79  	log.Oncef("Finished reconciling multi-cluster config map %v", req.NamespacedName)
    80  
    81  	return ctrl.Result{}, nil
    82  }
    83  
    84  // doReconcile performs the reconciliation operations for the MC config map
    85  func (r *Reconciler) doReconcile(ctx context.Context, mcConfigMap clustersv1alpha1.MultiClusterConfigMap, log vzlog.VerrazzanoLogger) (ctrl.Result, error) {
    86  	// delete the wrapped resource since MC is being deleted
    87  	if !mcConfigMap.ObjectMeta.DeletionTimestamp.IsZero() {
    88  		err := clusters.DeleteAssociatedResource(ctx, r.Client, &mcConfigMap, finalizerName, &corev1.ConfigMap{}, types.NamespacedName{Namespace: mcConfigMap.Namespace, Name: mcConfigMap.Name})
    89  		if err != nil {
    90  			log.Errorf("Failed to delete associated configmap and finalizer: %v", err)
    91  		}
    92  		return ctrl.Result{}, err
    93  	}
    94  
    95  	oldState := clusters.SetEffectiveStateIfChanged(mcConfigMap.Spec.Placement, &mcConfigMap.Status)
    96  	if !clusters.IsPlacedInThisCluster(ctx, r, mcConfigMap.Spec.Placement) {
    97  		if oldState != mcConfigMap.Status.State {
    98  			// This must be done whether the resource is placed in this cluster or not, because we
    99  			// could be in an admin cluster and receive cluster level statuses from managed clusters,
   100  			// which can change our effective state
   101  			err := r.Status().Update(ctx, &mcConfigMap)
   102  			if err != nil {
   103  				return ctrl.Result{}, err
   104  			}
   105  		}
   106  		// if this mc config map is no longer placed on this cluster, remove the associated config map
   107  		err := clusters.DeleteAssociatedResource(ctx, r.Client, &mcConfigMap, finalizerName, &corev1.ConfigMap{}, types.NamespacedName{Namespace: mcConfigMap.Namespace, Name: mcConfigMap.Name})
   108  		return ctrl.Result{}, err
   109  	}
   110  
   111  	log.Debug("MultiClusterConfigMap create or update with underlying ConfigMap",
   112  		"ConfigMap", mcConfigMap.Spec.Template.Metadata.Name,
   113  		"placement", mcConfigMap.Spec.Placement.Clusters[0].Name)
   114  	// Immutable ConfigMaps are not supported - we need a webhook to validate, or add the support
   115  	opResult, err := r.createOrUpdateConfigMap(ctx, mcConfigMap)
   116  
   117  	// Add our finalizer if not already added
   118  	if err == nil {
   119  		_, err = clusters.AddFinalizer(ctx, r.Client, &mcConfigMap, finalizerName)
   120  	}
   121  
   122  	ctrlResult, updateErr := r.updateStatus(ctx, &mcConfigMap, opResult, err)
   123  
   124  	// if an error occurred in createOrUpdate, return that error with a requeue
   125  	// even if update status succeeded
   126  	if err != nil {
   127  		res := ctrl.Result{Requeue: true, RequeueAfter: clusters.GetRandomRequeueDelay()}
   128  		return res, err
   129  	}
   130  
   131  	return ctrlResult, updateErr
   132  }
   133  
   134  // SetupWithManager registers our controller with the manager
   135  func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
   136  	return ctrl.NewControllerManagedBy(mgr).
   137  		For(&clustersv1alpha1.MultiClusterConfigMap{}).
   138  		Complete(r)
   139  }
   140  
   141  func (r *Reconciler) fetchMultiClusterConfigMap(ctx context.Context, name types.NamespacedName, mcConfigMap *clustersv1alpha1.MultiClusterConfigMap) error {
   142  	return r.Get(ctx, name, mcConfigMap)
   143  }
   144  
   145  func (r *Reconciler) createOrUpdateConfigMap(ctx context.Context, mcConfigMap clustersv1alpha1.MultiClusterConfigMap) (controllerutil.OperationResult, error) {
   146  	var configMap corev1.ConfigMap
   147  	configMap.Namespace = mcConfigMap.Namespace
   148  	configMap.Name = mcConfigMap.Name
   149  
   150  	return controllerutil.CreateOrUpdate(ctx, r.Client, &configMap, func() error {
   151  		r.mutateConfigMap(mcConfigMap, &configMap)
   152  		return nil
   153  	})
   154  }
   155  
   156  // mutateConfigMap mutates the K8S ConfigMap to reflect the contents of the parent MultiClusterConfigMap
   157  func (r *Reconciler) mutateConfigMap(mcConfigMap clustersv1alpha1.MultiClusterConfigMap, configMap *corev1.ConfigMap) {
   158  	configMap.Data = mcConfigMap.Spec.Template.Data
   159  	configMap.BinaryData = mcConfigMap.Spec.Template.BinaryData
   160  	configMap.Immutable = mcConfigMap.Spec.Template.Immutable
   161  	configMap.Labels = mcConfigMap.Spec.Template.Metadata.Labels
   162  	if configMap.Labels == nil {
   163  		configMap.Labels = map[string]string{}
   164  	}
   165  	configMap.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault
   166  
   167  	configMap.Annotations = mcConfigMap.Spec.Template.Metadata.Annotations
   168  }
   169  
   170  func (r *Reconciler) updateStatus(ctx context.Context, mcConfigMap *clustersv1alpha1.MultiClusterConfigMap, opResult controllerutil.OperationResult, err error) (ctrl.Result, error) {
   171  	clusterName := clusters.GetClusterName(ctx, r.Client)
   172  	newCondition := clusters.GetConditionFromResult(err, opResult, "ConfigMap")
   173  	return clusters.UpdateStatus(mcConfigMap, &mcConfigMap.Status, mcConfigMap.Spec.Placement, newCondition, clusterName,
   174  		r.AgentChannel, func() error { return r.Status().Update(ctx, mcConfigMap) })
   175  }