From e0d9e89690ea66bbf64c5e234872b33b3c736ebe Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Fri, 10 Jul 2026 17:07:30 -0300 Subject: [PATCH 1/4] feat: cache project-id --- cmd/main.go | 8 + internal/cache/resource_cache.go | 45 ++++ internal/cache/resource_cache_test.go | 198 ++++++++++++++++++ .../infisicalstaticsecret_controller.go | 4 +- .../services/infisicalstaticsecret/handler.go | 5 + .../infisicalstaticsecret/reconciler.go | 27 ++- .../infisicalstaticsecret/reconciler_test.go | 4 +- 7 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 internal/cache/resource_cache.go create mode 100644 internal/cache/resource_cache_test.go diff --git a/cmd/main.go b/cmd/main.go index 22de30c..69c3e41 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -276,6 +276,13 @@ func main() { } defer authCache.Cleanup() + resourceCache, err := inmemoryCache.NewResourceCache(5 * time.Minute) + if err != nil { + setupLog.Error(err, "unable to start resource cache") + os.Exit(1) + } + defer resourceCache.Cleanup() + authStrategyResolver := auth.NewAuthStrategyResolver(mgr.GetClient(), authCache, ctrl.Log, isNamespaceScoped) template.InitializeTemplateFunctions() @@ -332,6 +339,7 @@ func main() { BaseLogger: ctrl.Log, IsNamespaceScoped: isNamespaceScoped, AuthResolver: authStrategyResolver, + ResourceCache: resourceCache, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "InfisicalStaticSecret") os.Exit(1) diff --git a/internal/cache/resource_cache.go b/internal/cache/resource_cache.go new file mode 100644 index 0000000..9987f3a --- /dev/null +++ b/internal/cache/resource_cache.go @@ -0,0 +1,45 @@ +package cache + +import ( + "fmt" + "time" + + "github.com/dgraph-io/ristretto/v2" +) + +type ResourceCache struct { + cache *ristretto.Cache[string, string] + ttl time.Duration +} + +func NewResourceCache(ttl time.Duration) (*ResourceCache, error) { + cache, err := ristretto.NewCache(&ristretto.Config[string, string]{ + NumCounters: 1000, + MaxCost: 1 << 20, + BufferItems: 64, + IgnoreInternalCost: true, + }) + if err != nil { + return nil, fmt.Errorf("failed to create resource cache: %w", err) + } + return &ResourceCache{cache: cache, ttl: ttl}, nil +} + +func (c *ResourceCache) Get(key string) (string, bool) { + return c.cache.Get(key) +} + +func (c *ResourceCache) Set(key string, value string) { + c.cache.SetWithTTL(key, value, 1, c.ttl) + c.cache.Wait() +} + +func (c *ResourceCache) Cleanup() { + if c.cache != nil { + c.cache.Close() + } +} + +func ProjectBySlugCacheKey(slug string) string { + return fmt.Sprintf("project_by_slug_%s", slug) +} diff --git a/internal/cache/resource_cache_test.go b/internal/cache/resource_cache_test.go new file mode 100644 index 0000000..ea80a02 --- /dev/null +++ b/internal/cache/resource_cache_test.go @@ -0,0 +1,198 @@ +package cache_test + +import ( + "fmt" + "sync" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/Infisical/infisical/k8-operator/internal/cache" +) + +var _ = Describe("ResourceCache", func() { + var resourceCache *cache.ResourceCache + + AfterEach(func() { + if resourceCache != nil { + resourceCache.Cleanup() + } + }) + + Describe("basic operations", func() { + BeforeEach(func() { + var err error + resourceCache, err = cache.NewResourceCache(30 * time.Second) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should store and retrieve a value", func() { + resourceCache.Set("key-1", "value-1") + + result, found := resourceCache.Get("key-1") + Expect(found).To(BeTrue()) + Expect(result).To(Equal("value-1")) + }) + + It("should return false for a key that was never set", func() { + _, found := resourceCache.Get("nonexistent") + Expect(found).To(BeFalse()) + }) + + It("should overwrite an existing key", func() { + resourceCache.Set("key-1", "original") + resourceCache.Set("key-1", "updated") + + result, found := resourceCache.Get("key-1") + Expect(found).To(BeTrue()) + Expect(result).To(Equal("updated")) + }) + + It("should store multiple distinct keys", func() { + resourceCache.Set("key-a", "value-a") + resourceCache.Set("key-b", "value-b") + resourceCache.Set("key-c", "value-c") + + for _, pair := range []struct{ k, v string }{ + {"key-a", "value-a"}, + {"key-b", "value-b"}, + {"key-c", "value-c"}, + } { + result, found := resourceCache.Get(pair.k) + Expect(found).To(BeTrue()) + Expect(result).To(Equal(pair.v)) + } + }) + }) + + Describe("TTL expiration", func() { + BeforeEach(func() { + var err error + resourceCache, err = cache.NewResourceCache(2 * time.Second) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should not find a key after its TTL expires", func() { + resourceCache.Set("ephemeral", "gone-soon") + + result, found := resourceCache.Get("ephemeral") + Expect(found).To(BeTrue()) + Expect(result).To(Equal("gone-soon")) + + time.Sleep(3 * time.Second) + + _, found = resourceCache.Get("ephemeral") + Expect(found).To(BeFalse()) + }) + }) + + Describe("parallel access", func() { + BeforeEach(func() { + var err error + resourceCache, err = cache.NewResourceCache(30 * time.Second) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should handle concurrent writes to different keys", func() { + const goroutines = 50 + var wg sync.WaitGroup + wg.Add(goroutines) + + for i := 0; i < goroutines; i++ { + go func(idx int) { + defer wg.Done() + key := fmt.Sprintf("concurrent-key-%d", idx) + value := fmt.Sprintf("concurrent-value-%d", idx) + resourceCache.Set(key, value) + }(i) + } + wg.Wait() + + for i := 0; i < goroutines; i++ { + key := fmt.Sprintf("concurrent-key-%d", i) + expected := fmt.Sprintf("concurrent-value-%d", i) + result, found := resourceCache.Get(key) + Expect(found).To(BeTrue()) + Expect(result).To(Equal(expected)) + } + }) + + It("should handle concurrent reads and writes to the same key", func() { + resourceCache.Set("shared", "initial") + + const goroutines = 50 + var wg sync.WaitGroup + wg.Add(goroutines * 2) + + for i := 0; i < goroutines; i++ { + go func(idx int) { + defer wg.Done() + resourceCache.Set("shared", fmt.Sprintf("writer-%d", idx)) + }(i) + + go func() { + defer wg.Done() + val, found := resourceCache.Get("shared") + if found { + Expect(val).NotTo(BeEmpty()) + } + }() + } + wg.Wait() + + result, found := resourceCache.Get("shared") + Expect(found).To(BeTrue()) + Expect(result).To(HavePrefix("writer-")) + }) + + It("should return the correct value per key under parallel access", func() { + keys := []string{"project-a", "project-b", "project-c"} + values := []string{"data-a", "data-b", "data-c"} + + for i, k := range keys { + resourceCache.Set(k, values[i]) + } + + const readersPerKey = 30 + var wg sync.WaitGroup + wg.Add(len(keys) * readersPerKey) + + for i, k := range keys { + expected := values[i] + for r := 0; r < readersPerKey; r++ { + go func(key, exp string) { + defer wg.Done() + result, found := resourceCache.Get(key) + Expect(found).To(BeTrue()) + Expect(result).To(Equal(exp)) + }(k, expected) + } + } + wg.Wait() + }) + }) + + Describe("cleanup", func() { + It("should not panic when calling Cleanup on a valid cache", func() { + var err error + resourceCache, err = cache.NewResourceCache(10 * time.Second) + Expect(err).NotTo(HaveOccurred()) + + resourceCache.Set("before-cleanup", "value") + Expect(func() { resourceCache.Cleanup() }).NotTo(Panic()) + resourceCache = nil + }) + }) + + Describe("ProjectBySlugCacheKey", func() { + It("should produce a deterministic key from a slug", func() { + key := cache.ProjectBySlugCacheKey("my-project") + Expect(key).To(Equal("project_by_slug_my-project")) + }) + + It("should produce distinct keys for different slugs", func() { + Expect(cache.ProjectBySlugCacheKey("a")).NotTo(Equal(cache.ProjectBySlugCacheKey("b"))) + }) + }) +}) diff --git a/internal/controller/v1beta1/infisicalstaticsecret_controller.go b/internal/controller/v1beta1/infisicalstaticsecret_controller.go index 8977480..878d232 100644 --- a/internal/controller/v1beta1/infisicalstaticsecret_controller.go +++ b/internal/controller/v1beta1/infisicalstaticsecret_controller.go @@ -25,6 +25,7 @@ import ( secretsv1beta1 "github.com/Infisical/infisical/k8-operator/api/v1beta1" "github.com/Infisical/infisical/k8-operator/internal/api" "github.com/Infisical/infisical/k8-operator/internal/auth" + "github.com/Infisical/infisical/k8-operator/internal/cache" "github.com/Infisical/infisical/k8-operator/internal/model" "github.com/Infisical/infisical/k8-operator/internal/services/infisicalstaticsecret" "github.com/Infisical/infisical/k8-operator/internal/util" @@ -74,6 +75,7 @@ type InfisicalStaticSecretReconciler struct { Scheme *runtime.Scheme IsNamespaceScoped bool AuthResolver *auth.AuthStrategyResolver + ResourceCache *cache.ResourceCache SourceCh chan event.TypedGenericEvent[client.Object] } @@ -125,7 +127,7 @@ func (r *InfisicalStaticSecretReconciler) Reconcile(ctx context.Context, req ctr instantUpdates := staticSecretCRD.Spec.SyncOptions != nil && staticSecretCRD.Spec.SyncOptions.InstantUpdates - handler := infisicalstaticsecret.NewInfisicalStaticSecretHandler(r.Client, r.Scheme, r.IsNamespaceScoped, r.AuthResolver, logger) + handler := infisicalstaticsecret.NewInfisicalStaticSecretHandler(r.Client, r.Scheme, r.IsNamespaceScoped, r.AuthResolver, r.ResourceCache, logger) secretsCount, err := handler.SyncSecrets(ctx, &staticSecretCRD) if err != nil { var rateLimitErr *api.TooManyRequestsError diff --git a/internal/services/infisicalstaticsecret/handler.go b/internal/services/infisicalstaticsecret/handler.go index 267bec4..e97ad1c 100644 --- a/internal/services/infisicalstaticsecret/handler.go +++ b/internal/services/infisicalstaticsecret/handler.go @@ -10,6 +10,7 @@ import ( "github.com/Infisical/infisical/k8-operator/api/v1beta1" "github.com/Infisical/infisical/k8-operator/internal/api" "github.com/Infisical/infisical/k8-operator/internal/auth" + "github.com/Infisical/infisical/k8-operator/internal/cache" "github.com/Infisical/infisical/k8-operator/internal/model" "github.com/Infisical/infisical/k8-operator/internal/util" "github.com/Infisical/infisical/k8-operator/internal/util/sse" @@ -25,6 +26,7 @@ func NewInfisicalStaticSecretHandler( scheme *runtime.Scheme, isNamespaceScoped bool, authResolver *auth.AuthStrategyResolver, + resourceCache *cache.ResourceCache, logger logr.Logger, ) *InfisicalStaticSecretHandler { return &InfisicalStaticSecretHandler{ @@ -32,12 +34,14 @@ func NewInfisicalStaticSecretHandler( Scheme: scheme, IsNamespaceScoped: isNamespaceScoped, authResolver: authResolver, + resourceCache: resourceCache, logger: logger, reconciler: &InfisicalStaticSecretReconciler{ Client: client, Scheme: scheme, IsNamespaceScoped: isNamespaceScoped, authResolver: authResolver, + resourceCache: resourceCache, logger: logger, }, } @@ -49,6 +53,7 @@ type InfisicalStaticSecretHandler struct { Random *rand.Rand IsNamespaceScoped bool authResolver *auth.AuthStrategyResolver + resourceCache *cache.ResourceCache reconciler *InfisicalStaticSecretReconciler logger logr.Logger } diff --git a/internal/services/infisicalstaticsecret/reconciler.go b/internal/services/infisicalstaticsecret/reconciler.go index 1c2ec6a..3e523ff 100644 --- a/internal/services/infisicalstaticsecret/reconciler.go +++ b/internal/services/infisicalstaticsecret/reconciler.go @@ -11,6 +11,7 @@ import ( "github.com/Infisical/infisical/k8-operator/api/v1beta1" "github.com/Infisical/infisical/k8-operator/internal/api" "github.com/Infisical/infisical/k8-operator/internal/auth" + "github.com/Infisical/infisical/k8-operator/internal/cache" "github.com/Infisical/infisical/k8-operator/internal/constants" "github.com/Infisical/infisical/k8-operator/internal/crypto" "github.com/Infisical/infisical/k8-operator/internal/model" @@ -18,6 +19,7 @@ import ( "github.com/Infisical/infisical/k8-operator/internal/util" "github.com/go-logr/logr" + "github.com/go-resty/resty/v2" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8Errors "k8s.io/apimachinery/pkg/api/errors" @@ -142,9 +144,30 @@ type InfisicalStaticSecretReconciler struct { Scheme *runtime.Scheme IsNamespaceScoped bool authResolver *auth.AuthStrategyResolver + resourceCache *cache.ResourceCache logger logr.Logger } +func (r *InfisicalStaticSecretReconciler) getProjectIDBySlug(restClient *resty.Client, slug string) (string, error) { + cacheKey := cache.ProjectBySlugCacheKey(slug) + if r.resourceCache != nil { + if cached, ok := r.resourceCache.Get(cacheKey); ok { + return cached, nil + } + } + + project, err := api.FindProjectBySlug(restClient, slug) + if err != nil { + return "", err + } + + if r.resourceCache != nil { + r.resourceCache.Set(cacheKey, project.ID) + } + + return project.ID, nil +} + func (r *InfisicalStaticSecretReconciler) Validate(infisicalStaticSecret *v1beta1.InfisicalStaticSecret) error { if infisicalStaticSecret == nil { return model.ErrInvalidStaticSecretObject @@ -329,11 +352,11 @@ func (r *InfisicalStaticSecretReconciler) ListSecretsFromSources(ctx context.Con for _, source := range infisicalStaticSecret.Spec.Sources { projectID := source.ProjectId if source.ProjectSlug != "" { - project, err := api.FindProjectBySlug(restClient, source.ProjectSlug) + resolvedID, err := r.getProjectIDBySlug(restClient, source.ProjectSlug) if err != nil { return nil, nil, fmt.Errorf("failed to list secrets: %w", err) } - projectID = project.ID + projectID = resolvedID } requests = append(requests, api.ListSecretsRequest{ diff --git a/internal/services/infisicalstaticsecret/reconciler_test.go b/internal/services/infisicalstaticsecret/reconciler_test.go index cd2dbf1..4a2e234 100644 --- a/internal/services/infisicalstaticsecret/reconciler_test.go +++ b/internal/services/infisicalstaticsecret/reconciler_test.go @@ -14,11 +14,11 @@ import ( svc "github.com/Infisical/infisical/k8-operator/internal/services/infisicalstaticsecret" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + k8Errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - k8Errors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/interceptor" From 74b341e25393119fa2002c221d613ae9ebb6dec3 Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Fri, 10 Jul 2026 17:17:12 -0300 Subject: [PATCH 2/4] pass auth info for caching key --- internal/cache/resource_cache.go | 4 ++-- internal/cache/resource_cache_test.go | 12 ++++++++---- internal/services/infisicalstaticsecret/handler.go | 2 -- .../services/infisicalstaticsecret/reconciler.go | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/cache/resource_cache.go b/internal/cache/resource_cache.go index 9987f3a..e731ad0 100644 --- a/internal/cache/resource_cache.go +++ b/internal/cache/resource_cache.go @@ -40,6 +40,6 @@ func (c *ResourceCache) Cleanup() { } } -func ProjectBySlugCacheKey(slug string) string { - return fmt.Sprintf("project_by_slug_%s", slug) +func ProjectBySlugCacheKey(authNamespace, authName, slug string) string { + return fmt.Sprintf("project_by_slug_%s/%s/%s", authNamespace, authName, slug) } diff --git a/internal/cache/resource_cache_test.go b/internal/cache/resource_cache_test.go index ea80a02..fb0d01c 100644 --- a/internal/cache/resource_cache_test.go +++ b/internal/cache/resource_cache_test.go @@ -186,13 +186,17 @@ var _ = Describe("ResourceCache", func() { }) Describe("ProjectBySlugCacheKey", func() { - It("should produce a deterministic key from a slug", func() { - key := cache.ProjectBySlugCacheKey("my-project") - Expect(key).To(Equal("project_by_slug_my-project")) + It("should produce a deterministic key from auth ref and slug", func() { + key := cache.ProjectBySlugCacheKey("default", "my-auth", "my-project") + Expect(key).To(Equal("project_by_slug_default/my-auth/my-project")) }) It("should produce distinct keys for different slugs", func() { - Expect(cache.ProjectBySlugCacheKey("a")).NotTo(Equal(cache.ProjectBySlugCacheKey("b"))) + Expect(cache.ProjectBySlugCacheKey("ns", "auth", "a")).NotTo(Equal(cache.ProjectBySlugCacheKey("ns", "auth", "b"))) + }) + + It("should produce distinct keys for the same slug with different auth refs", func() { + Expect(cache.ProjectBySlugCacheKey("ns-a", "auth-a", "slug")).NotTo(Equal(cache.ProjectBySlugCacheKey("ns-b", "auth-b", "slug"))) }) }) }) diff --git a/internal/services/infisicalstaticsecret/handler.go b/internal/services/infisicalstaticsecret/handler.go index e97ad1c..d1d56eb 100644 --- a/internal/services/infisicalstaticsecret/handler.go +++ b/internal/services/infisicalstaticsecret/handler.go @@ -34,7 +34,6 @@ func NewInfisicalStaticSecretHandler( Scheme: scheme, IsNamespaceScoped: isNamespaceScoped, authResolver: authResolver, - resourceCache: resourceCache, logger: logger, reconciler: &InfisicalStaticSecretReconciler{ Client: client, @@ -53,7 +52,6 @@ type InfisicalStaticSecretHandler struct { Random *rand.Rand IsNamespaceScoped bool authResolver *auth.AuthStrategyResolver - resourceCache *cache.ResourceCache reconciler *InfisicalStaticSecretReconciler logger logr.Logger } diff --git a/internal/services/infisicalstaticsecret/reconciler.go b/internal/services/infisicalstaticsecret/reconciler.go index 3e523ff..b9b732a 100644 --- a/internal/services/infisicalstaticsecret/reconciler.go +++ b/internal/services/infisicalstaticsecret/reconciler.go @@ -148,8 +148,8 @@ type InfisicalStaticSecretReconciler struct { logger logr.Logger } -func (r *InfisicalStaticSecretReconciler) getProjectIDBySlug(restClient *resty.Client, slug string) (string, error) { - cacheKey := cache.ProjectBySlugCacheKey(slug) +func (r *InfisicalStaticSecretReconciler) getProjectIDBySlug(restClient *resty.Client, authRef v1beta1.NamespacedName, slug string) (string, error) { + cacheKey := cache.ProjectBySlugCacheKey(authRef.Namespace, authRef.Name, slug) if r.resourceCache != nil { if cached, ok := r.resourceCache.Get(cacheKey); ok { return cached, nil @@ -352,7 +352,7 @@ func (r *InfisicalStaticSecretReconciler) ListSecretsFromSources(ctx context.Con for _, source := range infisicalStaticSecret.Spec.Sources { projectID := source.ProjectId if source.ProjectSlug != "" { - resolvedID, err := r.getProjectIDBySlug(restClient, source.ProjectSlug) + resolvedID, err := r.getProjectIDBySlug(restClient, infisicalStaticSecret.Spec.InfisicalAuthRef, source.ProjectSlug) if err != nil { return nil, nil, fmt.Errorf("failed to list secrets: %w", err) } From 681b1554cf502f26bd137d0cf908399d7b9bb465 Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Fri, 10 Jul 2026 17:23:03 -0300 Subject: [PATCH 3/4] 30 min cache ttl --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index 69c3e41..b250399 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -276,7 +276,7 @@ func main() { } defer authCache.Cleanup() - resourceCache, err := inmemoryCache.NewResourceCache(5 * time.Minute) + resourceCache, err := inmemoryCache.NewResourceCache(30 * time.Minute) if err != nil { setupLog.Error(err, "unable to start resource cache") os.Exit(1) From 9ca6db34a9ce0579da478c3ab05a74071a20e90e Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Fri, 10 Jul 2026 19:48:32 -0300 Subject: [PATCH 4/4] use generics for cache --- cmd/main.go | 6 +++--- internal/cache/resource_cache.go | 16 ++++++++-------- internal/cache/resource_cache_test.go | 10 +++++----- .../v1beta1/infisicalstaticsecret_controller.go | 4 ++-- .../services/infisicalstaticsecret/handler.go | 4 ++-- .../services/infisicalstaticsecret/reconciler.go | 10 +++++----- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index b250399..b3b9d3a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -276,12 +276,12 @@ func main() { } defer authCache.Cleanup() - resourceCache, err := inmemoryCache.NewResourceCache(30 * time.Minute) + projectIDCache, err := inmemoryCache.NewResourceCache[string](30 * time.Minute) if err != nil { setupLog.Error(err, "unable to start resource cache") os.Exit(1) } - defer resourceCache.Cleanup() + defer projectIDCache.Cleanup() authStrategyResolver := auth.NewAuthStrategyResolver(mgr.GetClient(), authCache, ctrl.Log, isNamespaceScoped) @@ -339,7 +339,7 @@ func main() { BaseLogger: ctrl.Log, IsNamespaceScoped: isNamespaceScoped, AuthResolver: authStrategyResolver, - ResourceCache: resourceCache, + ProjectIDCache: projectIDCache, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "InfisicalStaticSecret") os.Exit(1) diff --git a/internal/cache/resource_cache.go b/internal/cache/resource_cache.go index e731ad0..322ad47 100644 --- a/internal/cache/resource_cache.go +++ b/internal/cache/resource_cache.go @@ -7,13 +7,13 @@ import ( "github.com/dgraph-io/ristretto/v2" ) -type ResourceCache struct { - cache *ristretto.Cache[string, string] +type ResourceCache[V any] struct { + cache *ristretto.Cache[string, V] ttl time.Duration } -func NewResourceCache(ttl time.Duration) (*ResourceCache, error) { - cache, err := ristretto.NewCache(&ristretto.Config[string, string]{ +func NewResourceCache[V any](ttl time.Duration) (*ResourceCache[V], error) { + cache, err := ristretto.NewCache(&ristretto.Config[string, V]{ NumCounters: 1000, MaxCost: 1 << 20, BufferItems: 64, @@ -22,19 +22,19 @@ func NewResourceCache(ttl time.Duration) (*ResourceCache, error) { if err != nil { return nil, fmt.Errorf("failed to create resource cache: %w", err) } - return &ResourceCache{cache: cache, ttl: ttl}, nil + return &ResourceCache[V]{cache: cache, ttl: ttl}, nil } -func (c *ResourceCache) Get(key string) (string, bool) { +func (c *ResourceCache[V]) Get(key string) (V, bool) { return c.cache.Get(key) } -func (c *ResourceCache) Set(key string, value string) { +func (c *ResourceCache[V]) Set(key string, value V) { c.cache.SetWithTTL(key, value, 1, c.ttl) c.cache.Wait() } -func (c *ResourceCache) Cleanup() { +func (c *ResourceCache[V]) Cleanup() { if c.cache != nil { c.cache.Close() } diff --git a/internal/cache/resource_cache_test.go b/internal/cache/resource_cache_test.go index fb0d01c..b340304 100644 --- a/internal/cache/resource_cache_test.go +++ b/internal/cache/resource_cache_test.go @@ -12,7 +12,7 @@ import ( ) var _ = Describe("ResourceCache", func() { - var resourceCache *cache.ResourceCache + var resourceCache *cache.ResourceCache[string] AfterEach(func() { if resourceCache != nil { @@ -23,7 +23,7 @@ var _ = Describe("ResourceCache", func() { Describe("basic operations", func() { BeforeEach(func() { var err error - resourceCache, err = cache.NewResourceCache(30 * time.Second) + resourceCache, err = cache.NewResourceCache[string](30 * time.Second) Expect(err).NotTo(HaveOccurred()) }) @@ -69,7 +69,7 @@ var _ = Describe("ResourceCache", func() { Describe("TTL expiration", func() { BeforeEach(func() { var err error - resourceCache, err = cache.NewResourceCache(2 * time.Second) + resourceCache, err = cache.NewResourceCache[string](2 * time.Second) Expect(err).NotTo(HaveOccurred()) }) @@ -90,7 +90,7 @@ var _ = Describe("ResourceCache", func() { Describe("parallel access", func() { BeforeEach(func() { var err error - resourceCache, err = cache.NewResourceCache(30 * time.Second) + resourceCache, err = cache.NewResourceCache[string](30 * time.Second) Expect(err).NotTo(HaveOccurred()) }) @@ -176,7 +176,7 @@ var _ = Describe("ResourceCache", func() { Describe("cleanup", func() { It("should not panic when calling Cleanup on a valid cache", func() { var err error - resourceCache, err = cache.NewResourceCache(10 * time.Second) + resourceCache, err = cache.NewResourceCache[string](10 * time.Second) Expect(err).NotTo(HaveOccurred()) resourceCache.Set("before-cleanup", "value") diff --git a/internal/controller/v1beta1/infisicalstaticsecret_controller.go b/internal/controller/v1beta1/infisicalstaticsecret_controller.go index 878d232..5d24e5e 100644 --- a/internal/controller/v1beta1/infisicalstaticsecret_controller.go +++ b/internal/controller/v1beta1/infisicalstaticsecret_controller.go @@ -75,7 +75,7 @@ type InfisicalStaticSecretReconciler struct { Scheme *runtime.Scheme IsNamespaceScoped bool AuthResolver *auth.AuthStrategyResolver - ResourceCache *cache.ResourceCache + ProjectIDCache *cache.ResourceCache[string] SourceCh chan event.TypedGenericEvent[client.Object] } @@ -127,7 +127,7 @@ func (r *InfisicalStaticSecretReconciler) Reconcile(ctx context.Context, req ctr instantUpdates := staticSecretCRD.Spec.SyncOptions != nil && staticSecretCRD.Spec.SyncOptions.InstantUpdates - handler := infisicalstaticsecret.NewInfisicalStaticSecretHandler(r.Client, r.Scheme, r.IsNamespaceScoped, r.AuthResolver, r.ResourceCache, logger) + handler := infisicalstaticsecret.NewInfisicalStaticSecretHandler(r.Client, r.Scheme, r.IsNamespaceScoped, r.AuthResolver, r.ProjectIDCache, logger) secretsCount, err := handler.SyncSecrets(ctx, &staticSecretCRD) if err != nil { var rateLimitErr *api.TooManyRequestsError diff --git a/internal/services/infisicalstaticsecret/handler.go b/internal/services/infisicalstaticsecret/handler.go index d1d56eb..2d3ef7f 100644 --- a/internal/services/infisicalstaticsecret/handler.go +++ b/internal/services/infisicalstaticsecret/handler.go @@ -26,7 +26,7 @@ func NewInfisicalStaticSecretHandler( scheme *runtime.Scheme, isNamespaceScoped bool, authResolver *auth.AuthStrategyResolver, - resourceCache *cache.ResourceCache, + projectIDCache *cache.ResourceCache[string], logger logr.Logger, ) *InfisicalStaticSecretHandler { return &InfisicalStaticSecretHandler{ @@ -40,7 +40,7 @@ func NewInfisicalStaticSecretHandler( Scheme: scheme, IsNamespaceScoped: isNamespaceScoped, authResolver: authResolver, - resourceCache: resourceCache, + projectIDCache: projectIDCache, logger: logger, }, } diff --git a/internal/services/infisicalstaticsecret/reconciler.go b/internal/services/infisicalstaticsecret/reconciler.go index b9b732a..b6449a7 100644 --- a/internal/services/infisicalstaticsecret/reconciler.go +++ b/internal/services/infisicalstaticsecret/reconciler.go @@ -144,14 +144,14 @@ type InfisicalStaticSecretReconciler struct { Scheme *runtime.Scheme IsNamespaceScoped bool authResolver *auth.AuthStrategyResolver - resourceCache *cache.ResourceCache + projectIDCache *cache.ResourceCache[string] logger logr.Logger } func (r *InfisicalStaticSecretReconciler) getProjectIDBySlug(restClient *resty.Client, authRef v1beta1.NamespacedName, slug string) (string, error) { cacheKey := cache.ProjectBySlugCacheKey(authRef.Namespace, authRef.Name, slug) - if r.resourceCache != nil { - if cached, ok := r.resourceCache.Get(cacheKey); ok { + if r.projectIDCache != nil { + if cached, ok := r.projectIDCache.Get(cacheKey); ok { return cached, nil } } @@ -161,8 +161,8 @@ func (r *InfisicalStaticSecretReconciler) getProjectIDBySlug(restClient *resty.C return "", err } - if r.resourceCache != nil { - r.resourceCache.Set(cacheKey, project.ID) + if r.projectIDCache != nil { + r.projectIDCache.Set(cacheKey, project.ID) } return project.ID, nil