pkg/controller/keyvault/event_mirror.go (145 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package keyvault import ( "context" "fmt" "strings" "github.com/Azure/aks-app-routing-operator/pkg/controller/controllername" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/Azure/aks-app-routing-operator/pkg/config" "github.com/Azure/aks-app-routing-operator/pkg/controller/metrics" "github.com/Azure/aks-app-routing-operator/pkg/util" ) var eventMirrorControllerName = controllername.New("keyvault", "event", "mirror") const ( involvedObjectKindField = "involvedObject.kind" eventReasonFailedMount = "FailedMount" reasonField = "reason" eventKindPod = "Pod" ) // EventMirrorSelector is a selector for Events that are relevant to the EventMirror controller var EventMirrorSelector = fields.AndSelectors( fields.ParseSelectorOrDie(fmt.Sprintf("%s=%s", involvedObjectKindField, eventKindPod)), fields.ParseSelectorOrDie(fmt.Sprintf("%s=%s", reasonField, eventReasonFailedMount)), ) // EventMirror copies events published to pod resources by the Keyvault CSI driver into ingress events. // This allows users to easily determine why a certificate might be missing for a given ingress. type EventMirror struct { client client.Client events record.EventRecorder } func NewEventMirror(manager ctrl.Manager, conf *config.Config) error { metrics.InitControllerMetrics(eventMirrorControllerName) if conf.DisableKeyvault { return nil } e := &EventMirror{ client: manager.GetClient(), events: manager.GetEventRecorderFor("aks-app-routing-operator"), } return eventMirrorControllerName.AddToController( ctrl. NewControllerManagedBy(manager). For(&corev1.Event{}). WithEventFilter(e.newPredicates()), manager.GetLogger(), ).Complete(e) } func (e *EventMirror) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var err error result := ctrl.Result{} // do metrics defer func() { // placing this call inside a closure allows for result and err to be bound after Reconcile executes // this makes sure they have the proper value // just calling defer metrics.HandleControllerReconcileMetrics(controllerName, result, err) would bind // the values of result and err to their zero values, since they were just instantiated metrics.HandleControllerReconcileMetrics(eventMirrorControllerName, result, err) }() logger, err := logr.FromContext(ctx) if err != nil { return result, err } logger = eventMirrorControllerName.AddToLogger(logger) logger.Info("getting event", "name", req.Name, "namespace", req.Namespace) event := &corev1.Event{} err = e.client.Get(ctx, req.NamespacedName, event) if err != nil { return result, client.IgnoreNotFound(err) } logger = logger.WithValues("generation", event.Generation) // Filter to include only keyvault mounting errors if !isKeyVaultMountingError(event) { logger.Info("ignoring event, not keyvault mounting error") return result, nil } // Get the owner (pod) podName := event.InvolvedObject.Name podNamespace := event.InvolvedObject.Namespace logger.Info("getting owner placeholder pod", "name", podName, "namespace", podNamespace) pod := &corev1.Pod{} pod.Name = podName pod.Namespace = podNamespace err = e.client.Get(ctx, client.ObjectKeyFromObject(pod), pod) if err != nil { return result, client.IgnoreNotFound(err) } if pod.Annotations == nil { logger.Info("ignoring event, pod has no annotations") return result, nil } // Get the owner (ingress) ingressName := pod.Annotations["kubernetes.azure.com/ingress-owner"] if ingressName == "" { logger.Info("ignoring event, pod has no ingress owner") return result, nil } ingressNamespace := pod.Namespace logger.Info("getting owner ingress", "name", ingressName, "namespace", ingressNamespace) ingress := &netv1.Ingress{} ingress.Namespace = ingressNamespace ingress.Name = ingressName err = e.client.Get(ctx, client.ObjectKeyFromObject(ingress), ingress) if err != nil { return result, client.IgnoreNotFound(err) } // Publish to the service also if ingress is owned by a service if name := util.FindOwnerKind(ingress.OwnerReferences, "Service"); name != "" { logger.Info("getting owner service", "name", name, "namespace", pod.Namespace) svcNamespace := pod.Namespace svc := &corev1.Service{} svc.Namespace = svcNamespace svc.Name = name err = e.client.Get(ctx, client.ObjectKeyFromObject(svc), svc) if err == nil { logger.Info("publishing FailedMount warning event to service", "service", svc.Name, "namespace", svc.Namespace) e.events.Event(svc, corev1.EventTypeWarning, "FailedMount", event.Message) } if err != nil && !k8serrors.IsNotFound(err) { return result, fmt.Errorf("getting owner service: %w", err) } } logger.Info("publishing FailedMount warning event to ingress", "ingress", ingress.Name, "namespace", ingress.Namespace) e.events.Event(ingress, corev1.EventTypeWarning, "FailedMount", event.Message) return result, nil } func (e *EventMirror) newPredicates() predicate.Predicate { return predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { return true }, UpdateFunc: func(e event.UpdateEvent) bool { return false }, DeleteFunc: func(e event.DeleteEvent) bool { return false }, GenericFunc: func(e event.GenericEvent) bool { return false }, } } func isKeyVaultMountingError(event *corev1.Event) bool { if event == nil { return false } return event.InvolvedObject.Kind == eventKindPod && event.Reason == eventReasonFailedMount && strings.HasPrefix(event.InvolvedObject.Name, "keyvault-") && strings.Contains(event.Message, "keyvault") }