@@ -30,6 +30,7 @@ import (
30
30
"sort"
31
31
"strings"
32
32
"sync"
33
+ "time"
33
34
34
35
"github.com/Masterminds/semver/v3"
35
36
"helm.sh/helm/v3/pkg/getter"
@@ -38,6 +39,7 @@ import (
38
39
39
40
"github.com/fluxcd/pkg/version"
40
41
42
+ "github.com/fluxcd/source-controller/internal/cache"
41
43
"github.com/fluxcd/source-controller/internal/helm"
42
44
"github.com/fluxcd/source-controller/internal/transport"
43
45
)
@@ -70,13 +72,52 @@ type ChartRepository struct {
70
72
tlsConfig * tls.Config
71
73
72
74
* sync.RWMutex
75
+
76
+ cacheInfo
77
+ }
78
+
79
+ type cacheInfo struct {
80
+ // In memory cache of the index.yaml file.
81
+ IndexCache * cache.Cache
82
+ // IndexKey is the cache key for the index.yaml file.
83
+ IndexKey string
84
+ // IndexTTL is the cache TTL for the index.yaml file.
85
+ IndexTTL time.Duration
86
+ // RecordIndexCacheMetric records the cache hit/miss metrics for the index.yaml file.
87
+ RecordIndexCacheMetric RecordMetricsFunc
88
+ }
89
+
90
+ // ChartRepositoryOptions is a function that can be passed to NewChartRepository
91
+ // to configure a ChartRepository.
92
+ type ChartRepositoryOptions func (* ChartRepository ) error
93
+
94
+ // RecordMetricsFunc is a function that records metrics.
95
+ type RecordMetricsFunc func (event string )
96
+
97
+ // WithMemoryCache returns a ChartRepositoryOptions that will enable the
98
+ // ChartRepository to cache the index.yaml file in memory.
99
+ // The cache key have to be safe in multi-tenancy environments,
100
+ // as otherwise it could be used as a vector to bypass the helm repository's authentication.
101
+ func WithMemoryCache (key string , c * cache.Cache , ttl time.Duration , rec RecordMetricsFunc ) ChartRepositoryOptions {
102
+ return func (r * ChartRepository ) error {
103
+ if c != nil {
104
+ if key == "" {
105
+ return errors .New ("cache key cannot be empty" )
106
+ }
107
+ }
108
+ r .IndexCache = c
109
+ r .IndexKey = key
110
+ r .IndexTTL = ttl
111
+ r .RecordIndexCacheMetric = rec
112
+ return nil
113
+ }
73
114
}
74
115
75
116
// NewChartRepository constructs and returns a new ChartRepository with
76
117
// the ChartRepository.Client configured to the getter.Getter for the
77
118
// repository URL scheme. It returns an error on URL parsing failures,
78
119
// or if there is no getter available for the scheme.
79
- func NewChartRepository (repositoryURL , cachePath string , providers getter.Providers , tlsConfig * tls.Config , opts []getter.Option ) (* ChartRepository , error ) {
120
+ func NewChartRepository (repositoryURL , cachePath string , providers getter.Providers , tlsConfig * tls.Config , opts []getter.Option , chartRepoOpts ... ChartRepositoryOptions ) (* ChartRepository , error ) {
80
121
u , err := url .Parse (repositoryURL )
81
122
if err != nil {
82
123
return nil , err
@@ -92,6 +133,13 @@ func NewChartRepository(repositoryURL, cachePath string, providers getter.Provid
92
133
r .Client = c
93
134
r .Options = opts
94
135
r .tlsConfig = tlsConfig
136
+
137
+ for _ , opt := range chartRepoOpts {
138
+ if err := opt (r ); err != nil {
139
+ return nil , err
140
+ }
141
+ }
142
+
95
143
return r , nil
96
144
}
97
145
@@ -292,14 +340,39 @@ func (r *ChartRepository) CacheIndex() (string, error) {
292
340
return hex .EncodeToString (h .Sum (nil )), nil
293
341
}
294
342
295
- // StrategicallyLoadIndex lazy-loads the Index from CachePath using
296
- // LoadFromCache if it does not HasIndex.
343
+ // CacheIndexInMemory attempts to cache the index in memory.
344
+ // It returns an error if it fails.
345
+ // The cache key have to be safe in multi-tenancy environments,
346
+ // as otherwise it could be used as a vector to bypass the helm repository's authentication.
347
+ func (r * ChartRepository ) CacheIndexInMemory () error {
348
+ // Cache the index if it was successfully retrieved
349
+ // and the chart was successfully built
350
+ if r .IndexCache != nil && r .Index != nil {
351
+ err := r .IndexCache .Set (r .IndexKey , r .Index , r .IndexTTL )
352
+ if err != nil {
353
+ return err
354
+ }
355
+ }
356
+
357
+ return nil
358
+ }
359
+
360
+ // StrategicallyLoadIndex lazy-loads the Index
361
+ // first from Indexcache,
362
+ // then from CachePath using oadFromCache if it does not HasIndex.
297
363
// If not HasCacheFile, a cache attempt is made using CacheIndex
298
364
// before continuing to load.
299
365
func (r * ChartRepository ) StrategicallyLoadIndex () (err error ) {
300
366
if r .HasIndex () {
301
367
return
302
368
}
369
+
370
+ if r .IndexCache != nil {
371
+ if found := r .LoadFromMemCache (); found {
372
+ return
373
+ }
374
+ }
375
+
303
376
if ! r .HasCacheFile () {
304
377
if _ , err = r .CacheIndex (); err != nil {
305
378
err = fmt .Errorf ("failed to strategically load index: %w" , err )
@@ -313,6 +386,28 @@ func (r *ChartRepository) StrategicallyLoadIndex() (err error) {
313
386
return
314
387
}
315
388
389
+ // LoadFromMemCache attempts to load the Index from the provided cache.
390
+ // It returns true if the Index was found in the cache, and false otherwise.
391
+ func (r * ChartRepository ) LoadFromMemCache () bool {
392
+ if index , found := r .IndexCache .Get (r .IndexKey ); found {
393
+ r .Lock ()
394
+ r .Index = index .(* repo.IndexFile )
395
+ r .Unlock ()
396
+
397
+ // record the cache hit
398
+ if r .RecordIndexCacheMetric != nil {
399
+ r .RecordIndexCacheMetric (cache .CacheEventTypeHit )
400
+ }
401
+ return true
402
+ }
403
+
404
+ // record the cache miss
405
+ if r .RecordIndexCacheMetric != nil {
406
+ r .RecordIndexCacheMetric (cache .CacheEventTypeMiss )
407
+ }
408
+ return false
409
+ }
410
+
316
411
// LoadFromCache attempts to load the Index from the configured CachePath.
317
412
// It returns an error if no CachePath is set, or if the load failed.
318
413
func (r * ChartRepository ) LoadFromCache () error {
@@ -375,6 +470,14 @@ func (r *ChartRepository) Unload() {
375
470
r .Index = nil
376
471
}
377
472
473
+ // SetMemCache sets the cache to use for this repository.
474
+ func (r * ChartRepository ) SetMemCache (key string , c * cache.Cache , ttl time.Duration , rec RecordMetricsFunc ) {
475
+ r .IndexKey = key
476
+ r .IndexCache = c
477
+ r .IndexTTL = ttl
478
+ r .RecordIndexCacheMetric = rec
479
+ }
480
+
378
481
// RemoveCache removes the CachePath if Cached.
379
482
func (r * ChartRepository ) RemoveCache () error {
380
483
if r == nil {
0 commit comments