Skip to content

Commit dc6fc1d

Browse files
committed
Cancel health checks on new reconciliation request
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent d1925bf commit dc6fc1d

File tree

7 files changed

+99
-102
lines changed

7 files changed

+99
-102
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ require (
2323
github.com/fluxcd/pkg/apis/acl v0.9.0
2424
github.com/fluxcd/pkg/apis/event v0.20.0
2525
github.com/fluxcd/pkg/apis/kustomize v1.13.0
26-
github.com/fluxcd/pkg/apis/meta v1.22.0
26+
github.com/fluxcd/pkg/apis/meta v1.22.1-0.20251010220540-14b66b2de1ab
2727
github.com/fluxcd/pkg/auth v0.32.0
2828
github.com/fluxcd/pkg/cache v0.12.0
2929
github.com/fluxcd/pkg/http/fetch v0.20.0
3030
github.com/fluxcd/pkg/kustomize v1.23.0
31-
github.com/fluxcd/pkg/runtime v0.88.0
31+
github.com/fluxcd/pkg/runtime v0.89.1-0.20251010220540-14b66b2de1ab
3232
github.com/fluxcd/pkg/ssa v0.60.0
3333
github.com/fluxcd/pkg/tar v0.15.0
3434
github.com/fluxcd/pkg/testserver v0.13.0

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ github.com/fluxcd/pkg/apis/event v0.20.0 h1:Vxd1kkS/CsQNPHTbmlL4qOcCmUmavEtaEOod
194194
github.com/fluxcd/pkg/apis/event v0.20.0/go.mod h1:wyY+8BHicfFP7sXzhMrKpZTQeojCsSpK9idAidjv61c=
195195
github.com/fluxcd/pkg/apis/kustomize v1.13.0 h1:GGf0UBVRIku+gebY944icVeEIhyg1P/KE3IrhOyJJnE=
196196
github.com/fluxcd/pkg/apis/kustomize v1.13.0/go.mod h1:TLKVqbtnzkhDuhWnAsN35977HvRfIjs+lgMuNro/LEc=
197-
github.com/fluxcd/pkg/apis/meta v1.22.0 h1:EHWQH5ZWml7i8eZ/AMjm1jxid3j/PQ31p+hIwCt6crM=
198-
github.com/fluxcd/pkg/apis/meta v1.22.0/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg=
197+
github.com/fluxcd/pkg/apis/meta v1.22.1-0.20251010220540-14b66b2de1ab h1:qV92SLmuKonakYhMSTX3//eRaJHVfCAGcmbdb/7tBrA=
198+
github.com/fluxcd/pkg/apis/meta v1.22.1-0.20251010220540-14b66b2de1ab/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg=
199199
github.com/fluxcd/pkg/auth v0.32.0 h1:D0RkbWlT2gqcEaEr6GLnm1XP1KDIYQI8zWzuZVnsE5I=
200200
github.com/fluxcd/pkg/auth v0.32.0/go.mod h1:Yhe6p3/wTUj80yrOqhpsbA48hQRM14OKwo3Qr4199XM=
201201
github.com/fluxcd/pkg/cache v0.12.0 h1:mabABT3jIfuo84VbIW+qvfqMZ7PbM5tXQgQvA2uo2rc=
@@ -206,8 +206,8 @@ github.com/fluxcd/pkg/http/fetch v0.20.0 h1:/Lvcu1JzABBLuQYuLKYh1K02a+RqbP4b5wIZ
206206
github.com/fluxcd/pkg/http/fetch v0.20.0/go.mod h1:9inwDiGOpuo14Rp06TpcgsYSkvp4YM+uWCjgDmpXMNk=
207207
github.com/fluxcd/pkg/kustomize v1.23.0 h1:4tNh30OsIj96YRfVP7qP0Fv3QTwdBo/udfZIcccL6NI=
208208
github.com/fluxcd/pkg/kustomize v1.23.0/go.mod h1:ZojUvmI4RiHk3BH3L3mBQ4ZDbNkiWfX9LvOMBjKq5Tc=
209-
github.com/fluxcd/pkg/runtime v0.88.0 h1:EFPJ0jnRino6yUEwiNtQTpUNyCf96N2MJb+S7LVG648=
210-
github.com/fluxcd/pkg/runtime v0.88.0/go.mod h1:qkmPX009tgiWufQ2Vj0QhyNgEU+0Cnz7Xy/naihLM10=
209+
github.com/fluxcd/pkg/runtime v0.89.1-0.20251010220540-14b66b2de1ab h1:MuF0vVykSXQoVP9vTYjP6xUoj41TUhuIXyL8NQG5fJU=
210+
github.com/fluxcd/pkg/runtime v0.89.1-0.20251010220540-14b66b2de1ab/go.mod h1:qkmPX009tgiWufQ2Vj0QhyNgEU+0Cnz7Xy/naihLM10=
211211
github.com/fluxcd/pkg/sourceignore v0.15.0 h1:tB30fuk4jlB3UGlR7ppJguZ3zaJh1iwuTCEufs91jSM=
212212
github.com/fluxcd/pkg/sourceignore v0.15.0/go.mod h1:mZ9X6gNtNkq9ZsD35LebEYjePc7DRvB2JdowMNoj6IU=
213213
github.com/fluxcd/pkg/ssa v0.60.0 h1:ikA78TWSLDmIc8I/goGAU/buYF6jto/gswE5hnOfWGk=

internal/controller/kustomization_controller.go

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,11 @@ type KustomizationReconciler struct {
114114

115115
// Feature gates
116116

117-
AdditiveCELDependencyCheck bool
118-
AllowExternalArtifact bool
119-
CancelHealthCheckOnNewRevision bool
120-
FailFast bool
121-
GroupChangeLog bool
122-
StrictSubstitutions bool
117+
AdditiveCELDependencyCheck bool
118+
AllowExternalArtifact bool
119+
FailFast bool
120+
GroupChangeLog bool
121+
StrictSubstitutions bool
123122
}
124123

125124
func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
@@ -271,6 +270,17 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
271270
return ctrl.Result{RequeueAfter: r.DependencyRequeueInterval}, nil
272271
}
273272

