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 }