Skip to content

Commit 247d2e8

Browse files
Merge pull request #14 from Checkmarx/miryamFoifer/LimitedHelmContext
Support Limited Context Of Helm (AST-106346)
2 parents 70ab2cf + 5164803 commit 247d2e8

File tree

3 files changed

+325
-8
lines changed

3 files changed

+325
-8
lines changed

pkg/imagesExtractor/files_extractor.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
type ImagesExtractor interface {
1616
ExtractAndMergeImagesFromFiles(files types.FileImages, images []types.ImageModel,
1717
settingsFiles map[string]map[string]string) ([]types.ImageModel, error)
18-
ExtractFiles(scanPath string) (types.FileImages, map[string]map[string]string, string, error)
18+
ExtractFiles(scanPath string, isFullHelmDirectory ...bool) (types.FileImages, map[string]map[string]string, string, error)
1919
SaveObjectToFile(folderPath string, obj interface{}) error
2020
ExtractAndMergeImagesFromFilesWithLineInfo(files types.FileImages, images []types.ImageModel, settingsFiles map[string]map[string]string) ([]types.ImageModel, error)
2121
}
@@ -52,7 +52,13 @@ func (ie *imagesExtractor) ExtractAndMergeImagesFromFiles(files types.FileImages
5252
return imagesFromFiles, nil
5353
}
5454

