From f89a5760de71f2270cdf6cf5726e2b82adaa2b42 Mon Sep 17 00:00:00 2001 From: Pavan <25031267+Pavan-SAP@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:43:57 +0200 Subject: [PATCH] [FEAT] Operator: namespace scoped secret informers used Enable namespace scoped secret informers. TODO: - [] make it opt-in via controller config. --- internal/controller/controller.go | 10 ++- internal/controller/informers.go | 63 ++++++++++++++++--- .../controller/reconcile-capapplication.go | 3 + internal/controller/reconcile.go | 25 ++++---- internal/util/vcap-credentials.go | 8 +-- internal/util/vcap-credentials_test.go | 2 +- 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 21e897f8..8bc35c10 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -52,9 +52,12 @@ type Controller struct { gardenerCertInformerFactory gardenerCertInformers.SharedInformerFactory certManagerInformerFactory certManagerInformers.SharedInformerFactory gardenerDNSInformerFactory gardenerDNSInformers.SharedInformerFactory - queues map[int]workqueue.TypedRateLimitingInterface[QueueItem] - eventBroadcaster events.EventBroadcaster - eventRecorder events.EventRecorder + // per-namespace secret informers + nsSecretInformers map[string]informers.SharedInformerFactory + nsSecretInformersMu sync.RWMutex + queues map[int]workqueue.TypedRateLimitingInterface[QueueItem] + eventBroadcaster events.EventBroadcaster + eventRecorder events.EventRecorder } var ( @@ -134,6 +137,7 @@ func NewController(client kubernetes.Interface, crdClient versioned.Interface, i gardenerCertInformerFactory: gardenerCertInformerFactory, certManagerInformerFactory: certManagerInformerFactory, gardenerDNSInformerFactory: gardenerDNSInformerFactory, + nsSecretInformers: map[string]informers.SharedInformerFactory{}, queues: queues, eventBroadcaster: eventBroadcaster, eventRecorder: recorder, diff --git a/internal/controller/informers.go b/internal/controller/informers.go index d6b5935c..47fee1a0 100644 --- a/internal/controller/informers.go +++ b/internal/controller/informers.go @@ -6,12 +6,15 @@ SPDX-License-Identifier: Apache-2.0 package controller import ( + "context" "reflect" "time" "github.com/sap/cap-operator/pkg/apis/sme.sap.com/v1alpha1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" ) @@ -80,7 +83,6 @@ func (c *Controller) initializeInformers() { c.registerDomainListeners() c.registerClusterDomainListeners() c.registerJobListeners() - c.registerSecretListeners() c.registerGatewayListeners() c.registerVirtualServiceListeners() c.registerDestinationRuleListeners() @@ -100,10 +102,6 @@ func (c *Controller) initializeInformers() { } func (c *Controller) getEventHandlerFuncsForResource(res int) cache.ResourceEventHandlerFuncs { - _, ok := QueueMapping[res] - if !ok { - return cache.ResourceEventHandlerFuncs{} - } return cache.ResourceEventHandlerFuncs{ AddFunc: func(new any) { c.enqueueModifiedResource(res, new, nil) @@ -162,11 +160,6 @@ func (c *Controller) registerDestinationRuleListeners() { AddEventHandler(c.getEventHandlerFuncsForResource(ResourceDestinationRule)) } -func (c *Controller) registerSecretListeners() { - c.kubeInformerFactory.Core().V1().Secrets().Informer(). - AddEventHandler(c.getEventHandlerFuncsForResource(ResourceSecret)) -} - func (c *Controller) registerGatewayListeners() { c.istioInformerFactory.Networking().V1().Gateways().Informer(). AddEventHandler(c.getEventHandlerFuncsForResource(ResourceGateway)) @@ -187,6 +180,56 @@ func (c *Controller) registerGardenerDNSEntrytListeners() { AddEventHandler(c.getEventHandlerFuncsForResource(ResourceDNSEntry)) } +// Creates and Start a namespace-scoped secret informer for the namespace if one does not already exist. +func (c *Controller) ensureSecretInformerForNamespace(namespace string, ctx context.Context) { + c.nsSecretInformersMu.Lock() + defer c.nsSecretInformersMu.Unlock() + + if c.nsSecretInformers == nil { + c.nsSecretInformers = map[string]informers.SharedInformerFactory{} + } + + if _, exists := c.nsSecretInformers[namespace]; exists { + return + } + + factory := informers.NewSharedInformerFactoryWithOptions( + c.kubeClient, + 30*time.Minute, + informers.WithNamespace(namespace), + ) + + // Register + factory.Core().V1().Secrets().Informer().AddEventHandler(c.secretEventHandlers()) + + factory.Start(ctx.Done()) + factory.WaitForCacheSync(ctx.Done()) + + c.nsSecretInformers[namespace] = factory + klog.InfoS("started namespace-scoped secret informer", "namespace", namespace) +} + +// returns the secret lister for the given namespace. +func (c *Controller) secretListerForNamespace(namespace string) corev1listers.SecretNamespaceLister { + c.nsSecretInformersMu.RLock() + var factory informers.SharedInformerFactory + if c.nsSecretInformers != nil { + factory = c.nsSecretInformers[namespace] + } + c.nsSecretInformersMu.RUnlock() + + if factory != nil { + return factory.Core().V1().Secrets().Lister().Secrets(namespace) + } + return c.kubeInformerFactory.Core().V1().Secrets().Lister().Secrets(namespace) +} + +func (c *Controller) secretEventHandlers() cache.ResourceEventHandlerFuncs { + // Ignore notifications for objects for now --> we primarily only want to list these secrets for now. + // This could be implemented to used to add potentional handlers for Credential Rotation based rollouts + return cache.ResourceEventHandlerFuncs{} +} + func (c *Controller) enqueueModifiedResource(sourceKey int, new, old any) { newObj, newOk := getMetaObject(new) oldObj, oldOk := getMetaObject(old) diff --git a/internal/controller/reconcile-capapplication.go b/internal/controller/reconcile-capapplication.go index 7a18c54b..e81d55a0 100644 --- a/internal/controller/reconcile-capapplication.go +++ b/internal/controller/reconcile-capapplication.go @@ -45,6 +45,9 @@ func (c *Controller) reconcileCAPApplication(ctx context.Context, item QueueItem } ca := cached.DeepCopy() + // ensure a namespace-scoped secret informer is running for this CA's namespace + c.ensureSecretInformerForNamespace(ca.Namespace, ctx) + // prepare annotations, labels, finalizers if c.prepareCAPApplication(ca) { if err = c.updateCAPApplication(ctx, ca); err == nil { diff --git a/internal/controller/reconcile.go b/internal/controller/reconcile.go index b2a6bf03..351af441 100644 --- a/internal/controller/reconcile.go +++ b/internal/controller/reconcile.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" + corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" ) @@ -286,11 +286,11 @@ func (c *Controller) getCachedCAPApplicationVersions(ca *v1alpha1.CAPApplication func (c *Controller) checkSecretsExist(serviceInfos []v1alpha1.ServiceInfo, namespace string) error { var err error - secretLister := c.kubeInformerFactory.Core().V1().Secrets().Lister() + secretLister := c.secretListerForNamespace(namespace) for _, service := range serviceInfos { secretName := service.Secret - if _, err = secretLister.Secrets(namespace).Get(secretName); err != nil { + if _, err = secretLister.Get(secretName); err != nil { break } } @@ -300,11 +300,11 @@ func (c *Controller) checkSecretsExist(serviceInfos []v1alpha1.ServiceInfo, name func (c *Controller) checkAndPreserveSecrets(serviceInfos []v1alpha1.ServiceInfo, namespace string) error { var err error var secret *corev1.Secret - secretLister := c.kubeInformerFactory.Core().V1().Secrets().Lister() + secretLister := c.secretListerForNamespace(namespace) for _, service := range serviceInfos { secretName := service.Secret - if secret, err = secretLister.Secrets(namespace).Get(secretName); err != nil { + if secret, err = secretLister.Get(secretName); err != nil { break } // Add finalizer to preserve Secret from being deleted accidentally @@ -320,12 +320,12 @@ func (c *Controller) checkAndPreserveSecrets(serviceInfos []v1alpha1.ServiceInfo func (c *Controller) cleanupPreservedSecrets(serviceInfos []v1alpha1.ServiceInfo, namespace string) error { var err error var secret *corev1.Secret - secretLister := c.kubeInformerFactory.Core().V1().Secrets().Lister() + secretLister := c.secretListerForNamespace(namespace) for _, service := range serviceInfos { secretName := service.Secret // Check if a secret exists - if secret, err = secretLister.Secrets(namespace).Get(secretName); err != nil && !k8sErrors.IsNotFound(err) { + if secret, err = secretLister.Get(secretName); err != nil && !k8sErrors.IsNotFound(err) { break } // Remove finalizer from preserved Secret (if one exists) to allow it to be cleaned up if needed @@ -386,10 +386,10 @@ func getConsumedServiceInfos(consumedServicesMap map[string]string, serviceInfos return consumedServiceInfo } -func generateVCAPEnv(ns string, serviceInfos []v1alpha1.ServiceInfo, kubeInformerFactory informers.SharedInformerFactory) ([]byte, error) { +func generateVCAPEnv(ns string, serviceInfos []v1alpha1.ServiceInfo, secretLister corev1listers.SecretNamespaceLister) ([]byte, error) { envVCAPServices := map[string][]map[string]any{} for _, serviceInfo := range serviceInfos { - entry, err := util.CreateVCAPEntryFromSecret(&serviceInfo, ns, nil, kubeInformerFactory) + entry, err := util.CreateVCAPEntryFromSecret(&serviceInfo, ns, nil, secretLister) if err != nil { return nil, err } @@ -406,9 +406,10 @@ func generateVCAPEnv(ns string, serviceInfos []v1alpha1.ServiceInfo, kubeInforme return json.Marshal(envVCAPServices) } -func (c Controller) createVCAPSecret(namePrefix string, ns string, ownerRef metav1.OwnerReference, serviceInfos []v1alpha1.ServiceInfo) (string, error) { +func (c *Controller) createVCAPSecret(namePrefix string, ns string, ownerRef metav1.OwnerReference, serviceInfos []v1alpha1.ServiceInfo) (string, error) { // Generate VCAP_SERVICES env. variable - vcapEnv, err := generateVCAPEnv(ns, serviceInfos, c.kubeInformerFactory) + secretsLister := c.secretListerForNamespace(ns) + vcapEnv, err := generateVCAPEnv(ns, serviceInfos, secretsLister) if err != nil { return "", err } @@ -417,7 +418,7 @@ func (c Controller) createVCAPSecret(namePrefix string, ns string, ownerRef meta LabelSecretOwnerHash: sha1Sum(ns, ownerRef.Name, namePrefix), } - secretList, err := c.kubeInformerFactory.Core().V1().Secrets().Lister().Secrets(ns).List(labels.SelectorFromSet(secretLabels)) + secretList, err := secretsLister.List(labels.SelectorFromSet(secretLabels)) if err != nil { // Some error occurred return "", err } else if len(secretList) > 0 { // Existing secret present diff --git a/internal/util/vcap-credentials.go b/internal/util/vcap-credentials.go index 4e7b080f..db6a6df4 100644 --- a/internal/util/vcap-credentials.go +++ b/internal/util/vcap-credentials.go @@ -13,8 +13,8 @@ import ( "github.com/sap/cap-operator/pkg/apis/sme.sap.com/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + corev1listers "k8s.io/client-go/listers/core/v1" ) // See Kubernetes-Service-Bindings/doc @@ -83,11 +83,11 @@ func ReadServiceCredentialsFromSecret[T any](serviceInfo *v1alpha1.ServiceInfo, return ParseJSON[T](serviceCredInfo) } -func CreateVCAPEntryFromSecret(serviceInfo *v1alpha1.ServiceInfo, ns string, kubeClient kubernetes.Interface, kubeInformerFactory informers.SharedInformerFactory) (entry map[string]any, err error) { +func CreateVCAPEntryFromSecret(serviceInfo *v1alpha1.ServiceInfo, ns string, kubeClient kubernetes.Interface, secretsLister corev1listers.SecretNamespaceLister) (entry map[string]any, err error) { var secret *corev1.Secret // Get secret - if kubeInformerFactory != nil { - secret, err = kubeInformerFactory.Core().V1().Secrets().Lister().Secrets(ns).Get(serviceInfo.Secret) + if secretsLister != nil { + secret, err = secretsLister.Get(serviceInfo.Secret) } else { secret, err = kubeClient.CoreV1().Secrets(ns).Get(context.TODO(), serviceInfo.Secret, metav1.GetOptions{}) } diff --git a/internal/util/vcap-credentials_test.go b/internal/util/vcap-credentials_test.go index 6a359c44..6a0fc9be 100644 --- a/internal/util/vcap-credentials_test.go +++ b/internal/util/vcap-credentials_test.go @@ -165,7 +165,7 @@ func testCreateVCAPEntryFromSecret(t *testing.T) { for i := range cases { t.Run(cases[i].name, func(t *testing.T) { config := &cases[i] - entry, err := CreateVCAPEntryFromSecret(config.serviceInfo, config.namespace, c, kubeInformerFactory) + entry, err := CreateVCAPEntryFromSecret(config.serviceInfo, config.namespace, c, kubeInformerFactory.Core().V1().Secrets().Lister().Secrets(config.namespace)) if err != nil { if !config.expectError { t.Errorf("unexpected error in test case: %s", config.name)