From b1d7115b5d3c71f851365b02140262480235b0d4 Mon Sep 17 00:00:00 2001 From: Or Toren <129293360+orto17@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:36:02 +0200 Subject: [PATCH] Enable conan support (#781) * conan package handler * IsFileExists fix * conan package handler * IsFileExists fix * conan tests * fixed installation command * test fix * Install Conan in test yaml * Install Conan in test yaml * Install Conan in test yaml * one more commit * conan profile detect * removed installation part * GetAllDescriptorFilesFullPaths * fixed tests data structure * error messages change * static analysis fix * tests update * removed logNoInstallationMessage --- .github/workflows/test.yml | 5 ++ packagehandlers/commonpackagehandler.go | 2 + packagehandlers/conanpackagehandler.go | 75 +++++++++++++++++++++++++ packagehandlers/packagehandlers_test.go | 24 ++++++++ testdata/projects/conan/conanfile.py | 23 ++++++++ testdata/projects/conan/conanfile.txt | 4 ++ testdata/projects/conan/profile | 8 +++ 7 files changed, 141 insertions(+) create mode 100644 packagehandlers/conanpackagehandler.go create mode 100644 testdata/projects/conan/conanfile.py create mode 100644 testdata/projects/conan/conanfile.txt create mode 100644 testdata/projects/conan/profile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af7d0609b..423d5b91e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -136,6 +136,11 @@ jobs: distribution: "adopt" java-version: "11" + - name: Install Conan + run: | + python -m pip install conan + conan profile detect + # Generate mocks - name: Generate mocks run: go generate ./... diff --git a/packagehandlers/commonpackagehandler.go b/packagehandlers/commonpackagehandler.go index 211519d7c..a5643af33 100644 --- a/packagehandlers/commonpackagehandler.go +++ b/packagehandlers/commonpackagehandler.go @@ -42,6 +42,8 @@ func GetCompatiblePackageHandler(vulnDetails *utils.VulnerabilityDetails, detail handler = &GradlePackageHandler{} case techutils.Pnpm: handler = &PnpmPackageHandler{} + case techutils.Conan: + handler = &ConanPackageHandler{} default: handler = &UnsupportedPackageHandler{} } diff --git a/packagehandlers/conanpackagehandler.go b/packagehandlers/conanpackagehandler.go new file mode 100644 index 000000000..5ffc13044 --- /dev/null +++ b/packagehandlers/conanpackagehandler.go @@ -0,0 +1,75 @@ +package packagehandlers + +import ( + "fmt" + "github.com/jfrog/frogbot/v2/utils" + "github.com/jfrog/jfrog-client-go/utils/log" + "os" + "strings" +) + +const ( + conanFileTxt = "conanfile.txt" + conanFilePy = "conanfile.py" +) + +type ConanPackageHandler struct { + CommonPackageHandler +} + +func (conan *ConanPackageHandler) UpdateDependency(vulnDetails *utils.VulnerabilityDetails) error { + if vulnDetails.IsDirectDependency { + return conan.updateDirectDependency(vulnDetails) + } else { + return &utils.ErrUnsupportedFix{ + PackageName: vulnDetails.ImpactedDependencyName, + FixedVersion: vulnDetails.SuggestedFixedVersion, + ErrorType: utils.IndirectDependencyFixNotSupported, + } + } +} + +func (conan *ConanPackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) { + conanDescriptors, err := conan.CommonPackageHandler.GetAllDescriptorFilesFullPaths([]string{conanFileTxt, conanFilePy}) + if err != nil { + err = fmt.Errorf("failed while searching for Conan descriptor files in project: %s", err.Error()) + return + } + isAnyDescriptorFileChanged := false + for _, descriptor := range conanDescriptors { + var isFileChanged bool + isFileChanged, err = conan.updateConanFile(descriptor, vulnDetails) + if err != nil { + return + } + isAnyDescriptorFileChanged = isAnyDescriptorFileChanged || isFileChanged + } + if !isAnyDescriptorFileChanged { + err = fmt.Errorf("impacted package '%s' was not found or could not be fixed in all descriptor files", vulnDetails.ImpactedDependencyName) + } else { + log.Info("Requirements file was updated with a suggested fix version, but no installation was performed. " + + "In order to update the dependencies, please run 'conan install' command") + } + return +} + +func (conan *ConanPackageHandler) updateConanFile(conanFilePath string, vulnDetails *utils.VulnerabilityDetails) (isFileChanged bool, err error) { + data, err := os.ReadFile(conanFilePath) + if err != nil { + return false, fmt.Errorf("an error occurred while attempting to read the requirements file '%s': %s\n", conanFilePath, err.Error()) + } + currentFile := string(data) + fixedPackage := vulnDetails.ImpactedDependencyName + "/" + vulnDetails.SuggestedFixedVersion + impactedDependency := vulnDetails.ImpactedDependencyName + "/" + vulnDetails.ImpactedDependencyVersion + fixedFile := strings.Replace(currentFile, impactedDependency, strings.ToLower(fixedPackage), 1) + + if fixedFile == currentFile { + log.Debug(fmt.Sprintf("impacted dependency '%s' not found in descriptor '%s', moving to the next descriptor if exists...", impactedDependency, conanFilePath)) + return false, nil + } + if err = os.WriteFile(conanFilePath, []byte(fixedFile), 0600); err != nil { + err = fmt.Errorf("an error occured while writing the fixed version of %s to the requirements file '%s': %s", vulnDetails.ImpactedDependencyName, conanFilePath, err.Error()) + } + isFileChanged = true + return +} diff --git a/packagehandlers/packagehandlers_test.go b/packagehandlers/packagehandlers_test.go index a2a322a7a..812eb4528 100644 --- a/packagehandlers/packagehandlers_test.go +++ b/packagehandlers/packagehandlers_test.go @@ -344,6 +344,30 @@ func TestUpdateDependency(t *testing.T) { descriptorsToCheck: []string{"package.json"}, }, }, + + // Conan test cases + { + { + vulnDetails: &utils.VulnerabilityDetails{ + SuggestedFixedVersion: "3.0.14", + IsDirectDependency: true, + VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: techutils.Conan, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "openssl", ImpactedDependencyVersion: "3.0.9"}}, + }, + scanDetails: scanDetails, + fixSupported: true, + testDirName: "conan", + descriptorsToCheck: []string{"conanfile.py", "conanfile.txt"}, + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + SuggestedFixedVersion: "3.0.14", + IsDirectDependency: false, + VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: techutils.Conan, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "openssl", ImpactedDependencyVersion: "3.0.9"}}, + }, + scanDetails: scanDetails, + fixSupported: false, + }, + }, } for _, testBatch := range testCases { diff --git a/testdata/projects/conan/conanfile.py b/testdata/projects/conan/conanfile.py new file mode 100644 index 000000000..85e03de26 --- /dev/null +++ b/testdata/projects/conan/conanfile.py @@ -0,0 +1,23 @@ +from conan import ConanFile + +class MyPackage(ConanFile): + name = "my_package" + version = "1.0.0" + + requires = [ + "zlib/1.3.1", + "openssl/3.0.9", + "meson/1.4.1" + ] + + def build_requirements(self): + self.build_requires("meson/1.4.1") + + def build(self): + pass + + def package(self): + pass + + def package_info(self): + pass \ No newline at end of file diff --git a/testdata/projects/conan/conanfile.txt b/testdata/projects/conan/conanfile.txt new file mode 100644 index 000000000..dd67a13c7 --- /dev/null +++ b/testdata/projects/conan/conanfile.txt @@ -0,0 +1,4 @@ +[requires] +zlib/1.3.1 +openssl/3.0.9 +meson/1.4.1 \ No newline at end of file diff --git a/testdata/projects/conan/profile b/testdata/projects/conan/profile new file mode 100644 index 000000000..86745c5f9 --- /dev/null +++ b/testdata/projects/conan/profile @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=gnu17 +compiler.libcxx=libstdc++11 +compiler.version=11 +os=Linux \ No newline at end of file