Skip to content

Commit 99c6dbb

Browse files
authored
Merge pull request #1112 from fluxcd/fix-provider-state-machine
Reintroduce default state machine for Provider controller
2 parents f5ddc97 + c32f9e1 commit 99c6dbb

File tree

6 files changed

+87
-78
lines changed

6 files changed

+87
-78
lines changed

internal/controller/finalizer_predicate.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
// that have the flux finalizer.
2929
type finalizerPredicate struct {
3030
predicate.Funcs
31-
observeDeletion bool
3231
}
3332

3433
// Create allows events for objects with flux finalizer that have beed created.
@@ -46,6 +45,6 @@ func (finalizerPredicate) Update(e event.UpdateEvent) bool {
4645

4746
// Delete allows events for objects with flux finalizer that have been marked
4847
// for deletion.
49-
func (f finalizerPredicate) Delete(e event.DeleteEvent) bool {
50-
return f.observeDeletion || controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
48+
func (finalizerPredicate) Delete(e event.DeleteEvent) bool {
49+
return controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
5150
}

internal/controller/provider_controller.go

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package controller
1919
import (
2020
"context"
2121

22-
corev1 "k8s.io/api/core/v1"
2322
kuberecorder "k8s.io/client-go/tools/record"
2423
ctrl "sigs.k8s.io/controller-runtime"
2524
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -45,42 +44,21 @@ type ProviderReconciler struct {
4544
client.Client
4645
kuberecorder.EventRecorder
4746

48-
ControllerName string
49-
TokenCache *cache.TokenCache
47+
TokenCache *cache.TokenCache
5048
}
5149

5250
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
5351
return ctrl.NewControllerManagedBy(mgr).
54-
For(&apiv1beta3.Provider{}, builder.WithPredicates(finalizerPredicate{observeDeletion: true})).
52+
For(&apiv1beta3.Provider{}, builder.WithPredicates(providerPredicate{})).
5553
Complete(r)
5654
}
5755

5856
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
59-
log := ctrl.LoggerFrom(ctx)
60-
6157
obj := &apiv1beta3.Provider{}
6258
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
6359
return ctrl.Result{}, client.IgnoreNotFound(err)
6460
}
6561

66-
// Examine if the object is under deletion.
67-
var delete bool
68-
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
69-
delete = true
70-
r.TokenCache.DeleteEventsForObject(apiv1beta3.ProviderKind, obj.GetName(), obj.GetNamespace(), notifier.OperationPost)
71-
}
72-
73-
// Early return if no migration is needed.
74-
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
75-
return ctrl.Result{}, nil
76-
}
77-
78-
// Skip if it's suspend and not being deleted.
79-
if obj.Spec.Suspend && !delete {
80-
log.Info("reconciliation is suspended for this object")
81-
return ctrl.Result{}, nil
82-
}
83-
8462
patcher, err := patch.NewHelper(obj, r.Client)
8563
if err != nil {
8664
return ctrl.Result{}, err
@@ -92,11 +70,29 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
9270
}
9371
}()
9472

95-
// Remove the notification-controller finalizer.
96-
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
73+
// Examine if the object is under deletion.
74+
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
75+
return r.reconcileDelete(obj)
76+
}
9777

98-
log.Info("removed finalizer from Provider to migrate to static Provider")
99-
r.Event(obj, corev1.EventTypeNormal, "Migration", "removed finalizer from Provider to migrate to static Provider")
78+
// Add finalizer if it doesn't exist.
79+
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
80+
controllerutil.AddFinalizer(obj, apiv1.NotificationFinalizer)
81+
}
10082

10183
return
10284
}
85+
86+
// reconcileDelete handles the deletion of the object.
87+
// It cleans up the caches and removes the finalizer.
88+
func (r *ProviderReconciler) reconcileDelete(obj *apiv1beta3.Provider) (ctrl.Result, error) {
89+
// Remove our finalizer from the list
90+
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
91+
92+
// Cleanup caches.
93+
r.TokenCache.DeleteEventsForObject(apiv1beta3.ProviderKind,
94+
obj.GetName(), obj.GetNamespace(), notifier.OperationPost)
95+
96+
// Stop reconciliation as the object is being deleted
97+
return ctrl.Result{}, nil
98+
}

internal/controller/provider_controller_test.go

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,61 +52,29 @@ func TestProviderReconciler(t *testing.T) {
5252
}
5353
providerKey := client.ObjectKeyFromObject(provider)
5454

