Files
Oliver BählerandGitHub 2252c530f4 feat(performance): removed duplicate client calls from all admission paths (#2054)
* chore

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-07-28 14:32:42 +02:00

208 lines
5.6 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package invalidator
import (
"context"
"errors"
"fmt"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/reconcile"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/internal/cache"
"github.com/projectcapsule/capsule/internal/controllers/utils"
"github.com/projectcapsule/capsule/internal/metrics"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
"github.com/projectcapsule/capsule/pkg/runtime/predicates"
)
type CacheInvalidator struct {
client.Client
Rest *rest.Config
Log logr.Logger
reader client.Reader
configName string
metrics *metrics.ConfigRecorder
Configuration configuration.Configuration
RegistryCache *cache.RegistryRuleSetCache
TargetsCache *cache.CompiledTargetsCache[string]
JSONPathCache *cache.JSONPathCache
CELCache *cache.CELCache
ImpersonationCache *cache.ImpersonationCache
RegexCache *cache.RegexCache
}
func (r *CacheInvalidator) NeedLeaderElection() bool {
return false
}
// Start is the Runnable function triggered upon Manager start-up to perform cache population.
func (r *CacheInvalidator) Start(ctx context.Context) error {
if err := r.rebuildCaches(ctx, r.Log); err != nil {
r.Log.Error(err, "cache population failed")
return nil
}
<-ctx.Done()
return nil
}
func (r *CacheInvalidator) SetupWithManager(
mgr ctrl.Manager,
ctrlConfig utils.ControllerOptions,
metrics *metrics.ConfigRecorder,
) (err error) {
r.configName = ctrlConfig.ConfigurationName
r.reader = mgr.GetAPIReader()
r.metrics = metrics
err = ctrl.NewControllerManagedBy(mgr).
Named("config/caches").
For(
&capsulev1beta2.CapsuleConfiguration{},
builder.WithPredicates(
predicate.GenerationChangedPredicate{},
predicates.NamesMatchingPredicate{Names: []string{ctrlConfig.ConfigurationName}},
),
).
Watches(
&capsulev1beta2.CapsuleConfiguration{},
handler.Funcs{
UpdateFunc: func(ctx context.Context, updateEvent event.TypedUpdateEvent[client.Object], limitingInterface workqueue.TypedRateLimitingInterface[reconcile.Request]) {
if err := r.rebuildImpersonationCache(ctx, r.Log); err != nil {
r.Log.Error(err, "unable to invalidate impersonation cache")
}
},
},
builder.WithPredicates(
predicates.CapsuleConfigSpecImpersonationChangedPredicate{},
predicates.NamesMatchingPredicate{Names: []string{ctrlConfig.ConfigurationName}},
),
).
WatchesMetadata(
&corev1.ServiceAccount{},
handler.Funcs{
DeleteFunc: func(
ctx context.Context,
e event.TypedDeleteEvent[client.Object],
q workqueue.TypedRateLimitingInterface[reconcile.Request],
) {
if err := r.invalidateServiceAccount(ctx, e.Object); err != nil {
r.Log.Error(err, "unable to invalidate serviceaccount cache",
"namespace", e.Object.GetNamespace(),
"name", e.Object.GetName(),
)
}
},
},
builder.WithPredicates(predicate.Funcs{
DeleteFunc: func(e event.DeleteEvent) bool {
return true
},
CreateFunc: func(e event.CreateEvent) bool {
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
return false
},
GenericFunc: func(e event.GenericEvent) bool {
return false
},
},
),
).
WithOptions(ctrlConfig.Runtime.ToControllerOptions()).
Complete(r)
if err != nil {
return err
}
// register Start(ctx) as a manager runnable.
return mgr.Add(r)
}
func (r *CacheInvalidator) Reconcile(ctx context.Context, request reconcile.Request) (res reconcile.Result, err error) {
log := r.Log.WithValues("configuration", request.Name)
log.V(5).Info("invalidating and rebuilding caches")
cfg := configuration.NewCapsuleConfiguration(ctx, r.Client, r.reader, r.Rest, request.Name)
instance := &capsulev1beta2.CapsuleConfiguration{}
if err = r.Get(ctx, request.NamespacedName, instance); err != nil {
if apierrors.IsNotFound(err) {
log.V(5).Info("requested object not found, could have been deleted after reconcile request")
return reconcile.Result{}, nil
}
log.Error(err, "error reading the object")
return res, err
}
if err := r.rebuildCaches(ctx, log); err != nil {
return res, err
}
interval := cfg.CacheInvalidation()
return reconcile.Result{
Requeue: true,
RequeueAfter: interval.Duration,
}, err
}
// invalidateCaches invokes for all caches their invalidation functions.
func (r *CacheInvalidator) rebuildCaches(
ctx context.Context,
log logr.Logger,
) error {
var errs []error
if err := r.rebuildRegexCache(ctx, log); err != nil {
errs = append(errs, fmt.Errorf("rebuild Regex cache: %w", err))
}
if err := r.rebuildJSONPathCache(ctx, log); err != nil {
errs = append(errs, fmt.Errorf("rebuild JSONPath cache: %w", err))
}
if err := r.rebuildTargetsCache(ctx, log); err != nil {
errs = append(errs, fmt.Errorf("rebuild targets cache: %w", err))
}
if err := r.rebuildRuleStatusRegistryCache(ctx, log); err != nil {
errs = append(errs, fmt.Errorf("rebuild registry cache: %w", err))
}
if err := r.rebuildImpersonationCache(ctx, log); err != nil {
errs = append(errs, fmt.Errorf("rebuild impersonation cache: %w", err))
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}