gokubernetesoperator-sdkkubebuilder

Maximize the number of CustomResources that a CustomResourceDefinition can have | kubebuilder & operator-sdk


I'm developing a kubernetes operator that represents a very simple api and a controller. I would like to maximize the number of the CustomResources those could belonging to the specific CustomResourceDefinition that the operator defines. (As specially I would like to allow just one CR, if it is already defined, the operator should throw an error message and skip reconciling it.) If I generate the api, there is a KindList struct default generated, and if I understand correctly, it should keep track of the CRs already defined for my CRD. It is also added to the scheme by default. See the example from kubebuilder documentation:

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// CronJob is the Schema for the cronjobs API
type CronJob struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   CronJobSpec   `json:"spec,omitempty"`
    Status CronJobStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// CronJobList contains a list of CronJob
type CronJobList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []CronJob `json:"items"`
}

func init() {
    SchemeBuilder.Register(&CronJob{}, &CronJobList{})
}

Unfortunately, I can not find out how to access this List from the controller. I have tried like this, but r.Get can not accept cacheList:

cronjob/cronjob_controller.go

package controllers

import (
    "context"

    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"

    cronjobv1alpha1 "github.com/example/cronjob-operator/api/v1alpha1"
)

func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    // TODO(user): your logic here

    cronjob := cronjobv1alpha1.Memcached{}
    if err := r.Get(ctx, req.NamespacedName, &cronjob); err != nil {
        return ctrl.Result{}, err
    }

    cronjobList := cachev1alpha1.MemcachedList{}
    if err := r.Get(ctx, req.NamespacedName, &cronjobList); err != nil {
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}

If I get the list, I could validate the length of it, and do or skip the reconcile.

Is it even a correct approach? Is there a better way to achieve my goal? Should I create a webhook instead?


Solution

  • Assuming you are using the default sigs.k8s.io/controller-runtime/pkg/client's client.Client, you get access to the List() function.

    In your case r.List(...).

    Usage:

    case 1: list by label

    func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        cronjobList := cronjobv1alpha1.CronJobList{}
        err = r.List(ctx, &cronjobList, client.MatchingLabels{"foo": "bar"})
        if err != nil {
            return ctrl.Result{}, err
        }
    }
    

    case 2: list all in namespace

    func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        cronjobList := cronjobv1alpha1.CronJobList{}
        err = r.List(ctx, &cronjobList, client.InNamespace("default"))
        if err != nil {
            return ctrl.Result{}, err
        }
    }
    

    case 3: list by field i.e. metadata.name

    // in your Reconciler Setup function create an index
    func SetupWithManager(mgr ctrl.Manager) error {
        r := &CronJobReconciler{
            Client:   mgr.GetClient(),
        }
        mgr.GetFieldIndexer().IndexField(context.TODO(), &cronjobv1alpha1.CronJob{}, "metadata.name", NameIndexer)
    
        return ctrl.NewControllerManagedBy(mgr).
            For(&cronjobv1alpha1.CronJob{}).
            Complete(r)
    }    
    
    func NameIndexer(o client.Object) []string {
        m := o.(*cronjobv1alpha1.CronJob)
        return []string{m.ObjectMeta.Name}
    }
    
    func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        cronjobList := cronjobv1alpha1.CronJobList{}
        err = r.List(ctx, &cronjobList, client.MatchingFields{"metadata.name": "test"})
        if err != nil {
            return ctrl.Result{}, err
        }
    }