Skip to content

Commit

Permalink
Merge pull request #16 from controlplaneio-fluxcd/disable-reconcile
Browse files Browse the repository at this point in the history
Add support for disabling the reconciliation
  • Loading branch information
stefanprodan authored Jun 3, 2024
2 parents 6fa6d4c + dd74983 commit 95a962f
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
6 changes: 6 additions & 0 deletions api/v1/fluxinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ func (in *FluxInstance) SetConditions(conditions []metav1.Condition) {
in.Status.Conditions = conditions
}

// IsDisabled returns true if the object has the reconcile annotation set to 'disabled'.
func (in *FluxInstance) IsDisabled() bool {
val, ok := in.GetAnnotations()[ReconcileAnnotation]
return ok && strings.ToLower(val) == DisabledValue
}

// GetInterval returns the interval at which the object should be reconciled.
// If no interval is set, the default is 60 minutes.
func (in *FluxInstance) GetInterval() time.Duration {
Expand Down
9 changes: 9 additions & 0 deletions internal/controller/fluxinstance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package controller

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -89,6 +90,14 @@ func (r *FluxInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{Requeue: true}, nil
}

// Pause reconciliation if the object has the reconcile annotation set to 'disabled'.
if obj.IsDisabled() {
msg := "Reconciliation in disabled"
log.Error(errors.New("can't reconcile instance"), msg)
r.Event(obj, corev1.EventTypeWarning, "ReconciliationDisabled", msg)
return ctrl.Result{}, nil
}

// Reconcile the object.
return r.reconcile(ctx, obj, patcher)
}
Expand Down
100 changes: 100 additions & 0 deletions internal/controller/fluxinstance_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,106 @@ func TestFluxInstanceReconciler_Downgrade(t *testing.T) {
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
}

func TestFluxInstanceReconciler_Disabled(t *testing.T) {
g := NewWithT(t)
reconciler := getFluxInstanceReconciler()
spec := getDefaultFluxSpec()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

ns, err := testEnv.CreateNamespace(ctx, "test")
g.Expect(err).ToNot(HaveOccurred())

obj := &fluxcdv1.FluxInstance{
ObjectMeta: metav1.ObjectMeta{
Name: ns.Name,
Namespace: ns.Name,
},
Spec: spec,
}

err = testClient.Create(ctx, obj)
g.Expect(err).ToNot(HaveOccurred())

// Initialize the instance.
r, err := reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.Requeue).To(BeTrue())

// Install the instance.
r, err = reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())

// Check if the instance was installed.
result := &fluxcdv1.FluxInstance{}
err = testClient.Get(ctx, client.ObjectKeyFromObject(obj), result)
g.Expect(err).ToNot(HaveOccurred())
checkInstanceReadiness(g, result)

// Disable the instance reconciliation.
resultP := result.DeepCopy()
resultP.SetAnnotations(
map[string]string{
fluxcdv1.ReconcileAnnotation: fluxcdv1.DisabledValue,
})
resultP.Spec.Components = []fluxcdv1.Component{"source-controller"}
err = testClient.Patch(ctx, resultP, client.MergeFrom(result))
g.Expect(err).ToNot(HaveOccurred())

// Reconcile the instance with disabled reconciliation.
r, err = reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.IsZero()).To(BeTrue())

// Check the final status.
resultFinal := &fluxcdv1.FluxInstance{}
err = testClient.Get(ctx, client.ObjectKeyFromObject(obj), resultFinal)
g.Expect(err).ToNot(HaveOccurred())

// Check if events were recorded for each step.
events := getEvents(result.Name)
g.Expect(events).To(HaveLen(3))
g.Expect(events[0].Reason).To(Equal(meta.ProgressingReason))
g.Expect(events[1].Reason).To(Equal(meta.ReconciliationSucceededReason))
g.Expect(events[2].Reason).To(Equal("ReconciliationDisabled"))

// Check that resources were not deleted.
kc := &appsv1.Deployment{}
err = testClient.Get(ctx, types.NamespacedName{Name: "kustomize-controller", Namespace: ns.Name}, kc)
g.Expect(err).ToNot(HaveOccurred())

// Enable the instance reconciliation.
resultP = resultFinal.DeepCopy()
resultP.SetAnnotations(
map[string]string{
fluxcdv1.ReconcileAnnotation: fluxcdv1.EnabledValue,
})
err = testClient.Patch(ctx, resultP, client.MergeFrom(result))
g.Expect(err).ToNot(HaveOccurred())

// Uninstall the instance.
err = testClient.Delete(ctx, obj)
g.Expect(err).ToNot(HaveOccurred())

r, err = reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.IsZero()).To(BeTrue())

// Check that resources were not deleted.
sc := &appsv1.Deployment{}
err = testClient.Get(ctx, types.NamespacedName{Name: "source-controller", Namespace: ns.Name}, sc)
g.Expect(err).To(HaveOccurred())
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
}

func TestFluxInstanceReconciler_Profiles(t *testing.T) {
g := NewWithT(t)
reconciler := getFluxInstanceReconciler()
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/fluxinstance_uninstaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (r *FluxInstanceReconciler) uninstall(ctx context.Context,
reconcileStart := time.Now()
log := ctrl.LoggerFrom(ctx)

if obj.Status.Inventory == nil || len(obj.Status.Inventory.Entries) == 0 {
if obj.IsDisabled() || obj.Status.Inventory == nil || len(obj.Status.Inventory.Entries) == 0 {
controllerutil.RemoveFinalizer(obj, fluxcdv1.Finalizer)
return ctrl.Result{}, nil
}
Expand Down

0 comments on commit 95a962f

Please sign in to comment.