273+
// Handle health check cancellation.
274+
if errors.Is(reconcileErr, &runtimeCtrl.QueueEventSource{}) {
275+
qes := reconcileErr.(*runtimeCtrl.QueueEventSource)
276+
ctrl.LoggerFrom(ctx).Info("New reconciliation triggered, canceling health checks", "trigger", qes)
277+
conditions.MarkFalse(obj,
278+
meta.ReadyCondition,
279+
meta.HealthCheckCanceledReason,
280+
"New reconciliation triggered by %s/%s/%s", qes.Kind, qes.Namespace, qes.Name)
281+
return ctrl.Result{}, nil
282+
}
283+
274284
// Broadcast the reconciliation failure and requeue at the specified retry interval.
275285
if reconcileErr != nil {
276286
log.Error(reconcileErr, fmt.Sprintf("Reconciliation failed after %s, next try in %s",
@@ -498,6 +508,11 @@ func (r *KustomizationReconciler) reconcile(
498508
isNewRevision,
499509
drifted,
500510
changeSet.ToObjMetadataSet()); err != nil {
511+
512+
if errors.Is(err, &runtimeCtrl.QueueEventSource{}) {
513+
return err
514+
}
515+
501516
obj.Status.History.Upsert(checksum, time.Now(), time.Since(reconcileStart), meta.HealthCheckFailedReason, historyMeta)
502517
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, "%s", err)
503518
return err
@@ -984,43 +999,15 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
984999
}
9851000

9861001
// Check the health with a default timeout of 30sec shorter than the reconciliation interval.
987-
healthCtx := ctx
988-
if r.CancelHealthCheckOnNewRevision {
989-
// Create a cancellable context for health checks that monitors for new revisions
990-
var cancel context.CancelFunc
991-
healthCtx, cancel = context.WithCancel(ctx)
992-
defer cancel()
993-
994-
// Start monitoring for new revisions to allow early cancellation
995-
go func() {
996-
ticker := time.NewTicker(5 * time.Second)
997-
defer ticker.Stop()
998-
999-
for {
1000-
select {
1001-
case <-healthCtx.Done():
1002-
return
1003-
case <-ticker.C:
1004-
// Get the latest source artifact
1005-
latestSrc, err := r.getSource(ctx, obj)
1006-
if err == nil && latestSrc.GetArtifact() != nil {
1007-
if newRevision := latestSrc.GetArtifact().Revision; newRevision != revision {
1008-
const msg = "New revision detected during health check, cancelling"
1009-
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
1010-
ctrl.LoggerFrom(ctx).Info(msg, "current", revision, "new", newRevision)
1011-
cancel()
1012-
return
1013-
}
1014-
}
1015-
}
1016-
}
1017-
}()
1018-
}
1002+
healthCtx := runtimeCtrl.GetInterruptContext(ctx)
10191003
if err := manager.WaitForSetWithContext(healthCtx, toCheck, ssa.WaitOptions{
10201004
Interval: 5 * time.Second,
10211005
Timeout: obj.GetTimeout(),
10221006
FailFast: r.FailFast,
10231007
}); err != nil {
1008+
if is, err := runtimeCtrl.IsObjectEnqueued(ctx); is {
1009+
return err
1010+
}
10241011
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, "%s", err)
10251012
conditions.MarkFalse(obj, meta.HealthyCondition, meta.HealthCheckFailedReason, "%s", err)
10261013
return fmt.Errorf("health check failed after %s: %w", time.Since(checkStart).String(), err)

internal/controller/kustomization_manager.go

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"sigs.k8s.io/controller-runtime/pkg/predicate"
3131
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3232

33+
runtimeCtrl "github.com/fluxcd/pkg/runtime/controller"
3334
"github.com/fluxcd/pkg/runtime/predicates"
3435
sourcev1 "github.com/fluxcd/source-controller/api/v1"
3536

@@ -38,9 +39,10 @@ import (
3839

3940
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
4041
type KustomizationReconcilerOptions struct {
41-
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
42-
WatchConfigsPredicate predicate.Predicate
43-
WatchExternalArtifacts bool
42+
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
43+
WatchConfigsPredicate predicate.Predicate
44+
WatchExternalArtifacts bool
45+
CancelHealthCheckOnNewRevision bool
4446
}
4547

4648
// SetupWithManager sets up the controller with the Manager.
@@ -129,43 +131,64 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
129131
return fmt.Errorf("failed creating index %s: %w", indexSecret, err)
130132
}
131133

132-
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
133-
For(&kustomizev1.Kustomization{}, builder.WithPredicates(
134-
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
135-
)).
134+
var blder *builder.Builder
135+
var toComplete reconcile.TypedReconciler[reconcile.Request]
136+
var enqueueRequestsFromMapFunc func(objKind string, fn handler.MapFunc) handler.EventHandler
137+
138+
ksPredicate := predicate.Or(
139+
predicate.GenerationChangedPredicate{},
140+
predicates.ReconcileRequestedPredicate{},
141+
)
142+
143+
if !opts.CancelHealthCheckOnNewRevision {
144+
toComplete = r
145+
enqueueRequestsFromMapFunc = func(objKind string, fn handler.MapFunc) handler.EventHandler {
146+
return handler.EnqueueRequestsFromMapFunc(fn)
147+
}
148+
blder = ctrl.NewControllerManagedBy(mgr).
149+
For(&kustomizev1.Kustomization{}, builder.WithPredicates(ksPredicate))
150+
} else {
151+
wr := runtimeCtrl.WrapReconciler(r)
152+
toComplete = wr
153+
enqueueRequestsFromMapFunc = wr.EnqueueRequestsFromMapFunc
154+
blder = runtimeCtrl.NewControllerManagedBy(mgr, wr).
155+
For(&kustomizev1.Kustomization{}, ksPredicate).Builder
156+
}
157+
158+
blder.
136159
Watches(
137160
&sourcev1.OCIRepository{},
138-
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexOCIRepository)),
161+
enqueueRequestsFromMapFunc(sourcev1.OCIRepositoryKind, r.requestsForRevisionChangeOf(indexOCIRepository)),
139162
builder.WithPredicates(SourceRevisionChangePredicate{}),
140163
).
141164
Watches(
142165
&sourcev1.GitRepository{},
143-
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexGitRepository)),
166+
enqueueRequestsFromMapFunc(sourcev1.GitRepositoryKind, r.requestsForRevisionChangeOf(indexGitRepository)),
144167
builder.WithPredicates(SourceRevisionChangePredicate{}),
145168
).
146169
Watches(
147170
&sourcev1.Bucket{},
148-
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexBucket)),
171+
enqueueRequestsFromMapFunc(sourcev1.BucketKind, r.requestsForRevisionChangeOf(indexBucket)),
149172
builder.WithPredicates(SourceRevisionChangePredicate{}),
150173
).
151174
WatchesMetadata(
152175
&corev1.ConfigMap{},
153-
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexConfigMap)),
176+
enqueueRequestsFromMapFunc("ConfigMap", r.requestsForConfigDependency(indexConfigMap)),
154177
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, opts.WatchConfigsPredicate),
155178
).
156179
WatchesMetadata(
157180
&corev1.Secret{},
158-
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexSecret)),
181+
enqueueRequestsFromMapFunc("Secret", r.requestsForConfigDependency(indexSecret)),
159182
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, opts.WatchConfigsPredicate),
160183
)
161184

