github.com/verrazzano/verrazzano@v1.7.0/platform-operator/controllers/configmaps/components/controller.go (about)

     1  // Copyright (c) 2023, 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 components
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	vzctrl "github.com/verrazzano/verrazzano/pkg/controller"
    10  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    11  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    12  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    13  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/spi"
    14  	"go.uber.org/zap"
    15  	v1 "k8s.io/api/core/v1"
    16  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	ctrl "sigs.k8s.io/controller-runtime"
    19  	"sigs.k8s.io/controller-runtime/pkg/client"
    20  	"sigs.k8s.io/controller-runtime/pkg/controller"
    21  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    22  	"sigs.k8s.io/controller-runtime/pkg/event"
    23  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    24  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    25  	"time"
    26  )
    27  
    28  var shimComponents = map[string]spi.Component{}
    29  
    30  type ComponentConfigMapReconciler struct {
    31  	client.Client
    32  	Scheme *runtime.Scheme
    33  	DryRun bool
    34  }
    35  
    36  func initShimComponentList() {
    37  	// Add any shim components here that you want to test.
    38  	// For example,
    39  	// shimComponents[clusterapi.ComponentName] = clusterapi.NewComponent()
    40  }
    41  
    42  func (r *ComponentConfigMapReconciler) SetupWithManager(mgr ctrl.Manager) error {
    43  	initShimComponentList()
    44  	return ctrl.NewControllerManagedBy(mgr).
    45  		For(&v1.ConfigMap{}).
    46  		WithEventFilter(r.createComponentConfigMapPredicate()).
    47  		WithOptions(controller.Options{
    48  			MaxConcurrentReconciles: 10,
    49  		}).
    50  		Complete(r)
    51  }
    52  
    53  func (r *ComponentConfigMapReconciler) createComponentConfigMapPredicate() predicate.Predicate {
    54  	return predicate.Funcs{
    55  		CreateFunc: func(e event.CreateEvent) bool {
    56  			return r.isComponentConfigMap(e.Object)
    57  		},
    58  		DeleteFunc: func(e event.DeleteEvent) bool {
    59  			return r.isComponentConfigMap(e.Object)
    60  		},
    61  		UpdateFunc: func(e event.UpdateEvent) bool {
    62  			return r.isComponentConfigMap(e.ObjectNew)
    63  		},
    64  	}
    65  }
    66  
    67  func (r *ComponentConfigMapReconciler) isComponentConfigMap(o client.Object) bool {
    68  	configMap := o.(*v1.ConfigMap)
    69  	return configMap.Labels[devComponentConfigMapAPIVersionLabel] == devComponentConfigMapAPIVersionv1beta2 &&
    70  		configMap.Labels[devComponentConfigMapKindLabel] != ""
    71  }
    72  
    73  // Reconcile function for the ComponentConfigMapReconciler
    74  func (r *ComponentConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    75  	if ctx == nil {
    76  		ctx = context.Background()
    77  	}
    78  
    79  	verrazzanos := &vzapi.VerrazzanoList{}
    80  	err := r.List(ctx, verrazzanos)
    81  	if err != nil && !k8serrors.IsNotFound(err) {
    82  		zap.S().Errorf("Failed to get Verrazzanos %s/%s", req.Namespace, req.Name)
    83  		return newRequeueWithDelay(), err
    84  	}
    85  	if err != nil || len(verrazzanos.Items) == 0 {
    86  		zap.S().Debug("No Verrazzanos found in the cluster")
    87  		return newRequeueWithDelay(), err
    88  	}
    89  	vz := verrazzanos.Items[0]
    90  
    91  	zap.S().Infof("Reconciling component configmap %s/%s", req.Namespace, req.Name)
    92  	// Get the configmap for the request
    93  	configMap := &v1.ConfigMap{}
    94  	if err = r.Get(ctx, req.NamespacedName, configMap); err != nil {
    95  		if k8serrors.IsNotFound(err) {
    96  			return ctrl.Result{}, nil
    97  		}
    98  		zap.S().Errorf("Failed to get configmap %s/%s", req.Namespace, req.Name)
    99  		return newRequeueWithDelay(), err
   100  	}
   101  
   102  	if configMap.Namespace != vz.Namespace {
   103  		err = fmt.Errorf("Component ConfigMap must be in the same namespace as the Verrazzano resource, ConfigMap namespace: %s, Verrazzano namespace: %s", configMap.Namespace, vz.Namespace)
   104  		zap.S().Error(err)
   105  		return ctrl.Result{}, err
   106  	}
   107  
   108  	// Get the resource logger needed to log message using 'progress' and 'once' methods
   109  	log, err := vzlog.EnsureResourceLogger(&vzlog.ResourceConfig{
   110  		Name:           configMap.Name,
   111  		Namespace:      configMap.Namespace,
   112  		ID:             string(configMap.UID),
   113  		Generation:     configMap.Generation,
   114  		ControllerName: "verrazzanodevcomponent",
   115  	})
   116  	if err != nil {
   117  		zap.S().Errorf("Failed to create controller logger for component configmap %s/%s: %v", configMap.GetName(), configMap.GetNamespace(), err)
   118  		return newRequeueWithDelay(), err
   119  	}
   120  
   121  	var comp spi.Component
   122  	if configMap.Labels[devComponentConfigMapKindLabel] == devComponentConfigMapKindHelmComponent {
   123  		comp, err = newDevHelmComponent(configMap)
   124  	} else if configMap.Labels[devComponentConfigMapKindLabel] == devComponentConfigMapKindShimComponent {
   125  		comp, err = newDevShimComponent(configMap)
   126  	} else {
   127  		err = fmt.Errorf("%s is not a support configmap-kind, %s and %s are the only configmap-kind supported",
   128  			configMap.Labels[devComponentConfigMapKindLabel], devComponentConfigMapKindHelmComponent, devComponentConfigMapKindShimComponent)
   129  	}
   130  
   131  	if err != nil {
   132  		log.Errorf("Invalid configmap %s/%s: %v", configMap.GetNamespace(), configMap.GetName(), err)
   133  		return ctrl.Result{}, err
   134  	}
   135  
   136  	compCtx, err := spi.NewContext(log, r.Client, &vz, nil, false)
   137  	if err != nil {
   138  		log.Errorf("Failed to create context: %v", err)
   139  		return newRequeueWithDelay(), err
   140  	}
   141  
   142  	return r.processComponent(compCtx, comp, configMap)
   143  }
   144  
   145  func (r *ComponentConfigMapReconciler) processComponent(ctx spi.ComponentContext, comp spi.Component, configMap *v1.ConfigMap) (ctrl.Result, error) {
   146  	// check if component is being deleted
   147  	if !configMap.DeletionTimestamp.IsZero() {
   148  		// uninstall component
   149  		if err := doUninstall(ctx, comp); err != nil {
   150  			ctx.Log().Errorf("Error uninstalling dev component %s: %v", comp.Name(), err)
   151  			return newRequeueWithDelay(), err
   152  		}
   153  		ctx.Log().Infof("Successfully uninstalled dev component %s", comp.Name())
   154  
   155  		// remove finalizer to delete the component
   156  		controllerutil.RemoveFinalizer(configMap, constants.DevComponentFinalizer)
   157  		err := r.Update(context.TODO(), configMap)
   158  		if err != nil {
   159  			ctx.Log().Errorf("Error removing finalizer %s for dev component %s: %v", constants.DevComponentFinalizer, comp.Name(), err)
   160  			return newRequeueWithDelay(), err
   161  		}
   162  		ctx.Log().Infof("dev component %s has been successfully uninstalled", comp.Name())
   163  		return reconcile.Result{Requeue: true}, nil
   164  	}
   165  
   166  	// Check if our finalizer is already present and add it if not
   167  	if !controllerutil.ContainsFinalizer(configMap, constants.DevComponentFinalizer) {
   168  		configMap.Finalizers = append(configMap.Finalizers, constants.DevComponentFinalizer)
   169  		err := r.Update(context.TODO(), configMap)
   170  		if err != nil {
   171  			ctx.Log().Errorf("Error adding finalizer %s for dev component %s: %v", constants.DevComponentFinalizer, comp.Name(), err)
   172  			return newRequeueWithDelay(), err
   173  		}
   174  		ctx.Log().Infof("Successfully added finalizer %s to configmap %s for dev component %s", constants.DevComponentFinalizer, configMap.Name, comp.Name())
   175  		// adding finalizer to ConfigMap will trigger a requeue so no need to requeue here
   176  		return reconcile.Result{}, nil
   177  	}
   178  
   179  	// if the release has not been installed, install it
   180  	installed, err := comp.IsInstalled(ctx)
   181  	if err != nil {
   182  		ctx.Log().Errorf("Error checking release status for release %s: %v", comp.Name(), err)
   183  		return newRequeueWithDelay(), err
   184  	}
   185  	if !installed {
   186  		if err = doInstall(ctx, comp); err != nil {
   187  			ctx.Log().Errorf("Error installing dev component %s: %v", comp.Name(), err)
   188  			return newRequeueWithDelay(), err
   189  		}
   190  		ctx.Log().Infof("dev component %s has been successfully installed", comp.Name())
   191  		return reconcile.Result{}, nil
   192  	}
   193  
   194  	// if the release has already been installed, upgrade it
   195  	if err = doUpgrade(ctx, comp); err != nil {
   196  		ctx.Log().Errorf("Error upgrading dev component %s: %v", comp.Name(), err)
   197  		return newRequeueWithDelay(), err
   198  	}
   199  	ctx.Log().Infof("dev component %s has been successfully upgraded", comp.Name())
   200  	return reconcile.Result{}, nil
   201  
   202  }
   203  
   204  // Create a new Result that will cause reconcile to requeue after a short delay
   205  func newRequeueWithDelay() ctrl.Result {
   206  	return vzctrl.NewRequeueWithDelay(3, 5, time.Second)
   207  }
   208  
   209  func doInstall(ctx spi.ComponentContext, comp spi.Component) error {
   210  	if err := comp.PreInstall(ctx); err != nil {
   211  		return err
   212  	}
   213  	if err := comp.Install(ctx); err != nil {
   214  		return err
   215  	}
   216  	for {
   217  		if !comp.IsReady(ctx) {
   218  			ctx.Log().Progressf("Component %s has been installed. Waiting for the component to be ready", comp.Name())
   219  			time.Sleep(time.Second * 5)
   220  		} else {
   221  			break
   222  		}
   223  	}
   224  	return comp.PostInstall(ctx)
   225  }
   226  
   227  func doUpgrade(ctx spi.ComponentContext, comp spi.Component) error {
   228  	if err := comp.PreUpgrade(ctx); err != nil {
   229  		return err
   230  	}
   231  	if err := comp.Upgrade(ctx); err != nil {
   232  		return err
   233  	}
   234  	return comp.PostUpgrade(ctx)
   235  }
   236  
   237  func doUninstall(ctx spi.ComponentContext, comp spi.Component) error {
   238  	if err := comp.PreUninstall(ctx); err != nil {
   239  		return err
   240  	}
   241  	if err := comp.Uninstall(ctx); err != nil {
   242  		return err
   243  	}
   244  	return comp.PostUninstall(ctx)
   245  }