diff --git a/packageupdaters/commonpackageupdater_test.go b/packageupdaters/commonpackageupdater_test.go index 7afcea459..da9b52f45 100644 --- a/packageupdaters/commonpackageupdater_test.go +++ b/packageupdaters/commonpackageupdater_test.go @@ -525,7 +525,7 @@ func TestGradleFixVulnerabilityIfExists(t *testing.T) { gph := GradlePackageUpdater{} - descriptorFiles, err := gph.GetAllDescriptorFilesFullPaths([]string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix}) + descriptorFiles, err := getAllGradleDescriptorFilesFullPaths() assert.NoError(t, err) for _, descriptorFile := range descriptorFiles { @@ -595,6 +595,49 @@ func TestGradleIsVersionSupportedForFix(t *testing.T) { } } +func TestGetAllGradleDescriptorFilesFullPaths(t *testing.T) { + var testcases = []struct { + testProjectRepo string + expectedResultSuffixes []string + patternsToExclude []string + }{ + { + testProjectRepo: "gradle", + expectedResultSuffixes: []string{"build.gradle", filepath.Join("innerProjectForTest", "build.gradle.kts")}, + }, + { + testProjectRepo: "gradle", + expectedResultSuffixes: []string{"build.gradle"}, + patternsToExclude: []string{".*innerProjectForTest.*"}, + }, + } + + currDir, outerErr := os.Getwd() + assert.NoError(t, outerErr) + + for _, testcase := range testcases { + tmpDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", testcase.testProjectRepo), tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + + finalDirPath, err := os.Getwd() + assert.NoError(t, err) + + var expectedResults []string + for _, suffix := range testcase.expectedResultSuffixes { + expectedResults = append(expectedResults, filepath.Join(finalDirPath, suffix)) + } + + descriptorFilesFullPaths, err := getAllGradleDescriptorFilesFullPaths(testcase.patternsToExclude...) + assert.NoError(t, err) + assert.ElementsMatch(t, expectedResults, descriptorFilesFullPaths) + + assert.NoError(t, os.Chdir(currDir)) + assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) + } +} + func TestGetAllDescriptorFilesFullPaths(t *testing.T) { var testcases = []struct { testProjectRepo string diff --git a/packageupdaters/gradlepackageupdater.go b/packageupdaters/gradlepackageupdater.go index 504464473..250b74aef 100644 --- a/packageupdaters/gradlepackageupdater.go +++ b/packageupdaters/gradlepackageupdater.go @@ -2,10 +2,13 @@ package packageupdaters import ( "fmt" - "github.com/jfrog/frogbot/v2/utils" + "io/fs" "os" + "path/filepath" "regexp" "strings" + + "github.com/jfrog/frogbot/v2/utils" ) const ( @@ -22,6 +25,12 @@ var directMapWithVersionRegexp = getMapRegexpEntry("group") + "," + getMapRegexp var gradleDescriptorsSuffixes = []string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix} +// skipDirNamesWhenCollectingGradleDescriptors are directory base names to skip when walking for build.gradle / build.gradle.kts (outputs, tooling, VCS). +var skipDirNamesWhenCollectingGradleDescriptors = map[string]struct{}{ + ".git": {}, ".gradle": {}, "build": {}, "node_modules": {}, "out": {}, + ".idea": {}, "dist": {}, "bin": {}, ".vscode": {}, +} + func getMapRegexpEntry(mapEntry string) string { return fmt.Sprintf(directMapRegexpEntry, mapEntry) + apostrophes + "%s" + apostrophes } @@ -54,7 +63,7 @@ func (gph *GradlePackageUpdater) updateDirectDependency(vulnDetails *utils.Vulne // A gradle project may contain several descriptor files in several sub-modules. Each vulnerability may be found in each of the descriptor files. // Therefore we iterate over every descriptor file for each vulnerability and try to find and fix it. var descriptorFilesFullPaths []string - descriptorFilesFullPaths, err = gph.GetAllDescriptorFilesFullPaths(gradleDescriptorsSuffixes) + descriptorFilesFullPaths, err = getAllGradleDescriptorFilesFullPaths() if err != nil { return } @@ -76,6 +85,55 @@ func (gph *GradlePackageUpdater) updateDirectDependency(vulnDetails *utils.Vulne return } +// getAllGradleDescriptorFilesFullPaths walks the tree from the current directory and collects all build.gradle / build.gradle.kts files (multi-module projects). +func getAllGradleDescriptorFilesFullPaths(patternsToExclude ...string) (descriptorFilesFullPaths []string, err error) { + var regexpPatternsCompilers []*regexp.Regexp + for _, patternToExclude := range patternsToExclude { + regexpPatternsCompilers = append(regexpPatternsCompilers, regexp.MustCompile(patternToExclude)) + } + + err = filepath.WalkDir(".", func(path string, d fs.DirEntry, innerErr error) error { + if innerErr != nil { + return fmt.Errorf("an error has occurred when attempting to access or traverse the file system: %w", innerErr) + } + + if path == "." { + return nil + } + + for _, regexpCompiler := range regexpPatternsCompilers { + if regexpCompiler.FindString(path) != "" { + if d.IsDir() { + return filepath.SkipDir + } + return nil + } + } + + if d.IsDir() { + if _, skip := skipDirNamesWhenCollectingGradleDescriptors[filepath.Base(path)]; skip { + return filepath.SkipDir + } + return nil + } + + for _, suffix := range gradleDescriptorsSuffixes { + if strings.HasSuffix(path, suffix) { + absFilePath, absErr := filepath.Abs(path) + if absErr != nil { + return fmt.Errorf("couldn't retrieve file's absolute path for './%s': %w", path, absErr) + } + descriptorFilesFullPaths = append(descriptorFilesFullPaths, absFilePath) + } + } + return nil + }) + if err != nil { + err = fmt.Errorf("failed to get Gradle descriptor files absolute paths: %w", err) + } + return +} + // Checks if the impacted version is currently supported for fix func isVersionSupportedForFix(impactedVersion string) bool { if strings.Contains(impactedVersion, "+") ||