Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BOP-14][BOP-40] Support for deleting addon helm charts, namespaced addon helm charts #3

Merged
merged 3 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions controllers/addon_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"

boundlessv1alpha1 "github.com/mirantis/boundless-operator/api/v1alpha1"
Expand Down Expand Up @@ -52,11 +53,6 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, err
}

if instance.DeletionTimestamp != nil {
logger.Info("Should remove addon", "Name", req.Name)
return ctrl.Result{}, nil
}

chart := helm.Chart{
Name: instance.Spec.Chart.Name,
Repo: instance.Spec.Chart.Repo,
Expand All @@ -66,8 +62,42 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}

hc := helm.NewHelmChartController(r.Client, logger)

addonFinalizerName := "boundless.mirantis.com/finalizer"

if instance.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object. This is equivalent
// registering our finalizer.
if !controllerutil.ContainsFinalizer(instance, addonFinalizerName) {
controllerutil.AddFinalizer(instance, addonFinalizerName)
if err := r.Update(ctx, instance); err != nil {
return ctrl.Result{}, err
}
}
} else {
// The object is being deleted
if controllerutil.ContainsFinalizer(instance, addonFinalizerName) {
// our finalizer is present, so lets delete the helm chart
if err := hc.DeleteHelmChart(chart, instance.Spec.Namespace); err != nil {
// if fail to delete the helm chart here, return with error
// so that it can be retried
return ctrl.Result{}, err
}

// remove our finalizer from the list and update it.
controllerutil.RemoveFinalizer(instance, addonFinalizerName)
if err := r.Update(ctx, instance); err != nil {
return ctrl.Result{}, err
}
}

// Stop reconciliation as the item is being deleted
return ctrl.Result{}, nil
}

