99 "path/filepath"
1010 "runtime"
1111 "strings"
12- "sync"
1312 "time"
1413
1514 "github.com/jfrog/build-info-go/entities"
@@ -24,16 +23,15 @@ const (
2423
2524// HelmFlexPack implements the FlexPackManager interface for Helm package manager
2625type HelmFlexPack struct {
27- config HelmConfig
28- dependencies []DependencyInfo
29- dependencyGraph map [string ][]string
30- chartData * HelmChartYAML
31- lockData * HelmChartLock
32- cliPath string
33- cacheIndex map [string ]string
34- cacheIndexBuilt bool
35- repoAliases map [string ]string // Maps repository alias (without @) to URL
36- mu sync.RWMutex
26+ config HelmConfig
27+ dependencies []DependencyInfo
28+ dependencyGraph map [string ][]string
29+ chartData * HelmChartYAML
30+ lockData * HelmChartLock
31+ cliPath string
32+ cacheIndex map [string ]string
33+ repoAliases map [string ]string
34+ cacheDirectories []string
3735}
3836
3937// HelmConfig represents configuration for Helm FlexPack
@@ -125,8 +123,14 @@ func NewHelmFlexPack(config HelmConfig) (*HelmFlexPack, error) {
125123 return nil , fmt .Errorf ("failed to load Chart.yaml: %w" , err )
126124 }
127125
128- // Load Chart.lock if it exists (optional)
129- _ = hf .loadChartLock () // Ignore error if lock file doesn't exist
126+ // Load Chart.lock
127+ if err := hf .loadChartLock (); err != nil {
128+ return nil , fmt .Errorf ("failed to load Chart.lock: %w" , err )
129+ }
130+
131+ // Load cache directories and build cache index during initialization
132+ hf .cacheDirectories = hf .loadCacheDirectories ()
133+ hf .buildCacheIndex ()
130134
131135 return hf , nil
132136}
@@ -177,9 +181,6 @@ func (hf *HelmFlexPack) loadChartLock() error {
177181
178182 data , err := os .ReadFile (lockPath )
179183 if err != nil {
180- if os .IsNotExist (err ) {
181- return nil // Chart.lock is optional
182- }
183184 return fmt .Errorf ("failed to read Chart.lock: %w" , err )
184185 }
185186
@@ -193,115 +194,16 @@ func (hf *HelmFlexPack) loadChartLock() error {
193194
194195// getDependencies returns all dependencies (lazy loading)
195196func (hf * HelmFlexPack ) getDependencies () []DependencyInfo {
196- hf .mu .RLock ()
197197 if len (hf .dependencies ) > 0 {
198198 deps := hf .dependencies
199- hf .mu .RUnlock ()
200199 return deps
201200 }
202- hf .mu .RUnlock ()
203- hf .mu .Lock ()
204- defer hf .mu .Unlock ()
205- // Double-check after acquiring write lock
206- if len (hf .dependencies ) > 0 {
207- return hf .dependencies
208- }
209- // Resolve dependencies using hybrid approach
210- if err := hf .resolveDependencies (); err != nil {
211- // Fallback to file-based parsing if helm dependency list fails
212- hf .parseDependenciesFromChartYamlAndLockfile ()
213- }
201+ // Parse dependencies directly from Chart.yaml and Chart.lock
202+ hf .parseDependenciesFromChartYamlAndLockfile ()
214203 return hf .dependencies
215204}
216205
217- // resolveDependencies uses CLI to resolve dependencies (primary strategy)
218- func (hf * HelmFlexPack ) resolveDependencies () error {
219- // Try using helm dependency list command
220- cmd := exec .Command (hf .cliPath , "dependency" , "list" )
221- cmd .Dir = hf .config .WorkingDirectory
222- output , err := cmd .CombinedOutput ()
223- if err != nil {
224- outputStr := strings .TrimSpace (string (output ))
225- if outputStr != "" {
226- return fmt .Errorf ("helm dependency list failed: %w\n Output: %s" , err , outputStr )
227- }
228- return fmt .Errorf ("helm dependency list failed: %w" , err )
229- }
230- return hf .parseHelmDependencyList (string (output ))
231- }
232-
233- // parseHelmDependencyList parses output from `helm dependency list`
234- // Only includes dependencies that are declared in the current chart's Chart.yaml
235- func (hf * HelmFlexPack ) parseHelmDependencyList (output string ) error {
236- scanner := bufio .NewScanner (strings .NewReader (output ))
237- headerSkipped := false
238- // Build a set of valid dependency names from Chart.yaml to filter out dependencies from other directories
239- validDependencyNames := hf .getValidDependencyNames ()
240- for scanner .Scan () {
241- line := strings .TrimSpace (scanner .Text ())
242- if line == "" {
243- continue
244- }
245- if ! headerSkipped && hf .isHeaderLine (line ) {
246- headerSkipped = true
247- continue
248- }
249- headerSkipped = true
250- if dep := hf .parseDependencyLine (line ); dep != nil {
251- // Only include dependencies that are declared in the current chart's Chart.yaml
252- if ! hf .isValidDependency (dep .Name , validDependencyNames ) {
253- continue
254- }
255- // Resolve repository alias if needed
256- hf .resolveRepositoryAlias (dep )
257- hf .dependencies = append (hf .dependencies , * dep )
258- }
259- }
260- return scanner .Err ()
261- }
262-
263- // getValidDependencyNames returns a set of dependency names declared in Chart.yaml
264- func (hf * HelmFlexPack ) getValidDependencyNames () map [string ]bool {
265- validNames := make (map [string ]bool )
266- if hf .chartData == nil {
267- return validNames
268- }
269- for _ , dep := range hf .chartData .Dependencies {
270- validNames [dep .Name ] = true
271- }
272- return validNames
273- }
274-
275- // isValidDependency checks if a dependency name is declared in the current chart's Chart.yaml
276- func (hf * HelmFlexPack ) isValidDependency (depName string , validNames map [string ]bool ) bool {
277- // Only include dependencies that are explicitly declared in Chart.yaml
278- // This filters out dependencies from other directories that helm dependency list might return
279- return validNames [depName ]
280- }
281-
282- // isHeaderLine checks if a line is the header line
283- func (hf * HelmFlexPack ) isHeaderLine (line string ) bool {
284- return strings .Contains (line , "NAME" )
285- }
286-
287- // parseDependencyLine parses a single dependency line from helm dependency list output
288- // Format: NAME VERSION REPOSITORY STATUS
289- func (hf * HelmFlexPack ) parseDependencyLine (line string ) * DependencyInfo {
290- fields := strings .Fields (line )
291- if len (fields ) < 3 {
292- return nil
293- }
294- return & DependencyInfo {
295- Name : fields [0 ],
296- Version : fields [1 ],
297- Repository : fields [2 ],
298- Type : "helm" ,
299- ID : fmt .Sprintf ("%s:%s" , fields [0 ], fields [1 ]),
300- }
301- }
302-
303206// getHelmRepoList gets and parses the output of 'helm repo list' command
304- // Returns a map of repository alias (without @) to URL
305207func (hf * HelmFlexPack ) getHelmRepoList () (map [string ]string , error ) {
306208 cmd := exec .Command (hf .cliPath , "repo" , "list" )
307209 output , err := cmd .CombinedOutput ()
@@ -356,18 +258,14 @@ func (hf *HelmFlexPack) resolveRepositoryAlias(dep *DependencyInfo) {
356258 // Extract alias name (remove @ prefix)
357259 alias := strings .TrimPrefix (dep .Repository , "@" )
358260
359- // Load repo list if not already loaded
360- hf .mu .Lock ()
361261 if len (hf .repoAliases ) == 0 {
362262 repoMap , err := hf .getHelmRepoList ()
363263 if err != nil {
364264 log .Warn (fmt .Sprintf ("Failed to get helm repo list: %v" , err ))
365- hf .mu .Unlock ()
366265 return
367266 }
368267 hf .repoAliases = repoMap
369268 }
370- hf .mu .Unlock ()
371269
372270 // Resolve alias to URL
373271 if url , found := hf .repoAliases [alias ]; found {
@@ -445,7 +343,6 @@ func (hf *HelmFlexPack) calculateChecksumWithFallback(dep DependencyInfo) map[st
445343 }
446344
447345 // Strategy 2: Try to find dependency chart in charts/ directory
448- // When helm dependency build/update is run, dependencies are stored in charts/ subdirectory
449346 if chartFile := hf .findDependencyInChartsDir (dep .Name , dep .Version ); chartFile != "" {
450347 if Sha1 , Sha256 , Md5 , err := hf .calculateFileChecksum (chartFile ); err == nil {
451348 checksumMap ["sha1" ] = Sha1
@@ -464,11 +361,6 @@ func (hf *HelmFlexPack) calculateChecksumWithFallback(dep DependencyInfo) map[st
464361
465362// findChartFile searches for chart archive in Helm cache
466363func (hf * HelmFlexPack ) findChartFile (name , version string ) string {
467- // Build cache index if not already built
468- if ! hf .cacheIndexBuilt {
469- hf .buildCacheIndex ()
470- hf .cacheIndexBuilt = true
471- }
472364 key := fmt .Sprintf ("%s-%s" , name , version )
473365 if path , found := hf .cacheIndex [key ]; found {
474366 return path
@@ -493,12 +385,8 @@ func (hf *HelmFlexPack) findDependencyInChartsDir(name, version string) string {
493385}
494386
495387// buildCacheIndex builds an index of cached chart files
496- // Validates cache directories to prevent path traversal attacks
497388func (hf * HelmFlexPack ) buildCacheIndex () {
498- cacheDirs := hf .getCacheDirectories ()
499- for _ , cacheDir := range cacheDirs {
500- // Additional validation at point of use to prevent path traversal
501- // Even though getCacheDirectories() sanitizes paths, we validate again here
389+ for _ , cacheDir := range hf .cacheDirectories {
502390 cleanDir := filepath .Clean (cacheDir )
503391 if cleanDir == "" || cleanDir == "." || cleanDir == ".." {
504392 continue
@@ -566,8 +454,7 @@ func (hf *HelmFlexPack) buildCacheIndex() {
566454
567455// recursiveSearchCache performs recursive search for chart files
568456func (hf * HelmFlexPack ) searchCache (name , version string ) string {
569- cacheDirs := hf .getCacheDirectories ()
570- for _ , cacheDir := range cacheDirs {
457+ for _ , cacheDir := range hf .cacheDirectories {
571458 if foundPath := hf .searchCacheDirectory (cacheDir , name , version ); foundPath != "" {
572459 return foundPath
573460 }
@@ -653,13 +540,11 @@ func (hf *HelmFlexPack) findFileInDirectory(dir, pattern string) string {
653540 return foundPath
654541}
655542
656- // getCacheDirectories returns Helm cache directory paths
657- // Validates and sanitizes paths to prevent path traversal attacks
658- func (hf * HelmFlexPack ) getCacheDirectories () []string {
543+ // loadCacheDirectories loads and returns Helm cache directory paths
544+ func (hf * HelmFlexPack ) loadCacheDirectories () []string {
659545 var paths []string
660546
661547 // Environment variable override (highest priority)
662- // Validate and sanitize environment variable path to prevent path traversal
663548 if envPath := os .Getenv ("HELM_REPOSITORY_CACHE" ); envPath != "" {
664549 // Sanitize the path
665550 cleanPath := filepath .Clean (envPath )
@@ -723,6 +608,7 @@ func (hf *HelmFlexPack) getCacheDirectories() []string {
723608 existingPaths = append (existingPaths , absPath )
724609 }
725610 }
611+
726612 return existingPaths
727613}
728614
0 commit comments