162185
if opts.WatchExternalArtifacts {
163-
ctrlBuilder = ctrlBuilder.Watches(
186+
blder = blder.Watches(
164187
&sourcev1.ExternalArtifact{},
165-
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexExternalArtifact)),
188+
enqueueRequestsFromMapFunc(sourcev1.ExternalArtifactKind, r.requestsForRevisionChangeOf(indexExternalArtifact)),
166189
builder.WithPredicates(SourceRevisionChangePredicate{}),
167190
)
168191
}
169192

170-
return ctrlBuilder.WithOptions(controller.Options{RateLimiter: opts.RateLimiter}).Complete(r)
193+
return blder.WithOptions(controller.Options{RateLimiter: opts.RateLimiter}).Complete(toComplete)
171194
}

internal/controller/kustomization_wait_test.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,6 @@ func TestKustomizationReconciler_CancelHealthCheckOnNewRevision(t *testing.T) {
473473
resultK := &kustomizev1.Kustomization{}
474474
timeout := 60 * time.Second
475475

476-
reconciler.CancelHealthCheckOnNewRevision = true
477-
t.Cleanup(func() { reconciler.CancelHealthCheckOnNewRevision = false })
478-
479476
err := createNamespace(id)
480477
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
481478

@@ -616,15 +613,4 @@ spec:
616613
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
617614
return resultK.Status.LastAttemptedRevision == "main/"+fixedArtifact
618615
}, timeout, time.Second).Should(BeTrue())
619-
620-
// Check cancellation event was emitted
621-
events := getEvents(resultK.GetName(), nil)
622-
var found bool
623-
for _, e := range events {
624-
if e.Message == "New revision detected during health check, cancelling" {
625-
found = true
626-
break
627-
}
628-
}
629-
g.Expect(found).To(BeTrue(), "did not find event for health check cancellation")
630616
}

