github.com/verrazzano/verrazzano@v1.7.0/platform-operator/experimental/controllers/integration/single/reconciler.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 single
     5  
     6  import (
     7  	ctx "context"
     8  	"fmt"
     9  	"github.com/verrazzano/verrazzano-modules/pkg/controller/result"
    10  	"github.com/verrazzano/verrazzano-modules/pkg/controller/spi/controllerspi"
    11  	"github.com/verrazzano/verrazzano-modules/pkg/helm"
    12  	"github.com/verrazzano/verrazzano-modules/pkg/vzlog"
    13  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/certmanager/certmanager"
    14  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common"
    15  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/fluentoperator"
    16  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/istio"
    17  	"github.com/verrazzano/verrazzano/platform-operator/experimental/event"
    18  	"github.com/verrazzano/verrazzano/platform-operator/internal/config"
    19  	corev1 "k8s.io/api/core/v1"
    20  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    21  	"k8s.io/apimachinery/pkg/runtime"
    22  	"os"
    23  	"path"
    24  )
    25  
    26  // Create ModuleIntegrateAllRequestEvent for these modules
    27  var requireIntegrateAll = map[string]bool{
    28  	certmanager.ComponentName:              true,
    29  	fluentoperator.ComponentName:           true,
    30  	istio.ComponentName:                    true,
    31  	common.PrometheusOperatorComponentName: true,
    32  }
    33  
    34  // Reconcile reconciles the IntegrateSingleRequestEvent (in the form of a configmap)
    35  // to perform integration for a single module. Certain modules, such as prometheus-operator,
    36  // require that all integration charts for other modules be installed/upgraded. So in addition
    37  // to applying the chart for a single module, this reconciler may create second event,
    38  // the IntegrateCascadeRequestEvent which processed by the cascade integration controller.
    39  func (r Reconciler) Reconcile(spictx controllerspi.ReconcileContext, u *unstructured.Unstructured) result.Result {
    40  	log := vzlog.DefaultLogger()
    41  
    42  	// Get the configmap and convert into an event
    43  	cm := &corev1.ConfigMap{}
    44  	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, cm); err != nil {
    45  		spictx.Log.ErrorfThrottled(err.Error())
    46  		// This is a fatal error, don't requeue
    47  		return result.NewResult()
    48  	}
    49  	ev, err := event.ConfigMapToModuleIntegrationEvent(log, cm)
    50  	if err != nil {
    51  		spictx.Log.ErrorfThrottled(err.Error())
    52  		return result.NewResultShortRequeueDelayWithError(err)
    53  	}
    54  
    55  	// Either delete the Helm release or apply the integration chart to install/update the release
    56  	if ev.Action == event.Deleted {
    57  		res := r.deleteIntegrationRelease(log, ev)
    58  		if res.ShouldRequeue() {
    59  			return res
    60  		}
    61  	} else {
    62  		res := r.applyIntegrationChart(log, ev)
    63  		if res.ShouldRequeue() {
    64  			return res
    65  		}
    66  	}
    67  
    68  	// If needed, create an IntegrateCascadeRequestEvent using the same payload as the module event that was just processed
    69  	// this is needed to integrate related modules affected by this module
    70  	if ev.Cascade {
    71  		_, ok := requireIntegrateAll[ev.ModuleName]
    72  		if ok {
    73  			res := event.CreateModuleIntegrationCascadeEvent(log, r.Client, ev)
    74  			if res.ShouldRequeue() {
    75  				return res
    76  			}
    77  		}
    78  	}
    79  
    80  	// Delete the event.  This is safe to do since the integration controller
    81  	// is the only controller processing IntegrateSingleRequestEvent events
    82  	if err := r.Client.Delete(ctx.TODO(), cm); err != nil {
    83  		log.ErrorfThrottled("Failed to delete event configmap %s", cm.Name)
    84  		return result.NewResultShortRequeueDelayWithError(err)
    85  	}
    86  	return result.NewResult()
    87  }
    88  
    89  // applyIntegrationChart applies the integration chart for the module if chart exists
    90  func (r Reconciler) applyIntegrationChart(log vzlog.VerrazzanoLogger, ev *event.ModuleIntegrationEvent) result.Result {
    91  	// Get the chart directories
    92  	itegrationChartsDir := config.GetIntegrationChartsDir()
    93  
    94  	// Nothing to do if an integration chart doesn't exist for this module
    95  	moduleChartDir := path.Join(itegrationChartsDir, ev.ModuleName)
    96  	_, err := os.Stat(moduleChartDir)
    97  	if err != nil {
    98  		if os.IsNotExist(err) {
    99  			return result.NewResult()
   100  		}
   101  		log.ErrorfThrottled("Failed to check if integration chart exists for module %s: %v", ev.ModuleName, err)
   102  		return result.NewResultShortRequeueDelayWithError(err)
   103  	}
   104  
   105  	// Get the chart.yaml for this module
   106  	chartInfo, err := helm.GetChartInfo(moduleChartDir)
   107  	if err != nil {
   108  		log.ErrorfThrottled("Failed to read Chart.yaml for chart %s", moduleChartDir)
   109  		return result.NewResultShortRequeueDelayWithError(err)
   110  	}
   111  
   112  	// Perform a Helm install using the helm upgrade --install command
   113  	// Block until helm finishes (wait = true)
   114  	var opts = &helm.HelmReleaseOpts{
   115  		ReleaseName:  getReleaseName(ev.ResourceNSN.Name),
   116  		Namespace:    ev.TargetNamespace,
   117  		ChartPath:    moduleChartDir,
   118  		ChartVersion: chartInfo.Version,
   119  		Overrides:    []helm.HelmOverrides{},
   120  	}
   121  	_, err = helm.UpgradeRelease(log, opts, true, false)
   122  	if err != nil {
   123  		return result.NewResultShortRequeueDelayIfError(err)
   124  	}
   125  	return result.NewResult()
   126  }
   127  
   128  // deleteIntegrationRelease deletes the integration release
   129  func (r Reconciler) deleteIntegrationRelease(log vzlog.VerrazzanoLogger, ev *event.ModuleIntegrationEvent) result.Result {
   130  	relName := getReleaseName(ev.ResourceNSN.Name)
   131  	relNamespace := ev.TargetNamespace
   132  
   133  	// Check if release is installed
   134  	installed, err := helm.IsReleaseInstalled(relName, relNamespace)
   135  	if err != nil {
   136  		log.ErrorfThrottled("Failed checking if integration Helm release %s is installed: %v", relName, err.Error())
   137  		return result.NewResultShortRequeueDelayWithError(err)
   138  	}
   139  	if !installed {
   140  		return result.NewResult()
   141  	}
   142  
   143  	// Uninstall release
   144  	err = helm.Uninstall(log, relName, ev.TargetNamespace, false)
   145  	if err != nil {
   146  		log.ErrorfThrottled("Failed deleting integration Helm release %s: %v", relName, err.Error())
   147  		return result.NewResultShortRequeueDelayWithError(err)
   148  	}
   149  
   150  	return result.NewResult()
   151  }
   152  
   153  func getReleaseName(moduleName string) string {
   154  	return fmt.Sprintf("%s-%s", moduleName, "integration")
   155  }