5

We have an k8s operator (based on kubebuilder) which works as expected, now we need support for listening to secrets on the cluster.

The following code is working however I got event for all the secrets in the cluster which is not efficient,

I WANT to get the event only for specific secret, lets say secret with specific labels/annotation, how we can do it?

func (r *InvReconciler) SetupWithManager(mgr ctrl.Manager) error {
    manager := ctrl.NewControllerManagedBy(mgr).
        For(&corev1alpha1.Inv{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}))).
        WithOptions(controller.Options{
        })

    manager = manager.Watches(&source.Kind{Type: &v1.Secret{}}, handler.EnqueueRequestsFromMapFunc(func(a client.Object) []reconcile.Request {
        return r.secretHandler.GetSecret(a.GetNamespace(), a.GetName())
    }))

    return manager.Complete(r)
}

this is the function

func (secretReq secretHandler) GetSecret(namespace string, name string) []reconcile.Request {

    fmt.Println("secret is: ", namespace, "--", name)
    return nil
}

lets say secret like the following and only for this secret (with labels foo: bar )i'll get the event when it was created or modified

apiVersion: v1
kind: Secret
metadata:
  labels:
    foo: bar
  name: mysecret
  namespace: dev
type: Opaque
data:
  USER_NAME: YWRtaW4=
  PASSWORD: dGVzdBo=

Im not talking about an if statement after I got the event as it already bring all the secrets event in the cluster.

PJEM
  • 557
  • 7
  • 33
  • Unfortunately, this is not possible. See my answer for the details. – Cloudkollektiv Oct 03 '22 at 20:00
  • I don't have the answer for your issue, however, I know an open source controller that does what you want to achieve, and it's written in GO as well. Here it is https://github.com/mittwald/kubernetes-replicator, hope it helps! – Fares Oct 03 '22 at 20:09
  • @Fares, this does not include watching for CRUD changes as the OP wants to achieve. It only allows for replication / syncing of k8s resources. – Cloudkollektiv Oct 04 '22 at 07:43
  • 1
    Oh, I must be misunderstanding the need then, sorry about that – Fares Oct 04 '22 at 10:41
  • 1
    @Fares, no problem. I checked out the code in repo you posted and did not find any CRUD watcher. Now lets hope that I get some votes for my answer more often than not people who post bounties do not respond. – Cloudkollektiv Oct 04 '22 at 20:27

2 Answers2

3

According to this github source, you should be able to select specific objects (e.g. secrets) with EnqueueRequestForObject. However, it is not possible (yet) to watch only for specific secret CRUD changes.

EnqueueRequestForObject to watch for your CRD resource changes. In your CRD reconciler, you'd fetch all of the TLS secrets using a label selector based on the search definition and then run your merge logic with the matched secrets.

EnqueueRequestFromMapFunc to watch for secret changes and trigger a reconcile of one or more CRs. In your mapper function, you'd fetch all of the CRs. For each CR that has a search definition that matches the passed in secret, you'd create a new reconcile.Request for the CR, and return the list of requests, which would trigger your CRD reconciler for each CR that matched.

The cleanest way is using a label selector and then merge the results with your existing code. An example of using a label selector is given in this post:

func GetSecret(version string) (retVal interface{}, err error){
  clientset := GetClientOutOfCluster()
  labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{"version":version}}

  listOptions := metav1.ListOptions{
    LabelSelector: labels.Set(labelSelector.MatchLabels).String(),
    Limit:         100,
  }
  secretList, err := clientset.CoreV1().Secrets("namespace").List(listOptions)
  retVal = secretList.Items[0]
  return retVal, err
}
Cloudkollektiv
  • 11,852
  • 3
  • 44
  • 71
  • HI, sorry for the delay in my response, currently not feeling best :), thanks. not sure that I got how it make the "watch" better/ cleaner , as each changes in any secret on the cluster (also in you example) will invoke the `GetSecret` method, so how it can more `cleanset` ? as you write above...what am I missing here? could you provide example to illustrate it please ? – PJEM Oct 06 '22 at 06:47
1

This is possible, in a roundabout way. You need to configure the selector into the controller Manager that you use to set up your reconciler.

You can use label or field selectors for this. You can either set the same selector for all types of objects using DefaultSelector, or you can use SelectorsByObject to have different selectors for different types of objects.

import (
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/labels"
    "k8s.io/apimachinery/pkg/selection"
    "k8s.io/client-go/rest"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/cache"
    "sigs.k8s.io/controller-runtime/pkg/manager"
)

func startReconciler(cnf *rest.Config) error {
    mgr, err := ctrl.NewManager(cnf, manager.Options{
        NewCache: func(conf *rest.Config, opts cache.Options) (cache.Cache, error) {
            // Use this selector for everything that is not mentioned in SelectorsByObject
            opts.DefaultSelector = cache.ObjectSelector{
                Label: labels.SelectorFromSet(labels.Set{"foo": "bar"}),
            }
            // Specific selectors per type of object
            opts.SelectorsByObject[&corev1.Secret{}] = cache.ObjectSelector{
                Label: labels.SelectorFromSet(labels.Set{"foo": "bar"}),
            }
            return cache.New(conf, opts)
        },
    })
    if err != nil {
        return err
    }
    r := &InvReconciler{}
    if err := r.SetupWithManager(mgr); err != nil {
        return err
    }
Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • I don’t see any watcher in here, are you sure this watches for CRUD changes? – Cloudkollektiv Oct 05 '22 at 13:21
  • 1
    You need to call SetupWithManager with the manager created by this code, as the OP has already posted the the question. No charges needed from what the OP posted – Erwin Bolwidt Oct 05 '22 at 14:09
  • HI, thanks. so this code should be used in the main as I see it, am I right? is there a way to use it inside the controller (I mean the code inside `SetupWithManager`), if yes could you please provide an example? and how should I connect this `NewCache` function to `secret` object and not all the objects which contain `for:bar` labels? – PJEM Oct 06 '22 at 06:59
  • another question, when I get the event in the function how should I know which type of event it , create/update/delete ... – PJEM Oct 06 '22 at 07:09
  • @PJEM you an use SelectorsByObject to have a different selector per type of object. I have updated the code. – Erwin Bolwidt Oct 08 '22 at 07:11
  • Thanks a lot it works, I've provided the bounty. one last question how should I know that type of event is , In case I got update and especially `delete` for the secret? – PJEM Oct 08 '22 at 08:03
  • You just need to check - try to get the secrets that you expect to exist, and if they don't, then they got deleted – Erwin Bolwidt Oct 08 '22 at 08:12
  • @ErwinBolwidt - yes, but what about update ...any idea how to identify it in a clean way? as I dont want to read and than compere etc – PJEM Oct 11 '22 at 10:48
  • If you look at the [docs for the controller-runtime package](https://pkg.go.dev/sigs.k8s.io/controller-runtime#example-package), it has an example implementation of a reconciler. Reading and comparing seems to be the right approach. – Erwin Bolwidt Oct 12 '22 at 00:21
  • HI, can you please check the following to see if you can help?: https://stackoverflow.com/questions/74677080/controller-reconcile-on-object-changes – PJEM Dec 04 '22 at 13:47