Skip to content

Commit fadeba5

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

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

internal/controller/kustomization_controller.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"sigs.k8s.io/controller-runtime/pkg/client"
4545
"sigs.k8s.io/controller-runtime/pkg/controller"
4646
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
47+
"sigs.k8s.io/controller-runtime/pkg/event"
4748
"sigs.k8s.io/controller-runtime/pkg/handler"
4849
"sigs.k8s.io/controller-runtime/pkg/predicate"
4950
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -113,11 +114,40 @@ type KustomizationReconcilerOptions struct {
113114
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
114115
}
115116

117+
type kustomizationReadyChangePredicate struct {
118+
predicate.Funcs
119+
}
120+
121+
func (kustomizationReadyChangePredicate) Update(e event.UpdateEvent) bool {
122+
if e.ObjectNew == nil || e.ObjectOld == nil {
123+
return false
124+
}
125+
126+
newKs, ok := e.ObjectNew.(*kustomizev1.Kustomization)
127+
if !ok {
128+
return false
129+
}
130+
oldKs, ok := e.ObjectOld.(*kustomizev1.Kustomization)
131+
if !ok {
132+
return false
133+
}
134+
135+
if !conditions.IsReady(newKs) {
136+
return false
137+
}
138+
if !conditions.IsReady(oldKs) {
139+
return true
140+
}
141+
142+
return oldKs.Status.LastAppliedRevision != newKs.Status.LastAppliedRevision
143+
}
144+
116145
func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KustomizationReconcilerOptions) error {
117146
const (
118147
ociRepositoryIndexKey string = ".metadata.ociRepository"
119148
gitRepositoryIndexKey string = ".metadata.gitRepository"
120149
bucketIndexKey string = ".metadata.bucket"
150+
ksDependencyIndexKey string = ".metadata.dependsOn"
121151
)
122152

123153
// Index the Kustomizations by the OCIRepository references they (may) point at.
@@ -138,6 +168,12 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
138168
return fmt.Errorf("failed setting index fields: %w", err)
139169
}
140170

171+
// Index the Kustomizations by the dependsOn references they (may) point at.
172+
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, ksDependencyIndexKey,
173+
r.indexDependsOn()); err != nil {
174+
return fmt.Errorf("failed setting index fields: %w", err)
175+
}
176+
141177
r.requeueDependency = opts.DependencyRequeueInterval
142178
r.statusManager = fmt.Sprintf("gotk-%s", r.ControllerName)
143179
r.artifactFetchRetries = opts.HTTPRetry
@@ -146,6 +182,11 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
146182
For(&kustomizev1.Kustomization{}, builder.WithPredicates(
147183
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
148184
)).
185+
Watches(
186+
&kustomizev1.Kustomization{},
187+
handler.EnqueueRequestsFromMapFunc(r.requestsForDependentsWaiting(ksDependencyIndexKey)),
188+
builder.WithPredicates(kustomizationReadyChangePredicate{}),
189+
).
149190
Watches(
150191
&sourcev1b2.OCIRepository{},
151192
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(ociRepositoryIndexKey)),
@@ -271,7 +312,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
271312
if len(obj.Spec.DependsOn) > 0 {
272313
if err := r.checkDependencies(ctx, obj, artifactSource); err != nil {
273314
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())
315+
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s: %s", r.requeueDependency.String(), err)
275316
log.Info(msg)
276317
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
277318
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil

internal/controller/kustomization_indexers.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import (
2121
"fmt"
2222

2323
"github.com/fluxcd/pkg/runtime/conditions"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
ctrl "sigs.k8s.io/controller-runtime"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627
"sigs.k8s.io/controller-runtime/pkg/handler"
2728
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2829

30+
"github.com/fluxcd/pkg/apis/meta"
2931
"github.com/fluxcd/pkg/runtime/dependency"
3032
sourcev1 "github.com/fluxcd/source-controller/api/v1"
3133

@@ -78,6 +80,49 @@ func (r *KustomizationReconciler) requestsForRevisionChangeOf(indexKey string) h
7880
}
7981
}
8082

83+
func isNotReadyForDependency(k *kustomizev1.Kustomization) bool {
84+
c := conditions.Get(k, meta.ReadyCondition)
85+
if c == nil {
86+
return false
87+
}
88+
return c.Status == metav1.ConditionFalse && c.Reason == meta.DependencyNotReadyReason
89+
}
90+
91+
func (r *KustomizationReconciler) requestsForDependentsWaiting(indexKey string) handler.MapFunc {
92+
return func(ctx context.Context, obj client.Object) []reconcile.Request {
93+
log := ctrl.LoggerFrom(ctx)
94+
95+
var list kustomizev1.KustomizationList
96+
if err := r.List(ctx, &list, client.MatchingFields{
97+
indexKey: client.ObjectKeyFromObject(obj).String(),
98+
}); err != nil {
99+
log.Error(err, "failed to list objects for dependency change")
100+
return nil
101+
}
102+
var dd []dependency.Dependent
103+
for i, d := range list.Items {
104+
if isNotReadyForDependency(&list.Items[i]) {
105+
dd = append(dd, d.DeepCopy())
106+
}
107+
}
108+
sorted, err := dependency.Sort(dd)
109+
if err != nil {
110+
log.Error(err, "failed to sort dependents for dependency change")
111+
return nil
112+
}
113+
reqs := make([]reconcile.Request, len(sorted))
114+
source := fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())
115+
for i := range sorted {
116+
name := sorted[i].Name
117+
namespace := sorted[i].Namespace
118+
log.Info(fmt.Sprintf("request reconciliation of dependent of '%s': '%s/%s'", source, namespace, name))
119+
reqs[i].NamespacedName.Name = name
120+
reqs[i].NamespacedName.Namespace = namespace
121+
}
122+
return reqs
123+
}
124+
}
125+
81126
func (r *KustomizationReconciler) indexBy(kind string) func(o client.Object) []string {
82127
return func(o client.Object) []string {
83128
k, ok := o.(*kustomizev1.Kustomization)
@@ -96,3 +141,23 @@ func (r *KustomizationReconciler) indexBy(kind string) func(o client.Object) []s
96141
return nil
97142
}
98143
}
144+
145+
func (r *KustomizationReconciler) indexDependsOn() func(o client.Object) []string {
146+
return func(o client.Object) []string {
147+
k, ok := o.(*kustomizev1.Kustomization)
148+
if !ok {
149+
panic(fmt.Sprintf("Expected a Kustomization, got %T", o))
150+
}
151+
152+
deps := make([]string, len(k.Spec.DependsOn))
153+
for i, dep := range k.Spec.DependsOn {
154+
namespace := k.GetNamespace()
155+
if dep.Namespace != "" {
156+
namespace = dep.Namespace
157+
}
158+
deps[i] = fmt.Sprintf("%s/%s", namespace, dep.Name)
159+
}
160+
161+
return deps
162+
}
163+
}

0 commit comments

Comments
 (0)