Enqueue! Your father was a MapFunc

Time 3 minute read

Typically when creating a controller, you’ll make use of the fairly standard controller builder pattern –

func (r FunsiesReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&v1.Funsies{}).
        Complete(r)

func (r *FunsiesReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    return ctrl.Result{}, nil
}

This allows us to set up watches on v1.Funsies kinds so we can handle them appropriately in our Reconcile method.

But what happens if we want to trigger a Reconcile for all Funsiess based on some other object like corev1.Namespaces?

The builder provides a builder.Watches method that allows us to specify another object that we’d like to watch. Watches takes the kind we are watching, an event handler to handle the logic of what we want to do when we get an event for the object, and additionally options to further modify how the watcher behaves.

Watches(object client.Object, eventhandler handler.EventHandler, opts ...WatchesOption)`

Since we are wanting to react to namespace events, we know our first parameter will be &corev1.Namespace{}.

Next, we need to tell the watcher what to do once it gets an event for the namespace. handler.EnqueueRequestsFromMapFunc is our helper here. EnqueueRequestsFromMapFunc accepts a handler.MapFunc which allows us to control how we want to handle that watched object. The arguments for the handler.MapFunc are only a context and the object we are watching. This works for some simple one-to-one matching or translations from the watched object to the object we are reconciling. For example, if we only had a single v1.Funsies object per namespace that always ended with a constant suffix.

// We'll reuse the example in the docs for this instance
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/handler#example-EnqueueRequestsFromMapFunc
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
            return []reconcile.Request{
                {NamespacedName: types.NamespacedName{
                    Name:      a.GetName() + "-1",
                    Namespace: a.GetNamespace(),
                }},
                {NamespacedName: types.NamespacedName{
                    Name:      a.GetName() + "-2",
                    Namespace: a.GetNamespace(),
                }},
            }
})

But if we needed to do slightly more complicated interactions here, we can rely on closures to pass through additional useful parameters. Let’s say our desired outcome is to reconcile all v1.Funsies objects in response to a corev1.Namespace event. We can accomplish this with passing through the mgr.Client() and returning a handler.MapFunc.

We an call our Watches method like the following:

Watches(
    &corev1.Namespace{},
    handler.EnqueueRequestsFromMapFunc(lookupFunsiessInNamespace(mgr.GetClient())),
)

And then handle the necessary logic (lookups) in the lookupFunsiesInNamespace function:

func lookupFunsiessInNamespace(c client.Client) func(ctx context.Context, a client.Object) []reconcile.Request {
    return func(ctx context.Context, a client.Object) []reconcile.Request {
        squigglyList := &v1.FunsiesList{}
        if err := c.List(ctx, squigglyList, client.InNamespace(a.GetName())); err != nil {
            log.FromContext(ctx).Error(err, "unable to list squigglys in namespace", "namespace", a.GetName())
            return []reconcile.Request{}
        }

        requests := make([]reconcile.Request, 0, len(squigglyList.Items))
        for _, squiggly:= range squigglyList.Items {
            requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: a.GetName(), Name: squiggly.GetName()}})
        }

        return requests
    }
}

Additionally, if we need to get slightly fancier, we can provide handler.WatchesOptions. This would allow us to add in things like predicates to further customize the watch we are creating.

Watches(
    &corev1.Namespace{},
    handler.EnqueueRequestsFromMapFunc(reconcilePodsInNamespace(mgr.GetClient())),
    builder.WithPredicates(
        predicate.Or(
            predicate.GenerationChangedPredicate{},
            predicate.AnnotationChangedPredicate{},
        ),
    ),
)

Calendar Posted:
Person Posted By:
Folder Open Categories: Development Go Kubernetes