Implement chart fetch deduplication mechanism
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									04cf785e0a
								
							
						
					
					
						commit
						f187d09987
					
				|  | @ -1414,6 +1414,12 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | ||||||
| 					//    only on helm v3.
 | 					//    only on helm v3.
 | ||||||
| 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | ||||||
| 					//    WITHOUT the version flags.
 | 					//    WITHOUT the version flags.
 | ||||||
|  | 				} else { | ||||||
|  | 					// Check global chart cache first for non-OCI charts
 | ||||||
|  | 					cacheKey := st.getChartCacheKey(release) | ||||||
|  | 					if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) { | ||||||
|  | 						st.logger.Debugf("Chart %s:%s already downloaded, using cached version at %s", chartName, release.Version, cachedPath) | ||||||
|  | 						chartPath = cachedPath | ||||||
| 					} else { | 					} else { | ||||||
| 						chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate) | 						chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
|  | @ -1439,6 +1445,10 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | ||||||
| 						if err == nil { | 						if err == nil { | ||||||
| 							chartPath = filepath.Dir(fullChartPath) | 							chartPath = filepath.Dir(fullChartPath) | ||||||
| 						} | 						} | ||||||
|  | 
 | ||||||
|  | 						// Add to global chart cache
 | ||||||
|  | 						st.addToChartCache(cacheKey, chartPath) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				results <- &chartPrepareResult{ | 				results <- &chartPrepareResult{ | ||||||
|  | @ -4040,9 +4050,47 @@ func (st *HelmState) Reverse() { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Chart cache for both OCI and non-OCI charts to avoid duplicate downloads
 | ||||||
|  | type ChartCacheKey struct { | ||||||
|  | 	Chart   string | ||||||
|  | 	Version string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var downloadedCharts = make(map[ChartCacheKey]string) // key -> chart path
 | ||||||
|  | var downloadedChartsMutex sync.RWMutex | ||||||
|  | 
 | ||||||
|  | // Legacy OCI-specific cache (kept for backward compatibility)
 | ||||||
| var downloadedOCICharts = make(map[string]bool) | var downloadedOCICharts = make(map[string]bool) | ||||||
| var downloadedOCIMutex sync.RWMutex | var downloadedOCIMutex sync.RWMutex | ||||||
| 
 | 
 | ||||||
|  | // getChartCacheKey creates a cache key for a chart and version
 | ||||||
|  | func (st *HelmState) getChartCacheKey(release *ReleaseSpec) ChartCacheKey { | ||||||
|  | 	version := release.Version | ||||||
|  | 	if version == "" { | ||||||
|  | 		// Use empty string for latest version
 | ||||||
|  | 		version = "" | ||||||
|  | 	} | ||||||
|  | 	return ChartCacheKey{ | ||||||
|  | 		Chart:   release.Chart, | ||||||
|  | 		Version: version, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // checkChartCache checks if a chart is already downloaded and returns its path
 | ||||||
|  | func (st *HelmState) checkChartCache(key ChartCacheKey) (string, bool) { | ||||||
|  | 	downloadedChartsMutex.RLock() | ||||||
|  | 	defer downloadedChartsMutex.RUnlock() | ||||||
|  | 	path, exists := downloadedCharts[key] | ||||||
|  | 	return path, exists | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // addToChartCache adds a chart to the cache
 | ||||||
|  | func (st *HelmState) addToChartCache(key ChartCacheKey, path string) { | ||||||
|  | 	downloadedChartsMutex.Lock() | ||||||
|  | 	defer downloadedChartsMutex.Unlock() | ||||||
|  | 	downloadedCharts[key] = path | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, opts ChartPrepareOptions) (*string, error) { | func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, opts ChartPrepareOptions) (*string, error) { | ||||||
| 	qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm) | 	qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -4053,6 +4101,13 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Check global chart cache first
 | ||||||
|  | 	cacheKey := st.getChartCacheKey(release) | ||||||
|  | 	if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) { | ||||||
|  | 		st.logger.Debugf("OCI chart %s:%s already downloaded, using cached version at %s", chartName, chartVersion, cachedPath) | ||||||
|  | 		return &cachedPath, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if opts.OutputDirTemplate == "" { | 	if opts.OutputDirTemplate == "" { | ||||||
| 		tempDir = remote.CacheDir() | 		tempDir = remote.CacheDir() | ||||||
| 	} | 	} | ||||||
|  | @ -4085,6 +4140,7 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 	if st.fs.DirectoryExistsAt(chartPath) { | 	if st.fs.DirectoryExistsAt(chartPath) { | ||||||
| 		st.logger.Debugf("chart already exists at %s", chartPath) | 		st.logger.Debugf("chart already exists at %s", chartPath) | ||||||
| 	} else { | 	} else { | ||||||
|  | 		st.logger.Infof("Fetching %s", chartName) | ||||||
| 		flags := st.chartOCIFlags(release) | 		flags := st.chartOCIFlags(release) | ||||||
| 		flags = st.appendVerifyFlags(flags, release) | 		flags = st.appendVerifyFlags(flags, release) | ||||||
| 		flags = st.appendKeyringFlags(flags, release) | 		flags = st.appendKeyringFlags(flags, release) | ||||||
|  | @ -4113,6 +4169,9 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 
 | 
 | ||||||
| 	chartPath = filepath.Dir(fullChartPath) | 	chartPath = filepath.Dir(fullChartPath) | ||||||
| 
 | 
 | ||||||
|  | 	// Add to global chart cache
 | ||||||
|  | 	st.addToChartCache(cacheKey, chartPath) | ||||||
|  | 
 | ||||||
| 	return &chartPath, nil | 	return &chartPath, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4638,3 +4638,58 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) { | ||||||
| 		require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) | 		require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestChartCacheKey(t *testing.T) { | ||||||
|  | st := &HelmState{} | ||||||
|  | 
 | ||||||
|  | // Test case 1: release with version
 | ||||||
|  | release1 := &ReleaseSpec{ | ||||||
|  | Chart:   "stable/nginx", | ||||||
|  | Version: "1.2.3", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | key1 := st.getChartCacheKey(release1) | ||||||
|  | expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"} | ||||||
|  | 
 | ||||||
|  | if key1 != expected1 { | ||||||
|  | t.Errorf("Expected %+v, got %+v", expected1, key1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Test case 2: release without version
 | ||||||
|  | release2 := &ReleaseSpec{ | ||||||
|  | Chart: "stable/nginx", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | key2 := st.getChartCacheKey(release2) | ||||||
|  | expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""} | ||||||
|  | 
 | ||||||
|  | if key2 != expected2 { | ||||||
|  | t.Errorf("Expected %+v, got %+v", expected2, key2) | ||||||
|  | } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestChartCache(t *testing.T) { | ||||||
|  | st := &HelmState{} | ||||||
|  | 
 | ||||||
|  | // Create a test key
 | ||||||
|  | key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"} | ||||||
|  | path := "/tmp/test-chart" | ||||||
|  | 
 | ||||||
|  | // Initially, chart should not be in cache
 | ||||||
|  | _, exists := st.checkChartCache(key) | ||||||
|  | if exists { | ||||||
|  | t.Error("Chart should not be in cache initially") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add to cache
 | ||||||
|  | st.addToChartCache(key, path) | ||||||
|  | 
 | ||||||
|  | // Now chart should be in cache
 | ||||||
|  | cachedPath, exists := st.checkChartCache(key) | ||||||
|  | if !exists { | ||||||
|  | t.Error("Chart should be in cache after adding") | ||||||
|  | } | ||||||
|  | if cachedPath != path { | ||||||
|  | t.Errorf("Expected path %s, got %s", path, cachedPath) | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue