From 3d1134a975bf6f2564d7a67ad78238ecd42e1ef3 Mon Sep 17 00:00:00 2001 From: irbekrm Date: Thu, 4 May 2023 07:59:31 +0100 Subject: [PATCH] Update cainjector inejctable setup To work with latest controller runtime Signed-off-by: irbekrm --- pkg/controller/cainjector/indexers.go | 192 ++++++++++++-------------- pkg/controller/cainjector/setup.go | 27 +--- 2 files changed, 98 insertions(+), 121 deletions(-) diff --git a/pkg/controller/cainjector/indexers.go b/pkg/controller/cainjector/indexers.go index 1fe12d1cd..a86e5dafc 100644 --- a/pkg/controller/cainjector/indexers.go +++ b/pkg/controller/cainjector/indexers.go @@ -26,21 +26,42 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) -// setup for indexers used to trigger reconciliation on injected CA data. +const ( + // injectFromPath is the index key used to look up the value of inject-ca-from on targeted objects + injectFromPath = ".metadata.annotations.inject-ca-from" -// certificateToInjectableFunc converts a given certificate to the reconcile requests for the corresponding injectables -// (webhooks, api services, etc) that reference it. -type certificateToInjectableFunc func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request + // injectFromSecretPath is the index key used to look up the value of + // inject-ca-from-secret on targeted objects + injectFromSecretPath = ".metadata.annotations.inject-ca-from-secret" +) -// buildCertToInjectableFunc creates a certificateToInjectableFunc that maps from certificates to the given type of injectable. -func buildCertToInjectableFunc(listTyp runtime.Object, resourceName string) certificateToInjectableFunc { - return func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request { - log = log.WithValues("type", resourceName) - objs := listTyp.DeepCopyObject().(client.ObjectList) +// certFromSecretToInjectableMapFuncBuilder returns a handler.MapFunc that, for +// a Secret change, ensures that if this Secret is a Certificate Secret of +// Certificate that is configured as a CA source for an injectable via +// inject-ca-from annotation, a reconcile loop will be triggered for this +// injectable +func certFromSecretToInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + secretName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} + certName := owningCertForSecret(obj.(*corev1.Secret)) + if certName == nil { + return nil + } + log = log.WithValues("type", config.resourceName, "secret", secretName, "certificate", *certName) + + var cert cmapi.Certificate + // confirm that a service owns this cert + if err := cl.Get(context.Background(), *certName, &cert); err != nil { + // TODO(directxman12): check for not found error? + log.Error(err, "unable to fetch certificate that owns the secret") + return nil + } + objs := config.listType.DeepCopyObject().(client.ObjectList) if err := cl.List(context.Background(), objs, client.MatchingFields{injectFromPath: certName.String()}); err != nil { log.Error(err, "unable to fetch injectables associated with certificate") return nil @@ -68,82 +89,52 @@ func buildCertToInjectableFunc(listTyp runtime.Object, resourceName string) cert } } -// secretForCertificateMapper is a Mapper that converts secrets up to injectables, through certificates. -type secretForCertificateMapper struct { - Client client.Reader - log logr.Logger - certificateToInjectable certificateToInjectableFunc +// certFromSecretToInjectableMapFuncBuilder returns a handler.MapFunc that, for +// a Certificate change, ensures that if this Certificate that is configured as +// a CA source for an injectable via inject-ca-from annotation, a reconcile loop +// will be triggered for this injectable +func certToInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + certName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + log = log.WithValues("type", config.resourceName, "certificate", certName) + objs := config.listType.DeepCopyObject().(client.ObjectList) + if err := cl.List(context.Background(), objs, client.MatchingFields{injectFromPath: certName.String()}); err != nil { + log.Error(err, "unable to fetch injectables associated with certificate") + return nil + } + + var reqs []ctrl.Request + if err := meta.EachListItem(objs, func(obj runtime.Object) error { + metaInfo, err := meta.Accessor(obj) + if err != nil { + log.Error(err, "unable to get metadata from list item") + // continue on error + return nil + } + reqs = append(reqs, ctrl.Request{NamespacedName: types.NamespacedName{ + Name: metaInfo.GetName(), + Namespace: metaInfo.GetNamespace(), + }}) + return nil + }); err != nil { + log.Error(err, "unable get items from list") + return nil + } + + return reqs + } } -func (m *secretForCertificateMapper) Map(obj client.Object) []ctrl.Request { - // grab the certificate, if it exists - certName := owningCertForSecret(obj.(*corev1.Secret)) - if certName == nil { - return nil - } - - secretName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} - log := m.log.WithValues("secret", secretName, "certificate", *certName) - - var cert cmapi.Certificate - // confirm that a service owns this cert - if err := m.Client.Get(context.Background(), *certName, &cert); err != nil { - // TODO(directxman12): check for not found error? - log.Error(err, "unable to fetch certificate that owns the secret") - return nil - } - - return m.certificateToInjectable(log, m.Client, *certName) -} - -// certMapper is a mapper that converts Certificates up to injectables -type certMapper struct { - Client client.Reader - log logr.Logger - toInjectable certificateToInjectableFunc -} - -func (m *certMapper) Map(obj client.Object) []ctrl.Request { - certName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} - log := m.log.WithValues("certificate", certName) - return m.toInjectable(log, m.Client, certName) -} - -var ( - // injectFromPath is the index key used to look up the value of inject-ca-from on targeted objects - injectFromPath = ".metadata.annotations.inject-ca-from" -) - -// injectableCAFromIndexer is an IndexerFunc indexing on certificates -// referenced by injectables. -func injectableCAFromIndexer(rawObj client.Object) []string { - metaInfo, err := meta.Accessor(rawObj) - if err != nil { - return nil - } - - // skip invalid certificate names - certNameRaw := metaInfo.GetAnnotations()[cmapi.WantInjectAnnotation] - if certNameRaw == "" { - return nil - } - certName := splitNamespacedName(certNameRaw) - if certName.Namespace == "" { - return nil - } - - return []string{certNameRaw} -} - -// secretToInjectableFunc converts a given certificate to the reconcile requests for the corresponding injectables -// (webhooks, api services, etc) that reference it. -type secretToInjectableFunc func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request - -// buildSecretToInjectableFunc creates a certificateToInjectableFunc that maps from secrets to the given type of injectable. -func buildSecretToInjectableFunc(listTyp runtime.Object, resourceName string) secretToInjectableFunc { - return func(log logr.Logger, cl client.Reader, secretName types.NamespacedName) []ctrl.Request { - log = log.WithValues("type", resourceName) - objs := listTyp.DeepCopyObject().(client.ObjectList) +// secretForInjectableMapFuncBuilder returns a handler.MapFunc that, for a +// config for particular injectable type (i.e CRD, APIService) and a Secret, +// returns all injectables that have the inject-ca-from-secret annotion with the +// given secret name. This will be used in an event handler to ensure that +// changes to a Secret triggers a reconcile loop for the relevant injectable. +func secretForInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + secretName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + log = log.WithValues("type", config.resourceName, "secret", secretName) + objs := config.listType.DeepCopyObject().(client.ObjectList) // TODO: ensure that this is cache lister, not a direct client if err := cl.List(context.Background(), objs, client.MatchingFields{injectFromSecretPath: secretName.String()}); err != nil { log.Error(err, "unable to fetch injectables associated with secret") @@ -172,25 +163,26 @@ func buildSecretToInjectableFunc(listTyp runtime.Object, resourceName string) se } } -// secretForInjectableMapper is a Mapper that converts secrets to injectables -// via the 'inject-ca-from-secret' annotation -type secretForInjectableMapper struct { - Client client.Reader - log logr.Logger - secretToInjectable secretToInjectableFunc -} +// injectableCAFromIndexer is an IndexerFunc indexing on certificates +// referenced by injectables. +func injectableCAFromIndexer(rawObj client.Object) []string { + metaInfo, err := meta.Accessor(rawObj) + if err != nil { + return nil + } -func (m *secretForInjectableMapper) Map(obj client.Object) []ctrl.Request { - secretName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} - log := m.log.WithValues("secret", secretName) - return m.secretToInjectable(log, m.Client, secretName) -} + // skip invalid certificate names + certNameRaw := metaInfo.GetAnnotations()[cmapi.WantInjectAnnotation] + if certNameRaw == "" { + return nil + } + certName := splitNamespacedName(certNameRaw) + if certName.Namespace == "" { + return nil + } -var ( - // injectFromSecretPath is the index key used to look up the value of - // inject-ca-from-secret on targeted objects - injectFromSecretPath = ".metadata.annotations.inject-ca-from-secret" -) + return []string{certNameRaw} +} // injectableCAFromSecretIndexer is an IndexerFunc indexing on secrets // referenced by injectables. diff --git a/pkg/controller/cainjector/setup.go b/pkg/controller/cainjector/setup.go index e2bad4d1a..33ce1f948 100644 --- a/pkg/controller/cainjector/setup.go +++ b/pkg/controller/cainjector/setup.go @@ -32,7 +32,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/source" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util" @@ -93,10 +92,8 @@ var ( } ) -// registerAllInjectors registers all injectors and based on the -// graduation state of the injector decides how to log no kind/resource match errors +// RegisterAllInjectors sets up watches for all injectable and injector types that cainjector should watch func RegisterAllInjectors(ctx context.Context, mgr ctrl.Manager, opts SetupOptions) error { - // TODO: refactor sds := &secretDataSource{ client: mgr.GetClient(), } @@ -113,7 +110,6 @@ func RegisterAllInjectors(ctx context.Context, mgr ctrl.Manager, opts SetupOptio } injectorSetups := []setup{MutatingWebhookSetup, ValidatingWebhookSetup, APIServiceSetup, CRDSetup} // Registers a c/r controller for each of APIService, CustomResourceDefinition, Mutating/ValidatingWebhookConfiguration - // TODO: add a flag to allow users to configure which of these controllers should be registered for _, setup := range injectorSetups { log := ctrl.Log.WithValues("kind", setup.resourceName) if !opts.EnabledReconcilersFor[setup.resourceName] { @@ -169,11 +165,7 @@ func RegisterAllInjectors(ctx context.Context, mgr ctrl.Manager, opts SetupOptio // injectables is here where we define which // objects' events should trigger a reconcile. builder.WithPredicates(predicates)). - Watches(&source.Kind{Type: new(corev1.Secret)}, handler.EnqueueRequestsFromMapFunc((&secretForInjectableMapper{ - Client: mgr.GetClient(), - log: log, - secretToInjectable: buildSecretToInjectableFunc(setup.listType, setup.resourceName), - }).Map)) + Watches(new(corev1.Secret), handler.EnqueueRequestsFromMapFunc(secretForInjectableMapFuncBuilder(mgr.GetClient(), log, setup))) if opts.EnableCertificatesDataSource { // Index injectable with a new field. If the injectable's CA is // to be sourced from a Certificate's Secret, the field's value will be the @@ -184,17 +176,10 @@ func RegisterAllInjectors(ctx context.Context, mgr ctrl.Manager, opts SetupOptio err := fmt.Errorf("error making injectable indexable by inject-ca-from path: %w", err) return err } - b.Watches(&source.Kind{Type: new(corev1.Secret)}, handler.EnqueueRequestsFromMapFunc((&secretForCertificateMapper{ - Client: mgr.GetClient(), - log: log, - certificateToInjectable: buildCertToInjectableFunc(setup.listType, setup.resourceName), - }).Map)). - Watches(&source.Kind{Type: new(cmapi.Certificate)}, - handler.EnqueueRequestsFromMapFunc((&certMapper{ - Client: mgr.GetClient(), - log: log, - toInjectable: buildCertToInjectableFunc(setup.listType, setup.resourceName), - }).Map)) + b.Watches(new(corev1.Secret), handler.EnqueueRequestsFromMapFunc( + certFromSecretToInjectableMapFuncBuilder(mgr.GetClient(), log, setup))). + Watches(new(cmapi.Certificate), + handler.EnqueueRequestsFromMapFunc(certToInjectableMapFuncBuilder(mgr.GetClient(), log, setup))) } if err := b.Complete(r); err != nil { return fmt.Errorf("error registering controller for %s: %w", setup.objType.GetName(), err)