logger.Info("Creating Addon HelmChart resource", "Name", chart.Name, "Version", chart.Version)
if err2 := hc.CreateHelmChart(chart); err2 != nil {
if err2 := hc.CreateHelmChart(chart, instance.Spec.Namespace); err2 != nil {
logger.Error(err, "failed to install addon", "Name", chart.Name, "Version", chart.Version)
return ctrl.Result{Requeue: true}, err2
}
Expand Down
134 changes: 111 additions & 23 deletions controllers/blueprint_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package controllers
import (
"context"
"fmt"
"strings"

"github.com/go-logr/logr"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -16,6 +17,8 @@ import (
"github.com/mirantis/boundless-operator/pkg/controllers/installation"
)

const boundlessSystemNamespace = "boundless-system"

// BlueprintReconciler reconciles a Blueprint object
type BlueprintReconciler struct {
client.Client
Expand Down Expand Up @@ -68,45 +71,110 @@ func (r *BlueprintReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
}

for _, addon := range instance.Spec.Components.Addons {
if addon.Namespace == "" {
addon.Namespace = instance.Namespace
err, addonsToUninstall := r.getInstalledAddons(ctx, err, logger)
if err != nil {
return ctrl.Result{}, err
}

for _, addonSpec := range instance.Spec.Components.Addons {
if addonSpec.Namespace == "" {
addonSpec.Namespace = instance.Namespace
}

logger.Info("Reconciling addon", "Name", addon.Name)
err = r.createOrUpdateAddon(ctx, logger, addonResource(&addon))
logger.Info("Reconciling addonSpec", "Name", addonSpec.Name, "Spec.Namespace", addonSpec.Namespace)
addon := addonResource(&addonSpec)
err = r.createOrUpdateAddon(ctx, logger, addon)
if err != nil {
logger.Error(err, "Failed to reconcile addon", "Name", addon.Name)
logger.Error(err, "Failed to reconcile addonSpec", "Name", addonSpec.Name, "Spec.Namespace", addonSpec.Namespace)
return ctrl.Result{Requeue: true}, err
}

// if the addon is in the spec , we shouldn't uninstall it
delete(addonsToUninstall, addon.GetName())
}

if len(addonsToUninstall) > 0 {
err = r.deleteAddons(ctx, logger, addonsToUninstall)
if err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

func (r *BlueprintReconciler) createOrUpdateAddon(ctx context.Context, logger logr.Logger, obj client.Object) error {
// getInstalledAddons returns a map of addons that are presently installed in the cluster
func (r *BlueprintReconciler) getInstalledAddons(ctx context.Context, err error, logger logr.Logger) (error, map[string]boundlessv1alpha1.Addon) {
allAddonsInCluster := &boundlessv1alpha1.AddonList{}
err = r.List(ctx, allAddonsInCluster)
if err != nil {
return err, nil
}
logger.Info("existing addons are", "addonNames", allAddonsInCluster.Items)

addonsToUninstall := make(map[string]boundlessv1alpha1.Addon)
for _, addon := range allAddonsInCluster.Items {
addonsToUninstall[addon.GetName()] = addon
}

return nil, addonsToUninstall
}

// deleteAddons deletes provided addonsToUninstall from the cluster
tppolkow marked this conversation as resolved.
Show resolved Hide resolved
func (r *BlueprintReconciler) deleteAddons(ctx context.Context, logger logr.Logger, addonsToUninstall map[string]boundlessv1alpha1.Addon) error {
for _, addon := range addonsToUninstall {
logger.Info("Removing addon", "Name", addon.Name, "Namespace", addon.Spec.Namespace)
if err := r.Delete(ctx, &addon, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
logger.Error(err, "Failed to remove addon", "Name", addon.Name)
return err
}
}

return nil
}

func (r *BlueprintReconciler) createOrUpdateAddon(ctx context.Context, logger logr.Logger, addon *boundlessv1alpha1.Addon) error {
err := r.createNamespaceIfNotExist(ctx, logger, addon.Spec.Namespace)
if err != nil {
return err
}

existing := &boundlessv1alpha1.Addon{}
err := r.Get(ctx, client.ObjectKey{Name: obj.GetName(), Namespace: obj.GetNamespace()}, existing)
err = r.Get(ctx, client.ObjectKey{Name: addon.GetName(), Namespace: addon.GetNamespace()}, existing)
if err != nil {
if client.IgnoreNotFound(err) != nil {
return err
}
}

if existing.Name != "" {
logger.Info("Add-on already exists. Updating", "Name", existing.Name)
obj.SetResourceVersion(existing.GetResourceVersion())
err = r.Update(ctx, obj)
if err != nil {
return fmt.Errorf("failed to update add-on %s: %w", existing.Name, err)
logger.Info("Add-on already exists. Updating", "Name", existing.Name, "Spec.Namespace", existing.Spec.Namespace)

if existing.Spec.Namespace == addon.Spec.Namespace {
addon.SetResourceVersion(existing.GetResourceVersion())
err = r.Update(ctx, addon)
if err != nil {
return fmt.Errorf("failed to update add-on %s: %w", existing.Name, err)
}

return nil
} else {
// the addon spec has moved namespaces, we need to delete and re-create it
tppolkow marked this conversation as resolved.
Show resolved Hide resolved
logger.Info("Addon has moved namespaces, deleting old version of add on",
"Name", addon.Name,
"Old Namespace", existing.Spec.Namespace,
"New Namespace", addon.Spec.Namespace)
if err := r.Delete(ctx, existing, client.PropagationPolicy(metav1.DeletePropagationForeground)); client.IgnoreNotFound(err) != nil {
logger.Error(err, "Failed to remove old version of addon", "Name", existing.Name)
return err
}
}
return nil
}

logger.Info("Creating add-on", "Name", obj.GetName())
err = r.Create(ctx, obj)
logger.Info("Creating add-on", "Name", addon.GetName(), "Spec.Namespace", addon.Spec.Namespace)
err = r.Create(ctx, addon)
if err != nil {
return fmt.Errorf("failed to create add-on %s: %w", obj.GetName(), err)
return fmt.Errorf("failed to create add-on %s: %w", addon.GetName(), err)
}
return nil
}
Expand All @@ -130,7 +198,7 @@ func (r *BlueprintReconciler) createOrUpdateIngress(ctx context.Context, logger
return nil
}

logger.Info("Creating ingress", "Name", obj.GetName())
logger.Info("Creating ingress", "Name", existing.Name)
err = r.Create(ctx, obj)
if err != nil {
return fmt.Errorf("failed to create ingress %s: %w", obj.GetName(), err)
Expand All @@ -143,7 +211,7 @@ func ingressResource(spec *boundlessv1alpha1.IngressSpec) *boundlessv1alpha1.Ing
return &boundlessv1alpha1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: v1.NamespaceDefault,
Namespace: boundlessSystemNamespace,
},
Spec: boundlessv1alpha1.IngressSpec{
Enabled: spec.Enabled,
Expand All @@ -154,11 +222,10 @@ func ingressResource(spec *boundlessv1alpha1.IngressSpec) *boundlessv1alpha1.Ing
}

func addonResource(spec *boundlessv1alpha1.AddonSpec) *boundlessv1alpha1.Addon {
name := fmt.Sprintf("mke-%s", spec.Chart.Name)
return &boundlessv1alpha1.Addon{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: v1.NamespaceDefault,
Name: spec.Name,
Namespace: boundlessSystemNamespace,
},
Spec: boundlessv1alpha1.AddonSpec{
Name: spec.Name,
Expand All @@ -174,6 +241,27 @@ func addonResource(spec *boundlessv1alpha1.AddonSpec) *boundlessv1alpha1.Addon {
}
}

func (r *BlueprintReconciler) createNamespaceIfNotExist(ctx context.Context, logger logr.Logger, namespace string) error {
ns := corev1.Namespace{}
err := r.Get(ctx, client.ObjectKey{Name: namespace}, &ns)
if err != nil {
if strings.Contains(err.Error(), "not found") {
logger.Info("namespace does not exist, need to create", "Namespace", namespace)
tppolkow marked this conversation as resolved.
Show resolved Hide resolved
ns.ObjectMeta.Name = namespace
err = r.Create(ctx, &ns)
if err != nil {
return err
}

} else {
logger.Info("error checking namespace exists", "Namespace", namespace)
return err
}
}

return nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *BlueprintReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
2 changes: 1 addition & 1 deletion controllers/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct

hc := helm.NewHelmChartController(r.Client, logger)
logger.Info("Creating HelmChart resource", "Name", chart.Name, "Version", chart.Version)
if err2 := hc.CreateHelmChart(chart); err2 != nil {
if err2 := hc.CreateHelmChart(chart, instance.Namespace); err2 != nil {
logger.Error(err, "failed to install ingress controller", "Controller Type", instance.Spec.Provider, "Version", "v1alpha1")
return ctrl.Result{Requeue: true}, err2
}
Expand Down
58 changes: 45 additions & 13 deletions pkg/helm/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

const namespaceDefault = metav1.NamespaceDefault

type Chart struct {
Name string `yaml:"name"`
Repo string `yaml:"repo"`
Expand All @@ -36,15 +34,15 @@ func NewHelmChartController(client client.Client, logger logr.Logger) *Controlle
}
}

func (hc *Controller) CreateHelmChart(chartSpec Chart) error {
func (hc *Controller) CreateHelmChart(chartSpec Chart, namespace string) error {

helmChart := helmv1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: chartSpec.Name,
Namespace: namespaceDefault,
Namespace: namespace,
},
Spec: helmv1.HelmChartSpec{
TargetNamespace: namespaceDefault,
TargetNamespace: namespace,
Chart: chartSpec.Name,
Version: chartSpec.Version,
Repo: chartSpec.Repo,
Expand All @@ -56,17 +54,51 @@ func (hc *Controller) CreateHelmChart(chartSpec Chart) error {
return hc.createOrUpdateHelmChart(helmChart)
}

func (hc *Controller) createOrUpdateHelmChart(chart helmv1.HelmChart) error {
//log.Infof("Creating helm chart '%+v'", chart)
func (hc *Controller) DeleteHelmChart(chartSpec Chart, namespace string) error {

chart := helmv1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: chartSpec.Name,
Namespace: namespace,
},
Spec: helmv1.HelmChartSpec{
TargetNamespace: namespace,
Chart: chartSpec.Name,
Version: chartSpec.Version,
Repo: chartSpec.Repo,
Set: chartSpec.Set,
ValuesContent: chartSpec.Values,
},
}

// set a deadline for the Kubernetes API operations
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

// @TODO: create the namespace if it doesn't exist
//err := hc.createNsIfNotExists(ctx, chart.Namespace)
//if err != nil {
// return fmt.Errorf("failed to create namespace: %w", err)
//}
existing, err := hc.getExistingHelmChart(ctx, chart.Namespace, chart.Name)
if err != nil {
return err
}

if existing == nil {
hc.logger.Info("helm chart to clean up does not exist", "ChartName", chart.GetName())
tppolkow marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

err = hc.client.Delete(ctx, &chart)
if err != nil {
hc.logger.Info("failed to delete helm chart", "ChartName", chart.GetName())
tppolkow marked this conversation as resolved.
Show resolved Hide resolved
return err
}

hc.logger.Info("helm chart successfully deleted", "ChartName", chart.GetName())
return nil
}

func (hc *Controller) createOrUpdateHelmChart(chart helmv1.HelmChart) error {
// set a deadline for the Kubernetes API operations
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

existing, err := hc.getExistingHelmChart(ctx, chart.Namespace, chart.Name)
if err != nil {
Expand All @@ -80,7 +112,7 @@ func (hc *Controller) createOrUpdateHelmChart(chart helmv1.HelmChart) error {
err = hc.client.Update(ctx, &chart)
hc.logger.Info("helm chart updated", "ChartName", chart.GetName())
} else {
hc.logger.Info("helm chart does not exists, creating", "ChartName", chart.GetName())
hc.logger.Info("helm chart does not exists, creating", "ChartName", chart.GetName(), "Namespace", chart.GetNamespace())
err = hc.client.Create(ctx, &chart)
if err != nil {
return err
Expand Down