internal/controller/suite_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,9 @@ func TestMain(m *testing.M) {
187187
SOPSAgeSecret: sopsAgeSecret,
188188
}
189189
if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{
190-
WatchConfigsPredicate: predicate.Not(predicate.Funcs{}),
191-
WatchExternalArtifacts: true,
190+
WatchConfigsPredicate: predicate.Not(predicate.Funcs{}),
191+
WatchExternalArtifacts: true,
192+
CancelHealthCheckOnNewRevision: true,
192193
}); err != nil {
193194
panic(fmt.Sprintf("Failed to start KustomizationReconciler: %v", err))
194195
}

main.go

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -313,34 +313,34 @@ func main() {
313313
}
314314

315315
if err = (&controller.KustomizationReconciler{
316-
AdditiveCELDependencyCheck: additiveCELDependencyCheck,
317-
AllowExternalArtifact: allowExternalArtifact,
318-
CancelHealthCheckOnNewRevision: cancelHealthCheckOnNewRevision,
319-
APIReader: mgr.GetAPIReader(),
320-
ArtifactFetchRetries: httpRetry,
321-
Client: mgr.GetClient(),
322-
ClusterReader: clusterReader,
323-
ConcurrentSSA: concurrentSSA,
324-
ControllerName: controllerName,
325-
DefaultServiceAccount: defaultServiceAccount,
326-
DependencyRequeueInterval: requeueDependency,
327-
DisallowedFieldManagers: disallowedFieldManagers,
328-
EventRecorder: eventRecorder,
329-
FailFast: failFast,
330-
GroupChangeLog: groupChangeLog,
331-
KubeConfigOpts: kubeConfigOpts,
332-
Mapper: restMapper,
333-
Metrics: metricsH,
334-
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
335-
NoRemoteBases: noRemoteBases,
336-
SOPSAgeSecret: sopsAgeSecret,
337-
StatusManager: fmt.Sprintf("gotk-%s", controllerName),
338-
StrictSubstitutions: strictSubstitutions,
339-
TokenCache: tokenCache,
316+
AdditiveCELDependencyCheck: additiveCELDependencyCheck,
317+
AllowExternalArtifact: allowExternalArtifact,
318+
APIReader: mgr.GetAPIReader(),
319+
ArtifactFetchRetries: httpRetry,
320+
Client: mgr.GetClient(),
321+
ClusterReader: clusterReader,
322+
ConcurrentSSA: concurrentSSA,
323+
ControllerName: controllerName,
324+
DefaultServiceAccount: defaultServiceAccount,
325+
DependencyRequeueInterval: requeueDependency,
326+
DisallowedFieldManagers: disallowedFieldManagers,
327+
EventRecorder: eventRecorder,
328+
FailFast: failFast,
329+
GroupChangeLog: groupChangeLog,
330+
KubeConfigOpts: kubeConfigOpts,
331+
Mapper: restMapper,
332+
Metrics: metricsH,
333+
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
334+
NoRemoteBases: noRemoteBases,
335+
SOPSAgeSecret: sopsAgeSecret,
336+
StatusManager: fmt.Sprintf("gotk-%s", controllerName),
337+
StrictSubstitutions: strictSubstitutions,
338+
TokenCache: tokenCache,
340339
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
341-
RateLimiter: runtimeCtrl.GetRateLimiter(rateLimiterOptions),
342-
WatchConfigsPredicate: watchConfigsPredicate,
343-
WatchExternalArtifacts: allowExternalArtifact,
340+
RateLimiter: runtimeCtrl.GetRateLimiter(rateLimiterOptions),
341+
WatchConfigsPredicate: watchConfigsPredicate,
342+
WatchExternalArtifacts: allowExternalArtifact,
343+
CancelHealthCheckOnNewRevision: cancelHealthCheckOnNewRevision,
344344
}); err != nil {
345345
setupLog.Error(err, "unable to create controller", "controller", controllerName)
346346
os.Exit(1)

0 commit comments

Comments
 (0)