gokuberneteskubebuilder

How to watch for external custom resource changes in kubebuilder without importing the external type


Suppose I have bellow code snippet which setups a reconciler that watches external resource "External":

// SetupWithManager sets up the controller with the Manager.
func (r *SomethingReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&api.Something{}).
        WithOptions(controller.Options{
            MaxConcurrentReconciles: stdruntime.NumCPU(),
            RecoverPanic:            true,
        }).
        Watches(
            &source.Kind{Type: &somev1.External{}},
            handler.EnqueueRequestsFromMapFunc(r.findInternalObjectsForExternal),
            builder.WithPredicates(predicate.Funcs{
                UpdateFunc: func(ue event.UpdateEvent) bool { return true },
                DeleteFunc: func(de event.DeleteEvent) bool { return true },
            }),
        ).
        Complete(r)
}

My problem is that I can not import somev1.External type into my project because importing the go module containing this type would break my current project's dependencies.
Is there a way in kubebuilder to watch for external resources without having to explicitly importing their types? like GVK or something?


Solution

  • Yes it's possible.

    You can create a REST client for your resource in main.go as below:

    gvkExternal := schema.GroupVersionKind{
        Group:   "some.group.io",
        Version: "v1",
        Kind:    "External",
    }
    
    restClient, err := apiutil.RESTClientForGVK(gvkExternal, false, mgr.GetConfig(), serializer.NewCodecFactory(mgr.GetScheme()))
    if err != nil {
        setupLog.Error(err, "unable to create REST client")
    }
    

    Then add a field for this REST client (rest.Interface) to your reconciler (yournativeresource_controller.go) struct such as:

    type YourNativeResourceReconciler struct {
        client.Client
        Scheme        *runtime.Scheme
        // add this
        RESTClient    rest.Interface
    }
    

    Last, initialize your reconciler with this REST client (main.go):

    if err = (&controllers.YourNativeResourceReconciler{
        Client:        mgr.GetClient(),
        Scheme:        mgr.GetScheme(),
        RESTClient:    restClient,
    }).SetupWithManager(mgr); err != nil {
        setupLog.Error(err, "unable to create controller", "controller", "YourNativeResource")
        os.Exit(1)
    }
    

    Do not forget to add RBAC marker to your project (reconciler preferably) that will generate RBAC rules allowing you to manipulate External resource:

    //+kubebuilder:rbac:groups=some.group.io,resources=externals,verbs=get;list;watch;create;update;patch;delete
    

    After these steps, you can use REST client for manipulating External resource over YourNativeResource reconciler using r.RESTClient.

    EDIT:

    If you want to watch resources, dynamic clients may help. Create a dynamic client in main.go:

    dynamicClient, err := dynamic.NewForConfig(mgr.GetConfig())
    if err != nil {
        setupLog.Error(err, "unable to create dynamic client")
    }
    

    Apply above steps, add it to your reconciler etc. Then you will be able to watch External resource as below:

    resourceInterface := r.DynamicClient.Resource(schema.GroupVersionResource{
        Group:    "some.group.io",
        Version:  "",
        Resource: "externals",
    })
    externalWatcher, err := resourceInterface.Watch(ctx, metav1.ListOptions{})
    if err != nil {
        return err
    }
    
    defer externalWatcher.Stop()
    
    select {
    case event := <-externalWatcher.ResultChan():
        if event.Type == watch.Deleted {
            logger.Info("FINALIZER: An external resource is deleted.")
        }
    }