devin
devin

Reputation: 1120

Accessing a common field from Go generics with existing packages

I'm trying to create a generic function that creates k8s resources (custom and built-in) using Operator SDK.

package common

import (
    "context"
    "reflect"
    "time"

    "k8s.io/apimachinery/pkg/api/errors"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/types"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"
)

type Reconciler interface {
    client.Reader
    client.Writer
    GetScheme() *runtime.Scheme
}

func EnsureResource[T client.Object, P client.Object](r Reconciler, ctx context.Context, res T, parent P) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    existing := res
    if err := r.Get(ctx, types.NamespacedName{Name: res.GetName(), Namespace: res.GetNamespace()}, existing); err != nil && errors.IsNotFound(err) {
        ctrl.SetControllerReference(parent, res, r.GetScheme())

        if err = r.Create(ctx, res); err != nil {
            log.Error(err, "failed to create resource", "Name", res.GetName(), "Namespace", res.GetNamespace())
            return ctrl.Result{}, err
        }
        return ctrl.Result{RequeueAfter: 1 * time.Second}, nil // created successfully, requeue
    } else if err == nil && !reflect.DeepEqual(res.Spec, existing.Spec) {
        // exists already but modified
        r.Update(ctx, res)
    } else {
        log.Error(err, "couldn't fetch existing resource", "Name", res.GetName(), "Namespace", res.GetNamespace())
        return ctrl.Result{}, err
    }

    // already exists, finish reconcile
    return ctrl.Result{}, nil
}

The creation part works perfectly and can create any k8s resource. Now I am working on the part that detects updates to an existing resource. In the middle if block, I need to check if the Spec fields of res and existing are DeepEqual, but I can't imagine a way of accessing them with generics. client.Object is of course in k8s package. Also, some resource definitions that will be used as T and P will be from k8s api, and some were Custom Resources created by me, so not sure if I can use an interface here. Is there a reasonable way to achieve this?

Alternatively, is it even necessary to check for updates to an existing k8s resource and only propagate if necessary? For example, if I get a reconciliation event, but there is no difference between existing and new Specs, then will calling r.Update trigger reconciliation on all child resources?

Upvotes: 1

Views: 216

Answers (0)

Related Questions