Reintroduce default state machine for Provider controller
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
parent
f5ddc97108
commit
c32f9e1559
|
@ -28,7 +28,6 @@ import (
|
||||||
// that have the flux finalizer.
|
// that have the flux finalizer.
|
||||||
type finalizerPredicate struct {
|
type finalizerPredicate struct {
|
||||||
predicate.Funcs
|
predicate.Funcs
|
||||||
observeDeletion bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create allows events for objects with flux finalizer that have beed created.
|
// Create allows events for objects with flux finalizer that have beed created.
|
||||||
|
@ -46,6 +45,6 @@ func (finalizerPredicate) Update(e event.UpdateEvent) bool {
|
||||||
|
|
||||||
// Delete allows events for objects with flux finalizer that have been marked
|
// Delete allows events for objects with flux finalizer that have been marked
|
||||||
// for deletion.
|
// for deletion.
|
||||||
func (f finalizerPredicate) Delete(e event.DeleteEvent) bool {
|
func (finalizerPredicate) Delete(e event.DeleteEvent) bool {
|
||||||
return f.observeDeletion || controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
return controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package controller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
kuberecorder "k8s.io/client-go/tools/record"
|
kuberecorder "k8s.io/client-go/tools/record"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
|
@ -45,42 +44,21 @@ type ProviderReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
kuberecorder.EventRecorder
|
kuberecorder.EventRecorder
|
||||||
|
|
||||||
ControllerName string
|
|
||||||
TokenCache *cache.TokenCache
|
TokenCache *cache.TokenCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&apiv1beta3.Provider{}, builder.WithPredicates(finalizerPredicate{observeDeletion: true})).
|
For(&apiv1beta3.Provider{}, builder.WithPredicates(providerPredicate{})).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
||||||
log := ctrl.LoggerFrom(ctx)
|
|
||||||
|
|
||||||
obj := &apiv1beta3.Provider{}
|
obj := &apiv1beta3.Provider{}
|
||||||
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Examine if the object is under deletion.
|
|
||||||
var delete bool
|
|
||||||
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
|
||||||
delete = true
|
|
||||||
r.TokenCache.DeleteEventsForObject(apiv1beta3.ProviderKind, obj.GetName(), obj.GetNamespace(), notifier.OperationPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Early return if no migration is needed.
|
|
||||||
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if it's suspend and not being deleted.
|
|
||||||
if obj.Spec.Suspend && !delete {
|
|
||||||
log.Info("reconciliation is suspended for this object")
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
patcher, err := patch.NewHelper(obj, r.Client)
|
patcher, err := patch.NewHelper(obj, r.Client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
@ -92,11 +70,29 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Remove the notification-controller finalizer.
|
// Examine if the object is under deletion.
|
||||||
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
|
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
|
return r.reconcileDelete(obj)
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("removed finalizer from Provider to migrate to static Provider")
|
// Add finalizer if it doesn't exist.
|
||||||
r.Event(obj, corev1.EventTypeNormal, "Migration", "removed finalizer from Provider to migrate to static Provider")
|
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
|
||||||
|
controllerutil.AddFinalizer(obj, apiv1.NotificationFinalizer)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reconcileDelete handles the deletion of the object.
|
||||||
|
// It cleans up the caches and removes the finalizer.
|
||||||
|
func (r *ProviderReconciler) reconcileDelete(obj *apiv1beta3.Provider) (ctrl.Result, error) {
|
||||||
|
// Remove our finalizer from the list
|
||||||
|
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
|
||||||
|
|
||||||
|
// Cleanup caches.
|
||||||
|
r.TokenCache.DeleteEventsForObject(apiv1beta3.ProviderKind,
|
||||||
|
obj.GetName(), obj.GetNamespace(), notifier.OperationPost)
|
||||||
|
|
||||||
|
// Stop reconciliation as the object is being deleted
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -52,61 +52,29 @@ func TestProviderReconciler(t *testing.T) {
|
||||||
}
|
}
|
||||||
providerKey := client.ObjectKeyFromObject(provider)
|
providerKey := client.ObjectKeyFromObject(provider)
|
||||||
|
|
||||||
// Remove finalizer at create.
|
// Create without finalizer.
|
||||||
|
|
||||||
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, "foo.bar", apiv1.NotificationFinalizer)
|
|
||||||
provider.Spec = apiv1beta3.ProviderSpec{
|
provider.Spec = apiv1beta3.ProviderSpec{
|
||||||
Type: "slack",
|
Type: "generic",
|
||||||
}
|
}
|
||||||
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Should eventually have finalizer.
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
_ = testEnv.Get(ctx, providerKey, provider)
|
_ = testEnv.Get(ctx, providerKey, provider)
|
||||||
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
}, timeout, time.Second).Should(BeTrue())
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
// Remove finalizer at update.
|
// Remove finalizer.
|
||||||
|
|
||||||
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
|
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, apiv1.NotificationFinalizer)
|
controllerutil.RemoveFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Should eventually have finalizer again.
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
_ = testEnv.Get(ctx, providerKey, provider)
|
_ = testEnv.Get(ctx, providerKey, provider)
|
||||||
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
|
||||||
}, timeout, time.Second).Should(BeTrue())
|
|
||||||
|
|
||||||
// Remove finalizer at delete.
|
|
||||||
|
|
||||||
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
// Suspend the provider to prevent finalizer from getting removed.
|
|
||||||
// Ensure only flux finalizer is set to allow the object to be garbage
|
|
||||||
// collected at the end.
|
|
||||||
// NOTE: Suspending and updating finalizers are done separately here as
|
|
||||||
// doing them in a single patch results in flaky test where the finalizer
|
|
||||||
// update doesn't gets registered with the kube-apiserver, resulting in
|
|
||||||
// timeout waiting for finalizer to appear on the object below.
|
|
||||||
provider.Spec.Suspend = true
|
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
|
||||||
g.Eventually(func() bool {
|
|
||||||
_ = k8sClient.Get(ctx, providerKey, provider)
|
|
||||||
return provider.Spec.Suspend == true
|
|
||||||
}, timeout).Should(BeTrue())
|
|
||||||
|
|
||||||
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
// Add finalizer and verify that finalizer exists on the object using a live
|
|
||||||
// client.
|
|
||||||
provider.ObjectMeta.Finalizers = []string{apiv1.NotificationFinalizer}
|
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
|
||||||
g.Eventually(func() bool {
|
|
||||||
_ = k8sClient.Get(ctx, providerKey, provider)
|
|
||||||
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
}, timeout).Should(BeTrue())
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
// Delete the object and verify.
|
// Delete the object and verify.
|
||||||
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
Copyright 2025 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||||
|
|
||||||
|
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
|
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// providerPredicate implements predicate functions for the Provider API.
|
||||||
|
type providerPredicate struct{}
|
||||||
|
|
||||||
|
func (providerPredicate) Create(e event.CreateEvent) bool {
|
||||||
|
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Update(e event.UpdateEvent) bool {
|
||||||
|
if e.ObjectNew == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !controllerutil.ContainsFinalizer(e.ObjectNew, apiv1.NotificationFinalizer) ||
|
||||||
|
!e.ObjectNew.(*apiv1beta3.Provider).ObjectMeta.DeletionTimestamp.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Delete(e event.DeleteEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Generic(e event.GenericEvent) bool {
|
||||||
|
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
||||||
|
}
|
|
@ -83,7 +83,6 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
if err := (&ProviderReconciler{
|
if err := (&ProviderReconciler{
|
||||||
Client: testEnv,
|
Client: testEnv,
|
||||||
ControllerName: controllerName,
|
|
||||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||||
}).SetupWithManager(testEnv); err != nil {
|
}).SetupWithManager(testEnv); err != nil {
|
||||||
panic(fmt.Sprintf("Failed to start ProviderReconciler: %v", err))
|
panic(fmt.Sprintf("Failed to start ProviderReconciler: %v", err))
|
||||||
|
|
1
main.go
1
main.go
|
@ -197,7 +197,6 @@ func main() {
|
||||||
|
|
||||||
if err = (&controller.ProviderReconciler{
|
if err = (&controller.ProviderReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
ControllerName: controllerName,
|
|
||||||
EventRecorder: mgr.GetEventRecorderFor(controllerName),
|
EventRecorder: mgr.GetEventRecorderFor(controllerName),
|
||||||
TokenCache: tokenCache,
|
TokenCache: tokenCache,
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue