@@ -18,15 +18,90 @@ package k8s
1818
1919import (
2020 "context"
21+ "crypto/sha1"
22+ "encoding/hex"
2123 "fmt"
24+ "sync"
25+ "time"
2226
27+ corev1 "k8s.io/api/core/v1"
2328 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+ "k8s.io/client-go/informers"
30+ "k8s.io/client-go/tools/cache"
2431)
2532
33+ type cachedSecret struct {
34+ data map [string ]string
35+ }
36+
37+ var (
38+ secretCache = make (map [string ]cachedSecret )
39+ cacheLock sync.RWMutex
40+ )
41+
42+ // cacheKey is a helper to create sha1 hash based on a secret's name
43+ // and namespace. The hash is then used as an index for secretCache
44+ func cacheKey (ns , name string ) string {
45+ h := sha1 .New ()
46+ h .Write ([]byte (ns + "/" + name ))
47+
48+ return hex .EncodeToString (h .Sum (nil ))
49+ }
50+
51+ // StartSecretWatcher handles events on Secrets informer and updates
52+ // or invalidates the secretCache accordingly.
53+ // It should preferably be started early in the bootstraping process.
54+ func StartSecretWatcher (stopCh chan struct {}) error {
55+ client , err := NewK8sClient ()
56+ if err != nil {
57+ return fmt .Errorf ("failed to connect to Kubernetes: %w" , err )
58+ }
59+
60+ factory := informers .NewSharedInformerFactoryWithOptions (
61+ client ,
62+ time .Minute * 10 , // FIXME: We could optionally restrict this to specific NS.
63+ )
64+
65+ informer := factory .Core ().V1 ().Secrets ().Informer ()
66+ informer .AddEventHandler (cache.ResourceEventHandlerFuncs {
67+ UpdateFunc : func (oldObj , newObj any ) {
68+ newSecret := newObj .(* corev1.Secret )
69+ key := cacheKey (newSecret .Namespace , newSecret .Name )
70+
71+ cacheLock .Lock ()
72+ delete (secretCache , key )
73+ cacheLock .Unlock ()
74+ },
75+ DeleteFunc : func (obj any ) {
76+ secret := obj .(* corev1.Secret )
77+ key := cacheKey (secret .Namespace , secret .Name )
78+
79+ cacheLock .Lock ()
80+ delete (secretCache , key )
81+ cacheLock .Unlock ()
82+ },
83+ })
84+
85+ go informer .Run (stopCh )
86+ return nil
87+ }
88+
2689// GetSecret retrieves a Kubernetes Secret by its name and namespace.
2790// It returns a map of key-value pairs contained in the Secret's data.
2891// If the Secret does not exist or cannot be accessed, it returns an error.
2992func GetSecret (secretName , secretNamespace string ) (map [string ]string , error ) {
93+ key := cacheKey (secretNamespace , secretName )
94+
95+ // Return from cache if present
96+ cacheLock .RLock ()
97+ if entry , found := secretCache [key ]; found {
98+ cacheLock .RUnlock ()
99+
100+ return entry .data , nil
101+ }
102+ cacheLock .RUnlock ()
103+
104+ // The entry was not found in cache, fetch it from the API server
30105 client , err := NewK8sClient ()
31106 if err != nil {
32107 return nil , fmt .Errorf ("failed to connect to Kubernetes: %w" , err )
@@ -37,10 +112,17 @@ func GetSecret(secretName, secretNamespace string) (map[string]string, error) {
37112 return nil , fmt .Errorf ("failed to get secret %q in namespace %q information: %w" , secretName , secretNamespace , err )
38113 }
39114
40- secretData := map [string ]string {}
115+ secretData := make ( map [string ]string , len ( secret . Data ))
41116 for key , value := range secret .Data {
42117 secretData [key ] = string (value )
43118 }
44119
120+ // Cache the fetched secret
121+ cacheLock .Lock ()
122+ secretCache [key ] = cachedSecret {
123+ data : secretData ,
124+ }
125+ cacheLock .Unlock ()
126+
45127 return secretData , nil
46128}
0 commit comments