55-
func (ie *imagesExtractor) ExtractFiles(scanPath string) (types.FileImages, map[string]map[string]string, string, error) {
55+
func (ie *imagesExtractor) ExtractFiles(scanPath string, isFullHelmDirectory ...bool) (types.FileImages, map[string]map[string]string, string, error) {
56+
// Default to true (current behavior) if not provided
57+
fullHelmDir := true
58+
if len(isFullHelmDirectory) > 0 {
59+
fullHelmDir = isFullHelmDirectory[0]
60+
}
61+
5662
filesPath, err := extractCompressedPath(scanPath)
5763
if err != nil {
5864
log.Err(err).Msgf("Could not extract compressed folder")
@@ -95,13 +101,24 @@ func (ie *imagesExtractor) ExtractFiles(scanPath string) (types.FileImages, map[
95101
log.Warn().Msgf("Could not extract docker or docker compose files: %s", err.Error())
96102
}
97103

98-
helmCharts, err := findHelmCharts(filesPath)
99-
if err != nil {
100-
log.Warn().Msgf("Could not extract helm charts: %s", err.Error())
101-
}
102-
if len(helmCharts) > 0 {
103-
f.Helm = helmCharts
104+
if fullHelmDir {
105+
helmCharts, err := findHelmCharts(filesPath)
106+
if err != nil {
107+
log.Warn().Msgf("Could not extract helm charts: %s", err.Error())
108+
}
109+
if len(helmCharts) > 0 {
110+
f.Helm = helmCharts
111+
}
112+
} else {
113+
helmCharts, err := findHelmFilesInDirectory(scanPath)
114+
if err != nil {
115+
log.Warn().Msgf("Could not validate helm file: %s", err.Error())
116+
}
117+
if len(helmCharts) > 0 {
118+
f.Helm = helmCharts
119+
}
104120
}
121+
105122
printFilePaths(f.Dockerfile, "Successfully found dockerfiles")
106123
printFilePaths(f.DockerCompose, "Successfully found docker compose files")
107124

pkg/imagesExtractor/files_extractor_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,144 @@ func TestDockerComposeExtractorWithLineNumbersAndIndices(t *testing.T) {
473473

474474
t.Logf("Image '%s' found at line %d, indices [%d:%d]", img.Name, loc.Line, loc.StartIndex, loc.EndIndex)
475475
}
476+
477+
// TestExtractFilesWithFullHelmDirectoryFalse tests the ExtractFiles method when isFullHelmDirectory is false
478+
func TestExtractFilesWithFullHelmDirectoryFalse(t *testing.T) {
479+
extractor := NewImagesExtractor()
480+
481+
scenarios := []struct {
482+
Name string
483+
InputPath string
484+
ShouldHaveHelm bool
485+
ExpectedErrString string
486+
}{
487+
{
488+
Name: "ValidHelmDirectoryWithFalseFlag",
489+
InputPath: "../../test_files/helm-testcases",
490+
ShouldHaveHelm: true,
491+
ExpectedErrString: "",
492+
},
493+
{
494+
Name: "ValidHelmDirectoryWithTemplatesSubdirectory",
495+
InputPath: "../../test_files/helm-testcases/templates",
496+
ShouldHaveHelm: true,
497+
ExpectedErrString: "",
498+
},
499+
{
500+
Name: "NonHelmDirectoryWithFalseFlag",
501+
InputPath: "../../test_files/imageExtraction/dockerfiles",
502+
ShouldHaveHelm: false,
503+
ExpectedErrString: "",
504+
},
505+
}
506+
507+
// Run test scenarios
508+
for _, scenario := range scenarios {
509+
t.Run(scenario.Name, func(t *testing.T) {
510+
// Run the function with isFullHelmDirectory = false
511+
files, _, _, err := extractor.ExtractFiles(scenario.InputPath, false)
512+
513+
// Check for errors
514+
if scenario.ExpectedErrString != "" {
515+
if err == nil || !strings.Contains(err.Error(), scenario.ExpectedErrString) {
516+
t.Errorf("Expected error containing '%s' but got '%v'", scenario.ExpectedErrString, err)
517+
}
518+
} else {
519+
// Check that Helm charts are found when expected
520+
if scenario.ShouldHaveHelm {
521+
if len(files.Helm) == 0 {
522+
t.Errorf("Expected Helm charts to be found for scenario '%s'", scenario.Name)
523+
} else {
524+
// Verify basic structure
525+
chart := files.Helm[0]
526+
if chart.Directory != "../../test_files/helm-testcases" && chart.Directory != "..\\..\\test_files\\helm-testcases" {
527+
t.Errorf("Expected directory '../../test_files/helm-testcases', got '%s'", chart.Directory)
528+
}
529+
if chart.ValuesFile != "values-extra.yaml" {
530+
t.Errorf("Expected values file 'values-extra.yaml', got '%s'", chart.ValuesFile)
531+
}
532+
if len(chart.TemplateFiles) == 0 {
533+
t.Errorf("Expected template files to be found")
534+
}
535+
}
536+
} else {
537+
if len(files.Helm) > 0 {
538+
t.Errorf("Expected no Helm charts for scenario '%s', but found %d", scenario.Name, len(files.Helm))
539+
}
540+
}
541+
}
542+
})
543+
}
544+
}
545+
546+
// TestExtractFilesWithFullHelmDirectoryFalseErrorCases tests error scenarios when isFullHelmDirectory is false
547+
func TestExtractFilesWithFullHelmDirectoryFalseErrorCases(t *testing.T) {
548+
extractor := NewImagesExtractor()
549+
550+
scenarios := []struct {
551+
Name string
552+
InputPath string
553+
ExpectedErrString string
554+
}{
555+
{
556+
Name: "NonExistentDirectory",
557+
InputPath: "../../test_files/non-existent-directory",
558+
ExpectedErrString: "unsupported file type",
559+
},
560+
{
561+
Name: "FileInsteadOfDirectory",
562+
InputPath: "../../test_files/helm-testcases/Chart.yaml",
563+
ExpectedErrString: "unsupported file type",
564+
},
565+
}
566+
567+
// Run test scenarios
568+
for _, scenario := range scenarios {
569+
t.Run(scenario.Name, func(t *testing.T) {
570+
// Run the function with isFullHelmDirectory = false
571+
_, _, _, err := extractor.ExtractFiles(scenario.InputPath, false)
572+
573+
// Check for errors
574+
if err == nil || !strings.Contains(err.Error(), scenario.ExpectedErrString) {
575+
t.Errorf("Expected error containing '%s' but got '%v'", scenario.ExpectedErrString, err)
576+
}
577+
})
578+
}
579+
}
580+
581+
// TestExtractFilesWithFullHelmDirectoryFalseDefaultBehavior tests that the default behavior (no parameter) is the same as true
582+
func TestExtractFilesWithFullHelmDirectoryFalseDefaultBehavior(t *testing.T) {
583+
extractor := NewImagesExtractor()
584+
585+
// Test that calling ExtractFiles without the isFullHelmDirectory parameter
586+
// produces the same result as calling it with true
587+
scanPath := "../../test_files/imageExtraction"
588+
589+
filesDefault, settingsDefault, pathDefault, errDefault := extractor.ExtractFiles(scanPath)
590+
filesTrue, settingsTrue, pathTrue, errTrue := extractor.ExtractFiles(scanPath, true)
591+
592+
// Check that both calls return the same results
593+
if errDefault != nil || errTrue != nil {
594+
t.Errorf("Unexpected errors: default=%v, true=%v", errDefault, errTrue)
595+
}
596+
597+
if pathDefault != pathTrue {
598+
t.Errorf("Expected same path, got default=%s, true=%s", pathDefault, pathTrue)
599+
}
600+
601+
if !CompareDockerfiles(filesDefault.Dockerfile, filesTrue.Dockerfile) {
602+
t.Errorf("Dockerfiles mismatch between default and true calls")
603+
}
604+
605+
if !CompareDockerCompose(filesDefault.DockerCompose, filesTrue.DockerCompose) {
606+
t.Errorf("Docker Compose files mismatch between default and true calls")
607+
}
608+
609+
if !CompareHelm(filesDefault.Helm, filesTrue.Helm) {
610+
t.Errorf("Helm charts mismatch between default and true calls")
611+
}
612+
613+
if !CompareSettingsFiles(settingsDefault, settingsTrue) {
614+
t.Errorf("Settings files mismatch between default and true calls")
615+
}
616+
}

pkg/imagesExtractor/utils.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,162 @@ func printFilePaths(f []types.FilePath, message string) {
138138
}(), ", "))
139139
}
140140
}
141+
142+
func findHelmFilesInDirectory(scanPath string) ([]types.HelmChartInfo, error) {
143+
// Validate scan path and find helm directory
144+
helmDir, err := validateScanPathAndFindHelmDir(scanPath)
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
// Collect all valid Helm files
150+
valuesFiles, templateFiles, err := collectHelmFiles(helmDir, helmDir)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
return createHelmChartInfo(helmDir, valuesFiles, templateFiles), nil
156+
}
157+
158+
func validateScanPathAndFindHelmDir(scanPath string) (string, error) {
159+
// Check if directory exists
160+
if _, err := os.Stat(scanPath); os.IsNotExist(err) {
161+
return "", fmt.Errorf("directory does not exist: %s", scanPath)
162+
}
163+
164+
helmDir, isUnderHelm := findHelmDirectory(scanPath)
165+
if !isUnderHelm {
166+
return "", fmt.Errorf("no helm directory found in path hierarchy: %s", scanPath)
167+
}
168+
169+
relativeScanPath, err := filepath.Rel(helmDir, scanPath)
170+
if err != nil {
171+
return "", fmt.Errorf("could not get relative path: %v", err)
172+
}
173+
174+
fullScanPath := filepath.Join(helmDir, relativeScanPath)
175+
176+
scanPathInfo, err := os.Stat(fullScanPath)
177+
if err != nil {
178+
return "", fmt.Errorf("could not get scan path info: %v", err)
179+
}
180+
if !scanPathInfo.IsDir() {
181+
return "", fmt.Errorf("scan path must be a directory: %s", scanPath)
182+
}
183+
184+
return helmDir, nil
185+
}
186+
187+
func collectHelmFiles(scanPath, helmDir string) ([]string, []types.FilePath, error) {
188+
var valuesFiles []string
189+
var templateFiles []types.FilePath
190+
191+
err := filepath.Walk(helmDir, func(path string, info os.FileInfo, err error) error {
192+
if err != nil {
193+
return err
194+
}
195+
196+
if info.IsDir() {
197+
return nil
198+
}
199+
200+
if !isYAMLFile(path) {
201+
return nil
202+
}
203+
204+
if info.Name() == "Chart.yaml" {
205+
return nil
206+
}
207+
208+
relativePath, err := filepath.Rel(helmDir, path)
209+
if err != nil {
210+
return err
211+
}
212+
213+
if isValuesFile(path, helmDir) {
214+
valuesFiles = append(valuesFiles, relativePath)
215+
} else if isTemplateFile(relativePath) {
216+
templateFiles = append(templateFiles, types.FilePath{
217+
FullPath: path,
218+
RelativePath: relativePath,
219+
})
220+
}
221+
222+
return nil
223+
})
224+
225+
if err != nil {
226+
return nil, nil, fmt.Errorf("error walking directory: %v", err)
227+
}
228+
229+
return valuesFiles, templateFiles, nil
230+
}
231+
232+
func isValuesFile(filePath, helmDir string) bool {
233+
fileDir := filepath.Dir(filePath)
234+
isDirectlyUnderHelm := filepath.Clean(fileDir) == filepath.Clean(helmDir)
235+
236+
if !isDirectlyUnderHelm {
237+
return false
238+
}
239+
240+
fileName := filepath.Base(filePath)
241+
return strings.Contains(strings.ToLower(fileName), "values")
242+
}
243+
244+
func isTemplateFile(relativePath string) bool {
245+
relativeDir := filepath.Dir(relativePath)
246+
return strings.HasPrefix(relativeDir, "templates") || relativeDir == "templates"
247+
}
248+
249+
func createHelmChartInfo(helmDir string, valuesFiles []string, templateFiles []types.FilePath) []types.HelmChartInfo {
250+
if len(valuesFiles) == 0 && len(templateFiles) == 0 {
251+
return []types.HelmChartInfo{}
252+
}
253+
254+
helmChart := types.HelmChartInfo{
255+
Directory: helmDir,
256+
}
257+
258+
// Add values files (take the first one as ValuesFile, others could be added to TemplateFiles if needed)
259+
if len(valuesFiles) > 0 {
260+
helmChart.ValuesFile = valuesFiles[0]
261+
// Add remaining values files to template files for completeness
262+
for i := 1; i < len(valuesFiles); i++ {
263+
templateFiles = append(templateFiles, types.FilePath{
264+
FullPath: filepath.Join(helmDir, valuesFiles[i]),
265+
RelativePath: valuesFiles[i],
266+
})
267+
}
268+
}
269+
270+
helmChart.TemplateFiles = templateFiles
271+
272+
return []types.HelmChartInfo{helmChart}
273+
}
274+
275+
func findHelmDirectory(dirPath string) (string, bool) {
276+
currentDir := dirPath
277+
278+
for {
279+
dirName := filepath.Base(currentDir)
280+
if isHelmDirectoryName(dirName) {
281+
return currentDir, true
282+
}
283+
284+
parentDir := filepath.Dir(currentDir)
285+
if parentDir == currentDir {
286+
// Reached root directory
287+
break
288+
}
289+
currentDir = parentDir
290+
}
291+
292+
return "", false
293+
}
294+
295+
func isHelmDirectoryName(dirName string) bool {
296+
dirNameLower := strings.ToLower(dirName)
297+
298+
return strings.Contains(dirNameLower, "helm")
299+
}

0 commit comments

Comments
 (0)