55-
// Remove finalizer at create.
56-
57-
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, "foo.bar", apiv1.NotificationFinalizer)
55+
// Create without finalizer.
5856
provider.Spec = apiv1beta3.ProviderSpec{
59-
Type: "slack",
57+
Type: "generic",
6058
}
6159
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
6260

61+
// Should eventually have finalizer.
6362
g.Eventually(func() bool {
6463
_ = testEnv.Get(ctx, providerKey, provider)
65-
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
64+
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
6665
}, timeout, time.Second).Should(BeTrue())
6766

68-
// Remove finalizer at update.
69-
67+
// Remove finalizer.
7068
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
7169
g.Expect(err).ToNot(HaveOccurred())
72-
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, apiv1.NotificationFinalizer)
70+
controllerutil.RemoveFinalizer(provider, apiv1.NotificationFinalizer)
7371
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
7472

73+
// Should eventually have finalizer again.
7574
g.Eventually(func() bool {
7675
_ = testEnv.Get(ctx, providerKey, provider)
77-
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
78-
}, timeout, time.Second).Should(BeTrue())
79-
80-
// Remove finalizer at delete.
81-
82-
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
83-
g.Expect(err).ToNot(HaveOccurred())
84-
85-
// Suspend the provider to prevent finalizer from getting removed.
86-
// Ensure only flux finalizer is set to allow the object to be garbage
87-
// collected at the end.
88-
// NOTE: Suspending and updating finalizers are done separately here as
89-
// doing them in a single patch results in flaky test where the finalizer
90-
// update doesn't gets registered with the kube-apiserver, resulting in
91-
// timeout waiting for finalizer to appear on the object below.
92-
provider.Spec.Suspend = true
93-
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
94-
g.Eventually(func() bool {
95-
_ = k8sClient.Get(ctx, providerKey, provider)
96-
return provider.Spec.Suspend == true
97-
}, timeout).Should(BeTrue())
98-
99-
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
100-
g.Expect(err).ToNot(HaveOccurred())
101-
102-
// Add finalizer and verify that finalizer exists on the object using a live
103-
// client.
104-
provider.ObjectMeta.Finalizers = []string{apiv1.NotificationFinalizer}
105-
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
106-
g.Eventually(func() bool {
107-
_ = k8sClient.Get(ctx, providerKey, provider)
10876
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
109-
}, timeout).Should(BeTrue())
77+
}, timeout, time.Second).Should(BeTrue())
11078

11179
// Delete the object and verify.
11280
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
21+
"sigs.k8s.io/controller-runtime/pkg/event"
22+
23+
apiv1 "github.com/fluxcd/notification-controller/api/v1"
24+
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
25+
)
26+
27+
// providerPredicate implements predicate functions for the Provider API.
28+
type providerPredicate struct{}
29+
30+
func (providerPredicate) Create(e event.CreateEvent) bool {
31+
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
32+
}
33+
34+
func (providerPredicate) Update(e event.UpdateEvent) bool {
35+
if e.ObjectNew == nil {
36+
return false
37+
}
38+
return !controllerutil.ContainsFinalizer(e.ObjectNew, apiv1.NotificationFinalizer) ||
39+
!e.ObjectNew.(*apiv1beta3.Provider).ObjectMeta.DeletionTimestamp.IsZero()
40+
}
41+
42+
func (providerPredicate) Delete(e event.DeleteEvent) bool {
43+
return false
44+
}
45+
46+
func (providerPredicate) Generic(e event.GenericEvent) bool {
47+
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
48+
}

internal/controller/suite_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,8 @@ func TestMain(m *testing.M) {
8282
}
8383

8484
if err := (&ProviderReconciler{
85-
Client: testEnv,
86-
ControllerName: controllerName,
87-
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
85+
Client: testEnv,
86+
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
8887
}).SetupWithManager(testEnv); err != nil {
8988
panic(fmt.Sprintf("Failed to start ProviderReconciler: %v", err))
9089
}

main.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,9 @@ func main() {
196196
}
197197

198198
if err = (&controller.ProviderReconciler{
199-
Client: mgr.GetClient(),
200-
ControllerName: controllerName,
201-
EventRecorder: mgr.GetEventRecorderFor(controllerName),
202-
TokenCache: tokenCache,
199+
Client: mgr.GetClient(),
200+
EventRecorder: mgr.GetEventRecorderFor(controllerName),
201+
TokenCache: tokenCache,
203202
}).SetupWithManager(mgr); err != nil {
204203
setupLog.Error(err, "unable to create controller", "controller", "Provider")
205204
os.Exit(1)

0 commit comments

Comments
 (0)