Skip to content

Commit aa39ed4

Browse files
committed
queue immediate reconciliation on kustomization dependency
Signed-off-by: Daniele Fognini <[email protected]>
1 parent 2437aaf commit aa39ed4

File tree

3 files changed

+138
-1
lines changed

3 files changed

+138
-1
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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/event"
21+
"sigs.k8s.io/controller-runtime/pkg/predicate"
22+
23+
"github.com/fluxcd/pkg/runtime/conditions"
24+
25+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
26+
)
27+
28+
type KustomizationReadyChangePredicate struct {
29+
predicate.Funcs
30+
}
31+
32+
func (KustomizationReadyChangePredicate) Update(e event.UpdateEvent) bool {
33+
if e.ObjectNew == nil || e.ObjectOld == nil {
34+
return false
35+
}
36+
37+
newKs, ok := e.ObjectNew.(*kustomizev1.Kustomization)
38+
if !ok {
39+
return false
40+
}
41+
oldKs, ok := e.ObjectOld.(*kustomizev1.Kustomization)
42+
if !ok {
43+
return false
44+
}
45+
46+
if !conditions.IsReady(newKs) {
47+
return false
48+
}
49+
if !conditions.IsReady(oldKs) {
50+
return true
51+
}
52+
53+
return oldKs.Status.LastAppliedRevision != newKs.Status.LastAppliedRevision
54+
}

internal/controller/kustomization_controller.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
138138
return fmt.Errorf("failed setting index fields: %w", err)
139139
}
140140

141+
// Index the Kustomizations by the dependsOn references they (may) point at.
142+
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, dependsOnIndexKey,
143+
r.indexDependsOn); err != nil {
144+
return fmt.Errorf("failed setting index fields: %w", err)
145+
}
146+
141147
r.requeueDependency = opts.DependencyRequeueInterval
142148
r.statusManager = fmt.Sprintf("gotk-%s", r.ControllerName)
143149
r.artifactFetchRetries = opts.HTTPRetry
@@ -161,6 +167,11 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
161167
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(bucketIndexKey)),
162168
builder.WithPredicates(SourceRevisionChangePredicate{}),
163169
).
170+
Watches(
171+
&kustomizev1.Kustomization{},
172+
handler.EnqueueRequestsFromMapFunc(r.requestsForDependents),
173+
builder.WithPredicates(KustomizationReadyChangePredicate{}),
174+
).
164175
WithOptions(controller.Options{
165176
RateLimiter: opts.RateLimiter,
166177
}).
@@ -271,7 +282,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
271282
if len(obj.Spec.DependsOn) > 0 {
272283
if err := r.checkDependencies(ctx, obj, artifactSource); err != nil {
273284
conditions.MarkFalse(obj, meta.ReadyCondition, meta.DependencyNotReadyReason, "%s", err)
274-
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
285+
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s: %v", r.requeueDependency.String(), err)
275286
log.Info(msg)
276287
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
277288
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil

internal/controller/kustomization_indexers.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@ import (
2121
"fmt"
2222

2323
"github.com/fluxcd/pkg/runtime/conditions"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/types"
2426
ctrl "sigs.k8s.io/controller-runtime"
2527
"sigs.k8s.io/controller-runtime/pkg/client"
2628
"sigs.k8s.io/controller-runtime/pkg/handler"
2729
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2830

31+
"github.com/fluxcd/pkg/apis/meta"
2932
"github.com/fluxcd/pkg/runtime/dependency"
3033
sourcev1 "github.com/fluxcd/source-controller/api/v1"
3134

3235
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
3336
)
3437

38+
const (
39+
dependsOnIndexKey string = ".metadata.dependsOn"
40+
)
41+
3542
func (r *KustomizationReconciler) requestsForRevisionChangeOf(indexKey string) handler.MapFunc {
3643
return func(ctx context.Context, obj client.Object) []reconcile.Request {
3744
log := ctrl.LoggerFrom(ctx)
@@ -78,6 +85,53 @@ func (r *KustomizationReconciler) requestsForRevisionChangeOf(indexKey string) h
7885
}
7986
}
8087

88+
func isNotReadyForDependency(k *kustomizev1.Kustomization) bool {
89+
c := conditions.Get(k, meta.ReadyCondition)
90+
if c == nil {
91+
return false
92+
}
93+
return c.Status == metav1.ConditionFalse && c.Reason == meta.DependencyNotReadyReason
94+
}
95+
96+
func (r *KustomizationReconciler) requestsForDependents(ctx context.Context, obj client.Object) []reconcile.Request {
97+
log := ctrl.LoggerFrom(ctx)
98+
99+
var list kustomizev1.KustomizationList
100+
if err := r.List(ctx, &list, client.MatchingFields{
101+
dependsOnIndexKey: client.ObjectKeyFromObject(obj).String(),
102+
}); err != nil {
103+
log.Error(err, "failed to list objects for dependency change")
104+
return nil
105+
}
106+
var dd []dependency.Dependent
107+
for _, d := range list.Items {
108+
if isNotReadyForDependency(&d) {
109+
dd = append(dd, &d)
110+
}
111+
}
112+
sorted, err := dependency.Sort(dd)
113+
if err != nil {
114+
log.Error(err, "failed to sort dependents for dependency change")
115+
return nil
116+
}
117+
reqs := make([]reconcile.Request, 0, len(sorted))
118+
debugLog := log.V(1).WithValues("dependency", map[string]string{
119+
"name": obj.GetName(),
120+
"namespace": obj.GetNamespace(),
121+
})
122+
for _, d := range sorted {
123+
debugLog.Info("requesting reconciliation of dependent", "dependent", map[string]string{
124+
"name": d.Name,
125+
"namespace": d.Namespace,
126+
})
127+
reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{
128+
Name: d.Name,
129+
Namespace: d.Namespace,
130+
}})
131+
}
132+
return reqs
133+
}
134+
81135
func (r *KustomizationReconciler) indexBy(kind string) func(o client.Object) []string {
82136
return func(o client.Object) []string {
83137
k, ok := o.(*kustomizev1.Kustomization)
@@ -96,3 +150,21 @@ func (r *KustomizationReconciler) indexBy(kind string) func(o client.Object) []s
96150
return nil
97151
}
98152
}
153+
154+
func (r *KustomizationReconciler) indexDependsOn(o client.Object) []string {
155+
k, ok := o.(*kustomizev1.Kustomization)
156+
if !ok {
157+
panic(fmt.Sprintf("Expected a Kustomization, got %T", o))
158+
}
159+
160+
deps := make([]string, len(k.Spec.DependsOn))
161+
for i, dep := range k.Spec.DependsOn {
162+
namespace := k.GetNamespace()
163+
if dep.Namespace != "" {
164+
namespace = dep.Namespace
165+
}
166+
deps[i] = fmt.Sprintf("%s/%s", namespace, dep.Name)
167+
}
168+
169+
return deps
170+
}

0 commit comments

Comments
 (0)