diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 23a98a6e9..86487ff15 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -13,13 +13,14 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v3 + - name: Setup Go + uses: actions/setup-go@v5 with: go-version: 1.20.x + cache: false - name: Static Code Analysis - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: args: | --timeout 5m --out-${NO_FUTURE}format colored-line-number --enable errcheck,gosimple,govet,ineffassign,staticcheck,typecheck,unused,gocritic,asasalint,asciicheck,errchkjson,exportloopref,forcetypeassert,makezero,nilerr,unparam,unconvert,wastedassign,usestdlibvars @@ -31,10 +32,11 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v3 + - name: Setup Go + uses: actions/setup-go@v5 with: go-version: 1.20.x + cache: false - name: Run Gosec Security Scanner # Temporarily set version 2.18.0 to workaround https://github.com/securego/gosec/issues/1046 diff --git a/.github/workflows/embedded-jar-test.yml b/.github/workflows/embedded-jar-test.yml deleted file mode 100644 index a2ff93497..000000000 --- a/.github/workflows/embedded-jar-test.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This test verifies that gradle-dep-tree.jar and maven-dep-tree.jar are kept up-to-date with the version specified in buildscripts/download-jars.js. -# It accomplishes this by downloading the JARs and executing a "git diff" command. -# In case there are any differences detected, the test will result in failure. -name: Embedded Jars Tests -on: - push: - branches: - - '**' - tags-ignore: - - '**' - pull_request: -jobs: - test: - runs-on: ubuntu-latest - env: - GOPROXY: direct - steps: - - uses: actions/checkout@v4 - - - name: Download JARs - run: buildscripts/download-jars.sh - - - name: Check Diff - run: git diff --exit-code - - - name: Log if Failure - run: echo "::warning::Please run ./buildscripts/download-jars to use compatible Maven and Gradle dependency tree JARs." - if: ${{ failure() }} - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3026f842..ea9a56db8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,13 +28,14 @@ jobs: - name: Install pipenv & poetry run: python -m pip install pipenv poetry - - name: Install Go - uses: actions/setup-go@v3 + - name: Setup Go + uses: actions/setup-go@v5 with: go-version: 1.20.x + cache: false - name: Install NuGet - uses: nuget/setup-nuget@v1 + uses: nuget/setup-nuget@v2 with: nuget-version: 6.x diff --git a/artifactory/commands/buildinfo/adddependencies.go b/artifactory/commands/buildinfo/adddependencies.go index fc389aa2b..d35d3731e 100644 --- a/artifactory/commands/buildinfo/adddependencies.go +++ b/artifactory/commands/buildinfo/adddependencies.go @@ -2,6 +2,7 @@ package buildinfo import ( "errors" + ioutils "github.com/jfrog/gofrog/io" regxp "regexp" "strconv" @@ -316,15 +317,12 @@ func convertFileInfoToDependencies(files map[string]*fileutils.FileDetails) []bu func searchItems(spec *spec.SpecFiles, servicesManager artifactory.ArtifactoryServicesManager) (resultReader *content.ContentReader, err error) { temp := []*content.ContentReader{} var searchParams services.SearchParams - var reader *content.ContentReader defer func() { for _, reader := range temp { - e := reader.Close() - if err == nil { - err = e - } + ioutils.Close(reader, &err) } }() + var reader *content.ContentReader for i := 0; i < len(spec.Files); i++ { searchParams, err = utils.GetSearchParams(spec.Get(i)) if err != nil { diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index 60c79e4e0..54ddebe93 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -118,12 +118,7 @@ func (dc *DotnetCommand) Exec() (err error) { return err } defer func() { - if callbackFunc != nil { - e := callbackFunc() - if err == nil { - err = e - } - } + err = errors.Join(err, callbackFunc()) }() if err = buildInfoModule.CalcDependencies(); err != nil { if dc.isDotnetTestCommand() { @@ -272,10 +267,7 @@ func InitNewConfig(configDirPath, repoName string, server *config.ServerDetails, } log.Debug("Nuget config file created at:", configFile.Name()) defer func() { - e := configFile.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(configFile.Close())) }() // We would prefer to write the NuGet configuration using the `nuget add source` command, diff --git a/artifactory/commands/generic/delete.go b/artifactory/commands/generic/delete.go index 38f93af67..367965dc2 100644 --- a/artifactory/commands/generic/delete.go +++ b/artifactory/commands/generic/delete.go @@ -1,6 +1,7 @@ package generic import ( + ioutils "github.com/jfrog/gofrog/io" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-client-go/artifactory/services" @@ -36,12 +37,7 @@ func (dc *DeleteCommand) Run() (err error) { if err != nil { return } - defer func() { - e := reader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(reader, &err) allowDelete := true if !dc.quiet { allowDelete, err = utils.ConfirmDelete(reader) @@ -72,10 +68,7 @@ func (dc *DeleteCommand) GetPathsToDelete() (contentReader *content.ContentReade var temp []*content.ContentReader defer func() { for _, reader := range temp { - e := reader.Close() - if err == nil { - err = e - } + ioutils.Close(reader, &err) } }() for i := 0; i < len(dc.Spec().Files); i++ { @@ -95,12 +88,7 @@ func (dc *DeleteCommand) GetPathsToDelete() (contentReader *content.ContentReade if err != nil { return nil, err } - defer func() { - e := tempMergedReader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(tempMergedReader, &err) // After merge, remove top chain dirs as we may encounter duplicates and collisions between files and directories to delete. // For example: // Reader1: {"a"} diff --git a/artifactory/commands/generic/download.go b/artifactory/commands/generic/download.go index 293508572..ef5c7f5ab 100644 --- a/artifactory/commands/generic/download.go +++ b/artifactory/commands/generic/download.go @@ -119,23 +119,13 @@ func (dc *DownloadCommand) download() (err error) { log.Error(err) } if summary != nil { - defer func() { - e := summary.ArtifactsDetailsReader.Close() - if err == nil { - err = e - } - }() + defer gofrog.Close(summary.ArtifactsDetailsReader, &err) // If 'detailed summary' was requested, then the reader should not be closed here. // It will be closed after it will be used to generate the summary. if dc.DetailedSummary() { dc.result.SetReader(summary.TransferDetailsReader) } else { - defer func() { - e := summary.TransferDetailsReader.Close() - if err == nil { - err = e - } - }() + defer gofrog.Close(summary.TransferDetailsReader, &err) } totalDownloaded = summary.TotalSucceeded totalFailed = summary.TotalFailed @@ -168,10 +158,7 @@ func (dc *DownloadCommand) download() (err error) { var tmpRoot string tmpRoot, err = createDownloadResultEmptyTmpReflection(summary.TransferDetailsReader) defer func() { - e := fileutils.RemoveTempDir(tmpRoot) - if err == nil { - err = e - } + err = errors.Join(err, fileutils.RemoveTempDir(tmpRoot)) }() if err != nil { return err diff --git a/artifactory/commands/generic/search.go b/artifactory/commands/generic/search.go index cad7f1890..b5cf9ff40 100644 --- a/artifactory/commands/generic/search.go +++ b/artifactory/commands/generic/search.go @@ -1,6 +1,7 @@ package generic import ( + "errors" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" clientartutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -26,7 +27,7 @@ func (sc *SearchCommand) Run() error { return err } -func (sc *SearchCommand) Search() (contentReader *content.ContentReader, err error) { +func (sc *SearchCommand) Search() (*content.ContentReader, error) { // Service Manager serverDetails, err := sc.ServerDetails() if errorutils.CheckError(err) != nil { @@ -38,28 +39,15 @@ func (sc *SearchCommand) Search() (contentReader *content.ContentReader, err err } // Search Loop log.Info("Searching artifacts...") - var searchResults []*content.ContentReader + + searchResults, callbackFunc, err := utils.SearchFiles(servicesManager, sc.Spec()) defer func() { - for _, reader := range searchResults { - e := reader.Close() - if err == nil { - err = e - } - } + err = errors.Join(err, callbackFunc()) }() - for i := 0; i < len(sc.Spec().Files); i++ { - searchParams, err := utils.GetSearchParams(sc.Spec().Get(i)) - if err != nil { - log.Error(err) - return nil, err - } - reader, err := servicesManager.SearchFiles(searchParams) - if err != nil { - log.Error(err) - return nil, err - } - searchResults = append(searchResults, reader) + if err != nil { + return nil, err } + reader, err := utils.AqlResultToSearchResult(searchResults) if err != nil { return nil, err diff --git a/artifactory/commands/generic/upload.go b/artifactory/commands/generic/upload.go index b16e83ffd..e346a9d53 100644 --- a/artifactory/commands/generic/upload.go +++ b/artifactory/commands/generic/upload.go @@ -5,10 +5,7 @@ import ( buildInfo "github.com/jfrog/build-info-go/entities" - "os" - "strconv" - "time" - + ioutils "github.com/jfrog/gofrog/io" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/build" "github.com/jfrog/jfrog-cli-core/v2/common/spec" @@ -19,6 +16,8 @@ import ( ioUtils "github.com/jfrog/jfrog-client-go/utils/io" "github.com/jfrog/jfrog-client-go/utils/io/content" "github.com/jfrog/jfrog-client-go/utils/log" + "strconv" + "time" ) type UploadCommand struct { @@ -81,7 +80,7 @@ func (uc *UploadCommand) upload() (err error) { } // Create Service Manager: - uc.uploadConfiguration.MinChecksumDeploySize, err = getMinChecksumDeploySize() + uc.uploadConfiguration.MinChecksumDeploySize, err = utils.GetMinChecksumDeploySize() if err != nil { return } @@ -140,12 +139,7 @@ func (uc *UploadCommand) upload() (err error) { } if summary != nil { artifactsDetailsReader = summary.ArtifactsDetailsReader - defer func() { - e := artifactsDetailsReader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(artifactsDetailsReader, &err) // If 'detailed summary' was requested, then the reader should not be closed here. // It will be closed after it will be used to generate the summary. if uc.DetailedSummary() { @@ -197,19 +191,6 @@ func (uc *UploadCommand) upload() (err error) { return } -func getMinChecksumDeploySize() (int64, error) { - minChecksumDeploySize := os.Getenv("JFROG_CLI_MIN_CHECKSUM_DEPLOY_SIZE_KB") - if minChecksumDeploySize == "" { - return services.DefaultMinChecksumDeploy, nil - } - minSize, err := strconv.ParseInt(minChecksumDeploySize, 10, 64) - err = errorutils.CheckError(err) - if err != nil { - return 0, err - } - return minSize * 1000, nil -} - func getUploadParams(f *spec.File, configuration *utils.UploadConfiguration, buildProps string, addVcsProps bool) (uploadParams services.UploadParams, err error) { uploadParams = services.NewUploadParams() uploadParams.CommonParams, err = f.ToCommonParams() @@ -277,12 +258,7 @@ func (uc *UploadCommand) handleSyncDeletes(syncDeletesProp string) (err error) { if err != nil { return err } - defer func() { - e := resultItems.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(resultItems, &err) _, err = servicesManager.DeleteFiles(resultItems) return err } diff --git a/artifactory/commands/golang/publish.go b/artifactory/commands/golang/publish.go index ba123c901..4b042c64c 100644 --- a/artifactory/commands/golang/publish.go +++ b/artifactory/commands/golang/publish.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "encoding/json" + "errors" "fmt" buildinfo "github.com/jfrog/build-info-go/entities" biutils "github.com/jfrog/build-info-go/utils" @@ -57,10 +58,7 @@ func publishPackage(packageVersion, targetRepo, buildName, buildNumber, projectK return nil, nil, err } defer func() { - e := fileutils.RemoveTempDir(tempDirPath) - if err == nil { - err = e - } + err = errors.Join(err, fileutils.RemoveTempDir(tempDirPath)) }() var zipArtifact *buildinfo.Artifact @@ -87,18 +85,17 @@ func publishPackage(packageVersion, targetRepo, buildName, buildNumber, projectK version := version.NewVersion(artifactoryVersion) if version.AtLeast(_go.ArtifactoryMinSupportedVersion) { log.Debug("Creating info file", projectPath) - pathToInfo, err := createInfoFile(packageVersion) + var pathToInfo string + pathToInfo, err = createInfoFile(packageVersion) if err != nil { return nil, nil, err } defer func() { - e := os.Remove(pathToInfo) - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(os.Remove(pathToInfo))) }() if collectBuildInfo { - infoArtifact, err := createInfoFileArtifact(pathToInfo, packageVersion) + var infoArtifact *buildinfo.Artifact + infoArtifact, err = createInfoFileArtifact(pathToInfo, packageVersion) if err != nil { return nil, nil, err } @@ -125,10 +122,7 @@ func createInfoFile(packageVersion string) (path string, err error) { return "", errorutils.CheckError(err) } defer func() { - e := file.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(file.Close())) }() _, err = file.Write(content) if err != nil { @@ -155,10 +149,7 @@ func readModFile(version, projectPath string, createArtifact bool) ([]byte, *bui return nil, nil, err } defer func() { - e := modFile.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(modFile.Close())) }() content, err := io.ReadAll(modFile) if err != nil { @@ -191,10 +182,7 @@ func archive(moduleName, version, projectPath, tempDir string, excludedPatterns openedFile = true defer func() { if openedFile { - e := tempFile.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(tempFile.Close())) } }() if err = archiveProject(tempFile, projectPath, moduleName, version, excludedPatterns); err != nil { @@ -216,10 +204,10 @@ func archive(moduleName, version, projectPath, tempDir string, excludedPatterns } } // Sync the file before renaming it - if err := tempFile.Sync(); err != nil { + if err = tempFile.Sync(); err != nil { return "", nil, err } - if err := tempFile.Close(); err != nil { + if err = tempFile.Close(); err != nil { return "", nil, err } openedFile = false diff --git a/artifactory/commands/npm/publish.go b/artifactory/commands/npm/publish.go index 5313d2d74..ea5e7e01f 100644 --- a/artifactory/commands/npm/publish.go +++ b/artifactory/commands/npm/publish.go @@ -3,7 +3,9 @@ package npm import ( "archive/tar" "compress/gzip" + "errors" "fmt" + ioutils "github.com/jfrog/gofrog/io" "io" "os" "path/filepath" @@ -37,7 +39,7 @@ type NpmPublishCommandArgs struct { executablePath string workingDirectory string collectBuildInfo bool - packedFilePath string + packedFilePaths []string packageInfo *biutils.PackageInfo publishPath string tarballProvided bool @@ -172,11 +174,11 @@ func (npc *NpmPublishCommand) Run() (err error) { return err } // We should delete the tarball we created - return deleteCreatedTarballAndError(npc.packedFilePath, err) + return errors.Join(err, deleteCreatedTarball(npc.packedFilePaths)) } if !npc.tarballProvided { - if err := deleteCreatedTarball(npc.packedFilePath); err != nil { + if err := deleteCreatedTarball(npc.packedFilePaths); err != nil { return err } } @@ -197,12 +199,7 @@ func (npc *NpmPublishCommand) Run() (err error) { if err != nil { return err } - defer func() { - e := npc.artifactsDetailsReader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(npc.artifactsDetailsReader, &err) err = npmModule.AddArtifacts(buildArtifacts...) if err != nil { return errorutils.CheckError(err) @@ -217,6 +214,7 @@ func (npc *NpmPublishCommand) CommandName() string { } func (npc *NpmPublishCommand) preparePrerequisites() error { + npc.packedFilePaths = make([]string, 0) currentDir, err := os.Getwd() if err != nil { return errorutils.CheckError(err) @@ -251,7 +249,7 @@ func (npc *NpmPublishCommand) preparePrerequisites() error { func (npc *NpmPublishCommand) pack() error { log.Debug("Creating npm package.") - packageFileName, err := npm.Pack(npc.npmArgs, npc.executablePath) + packedFileNames, err := npm.Pack(npc.npmArgs, npc.executablePath) if err != nil { return err } @@ -261,8 +259,10 @@ func (npc *NpmPublishCommand) pack() error { return err } - npc.packedFilePath = filepath.Join(tarballDir, packageFileName) - log.Debug("Created npm package at", npc.packedFilePath) + for _, packageFileName := range packedFileNames { + npc.packedFilePaths = append(npc.packedFilePaths, filepath.Join(tarballDir, packageFileName)) + } + return nil } @@ -279,34 +279,36 @@ func (npc *NpmPublishCommand) getTarballDir() (string, error) { return dest, nil } -func (npc *NpmPublishCommand) publish() error { - log.Debug("Deploying npm package.") - if err := npc.readPackageInfoFromTarball(); err != nil { - return err - } - target := fmt.Sprintf("%s/%s", npc.repo, npc.packageInfo.GetDeployPath()) - - // If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment. - if npc.xrayScan { - fileSpec := spec.NewBuilder(). - Pattern(npc.packedFilePath). - Target(npc.repo + "/"). - BuildSpec() - err := commandsutils.ConditionalUploadScanFunc(npc.serverDetails, fileSpec, 1, npc.scanOutputFormat) - if err != nil { - return err +func (npc *NpmPublishCommand) publish() (err error) { + for _, packedFilePath := range npc.packedFilePaths { + log.Debug("Deploying npm package.") + if err = npc.readPackageInfoFromTarball(packedFilePath); err != nil { + return + } + target := fmt.Sprintf("%s/%s", npc.repo, npc.packageInfo.GetDeployPath()) + + // If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment. + if npc.xrayScan { + fileSpec := spec.NewBuilder(). + Pattern(packedFilePath). + Target(npc.repo + "/"). + BuildSpec() + if err = commandsutils.ConditionalUploadScanFunc(npc.serverDetails, fileSpec, 1, npc.scanOutputFormat); err != nil { + return + } } + err = errors.Join(err, npc.doDeploy(target, npc.serverDetails, packedFilePath)) } - return npc.doDeploy(target, npc.serverDetails) + return } -func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerDetails) error { +func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerDetails, packedFilePath string) error { servicesManager, err := utils.CreateServiceManager(artDetails, -1, 0, false) if err != nil { return err } up := services.NewUploadParams() - up.CommonParams = &specutils.CommonParams{Pattern: npc.packedFilePath, Target: target} + up.CommonParams = &specutils.CommonParams{Pattern: packedFilePath, Target: target} var totalFailed int if npc.collectBuildInfo || npc.detailedSummary { if npc.collectBuildInfo { @@ -341,12 +343,11 @@ func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerD } } if npc.detailedSummary { - npc.result.SetReader(summary.TransferDetailsReader) - npc.result.SetFailCount(totalFailed) - npc.result.SetSuccessCount(summary.TotalSucceeded) + if err = npc.setDetailedSummary(summary); err != nil { + return err + } } else { - err = summary.TransferDetailsReader.Close() - if err != nil { + if err = summary.TransferDetailsReader.Close(); err != nil { return err } } @@ -364,6 +365,29 @@ func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerD return nil } +func (npc *NpmPublishCommand) setDetailedSummary(summary *specutils.OperationSummary) (err error) { + npc.result.SetFailCount(npc.result.FailCount() + summary.TotalFailed) + npc.result.SetSuccessCount(npc.result.SuccessCount() + summary.TotalSucceeded) + if npc.result.Reader() == nil { + npc.result.SetReader(summary.TransferDetailsReader) + } else { + if err = npc.appendReader(summary); err != nil { + return + } + } + return +} + +func (npc *NpmPublishCommand) appendReader(summary *specutils.OperationSummary) error { + readersSlice := []*content.ContentReader{npc.result.Reader(), summary.TransferDetailsReader} + reader, err := content.MergeReaders(readersSlice, content.DefaultKey) + if err != nil { + return err + } + npc.result.SetReader(reader) + return nil +} + func (npc *NpmPublishCommand) setPublishPath() error { log.Debug("Reading Package Json.") @@ -394,21 +418,19 @@ func (npc *NpmPublishCommand) setPackageInfo() error { } log.Debug("The provided path is not a directory, we assume this is a compressed npm package") npc.tarballProvided = true - npc.packedFilePath = npc.publishPath - return npc.readPackageInfoFromTarball() + // Sets the location of the provided tarball + npc.packedFilePaths = []string{npc.publishPath} + return npc.readPackageInfoFromTarball(npc.publishPath) } -func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) { - log.Debug("Extracting info from npm package:", npc.packedFilePath) - tarball, err := os.Open(npc.packedFilePath) +func (npc *NpmPublishCommand) readPackageInfoFromTarball(packedFilePath string) (err error) { + log.Debug("Extracting info from npm package:", packedFilePath) + tarball, err := os.Open(packedFilePath) if err != nil { return errorutils.CheckError(err) } defer func() { - e := tarball.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(tarball.Close())) }() gZipReader, err := gzip.NewReader(tarball) if err != nil { @@ -420,7 +442,7 @@ func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) { hdr, err := tarReader.Next() if err != nil { if err == io.EOF { - return errorutils.CheckErrorf("Could not find 'package.json' in the compressed npm package: " + npc.packedFilePath) + return errorutils.CheckErrorf("Could not find 'package.json' in the compressed npm package: " + packedFilePath) } return errorutils.CheckError(err) } @@ -429,25 +451,18 @@ func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) { if err != nil { return errorutils.CheckError(err) } - npc.packageInfo, err = biutils.ReadPackageInfo(packageJson, npc.npmVersion) return err } } } -func deleteCreatedTarballAndError(packedFilePath string, currentError error) error { - if err := deleteCreatedTarball(packedFilePath); err != nil { - errorText := fmt.Sprintf("Two errors occurred: \n%s \n%s", currentError, err) - return errorutils.CheckErrorf(errorText) - } - return currentError -} - -func deleteCreatedTarball(packedFilePath string) error { - if err := os.Remove(packedFilePath); err != nil { - return errorutils.CheckError(err) +func deleteCreatedTarball(packedFilesPath []string) error { + for _, packedFilePath := range packedFilesPath { + if err := os.Remove(packedFilePath); err != nil { + return errorutils.CheckError(err) + } + log.Debug("Successfully deleted the created npm package:", packedFilePath) } - log.Debug("Successfully deleted the created npm package:", packedFilePath) return nil } diff --git a/artifactory/commands/npm/publish_test.go b/artifactory/commands/npm/publish_test.go index 91174289e..d49d3cf36 100644 --- a/artifactory/commands/npm/publish_test.go +++ b/artifactory/commands/npm/publish_test.go @@ -9,10 +9,26 @@ import ( func TestReadPackageInfoFromTarball(t *testing.T) { npmPublish := NewNpmPublishCommand() - npmPublish.packedFilePath = filepath.Join("..", "testdata", "npm", "npm-example-0.0.3.tgz") - err := npmPublish.readPackageInfoFromTarball() - assert.NoError(t, err) - assert.Equal(t, "npm-example", npmPublish.packageInfo.Name) - assert.Equal(t, "0.0.3", npmPublish.packageInfo.Version) + var testCases = []struct { + filePath string + packageName string + packageVersion string + }{ + { + filePath: filepath.Join("..", "testdata", "npm", "npm-example-0.0.3.tgz"), + packageName: "npm-example", + packageVersion: "0.0.3", + }, { + filePath: filepath.Join("..", "testdata", "npm", "npm-example-0.0.4.tgz"), + packageName: "npm-example", + packageVersion: "0.0.4", + }, + } + for _, test := range testCases { + err := npmPublish.readPackageInfoFromTarball(test.filePath) + assert.NoError(t, err) + assert.Equal(t, test.packageName, npmPublish.packageInfo.Name) + assert.Equal(t, test.packageVersion, npmPublish.packageInfo.Version) + } } diff --git a/artifactory/commands/python/dependencies/cache.go b/artifactory/commands/python/dependencies/cache.go index 3a15ad8df..71b5dd215 100644 --- a/artifactory/commands/python/dependencies/cache.go +++ b/artifactory/commands/python/dependencies/cache.go @@ -3,6 +3,7 @@ package dependencies import ( "encoding/json" buildinfo "github.com/jfrog/build-info-go/entities" + ioutils "github.com/jfrog/gofrog/io" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "io" @@ -31,12 +32,7 @@ func GetProjectDependenciesCache(cacheDir string) (cache *DependenciesCache, err if errorutils.CheckError(err) != nil { return nil, err } - defer func() { - e := jsonFile.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(jsonFile, &err) byteValue, err := io.ReadAll(jsonFile) if errorutils.CheckError(err) != nil { return nil, err @@ -66,12 +62,7 @@ func UpdateDependenciesCache(updatedMap map[string]buildinfo.Dependency, cacheDi if err != nil { return errorutils.CheckError(err) } - defer func() { - e := cacheFile.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(cacheFile, &err) _, err = cacheFile.Write(content) if err != nil { return errorutils.CheckError(err) diff --git a/artifactory/commands/python/dependencies/dependencies.go b/artifactory/commands/python/dependencies/dependencies.go index 8ab970851..142855d7c 100644 --- a/artifactory/commands/python/dependencies/dependencies.go +++ b/artifactory/commands/python/dependencies/dependencies.go @@ -3,6 +3,7 @@ package dependencies import ( "encoding/json" "fmt" + ioutils "github.com/jfrog/gofrog/io" "io" "strings" @@ -96,12 +97,7 @@ func getDependencyChecksumFromArtifactory(servicesManager artifactory.Artifactor if err != nil { return } - defer func() { - e := stream.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(stream, &err) result, err := io.ReadAll(stream) if err != nil { return diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index b9367b71c..da91baa99 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -47,10 +47,7 @@ func (pc *PoetryCommand) Run() (err error) { } defer func() { if pythonBuildInfo != nil && err != nil { - e := pythonBuildInfo.Clean() - if e != nil { - err = errors.New(err.Error() + "\n" + e.Error()) - } + err = errors.Join(err, pythonBuildInfo.Clean()) } }() err = pc.SetPypiRepoUrlWithCredentials() diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index defac7960..8d2de8b3e 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -43,10 +43,7 @@ func (pc *PythonCommand) Run() (err error) { } defer func() { if pythonBuildInfo != nil && err != nil { - e := pythonBuildInfo.Clean() - if e != nil { - err = errors.New(err.Error() + "\n" + e.Error()) - } + err = errors.Join(err, pythonBuildInfo.Clean()) } }() err = pc.SetPypiRepoUrlWithCredentials() diff --git a/artifactory/commands/terraform/terraformpublish.go b/artifactory/commands/terraform/terraformpublish.go index 6dbdefd36..0a6e854e7 100644 --- a/artifactory/commands/terraform/terraformpublish.go +++ b/artifactory/commands/terraform/terraformpublish.go @@ -1,7 +1,9 @@ package terraform import ( + "errors" buildInfo "github.com/jfrog/build-info-go/entities" + ioutils "github.com/jfrog/gofrog/io" "github.com/jfrog/gofrog/parallel" commandsUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -304,12 +306,7 @@ func readArtifactsFromSummary(summary *servicesUtils.OperationSummary) (artifact if artifactsDetailsReader == nil { return []buildInfo.Artifact{}, nil } - defer func() { - e := artifactsDetailsReader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(artifactsDetailsReader, &err) return servicesUtils.ConvertArtifactsDetailsToBuildInfoArtifacts(artifactsDetailsReader) } @@ -339,10 +336,7 @@ func checkIfTerraformModule(path string) (isModule bool, err error) { return false, errorutils.CheckError(err) } defer func() { - e := d.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, d.Close()) }() files, err := d.Readdir(-1) diff --git a/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz b/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz new file mode 100644 index 000000000..5d3f4db17 Binary files /dev/null and b/artifactory/commands/testdata/npm/npm-example-0.0.4.tgz differ diff --git a/artifactory/commands/transferconfig/transferconfig.go b/artifactory/commands/transferconfig/transferconfig.go index ddc6ce01c..42af06f40 100644 --- a/artifactory/commands/transferconfig/transferconfig.go +++ b/artifactory/commands/transferconfig/transferconfig.go @@ -3,6 +3,7 @@ package transferconfig import ( "bytes" "context" + "errors" "fmt" "net/http" "os" @@ -105,10 +106,7 @@ func (tcc *TransferConfigCommand) Run() (err error) { tcc.LogTitle("Phase 2/5 - Export configuration from the source Artifactory") exportPath, cleanUp, err := tcc.exportSourceArtifactory() defer func() { - cleanUpErr := cleanUp() - if err == nil { - err = cleanUpErr - } + err = errors.Join(err, cleanUp()) }() if err != nil { return @@ -313,9 +311,7 @@ func (tcc *TransferConfigCommand) getEncryptedItems(selectedSourceRepos map[util return "", nil, err } defer func() { - if reactivationErr := reactivateKeyEncryption(); err == nil { - err = reactivationErr - } + err = errors.Join(err, reactivateKeyEncryption()) }() // Download artifactory.config.xml from the source Artifactory server. diff --git a/artifactory/commands/transferconfig/utils.go b/artifactory/commands/transferconfig/utils.go index 75d27310c..58c3b62c5 100644 --- a/artifactory/commands/transferconfig/utils.go +++ b/artifactory/commands/transferconfig/utils.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "compress/flate" + "errors" "io" "os" "path/filepath" @@ -31,10 +32,7 @@ func archiveConfig(exportPath string, configXml string) (buffer *bytes.Buffer, r return flate.NewWriter(out, flate.BestCompression) }) defer func() { - closeErr := writer.Close() - if retErr == nil { - retErr = errorutils.CheckError(closeErr) - } + retErr = errors.Join(retErr, errorutils.CheckError(writer.Close())) }() err := handleTypoInAccessBootstrap(exportPath) diff --git a/artifactory/commands/transferconfigmerge/transferconfigmerge.go b/artifactory/commands/transferconfigmerge/transferconfigmerge.go index ea1c0d273..cf2b7a986 100644 --- a/artifactory/commands/transferconfigmerge/transferconfigmerge.go +++ b/artifactory/commands/transferconfigmerge/transferconfigmerge.go @@ -1,6 +1,7 @@ package transferconfigmerge import ( + "errors" "fmt" "reflect" "strings" @@ -358,9 +359,7 @@ func (tcmc *TransferConfigMergeCommand) decryptAndGetAllRemoteRepositories(remot return } defer func() { - if reactivationErr := reactivateKeyEncryption(); err == nil { - err = reactivationErr - } + err = errors.Join(err, reactivateKeyEncryption()) }() var remoteRepositoryKeys []string for _, remoteRepositoryDetails := range remoteRepositoriesDetails { diff --git a/artifactory/commands/transferfiles/api/types.go b/artifactory/commands/transferfiles/api/types.go index 7cbadd8f3..708ee49f0 100644 --- a/artifactory/commands/transferfiles/api/types.go +++ b/artifactory/commands/transferfiles/api/types.go @@ -47,6 +47,8 @@ type UploadChunk struct { CheckExistenceInFilestore bool `json:"check_existence_in_filestore,omitempty"` // True if should skip file filtering in the Data Transfer plugin SkipFileFiltering bool `json:"skip_file_filtering,omitempty"` + // Minimum file size in bytes for which JFrog CLI performs checksum deploy optimization + MinCheckSumDeploySize int64 `json:"min_checksum_deploy_size,omitempty"` } type FileRepresentation struct { diff --git a/artifactory/commands/transferfiles/delayedartifactshandler.go b/artifactory/commands/transferfiles/delayedartifactshandler.go index a5a20cfc9..caf80fcde 100644 --- a/artifactory/commands/transferfiles/delayedartifactshandler.go +++ b/artifactory/commands/transferfiles/delayedartifactshandler.go @@ -2,6 +2,7 @@ package transferfiles import ( "encoding/json" + "errors" "fmt" "os" "path" @@ -62,9 +63,7 @@ func getDelaysFilePrefix(repoKey string, phaseStartTime string) string { func (mng *TransferDelayedArtifactsMng) start() (err error) { defer func() { if mng.delayedWriter != nil { - if e := mng.delayedWriter.close(); err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(mng.delayedWriter.close())) } }() diff --git a/artifactory/commands/transferfiles/errorshandler.go b/artifactory/commands/transferfiles/errorshandler.go index db9a8cb3e..b075d03b9 100644 --- a/artifactory/commands/transferfiles/errorshandler.go +++ b/artifactory/commands/transferfiles/errorshandler.go @@ -2,6 +2,7 @@ package transferfiles import ( "encoding/json" + "errors" "fmt" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/api" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/state" @@ -119,10 +120,7 @@ func (mng *TransferErrorsMng) start() (err error) { return err } defer func() { - e := mng.errorWriterMng.retryable.closeWriter() - if err == nil { - err = e - } + err = errors.Join(err, mng.errorWriterMng.retryable.closeWriter()) }() writerMng.retryable = errorWriter{writer: writerRetry, filePath: retryFilePath} // Init the content writer which is responsible for writing 'skipped errors' into files. @@ -136,10 +134,7 @@ func (mng *TransferErrorsMng) start() (err error) { return err } defer func() { - e := mng.errorWriterMng.skipped.closeWriter() - if err == nil { - err = e - } + err = errors.Join(err, mng.errorWriterMng.skipped.closeWriter()) }() writerMng.skipped = errorWriter{writer: writerSkip, filePath: skipFilePath} mng.errorWriterMng = writerMng diff --git a/artifactory/commands/transferfiles/fulltransfer.go b/artifactory/commands/transferfiles/fulltransfer.go index 4c7a38118..a60d48e41 100644 --- a/artifactory/commands/transferfiles/fulltransfer.go +++ b/artifactory/commands/transferfiles/fulltransfer.go @@ -175,7 +175,8 @@ func (m *fullTransferPhase) searchAndHandleFolderContents(params folderParams, p CheckExistenceInFilestore: m.checkExistenceInFilestore, // Skip file filtering in the Data Transfer plugin if it is already enabled in the JFrog CLI. // The local generated filter is enabled in the JFrog CLI for target Artifactory servers >= 7.55. - SkipFileFiltering: m.locallyGeneratedFilter.IsEnabled(), + SkipFileFiltering: m.locallyGeneratedFilter.IsEnabled(), + MinCheckSumDeploySize: m.minCheckSumDeploySize, } var result []servicesUtils.ResultItem diff --git a/artifactory/commands/transferfiles/longpropertycheck.go b/artifactory/commands/transferfiles/longpropertycheck.go index 99c091a1a..5791c2fb8 100644 --- a/artifactory/commands/transferfiles/longpropertycheck.go +++ b/artifactory/commands/transferfiles/longpropertycheck.go @@ -2,6 +2,7 @@ package transferfiles import ( "encoding/json" + "errors" "fmt" "io" "sync" @@ -228,10 +229,7 @@ func runAqlService(serviceManager artifactory.ArtifactoryServicesManager, query } defer func() { if reader != nil { - e := reader.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(reader.Close())) } }() respBody, err := io.ReadAll(reader) diff --git a/artifactory/commands/transferfiles/phase.go b/artifactory/commands/transferfiles/phase.go index bb93adf87..99ced8985 100644 --- a/artifactory/commands/transferfiles/phase.go +++ b/artifactory/commands/transferfiles/phase.go @@ -38,6 +38,7 @@ type transferPhase interface { setPackageType(packageType string) setDisabledDistinctiveAql() setStopSignal(stopSignal chan os.Signal) + setMinCheckSumDeploySize(minCheckSumDeploySize int64) StopGracefully() } @@ -62,6 +63,7 @@ type phaseBase struct { stopSignal chan os.Signal // Optimization in Artifactory version 7.37 and above enables the exclusion of setting DISTINCT in SQL queries disabledDistinctiveAql bool + minCheckSumDeploySize int64 } func (pb *phaseBase) ShouldStop() bool { @@ -147,6 +149,10 @@ func (pb *phaseBase) setDisabledDistinctiveAql() { pb.disabledDistinctiveAql = true } +func (pb *phaseBase) setMinCheckSumDeploySize(minCheckSumDeploySize int64) { + pb.minCheckSumDeploySize = minCheckSumDeploySize +} + func (pb *phaseBase) setStopSignal(stopSignal chan os.Signal) { pb.stopSignal = stopSignal } diff --git a/artifactory/commands/transferfiles/transfer.go b/artifactory/commands/transferfiles/transfer.go index 62b451026..92840e75f 100644 --- a/artifactory/commands/transferfiles/transfer.go +++ b/artifactory/commands/transferfiles/transfer.go @@ -390,15 +390,16 @@ func (tdc *TransferFilesCommand) transferSingleRepo(sourceRepoKey string, target return } defer func() { - e := restoreFunc() - if err == nil { - err = e - } + err = errors.Join(err, restoreFunc()) }() if err = tdc.initCurThreads(buildInfoRepo); err != nil { return } + minChecksumDeploySize, err := utils.GetMinChecksumDeploySize() + if err != nil { + return + } for currentPhaseId := 0; currentPhaseId < NumberOfPhases; currentPhaseId++ { if tdc.shouldStop() { return @@ -413,7 +414,7 @@ func (tdc *TransferFilesCommand) transferSingleRepo(sourceRepoKey string, target if err = tdc.stateManager.SetRepoPhase(currentPhaseId); err != nil { return } - if err = tdc.startPhase(newPhase, sourceRepoKey, buildInfoRepo, *repoSummary, srcUpService); err != nil { + if err = tdc.startPhase(newPhase, sourceRepoKey, buildInfoRepo, *repoSummary, srcUpService, minChecksumDeploySize); err != nil { return } } @@ -479,8 +480,8 @@ func (tdc *TransferFilesCommand) removeOldFilesIfNeeded(repos []string) error { return nil } -func (tdc *TransferFilesCommand) startPhase(newPhase *transferPhase, repo string, buildInfoRepo bool, repoSummary serviceUtils.RepositorySummary, srcUpService *srcUserPluginService) error { - tdc.initNewPhase(*newPhase, srcUpService, repoSummary, repo, buildInfoRepo) +func (tdc *TransferFilesCommand) startPhase(newPhase *transferPhase, repo string, buildInfoRepo bool, repoSummary serviceUtils.RepositorySummary, srcUpService *srcUserPluginService, minChecksumDeploySize int64) error { + tdc.initNewPhase(*newPhase, srcUpService, repoSummary, repo, buildInfoRepo, minChecksumDeploySize) skip, err := (*newPhase).shouldSkipPhase() if err != nil || skip { return err @@ -546,7 +547,7 @@ func (tdc *TransferFilesCommand) handleStop(srcUpService *srcUserPluginService) }, &newPhase } -func (tdc *TransferFilesCommand) initNewPhase(newPhase transferPhase, srcUpService *srcUserPluginService, repoSummary serviceUtils.RepositorySummary, repoKey string, buildInfoRepo bool) { +func (tdc *TransferFilesCommand) initNewPhase(newPhase transferPhase, srcUpService *srcUserPluginService, repoSummary serviceUtils.RepositorySummary, repoKey string, buildInfoRepo bool, minChecksumDeploySize int64) { newPhase.setContext(tdc.context) newPhase.setRepoKey(repoKey) newPhase.setCheckExistenceInFilestore(tdc.checkExistenceInFilestore) @@ -561,6 +562,7 @@ func (tdc *TransferFilesCommand) initNewPhase(newPhase transferPhase, srcUpServi newPhase.setPackageType(repoSummary.PackageType) newPhase.setLocallyGeneratedFilter(tdc.locallyGeneratedFilter) newPhase.setStopSignal(tdc.stopSignal) + newPhase.setMinCheckSumDeploySize(minChecksumDeploySize) } // Get all local and build-info repositories of the input server @@ -638,10 +640,7 @@ func (tdc *TransferFilesCommand) cleanup(originalErr error, sourceRepos []string err = originalErr // Quit progress bar (before printing logs) if tdc.progressbar != nil { - e := tdc.progressbar.Quit() - if err == nil { - err = e - } + err = errors.Join(err, tdc.progressbar.Quit()) } // Transferring finished successfully if originalErr == nil { diff --git a/artifactory/commands/transferfiles/utils.go b/artifactory/commands/transferfiles/utils.go index a9b77f067..622f768e4 100644 --- a/artifactory/commands/transferfiles/utils.go +++ b/artifactory/commands/transferfiles/utils.go @@ -430,6 +430,7 @@ func uploadByChunks(files []api.FileRepresentation, uploadTokensChan chan Upload TargetAuth: createTargetAuth(base.targetRtDetails, base.proxyKey), CheckExistenceInFilestore: base.checkExistenceInFilestore, SkipFileFiltering: base.locallyGeneratedFilter.IsEnabled(), + MinCheckSumDeploySize: base.minCheckSumDeploySize, } for _, item := range files { diff --git a/artifactory/commands/utils/precheckrunner/checkrunner.go b/artifactory/commands/utils/precheckrunner/checkrunner.go index eca8a5e2e..62b435453 100644 --- a/artifactory/commands/utils/precheckrunner/checkrunner.go +++ b/artifactory/commands/utils/precheckrunner/checkrunner.go @@ -2,6 +2,7 @@ package precheckrunner import ( "context" + "errors" "fmt" "github.com/gookit/color" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -116,9 +117,7 @@ func (pcr *PreCheckRunner) Run(context context.Context, serverDetails *config.Se } // Execute checks defer func() { - if e := pcr.cleanup(); e != nil && err == nil { - err = e - } + err = errors.Join(err, pcr.cleanup()) }() var checkPassed bool for i, check := range pcr.checks { diff --git a/artifactory/commands/utils/result_test.go b/artifactory/commands/utils/result_test.go index 81423a2ff..380fdbe76 100644 --- a/artifactory/commands/utils/result_test.go +++ b/artifactory/commands/utils/result_test.go @@ -2,6 +2,7 @@ package utils import ( biutils "github.com/jfrog/build-info-go/utils" + ioutils "github.com/jfrog/gofrog/io" testsutils "github.com/jfrog/jfrog-client-go/utils/tests" "os" "path" @@ -43,12 +44,7 @@ func createTempDeployableArtifactFile() (filePath string, err error) { if errorutils.CheckError(err) != nil { return } - defer func() { - e := summary.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(summary, &err) tmpDir, err := fileutils.CreateTempDir() if err != nil { return diff --git a/artifactory/commands/utils/transfer.go b/artifactory/commands/utils/transfer.go index 71fabd4b1..ae9117b25 100644 --- a/artifactory/commands/utils/transfer.go +++ b/artifactory/commands/utils/transfer.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/gocarina/gocsv" + ioutils "github.com/jfrog/gofrog/io" logutils "github.com/jfrog/jfrog-cli-core/v2/utils/log" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -54,12 +55,7 @@ func CreateCSVFile(filePrefix string, items interface{}, timeStarted time.Time) return } csvPath = summaryCsv.Name() - defer func() { - e := summaryCsv.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(summaryCsv, &err) // Marshal JSON typed items array to CSV file err = errorutils.CheckError(gocsv.MarshalFile(items, summaryCsv)) return diff --git a/artifactory/commands/utils/yarnutils.go b/artifactory/commands/utils/yarnutils.go index cde89b031..6d7880152 100644 --- a/artifactory/commands/utils/yarnutils.go +++ b/artifactory/commands/utils/yarnutils.go @@ -2,6 +2,7 @@ package utils import ( "encoding/json" + ioutils "github.com/jfrog/gofrog/io" "io" "strconv" "strings" @@ -54,12 +55,7 @@ func getDependencyInfo(name, ver string, previousBuildDependencies map[string]*e if err != nil { return } - defer func() { - e := stream.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(stream, &err) var result []byte result, err = io.ReadAll(stream) if err != nil { diff --git a/artifactory/utils/container/buildinfo.go b/artifactory/utils/container/buildinfo.go index 7a42f4ac4..412f2f4ab 100644 --- a/artifactory/utils/container/buildinfo.go +++ b/artifactory/utils/container/buildinfo.go @@ -2,6 +2,7 @@ package container import ( "encoding/json" + ioutils "github.com/jfrog/gofrog/io" "os" "path" "strings" @@ -94,12 +95,7 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut return } reader := content.NewContentReader(pathToFile, content.DefaultKey) - defer func() { - e := reader.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(reader, &err) _, err = serviceManager.SetProps(services.PropsParams{Reader: reader, Props: props}) return } @@ -118,9 +114,7 @@ func writeLayersToFile(layers []utils.ResultItem) (filePath string, err error) { if err != nil { return } - defer func() { - err = writer.Close() - }() + defer ioutils.Close(writer, &err) for _, layer := range layers { writer.Write(layer) } @@ -200,11 +194,7 @@ func performSearch(imagePathPattern string, serviceManager artifactory.Artifacto if err != nil { return nil, err } - defer func() { - if deferErr := reader.Close(); err == nil { - err = deferErr - } - }() + defer ioutils.Close(reader, &err) resultMap = make(map[string]*utils.ResultItem) for resultItem := new(utils.ResultItem); reader.NextRecord(resultItem) == nil; resultItem = new(utils.ResultItem) { resultMap[resultItem.Name] = resultItem @@ -224,11 +214,7 @@ func performMultiPlatformImageSearch(imagePathPattern string, serviceManager art if err != nil { return nil, err } - defer func() { - if deferErr := reader.Close(); err == nil { - err = deferErr - } - }() + defer ioutils.Close(reader, &err) pathToSha2 := make(map[string]string) pathToImageLayers := make(map[string][]*utils.ResultItem) resultMap = make(map[string][]*utils.ResultItem) diff --git a/artifactory/utils/npm/config-list.go b/artifactory/utils/npm/config-list.go index 477648dbf..d129ef53c 100644 --- a/artifactory/utils/npm/config-list.go +++ b/artifactory/utils/npm/config-list.go @@ -1,6 +1,7 @@ package npm import ( + "errors" gofrogcmd "github.com/jfrog/gofrog/io" npmutils "github.com/jfrog/jfrog-cli-core/v2/utils/npm" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -12,10 +13,7 @@ import ( func GetConfigList(npmFlags []string, executablePath string) (data []byte, err error) { pipeReader, pipeWriter := io.Pipe() defer func(pipeReader *io.PipeReader) { - e := pipeReader.Close() - if err == nil { - err = e - } + err = errors.Join(err, pipeReader.Close()) }(pipeReader) npmFlags = append(npmFlags, "--json=false") diff --git a/artifactory/utils/npm/pack.go b/artifactory/utils/npm/pack.go index 0cefe8cd8..9f9686709 100644 --- a/artifactory/utils/npm/pack.go +++ b/artifactory/utils/npm/pack.go @@ -8,13 +8,13 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) -func Pack(npmFlags []string, executablePath string) (string, error) { +func Pack(npmFlags []string, executablePath string) ([]string, error) { configListCmdConfig := createPackCmdConfig(executablePath, npmFlags) output, err := gofrogcmd.RunCmdOutput(configListCmdConfig) if err != nil { - return "", errorutils.CheckError(err) + return []string{}, errorutils.CheckError(err) } - return getPackageFileNameFromOutput(output) + return getPackageFileNameFromOutput(output), nil } func createPackCmdConfig(executablePath string, splitFlags []string) *npmutils.NpmConfig { @@ -27,8 +27,7 @@ func createPackCmdConfig(executablePath string, splitFlags []string) *npmutils.N } } -func getPackageFileNameFromOutput(output string) (string, error) { +func getPackageFileNameFromOutput(output string) []string { output = strings.TrimSpace(output) - lines := strings.Split(output, "\n") - return strings.TrimSpace(lines[len(lines)-1]), nil + return strings.Split(output, "\n") } diff --git a/artifactory/utils/npm/pack_test.go b/artifactory/utils/npm/pack_test.go index 8b8533b4b..68ec6c5c8 100644 --- a/artifactory/utils/npm/pack_test.go +++ b/artifactory/utils/npm/pack_test.go @@ -1,36 +1,66 @@ package npm import ( + biutils "github.com/jfrog/build-info-go/build/utils" + "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-client-go/utils/log" + testsUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" "os" "path/filepath" "testing" ) -const testdataDir = "../testdata/npm/" +const minimumWorkspacesNpmVersion = "7.24.2" -func TestGetPackageFileNameFromOutput(t *testing.T) { - tests := []struct { - testName string - outputTestDataFile string - expectedPackageFilename string - }{ - {"Get package filename for npm 6", "npmPackOutputV6", "npm-example-0.0.3.tgz"}, - {"Get package filename for npm 7", "npmPackOutputV7", "npm-example-ver0.0.3.tgz"}, - } - for _, test := range tests { - t.Run(test.testName, func(t *testing.T) { - output, err := os.ReadFile(filepath.Join(testdataDir, test.outputTestDataFile)) - if err != nil { - assert.NoError(t, err) - return - } - actualFilename, err := getPackageFileNameFromOutput(string(output)) - if err != nil { - assert.NoError(t, err) - return - } - assert.Equal(t, test.expectedPackageFilename, actualFilename) - }) +func TestNpmPackWorkspaces(t *testing.T) { + + npmVersion, executablePath, err := biutils.GetNpmVersionAndExecPath(nil) + assert.NoError(t, err) + // In npm under v7 skip test + if npmVersion.Compare(minimumWorkspacesNpmVersion) > 0 { + log.Info("Test skipped as this function in not supported in npm version " + npmVersion.GetVersion()) + return } + + tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + + npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-workspaces") + err = utils.CopyDir(npmProjectPath, tmpDir, true, nil) + assert.NoError(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir) + defer chdirCallback() + + packedFileNames, err := Pack([]string{"--workspaces", "--verbose"}, executablePath) + assert.NoError(t, err) + + expected := []string{"module1-1.0.0.tgz", "module2-1.0.0.tgz"} + assert.Equal(t, expected, packedFileNames) +} + +func TestNpmPack(t *testing.T) { + + _, executablePath, err := biutils.GetNpmVersionAndExecPath(nil) + assert.NoError(t, err) + tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-workspaces") + err = utils.CopyDir(npmProjectPath, tmpDir, false, nil) + assert.NoError(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir) + defer chdirCallback() + + packedFileNames, err := Pack([]string{"--verbose"}, executablePath) + assert.NoError(t, err) + + expected := []string{"npm-pack-test-1.0.0.tgz"} + assert.Equal(t, expected, packedFileNames) } diff --git a/artifactory/utils/search.go b/artifactory/utils/search.go index a8888c82e..c72d68d76 100644 --- a/artifactory/utils/search.go +++ b/artifactory/utils/search.go @@ -2,6 +2,9 @@ package utils import ( "encoding/json" + "errors" + ioutils "github.com/jfrog/gofrog/io" + "github.com/jfrog/jfrog-client-go/artifactory" "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-client-go/artifactory/services" @@ -68,12 +71,7 @@ func AqlResultToSearchResult(readers []*content.ContentReader) (contentReader *c if err != nil { return nil, err } - defer func() { - e := writer.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(writer, &err) for _, reader := range readers { for searchResult := new(utils.ResultItem); reader.NextRecord(searchResult) == nil; searchResult = new(utils.ResultItem) { if err != nil { @@ -147,12 +145,7 @@ func SearchResultNoDate(reader *content.ContentReader) (contentReader *content.C if err != nil { return nil, err } - defer func() { - e := writer.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(writer, &err) for resultItem := new(SearchResult); reader.NextRecord(resultItem) == nil; resultItem = new(SearchResult) { if err != nil { return nil, err @@ -170,3 +163,28 @@ func SearchResultNoDate(reader *content.ContentReader) (contentReader *content.C contentReader = content.NewContentReader(writer.GetFilePath(), writer.GetArrayKey()) return } + +func SearchFiles(servicesManager artifactory.ArtifactoryServicesManager, spec *spec.SpecFiles) (searchResults []*content.ContentReader, callbackFunc func() error, err error) { + callbackFunc = func() error { + var errs error + for _, reader := range searchResults { + errs = errors.Join(errs, reader.Close()) + } + return errs + } + + var curSearchParams services.SearchParams + var curReader *content.ContentReader + for i := 0; i < len(spec.Files); i++ { + curSearchParams, err = GetSearchParams(spec.Get(i)) + if err != nil { + return + } + curReader, err = servicesManager.SearchFiles(curSearchParams) + if err != nil { + return + } + searchResults = append(searchResults, curReader) + } + return +} diff --git a/artifactory/utils/testdata/npm/npmPackOutputV6 b/artifactory/utils/testdata/npm/npmPackOutputV6 deleted file mode 100644 index b0eb2668b..000000000 --- a/artifactory/utils/testdata/npm/npmPackOutputV6 +++ /dev/null @@ -1,28 +0,0 @@ -> npm-example@0.0.3 prepack /Users/robin/proj/project-examples/npm-example -> echo pre-helloworld - -pre-helloworld - -> npm-example@0.0.3 postpack /Users/robin/proj/project-examples/npm-example -> echo post-helloworld - -post-helloworld -npm notice -npm notice 📦 npm-example@0.0.3 -npm notice === Tarball Contents === -npm notice 181B helloworld.js -npm notice 276B package.json -npm notice 2.8kB README.md -npm notice 5.5kB npm-example-ver0.0.3.tgz -npm notice 97B .jfrog/projects/npm.yaml -npm notice === Tarball Details === -npm notice name: npm-example -npm notice version: 0.0.3 -npm notice filename: npm-example-0.0.3.tgz -npm notice package size: 7.5 kB -npm notice unpacked size: 8.8 kB -npm notice shasum: fd0a95ccbb62ff833cd89cf4bb5296486c9a63aa -npm notice integrity: sha512-pMRH9mUXGZzeC[...]eJk8tQc1qSbRA== -npm notice total files: 5 -npm notice -npm-example-0.0.3.tgz \ No newline at end of file diff --git a/artifactory/utils/testdata/npm/npmPackOutputV7 b/artifactory/utils/testdata/npm/npmPackOutputV7 deleted file mode 100644 index 602a97930..000000000 --- a/artifactory/utils/testdata/npm/npmPackOutputV7 +++ /dev/null @@ -1,28 +0,0 @@ -> npm-example@ver0.0.3 prepack -> echo pre-helloworld - -pre-helloworld - -> npm-example@ver0.0.3 postpack -> echo post-helloworld - -post-helloworld -npm notice -npm notice 📦 npm-example@ver0.0.3 -npm notice === Tarball Contents === -npm notice 2.8kB README.md -npm notice 97B .jfrog/projects/npm.yaml -npm notice 181B helloworld.js -npm notice 3.5kB npm-example-ver0.0.3.tgz -npm notice 279B package.json -npm notice === Tarball Details === -npm notice name: npm-example -npm notice version: ver0.0.3 -npm notice filename: npm-example-ver0.0.3.tgz -npm notice package size: 5.5 kB -npm notice unpacked size: 6.8 kB -npm notice shasum: e3af25617b6c58c7f803d919949fc3d8993ce9dc -npm notice integrity: sha512-nTrTk6ph83jLL[...]ipNTJhWUci8Wg== -npm notice total files: 5 -npm notice -npm-example-ver0.0.3.tgz \ No newline at end of file diff --git a/artifactory/utils/transfersettings.go b/artifactory/utils/transfersettings.go index 1a3d36fd8..705743c31 100644 --- a/artifactory/utils/transfersettings.go +++ b/artifactory/utils/transfersettings.go @@ -3,6 +3,7 @@ package utils import ( "bytes" "encoding/json" + "errors" "os" "path/filepath" @@ -54,10 +55,7 @@ func LoadTransferSettings() (settings *TransferSettings, err error) { unlockFunc, err := lock.CreateLock(filepath.Join(locksDirPath, transferSettingsLockFile)) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return @@ -99,10 +97,7 @@ func SaveTransferSettings(settings *TransferSettings) (err error) { unlockFunc, err := lock.CreateLock(filepath.Join(locksDirPath, transferSettingsLockFile)) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return diff --git a/artifactory/utils/upload.go b/artifactory/utils/upload.go index 11829a5ab..ed00f67f9 100644 --- a/artifactory/utils/upload.go +++ b/artifactory/utils/upload.go @@ -1,8 +1,13 @@ package utils import ( + "os" + "strconv" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io" ) @@ -18,3 +23,16 @@ type UploadConfiguration struct { SplitCount int MinSplitSizeMB int64 } + +func GetMinChecksumDeploySize() (int64, error) { + minChecksumDeploySize := os.Getenv("JFROG_CLI_MIN_CHECKSUM_DEPLOY_SIZE_KB") + if minChecksumDeploySize == "" { + return services.DefaultMinChecksumDeploy, nil + } + minSize, err := strconv.ParseInt(minChecksumDeploySize, 10, 64) + err = errorutils.CheckError(err) + if err != nil { + return 0, err + } + return minSize * 1000, nil +} diff --git a/artifactory/utils/utils.go b/artifactory/utils/utils.go index ab4c6a882..1699a4d43 100644 --- a/artifactory/utils/utils.go +++ b/artifactory/utils/utils.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + ioutils "github.com/jfrog/gofrog/io" "io" "net/http" "net/url" @@ -227,11 +228,7 @@ func RemoteUnmarshal(serviceManager artifactory.ArtifactoryServicesManager, remo if err != nil { return } - defer func() { - if localErr := ioReaderCloser.Close(); err == nil { - err = localErr - } - }() + defer ioutils.Close(ioReaderCloser, &err) content, err := io.ReadAll(ioReaderCloser) if err != nil { return errorutils.CheckError(err) diff --git a/buildscripts/download-jars.sh b/buildscripts/download-jars.sh deleted file mode 100755 index 27f1d20b1..000000000 --- a/buildscripts/download-jars.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# Please use this script to download the JAR files for maven-dep-tree and gradle-dep-tree into the directory utils/java/. -# These JARs allow us to build Maven and Gradle dependency trees efficiently and without compilation. -# Learn more about them here: -# https://github.com/jfrog/gradle-dep-tree -# https://github.com/jfrog/maven-dep-tree - -# Once you have updated the versions mentioned below, please execute this script from the root directory of the jfrog-cli-core to ensure the JAR files are updated. -GRADLE_DEP_TREE_VERSION="3.0.1" -# Changing this version also requires a change in mavenDepTreeVersion within utils/java/mvn.go. -MAVEN_DEP_TREE_VERSION="1.1.0" - -curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/gradle-dep-tree/${GRADLE_DEP_TREE_VERSION}/gradle-dep-tree-${GRADLE_DEP_TREE_VERSION}.jar -o utils/java/resources/gradle-dep-tree.jar -curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/maven-dep-tree/${MAVEN_DEP_TREE_VERSION}/maven-dep-tree-${MAVEN_DEP_TREE_VERSION}.jar -o utils/java/resources/maven-dep-tree.jar diff --git a/common/build/buildutils.go b/common/build/buildutils.go index df222bd5c..4184403aa 100644 --- a/common/build/buildutils.go +++ b/common/build/buildutils.go @@ -120,10 +120,7 @@ func saveBuildData(action interface{}, buildName, buildNumber, projectKey string return err } defer func() { - e := tempFile.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(tempFile.Close())) }() _, err = tempFile.Write(content.Bytes()) return err @@ -149,10 +146,7 @@ func SaveBuildInfo(buildName, buildNumber, projectKey string, buildInfo *buildIn return err } defer func() { - e := tempFile.Close() - if err == nil { - err = errorutils.CheckError(e) - } + err = errors.Join(err, errorutils.CheckError(tempFile.Close())) }() _, err = tempFile.Write(content.Bytes()) return errorutils.CheckError(err) diff --git a/common/commands/config.go b/common/commands/config.go index a0802387c..53ce635e4 100644 --- a/common/commands/config.go +++ b/common/commands/config.go @@ -118,10 +118,7 @@ func (cc *ConfigCommand) Run() (err error) { unlockFunc, err := lock.CreateLock(lockDirPath) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return diff --git a/common/progressbar/filesprogressbar.go b/common/progressbar/filesprogressbar.go index 17de11da0..159f11af9 100644 --- a/common/progressbar/filesprogressbar.go +++ b/common/progressbar/filesprogressbar.go @@ -1,6 +1,7 @@ package progressbar import ( + "errors" "net/url" "os" "strings" @@ -323,10 +324,7 @@ func ExecWithProgress(cmd CommandWithProgress) (err error) { if progressBar != nil { cmd.SetProgress(progressBar) defer func() { - e := progressBar.Quit() - if err == nil { - err = e - } + err = errors.Join(err, progressBar.Quit()) }() } err = commands.Exec(cmd) diff --git a/go.mod b/go.mod index 0cc3899cd..a4c898986 100644 --- a/go.mod +++ b/go.mod @@ -7,25 +7,25 @@ require github.com/c-bata/go-prompt v0.2.5 // Should not be updated to 0.2.6 due require ( github.com/buger/jsonparser v1.1.1 github.com/chzyer/readline v1.5.1 - github.com/forPelevin/gomoji v1.1.8 + github.com/forPelevin/gomoji v1.2.0 github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 - github.com/jedib0t/go-pretty/v6 v6.5.4 - github.com/jfrog/build-info-go v1.9.23 + github.com/jedib0t/go-pretty/v6 v6.5.6 + github.com/jfrog/build-info-go v1.9.25 github.com/jfrog/gofrog v1.6.3 - github.com/jfrog/jfrog-client-go v1.37.1 + github.com/jfrog/jfrog-client-go v1.39.0 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.14 github.com/vbauerster/mpb/v7 v7.5.3 - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a - golang.org/x/mod v0.15.0 + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 + golang.org/x/mod v0.16.0 golang.org/x/sync v0.6.0 - golang.org/x/term v0.17.0 + golang.org/x/term v0.18.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -88,16 +88,16 @@ require ( github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/tools v0.19.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6 +// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240327154209-77a304635e42 -replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c +// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240319160313-0093dee91fc1 // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.3.3-0.20231223133729-ef57bd08cedc diff --git a/go.sum b/go.sum index 172a51532..a5a38cc2e 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/forPelevin/gomoji v1.1.8 h1:JElzDdt0TyiUlecy6PfITDL6eGvIaxqYH1V52zrd0qQ= -github.com/forPelevin/gomoji v1.1.8/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= +github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= +github.com/forPelevin/gomoji v1.2.0/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= @@ -79,16 +79,16 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/5l91s= -github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= +github.com/jedib0t/go-pretty/v6 v6.5.6 h1:nKXVLqPfAwY7sWcYXdNZZZ2fjqDpAtj9UeWupgfUxSg= +github.com/jedib0t/go-pretty/v6 v6.5.6/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c h1:M1QiuCYGCYN1IiGyxogrLzfetYGkkhE2pgDh5K4Wo9A= -github.com/jfrog/build-info-go v1.8.9-0.20240225113943-096bf22ca54c/go.mod h1:QHcKuesY4MrBVBuEwwBz4uIsX6mwYuMEDV09ng4AvAU= +github.com/jfrog/build-info-go v1.9.25 h1:IkjydGQA/HjOWjRaoKq1hOEgCCyBEJwQgXJSo4WVBSA= +github.com/jfrog/build-info-go v1.9.25/go.mod h1:doFB4bFDVHeGulD6GF9LzsrRaIOrSoklV9DgIAEqHgc= github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6 h1:W+79g2W3ARRhIZtBfG0t73fi4IlyiIRWwdm1tajOkkc= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240228121257-3414cc0ffcb6/go.mod h1:WhVrqiqhSNFwj58/RQIrJEd28PHH1LTD4eWE0vBXv1o= +github.com/jfrog/jfrog-client-go v1.39.0 h1:GZ1qbpUDzYz8ZEycYicDkbVMN2H0VSCuz8mUNTyf7tc= +github.com/jfrog/jfrog-client-go v1.39.0/go.mod h1:tUyEmxznphh0nwAGo6xz9Sps7RRW/TBMxIJZteo+j2k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -180,8 +180,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= @@ -211,14 +212,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -226,8 +227,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -260,15 +261,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -282,8 +283,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/lifecycle/common.go b/lifecycle/common.go index e185cb578..5e1b2d0a2 100644 --- a/lifecycle/common.go +++ b/lifecycle/common.go @@ -2,10 +2,12 @@ package lifecycle import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/lifecycle" "github.com/jfrog/jfrog-client-go/lifecycle/services" clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/distribution" ) const minimalLifecycleArtifactoryVersion = "7.63.2" @@ -14,7 +16,6 @@ type releaseBundleCmd struct { serverDetails *config.ServerDetails releaseBundleName string releaseBundleVersion string - signingKeyName string sync bool rbProjectKey string } @@ -49,3 +50,21 @@ func validateArtifactoryVersionSupported(serverDetails *config.ServerDetails) er return clientUtils.ValidateMinimumVersion(clientUtils.Artifactory, versionStr, minimalLifecycleArtifactoryVersion) } + +// If distribution rules are empty, distribute to all edges. +func getAggregatedDistRules(distributionRules *spec.DistributionRules) (aggregatedRules []*distribution.DistributionCommonParams) { + if isDistributionRulesEmpty(distributionRules) { + aggregatedRules = append(aggregatedRules, &distribution.DistributionCommonParams{SiteName: "*"}) + } else { + for _, rules := range distributionRules.DistributionRules { + aggregatedRules = append(aggregatedRules, rules.ToDistributionCommonParams()) + } + } + return +} + +func isDistributionRulesEmpty(distributionRules *spec.DistributionRules) bool { + return distributionRules == nil || + len(distributionRules.DistributionRules) == 0 || + len(distributionRules.DistributionRules) == 1 && distributionRules.DistributionRules[0].IsEmpty() +} diff --git a/lifecycle/createcommon.go b/lifecycle/createcommon.go index 4d5fba599..6b0e7f35c 100644 --- a/lifecycle/createcommon.go +++ b/lifecycle/createcommon.go @@ -1,11 +1,24 @@ package lifecycle import ( + "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/jfrog/jfrog-client-go/utils/errorutils" +) + +const ( + missingCreationSourcesErrMsg = "unexpected err while validating spec - could not detect any creation sources" + multipleCreationSourcesErrMsg = "multiple creation sources were detected in separate spec files. Only a single creation source should be provided. Detected:" + singleAqlErrMsg = "only a single aql query can be provided" ) type ReleaseBundleCreateCommand struct { releaseBundleCmd + signingKeyName string + spec *spec.SpecFiles + // Backward compatibility: buildsSpecPath string releaseBundlesSpecPath string } @@ -44,11 +57,18 @@ func (rbc *ReleaseBundleCreateCommand) SetReleaseBundleProject(rbProjectKey stri return rbc } +func (rbc *ReleaseBundleCreateCommand) SetSpec(spec *spec.SpecFiles) *ReleaseBundleCreateCommand { + rbc.spec = spec + return rbc +} + +// Deprecated func (rbc *ReleaseBundleCreateCommand) SetBuildsSpecPath(buildsSpecPath string) *ReleaseBundleCreateCommand { rbc.buildsSpecPath = buildsSpecPath return rbc } +// Deprecated func (rbc *ReleaseBundleCreateCommand) SetReleaseBundlesSpecPath(releaseBundlesSpecPath string) *ReleaseBundleCreateCommand { rbc.releaseBundlesSpecPath = releaseBundlesSpecPath return rbc @@ -72,8 +92,162 @@ func (rbc *ReleaseBundleCreateCommand) Run() error { return err } - if rbc.buildsSpecPath != "" { + sourceType, err := rbc.identifySourceType() + if err != nil { + return err + } + + switch sourceType { + case services.Aql: + return rbc.createFromAql(servicesManager, rbDetails, queryParams) + case services.Artifacts: + return rbc.createFromArtifacts(servicesManager, rbDetails, queryParams) + case services.Builds: return rbc.createFromBuilds(servicesManager, rbDetails, queryParams) + case services.ReleaseBundles: + return rbc.createFromReleaseBundles(servicesManager, rbDetails, queryParams) + default: + return errorutils.CheckErrorf("unknown source for release bundle creation was provided") + } +} + +func (rbc *ReleaseBundleCreateCommand) identifySourceType() (services.SourceType, error) { + switch { + case rbc.buildsSpecPath != "": + return services.Builds, nil + case rbc.releaseBundlesSpecPath != "": + return services.ReleaseBundles, nil + case rbc.spec != nil: + return validateAndIdentifyRbCreationSpec(rbc.spec.Files) + default: + return "", errorutils.CheckErrorf("a spec file input is mandatory") + } +} + +func validateAndIdentifyRbCreationSpec(files []spec.File) (services.SourceType, error) { + if len(files) == 0 { + return "", errorutils.CheckErrorf("spec must include at least one file group") + } + + var detectedCreationSources []services.SourceType + for _, file := range files { + sourceType, err := validateFile(file) + if err != nil { + return "", err + } + detectedCreationSources = append(detectedCreationSources, sourceType) + } + + if err := validateCreationSources(detectedCreationSources); err != nil { + return "", err + } + return detectedCreationSources[0], nil +} + +func validateCreationSources(detectedCreationSources []services.SourceType) error { + if len(detectedCreationSources) == 0 { + return errorutils.CheckErrorf(missingCreationSourcesErrMsg) + } + + // Assert single creation source. + for i := 1; i < len(detectedCreationSources); i++ { + if detectedCreationSources[i] != detectedCreationSources[0] { + return generateSingleCreationSourceErr(detectedCreationSources) + } + } + + // If aql, assert single file. + if detectedCreationSources[0] == services.Aql && len(detectedCreationSources) > 1 { + return errorutils.CheckErrorf(singleAqlErrMsg) + } + return nil +} + +func generateSingleCreationSourceErr(detectedCreationSources []services.SourceType) error { + var detectedStr []string + for _, source := range detectedCreationSources { + detectedStr = append(detectedStr, string(source)) + } + return errorutils.CheckErrorf( + "%s '%s'", multipleCreationSourcesErrMsg, coreutils.ListToText(detectedStr)) +} + +func validateFile(file spec.File) (services.SourceType, error) { + // Aql creation source: + isAql := len(file.Aql.ItemsFind) > 0 + + // Build creation source: + isBuild := len(file.Build) > 0 + isIncludeDeps, _ := file.IsIncludeDeps(false) + + // Bundle creation source: + isBundle := len(file.Bundle) > 0 + + // Build & bundle: + isProject := len(file.Project) > 0 + + // Artifacts creation source: + isPattern := len(file.Pattern) > 0 + isExclusions := len(file.Exclusions) > 0 && len(file.Exclusions[0]) > 0 + isProps := len(file.Props) > 0 + isExcludeProps := len(file.ExcludeProps) > 0 + isRecursive, err := file.IsRecursive(true) + if err != nil { + return "", errorutils.CheckErrorf("invalid value provided to the 'recursive' field. error: %s", err.Error()) + } + + // Unsupported: + isPathMapping := len(file.PathMapping.Input) > 0 || len(file.PathMapping.Output) > 0 + isTarget := len(file.Target) > 0 + isSortOrder := len(file.SortOrder) > 0 + isSortBy := len(file.SortBy) > 0 + isExcludeArtifacts, _ := file.IsExcludeArtifacts(false) + isGPGKey := len(file.PublicGpgKey) > 0 + isOffset := file.Offset > 0 + isLimit := file.Limit > 0 + isArchive := len(file.Archive) > 0 + isSymlinks, _ := file.IsSymlinks(false) + isRegexp := file.Regexp == "true" + isAnt := file.Ant == "true" + isExplode, _ := file.IsExplode(false) + isBypassArchiveInspection, _ := file.IsBypassArchiveInspection(false) + isTransitive, _ := file.IsTransitive(false) + + if isPathMapping || isTarget || isSortOrder || isSortBy || isExcludeArtifacts || isGPGKey || isOffset || isLimit || + isSymlinks || isArchive || isAnt || isRegexp || isExplode || isBypassArchiveInspection || isTransitive { + return "", errorutils.CheckErrorf("unsupported fields were provided in file spec. " + + "release bundle creation file spec only supports the following fields: " + + "'aql', 'build', 'includeDeps', 'bundle', 'project', 'pattern', 'exclusions', 'props', 'excludeProps' and 'recursive'") + } + if coreutils.SumTrueValues([]bool{isAql, isBuild, isBundle, isPattern}) != 1 { + return "", errorutils.CheckErrorf("exactly one creation source is supported (aql, builds, release bundles or pattern (artifacts))") + } + + switch { + case isAql: + return services.Aql, + validateCreationSource([]bool{isIncludeDeps, isProject, isExclusions, isProps, isExcludeProps, !isRecursive}, + "aql creation source supports no other fields") + case isBuild: + return services.Builds, + validateCreationSource([]bool{isExclusions, isProps, isExcludeProps, !isRecursive}, + "builds creation source only supports the 'includeDeps' and 'project' fields") + case isBundle: + return services.ReleaseBundles, + validateCreationSource([]bool{isIncludeDeps, isExclusions, isProps, isExcludeProps, !isRecursive}, + "release bundles creation source only supports the 'project' field") + case isPattern: + return services.Artifacts, + validateCreationSource([]bool{isIncludeDeps, isProject}, + "release bundles creation source only supports the 'exclusions', 'props', 'excludeProps' and 'recursive' fields") + default: + return "", errorutils.CheckErrorf("unexpected err in spec validation") + } +} + +func validateCreationSource(unsupportedFields []bool, errMsg string) error { + if coreutils.SumTrueValues(unsupportedFields) > 0 { + return errorutils.CheckErrorf(errMsg) } - return rbc.createFromReleaseBundles(servicesManager, rbDetails, queryParams) + return nil } diff --git a/lifecycle/createcommon_test.go b/lifecycle/createcommon_test.go new file mode 100644 index 000000000..a0c3b598f --- /dev/null +++ b/lifecycle/createcommon_test.go @@ -0,0 +1,76 @@ +package lifecycle + +import ( + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestValidateCreationSources(t *testing.T) { + testCases := []struct { + testName string + detectedCreationSources []services.SourceType + errExpected bool + errMsg string + }{ + {"missing creation sources", []services.SourceType{}, true, missingCreationSourcesErrMsg}, + {"single creation source", []services.SourceType{services.Aql, services.Artifacts, services.Builds}, + true, multipleCreationSourcesErrMsg + " 'aql, artifacts and builds'"}, + {"single aql err", []services.SourceType{services.Aql, services.Aql}, true, singleAqlErrMsg}, + {"valid aql", []services.SourceType{services.Aql}, false, ""}, + {"valid artifacts", []services.SourceType{services.Artifacts, services.Artifacts}, false, ""}, + {"valid builds", []services.SourceType{services.Builds, services.Builds}, false, ""}, + {"valid release bundles", []services.SourceType{services.ReleaseBundles, services.ReleaseBundles}, false, ""}, + } + for _, testCase := range testCases { + t.Run(testCase.testName, func(t *testing.T) { + err := validateCreationSources(testCase.detectedCreationSources) + if testCase.errExpected { + assert.EqualError(t, err, testCase.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateFile(t *testing.T) { + testCases := []struct { + testName string + file spec.File + errExpected bool + expectedSourceType services.SourceType + }{ + {"valid aql", spec.File{Aql: utils.Aql{ItemsFind: "abc"}}, false, services.Aql}, + {"valid build", spec.File{Build: "name/number", IncludeDeps: "true", Project: "project"}, false, services.Builds}, + {"valid bundle", spec.File{Bundle: "name/number", Project: "project"}, false, services.ReleaseBundles}, + {"valid artifacts", + spec.File{ + Pattern: "repo/path/file", + Exclusions: []string{"exclude"}, + Props: "prop", + ExcludeProps: "exclude prop", + Recursive: "false"}, false, services.Artifacts}, + {"invalid fields", spec.File{PathMapping: utils.PathMapping{Input: "input"}, Target: "target"}, true, ""}, + {"multiple creation sources", + spec.File{Aql: utils.Aql{ItemsFind: "abc"}, Build: "name/number", Bundle: "name/number", Pattern: "repo/path/file"}, + true, ""}, + {"invalid aql", spec.File{Aql: utils.Aql{ItemsFind: "abc"}, Props: "prop"}, true, ""}, + {"invalid builds", spec.File{Build: "name/number", Recursive: "false"}, true, ""}, + {"invalid bundles", spec.File{Bundle: "name/number", IncludeDeps: "true"}, true, ""}, + {"invalid artifacts", spec.File{Pattern: "repo/path/file", Project: "proj"}, true, ""}, + } + for _, testCase := range testCases { + t.Run(testCase.testName, func(t *testing.T) { + sourceType, err := validateFile(testCase.file) + if testCase.errExpected { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.expectedSourceType, sourceType) + } + }) + } +} diff --git a/lifecycle/createfromaql.go b/lifecycle/createfromaql.go new file mode 100644 index 000000000..12918bd75 --- /dev/null +++ b/lifecycle/createfromaql.go @@ -0,0 +1,13 @@ +package lifecycle + +import ( + "fmt" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" +) + +func (rbc *ReleaseBundleCreateCommand) createFromAql(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { + aqlQuery := fmt.Sprintf(`items.find(%s)`, rbc.spec.Get(0).Aql.ItemsFind) + return servicesManager.CreateReleaseBundleFromAql(rbDetails, queryParams, rbc.signingKeyName, aqlQuery) +} diff --git a/lifecycle/createfromartifacts.go b/lifecycle/createfromartifacts.go new file mode 100644 index 000000000..8a2633451 --- /dev/null +++ b/lifecycle/createfromartifacts.go @@ -0,0 +1,55 @@ +package lifecycle + +import ( + "errors" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + rtServicesUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/jfrog/jfrog-client-go/utils/io/content" + "github.com/jfrog/jfrog-client-go/utils/log" + "path" +) + +func (rbc *ReleaseBundleCreateCommand) createFromArtifacts(lcServicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) (err error) { + + rtServicesManager, err := utils.CreateServiceManager(rbc.serverDetails, 3, 0, false) + if err != nil { + return err + } + + log.Info("Searching artifacts...") + searchResults, callbackFunc, err := utils.SearchFiles(rtServicesManager, rbc.spec) + defer func() { + err = errors.Join(err, callbackFunc()) + }() + if err != nil { + return err + } + artifactsSource, err := aqlResultToArtifactsSource(searchResults) + if err != nil { + return err + } + + return lcServicesManager.CreateReleaseBundleFromArtifacts(rbDetails, queryParams, rbc.signingKeyName, artifactsSource) +} + +func aqlResultToArtifactsSource(readers []*content.ContentReader) (artifactsSource services.CreateFromArtifacts, err error) { + for _, reader := range readers { + for searchResult := new(rtServicesUtils.ResultItem); reader.NextRecord(searchResult) == nil; searchResult = new(rtServicesUtils.ResultItem) { + if err != nil { + return + } + artifactsSource.Artifacts = append(artifactsSource.Artifacts, services.ArtifactSource{ + Path: path.Join(searchResult.Repo, searchResult.Path, searchResult.Name), + Sha256: searchResult.Sha256, + }) + } + if err = reader.GetError(); err != nil { + return + } + reader.Reset() + } + return +} diff --git a/lifecycle/createfrombuilds.go b/lifecycle/createfrombuilds.go index a955b997a..02e54e644 100644 --- a/lifecycle/createfrombuilds.go +++ b/lifecycle/createfrombuilds.go @@ -3,6 +3,7 @@ package lifecycle import ( "encoding/json" rtUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" rtServices "github.com/jfrog/jfrog-client-go/artifactory/services" "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/lifecycle" @@ -14,27 +15,38 @@ import ( func (rbc *ReleaseBundleCreateCommand) createFromBuilds(servicesManager *lifecycle.LifecycleServicesManager, rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { - builds := CreateFromBuildsSpec{} - content, err := fileutils.ReadFile(rbc.buildsSpecPath) + var buildsSource services.CreateFromBuildsSource + var err error + if rbc.buildsSpecPath != "" { + buildsSource, err = rbc.getBuildSourceFromBuildsSpec() + } else { + buildsSource, err = rbc.convertSpecToBuildsSource(rbc.spec.Files) + } if err != nil { return err } - if err = json.Unmarshal(content, &builds); err != nil { - return errorutils.CheckError(err) - } - if len(builds.Builds) == 0 { + if len(buildsSource.Builds) == 0 { return errorutils.CheckErrorf("at least one build is expected in order to create a release bundle from builds") } - buildsSource, err := rbc.convertToBuildsSource(builds) + return servicesManager.CreateReleaseBundleFromBuilds(rbDetails, queryParams, rbc.signingKeyName, buildsSource) +} + +func (rbc *ReleaseBundleCreateCommand) getBuildSourceFromBuildsSpec() (buildsSource services.CreateFromBuildsSource, err error) { + builds := CreateFromBuildsSpec{} + content, err := fileutils.ReadFile(rbc.buildsSpecPath) if err != nil { - return err + return } - return servicesManager.CreateReleaseBundleFromBuilds(rbDetails, queryParams, rbc.signingKeyName, buildsSource) + if err = json.Unmarshal(content, &builds); errorutils.CheckError(err) != nil { + return + } + + return rbc.convertBuildsSpecToBuildsSource(builds) } -func (rbc *ReleaseBundleCreateCommand) convertToBuildsSource(builds CreateFromBuildsSpec) (services.CreateFromBuildsSource, error) { +func (rbc *ReleaseBundleCreateCommand) convertBuildsSpecToBuildsSource(builds CreateFromBuildsSpec) (services.CreateFromBuildsSource, error) { buildsSource := services.CreateFromBuildsSource{} for _, build := range builds.Builds { buildSource := services.BuildSource{BuildName: build.Name, IncludeDependencies: build.IncludeDependencies} @@ -49,6 +61,29 @@ func (rbc *ReleaseBundleCreateCommand) convertToBuildsSource(builds CreateFromBu return buildsSource, nil } +func (rbc *ReleaseBundleCreateCommand) convertSpecToBuildsSource(files []spec.File) (services.CreateFromBuildsSource, error) { + buildsSource := services.CreateFromBuildsSource{} + for _, file := range files { + buildName, buildNumber, err := rbc.getBuildDetailsFromIdentifier(file.Build, file.Project) + if err != nil { + return services.CreateFromBuildsSource{}, err + } + isIncludeDeps, err := file.IsIncludeDeps(false) + if err != nil { + return services.CreateFromBuildsSource{}, err + } + + buildSource := services.BuildSource{ + BuildName: buildName, + BuildNumber: buildNumber, + BuildRepository: utils.GetBuildInfoRepositoryByProject(file.Project), + IncludeDependencies: isIncludeDeps, + } + buildsSource.Builds = append(buildsSource.Builds, buildSource) + } + return buildsSource, nil +} + func (rbc *ReleaseBundleCreateCommand) getLatestBuildNumberIfEmpty(buildName, buildNumber, project string) (string, error) { if buildNumber != "" { return buildNumber, nil @@ -69,6 +104,22 @@ func (rbc *ReleaseBundleCreateCommand) getLatestBuildNumberIfEmpty(buildName, bu return buildNumber, nil } +func (rbc *ReleaseBundleCreateCommand) getBuildDetailsFromIdentifier(buildIdentifier, project string) (string, string, error) { + aqlService, err := rbc.getAqlService() + if err != nil { + return "", "", err + } + + buildName, buildNumber, err := utils.GetBuildNameAndNumberFromBuildIdentifier(buildIdentifier, project, aqlService) + if err != nil { + return "", "", err + } + if buildName == "" || buildNumber == "" { + return "", "", errorutils.CheckErrorf("could not identify a build info by the '%s' identifier in artifactory", buildIdentifier) + } + return buildName, buildNumber, nil +} + func (rbc *ReleaseBundleCreateCommand) getAqlService() (*rtServices.AqlService, error) { rtServiceManager, err := rtUtils.CreateServiceManager(rbc.serverDetails, 3, 0, false) if err != nil { diff --git a/lifecycle/createfrombundles.go b/lifecycle/createfrombundles.go index 1e9253351..7c18141de 100644 --- a/lifecycle/createfrombundles.go +++ b/lifecycle/createfrombundles.go @@ -2,6 +2,8 @@ package lifecycle import ( "encoding/json" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/lifecycle" "github.com/jfrog/jfrog-client-go/lifecycle/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -11,20 +13,21 @@ import ( func (rbc *ReleaseBundleCreateCommand) createFromReleaseBundles(servicesManager *lifecycle.LifecycleServicesManager, rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { - bundles := CreateFromReleaseBundlesSpec{} - content, err := fileutils.ReadFile(rbc.releaseBundlesSpecPath) + var releaseBundlesSource services.CreateFromReleaseBundlesSource + var err error + if rbc.releaseBundlesSpecPath != "" { + releaseBundlesSource, err = rbc.getReleaseBundlesSourceFromBundlesSpec() + } else { + releaseBundlesSource, err = rbc.convertSpecToReleaseBundlesSource(rbc.spec.Files) + } if err != nil { return err } - if err = json.Unmarshal(content, &bundles); err != nil { - return errorutils.CheckError(err) - } - if len(bundles.ReleaseBundles) == 0 { + if len(releaseBundlesSource.ReleaseBundles) == 0 { return errorutils.CheckErrorf("at least one release bundle is expected in order to create a release bundle from release bundles") } - releaseBundlesSource := rbc.convertToReleaseBundlesSource(bundles) return servicesManager.CreateReleaseBundleFromBundles(rbDetails, queryParams, rbc.signingKeyName, releaseBundlesSource) } @@ -41,6 +44,40 @@ func (rbc *ReleaseBundleCreateCommand) convertToReleaseBundlesSource(bundles Cre return releaseBundlesSource } +func (rbc *ReleaseBundleCreateCommand) convertSpecToReleaseBundlesSource(files []spec.File) (services.CreateFromReleaseBundlesSource, error) { + releaseBundlesSource := services.CreateFromReleaseBundlesSource{} + for _, file := range files { + name, version, err := utils.ParseNameAndVersion(file.Bundle, false) + if err != nil { + return releaseBundlesSource, err + } + if name == "" || version == "" { + return releaseBundlesSource, errorutils.CheckErrorf( + "invalid release bundle source was provided. Both name and version are mandatory. Provided name: '%s', version: '%s'", name, version) + } + rbSource := services.ReleaseBundleSource{ + ReleaseBundleName: name, + ReleaseBundleVersion: version, + ProjectKey: file.Project, + } + releaseBundlesSource.ReleaseBundles = append(releaseBundlesSource.ReleaseBundles, rbSource) + } + return releaseBundlesSource, nil +} + +func (rbc *ReleaseBundleCreateCommand) getReleaseBundlesSourceFromBundlesSpec() (releaseBundlesSource services.CreateFromReleaseBundlesSource, err error) { + releaseBundles := CreateFromReleaseBundlesSpec{} + content, err := fileutils.ReadFile(rbc.releaseBundlesSpecPath) + if err != nil { + return + } + if err = json.Unmarshal(content, &releaseBundles); errorutils.CheckError(err) != nil { + return + } + + return rbc.convertToReleaseBundlesSource(releaseBundles), nil +} + type CreateFromReleaseBundlesSpec struct { ReleaseBundles []SourceReleaseBundleSpec `json:"releaseBundles,omitempty"` } diff --git a/lifecycle/deletelocal.go b/lifecycle/deletelocal.go new file mode 100644 index 000000000..d1e434aa8 --- /dev/null +++ b/lifecycle/deletelocal.go @@ -0,0 +1,132 @@ +package lifecycle + +import ( + "errors" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/jfrog/jfrog-client-go/utils/log" + "strings" +) + +type ReleaseBundleDeleteCommand struct { + releaseBundleCmd + environment string + quiet bool +} + +func NewReleaseBundleDeleteCommand() *ReleaseBundleDeleteCommand { + return &ReleaseBundleDeleteCommand{} +} + +func (rbd *ReleaseBundleDeleteCommand) SetServerDetails(serverDetails *config.ServerDetails) *ReleaseBundleDeleteCommand { + rbd.serverDetails = serverDetails + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleDeleteCommand { + rbd.releaseBundleName = releaseBundleName + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleDeleteCommand { + rbd.releaseBundleVersion = releaseBundleVersion + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetSync(sync bool) *ReleaseBundleDeleteCommand { + rbd.sync = sync + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetReleaseBundleProject(rbProjectKey string) *ReleaseBundleDeleteCommand { + rbd.rbProjectKey = rbProjectKey + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetEnvironment(environment string) *ReleaseBundleDeleteCommand { + rbd.environment = environment + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) SetQuiet(quiet bool) *ReleaseBundleDeleteCommand { + rbd.quiet = quiet + return rbd +} + +func (rbd *ReleaseBundleDeleteCommand) CommandName() string { + return "rb_delete" +} + +func (rbd *ReleaseBundleDeleteCommand) ServerDetails() (*config.ServerDetails, error) { + return rbd.serverDetails, nil +} + +func (rbd *ReleaseBundleDeleteCommand) Run() error { + if err := validateArtifactoryVersionSupported(rbd.serverDetails); err != nil { + return err + } + + servicesManager, rbDetails, queryParams, err := rbd.getPrerequisites() + if err != nil { + return err + } + + if rbd.environment != "" { + return rbd.deletePromotionsOnly(servicesManager, rbDetails, queryParams) + } + return rbd.deleteLocalReleaseBundle(servicesManager, rbDetails, queryParams) +} + +func (rbd *ReleaseBundleDeleteCommand) deletePromotionsOnly(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, commonQueryParams services.CommonOptionalQueryParams) error { + + deletionSubject := fmt.Sprintf("all promotions to environment '%s' of release bundle '%s/%s'", rbd.environment, rbd.releaseBundleName, rbd.releaseBundleVersion) + if !rbd.confirmDelete(deletionSubject) { + return nil + } + + optionalQueryParams := services.GetPromotionsOptionalQueryParams{ProjectKey: commonQueryParams.ProjectKey} + response, err := servicesManager.GetReleaseBundleVersionPromotions(rbDetails, optionalQueryParams) + if err != nil { + return err + } + success := 0 + fail := 0 + for _, promotion := range response.Promotions { + if strings.EqualFold(promotion.Environment, rbd.environment) { + if curErr := servicesManager.DeleteReleaseBundleVersionPromotion(rbDetails, commonQueryParams, promotion.CreatedMillis.String()); curErr != nil { + err = errors.Join(err, curErr) + fail++ + } else { + success++ + } + } + } + if success == 0 && fail == 0 { + log.Info(fmt.Sprintf("No promotions were found for environment '%s'", rbd.environment)) + } else { + log.Info(fmt.Sprintf("Promotions deleted successfully: %d, failed: %d", success, fail)) + } + + return err +} + +func (rbd *ReleaseBundleDeleteCommand) deleteLocalReleaseBundle(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { + deletionSubject := fmt.Sprintf("release bundle '%s/%s' locally with all its promotions", rbd.releaseBundleName, rbd.releaseBundleVersion) + if !rbd.confirmDelete(deletionSubject) { + return nil + } + return servicesManager.DeleteReleaseBundleVersion(rbDetails, queryParams) +} + +func (rbd *ReleaseBundleDeleteCommand) confirmDelete(deletionSubject string) bool { + if rbd.quiet { + return true + } + return coreutils.AskYesNo( + fmt.Sprintf("Are you sure you want to delete %s?\n"+avoidConfirmationMsg, deletionSubject), false) +} diff --git a/lifecycle/deleteremote.go b/lifecycle/deleteremote.go new file mode 100644 index 000000000..6b1665131 --- /dev/null +++ b/lifecycle/deleteremote.go @@ -0,0 +1,159 @@ +package lifecycle + +import ( + "encoding/json" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/distribution" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +const avoidConfirmationMsg = "You can avoid this confirmation message by adding --quiet to the command." + +type ReleaseBundleRemoteDeleteCommand struct { + releaseBundleCmd + distributionRules *spec.DistributionRules + dryRun bool + quiet bool + maxWaitMinutes int +} + +func NewReleaseBundleRemoteDeleteCommand() *ReleaseBundleRemoteDeleteCommand { + return &ReleaseBundleRemoteDeleteCommand{} +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetServerDetails(serverDetails *config.ServerDetails) *ReleaseBundleRemoteDeleteCommand { + rbd.serverDetails = serverDetails + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleRemoteDeleteCommand { + rbd.releaseBundleName = releaseBundleName + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleRemoteDeleteCommand { + rbd.releaseBundleVersion = releaseBundleVersion + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetSync(sync bool) *ReleaseBundleRemoteDeleteCommand { + rbd.sync = sync + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetReleaseBundleProject(rbProjectKey string) *ReleaseBundleRemoteDeleteCommand { + rbd.rbProjectKey = rbProjectKey + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetDistributionRules(distributionRules *spec.DistributionRules) *ReleaseBundleRemoteDeleteCommand { + rbd.distributionRules = distributionRules + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetDryRun(dryRun bool) *ReleaseBundleRemoteDeleteCommand { + rbd.dryRun = dryRun + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetQuiet(quiet bool) *ReleaseBundleRemoteDeleteCommand { + rbd.quiet = quiet + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) SetMaxWaitMinutes(maxWaitMinutes int) *ReleaseBundleRemoteDeleteCommand { + rbd.maxWaitMinutes = maxWaitMinutes + return rbd +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) CommandName() string { + return "rb_remote_delete" +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) ServerDetails() (*config.ServerDetails, error) { + return rbd.serverDetails, nil +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) Run() error { + if err := validateArtifactoryVersionSupported(rbd.serverDetails); err != nil { + return err + } + + servicesManager, rbDetails, queryParams, err := rbd.getPrerequisites() + if err != nil { + return err + } + + return rbd.deleteRemote(servicesManager, rbDetails, queryParams) +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) deleteRemote(servicesManager *lifecycle.LifecycleServicesManager, + rbDetails services.ReleaseBundleDetails, queryParams services.CommonOptionalQueryParams) error { + + confirm, err := rbd.confirmDelete() + if err != nil || !confirm { + return err + } + + aggregatedRules := rbd.getAggregatedDistRules() + + return servicesManager.RemoteDeleteReleaseBundle(rbDetails, services.ReleaseBundleRemoteDeleteParams{ + DistributionRules: aggregatedRules, + DryRun: rbd.dryRun, + MaxWaitMinutes: rbd.maxWaitMinutes, + CommonOptionalQueryParams: queryParams, + }) +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) distributionRulesEmpty() bool { + return rbd.distributionRules == nil || + len(rbd.distributionRules.DistributionRules) == 0 || + len(rbd.distributionRules.DistributionRules) == 1 && rbd.distributionRules.DistributionRules[0].IsEmpty() +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) confirmDelete() (bool, error) { + if rbd.quiet { + return true, nil + } + + message := fmt.Sprintf("Are you sure you want to delete the release bundle '%s/%s' remotely ", rbd.releaseBundleName, rbd.releaseBundleVersion) + if rbd.distributionRulesEmpty() { + message += "from all edges?" + } else { + var distributionRulesBodies []distribution.DistributionRulesBody + for _, rule := range rbd.distributionRules.DistributionRules { + distributionRulesBodies = append(distributionRulesBodies, distribution.DistributionRulesBody{ + SiteName: rule.SiteName, + CityName: rule.CityName, + CountryCodes: rule.CountryCodes, + }) + } + bytes, err := json.Marshal(distributionRulesBodies) + if err != nil { + return false, errorutils.CheckError(err) + } + + log.Output(clientutils.IndentJson(bytes)) + message += "from all edges with the above distribution rules?" + } + + return coreutils.AskYesNo(message+"\n"+avoidConfirmationMsg, false), nil +} + +func (rbd *ReleaseBundleRemoteDeleteCommand) getAggregatedDistRules() (aggregatedRules []*distribution.DistributionCommonParams) { + if rbd.distributionRulesEmpty() { + aggregatedRules = append(aggregatedRules, &distribution.DistributionCommonParams{SiteName: "*"}) + } else { + for _, rules := range rbd.distributionRules.DistributionRules { + aggregatedRules = append(aggregatedRules, rules.ToDistributionCommonParams()) + } + } + return +} diff --git a/lifecycle/distribute.go b/lifecycle/distribute.go index 76a187330..76165ef56 100644 --- a/lifecycle/distribute.go +++ b/lifecycle/distribute.go @@ -1,21 +1,19 @@ package lifecycle import ( - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/lifecycle/services" - "github.com/jfrog/jfrog-client-go/utils/distribution" ) type ReleaseBundleDistributeCommand struct { - serverDetails *config.ServerDetails - distributeBundlesParams distribution.DistributionParams - distributionRules *spec.DistributionRules - dryRun bool - autoCreateRepo bool - pathMappingPattern string - pathMappingTarget string + releaseBundleCmd + distributionRules *spec.DistributionRules + dryRun bool + autoCreateRepo bool + pathMappingPattern string + pathMappingTarget string + maxWaitMinutes int } func NewReleaseBundleDistributeCommand() *ReleaseBundleDistributeCommand { @@ -27,8 +25,18 @@ func (rbd *ReleaseBundleDistributeCommand) SetServerDetails(serverDetails *confi return rbd } -func (rbd *ReleaseBundleDistributeCommand) SetDistributeBundleParams(params distribution.DistributionParams) *ReleaseBundleDistributeCommand { - rbd.distributeBundlesParams = params +func (rbd *ReleaseBundleDistributeCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleDistributeCommand { + rbd.releaseBundleName = releaseBundleName + return rbd +} + +func (rbd *ReleaseBundleDistributeCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleDistributeCommand { + rbd.releaseBundleVersion = releaseBundleVersion + return rbd +} + +func (rbd *ReleaseBundleDistributeCommand) SetReleaseBundleProject(rbProjectKey string) *ReleaseBundleDistributeCommand { + rbd.rbProjectKey = rbProjectKey return rbd } @@ -57,33 +65,41 @@ func (rbd *ReleaseBundleDistributeCommand) SetPathMappingTarget(pathMappingTarge return rbd } +func (rbd *ReleaseBundleDistributeCommand) SetSync(sync bool) *ReleaseBundleDistributeCommand { + rbd.sync = sync + return rbd +} + +func (rbd *ReleaseBundleDistributeCommand) SetMaxWaitMinutes(maxWaitMinutes int) *ReleaseBundleDistributeCommand { + rbd.maxWaitMinutes = maxWaitMinutes + return rbd +} + func (rbd *ReleaseBundleDistributeCommand) Run() error { if err := validateArtifactoryVersionSupported(rbd.serverDetails); err != nil { return err } - servicesManager, err := utils.CreateLifecycleServiceManager(rbd.serverDetails, rbd.dryRun) + servicesManager, rbDetails, _, err := rbd.getPrerequisites() if err != nil { return err } - for _, rule := range rbd.distributionRules.DistributionRules { - rbd.distributeBundlesParams.DistributionRules = append(rbd.distributeBundlesParams.DistributionRules, rule.ToDistributionCommonParams()) - } - pathMapping := services.PathMapping{ Pattern: rbd.pathMappingPattern, Target: rbd.pathMappingTarget, } - return servicesManager.DistributeReleaseBundle(services.ReleaseBundleDetails{ - ReleaseBundleName: rbd.distributeBundlesParams.Name, - ReleaseBundleVersion: rbd.distributeBundlesParams.Version, - }, services.DistributeReleaseBundleParams{ + distributeParams := services.DistributeReleaseBundleParams{ + Sync: rbd.sync, AutoCreateRepo: rbd.autoCreateRepo, - DistributionRules: rbd.distributeBundlesParams.DistributionRules, + MaxWaitMinutes: rbd.maxWaitMinutes, + DistributionRules: getAggregatedDistRules(rbd.distributionRules), PathMappings: []services.PathMapping{pathMapping}, - }) + ProjectKey: rbd.rbProjectKey, + } + + return servicesManager.DistributeReleaseBundle(rbDetails, distributeParams) } func (rbd *ReleaseBundleDistributeCommand) ServerDetails() (*config.ServerDetails, error) { diff --git a/lifecycle/export.go b/lifecycle/export.go new file mode 100644 index 000000000..41001e27f --- /dev/null +++ b/lifecycle/export.go @@ -0,0 +1,133 @@ +package lifecycle + +import ( + artUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/artifactory" + artServices "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + clientConfig "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "strings" +) + +type ReleaseBundleExportCommand struct { + releaseBundleCmd + modifications services.Modifications + downloadConfigurations artUtils.DownloadConfiguration + targetPath string +} + +func (rbe *ReleaseBundleExportCommand) Run() (err error) { + if err = validateArtifactoryVersionSupported(rbe.serverDetails); err != nil { + return + } + servicesManager, rbDetails, queryParams, err := rbe.getPrerequisites() + if err != nil { + return errorutils.CheckErrorf("Failed getting prerequisites for exporting command, error: '%s'", err.Error()) + } + // Start the Export process and wait for completion + log.Info("Exporting Release Bundle archive...") + exportResponse, err := servicesManager.ExportReleaseBundle(rbDetails, rbe.modifications, queryParams) + if err != nil { + return errorutils.CheckErrorf("Failed exporting release bundle, error: '%s'", err.Error()) + } + // Download the exported bundle + log.Debug("Downloading the exported bundle...") + downloaded, failed, err := rbe.downloadReleaseBundle(exportResponse, rbe.downloadConfigurations) + if err != nil || failed > 0 || downloaded < 1 { + return + } + log.Info("Successfully Downloaded Release Bundle archive") + return +} + +// Download the exported release bundle using artifactory service manager +func (rbe *ReleaseBundleExportCommand) downloadReleaseBundle(exportResponse services.ReleaseBundleExportedStatusResponse, downloadConfiguration artUtils.DownloadConfiguration) (downloaded int, failed int, err error) { + downloadParams := artServices.DownloadParams{ + CommonParams: &utils.CommonParams{ + Pattern: strings.TrimPrefix(exportResponse.RelativeUrl, "/"), + Target: rbe.targetPath, + }, + MinSplitSize: downloadConfiguration.MinSplitSize, + SplitCount: downloadConfiguration.SplitCount, + } + artifactoryServiceManager, err := createArtifactoryServiceManager(rbe.serverDetails) + if err != nil { + return + } + return artifactoryServiceManager.DownloadFiles(downloadParams) + +} +func (rbe *ReleaseBundleExportCommand) ServerDetails() (*config.ServerDetails, error) { + return rbe.serverDetails, nil +} + +func (rbe *ReleaseBundleExportCommand) CommandName() string { + return "rb_export" +} + +func NewReleaseBundleExportCommand() *ReleaseBundleExportCommand { + return &ReleaseBundleExportCommand{} +} +func (rbe *ReleaseBundleExportCommand) SetServerDetails(serverDetails *config.ServerDetails) *ReleaseBundleExportCommand { + rbe.serverDetails = serverDetails + return rbe +} + +func (rbe *ReleaseBundleExportCommand) SetReleaseBundleExportModifications(modifications services.Modifications) *ReleaseBundleExportCommand { + rbe.modifications = modifications + return rbe +} +func (rbe *ReleaseBundleExportCommand) SetReleaseBundleName(releaseBundleName string) *ReleaseBundleExportCommand { + rbe.releaseBundleName = releaseBundleName + return rbe +} + +func (rbe *ReleaseBundleExportCommand) SetReleaseBundleVersion(releaseBundleVersion string) *ReleaseBundleExportCommand { + rbe.releaseBundleVersion = releaseBundleVersion + return rbe +} + +func (rbe *ReleaseBundleExportCommand) SetProject(project string) *ReleaseBundleExportCommand { + rbe.rbProjectKey = project + return rbe +} + +func (rbe *ReleaseBundleExportCommand) SetDownloadConfiguration(downloadConfig artUtils.DownloadConfiguration) *ReleaseBundleExportCommand { + rbe.downloadConfigurations = downloadConfig + return rbe +} + +func (rbe *ReleaseBundleExportCommand) SetTargetPath(target string) *ReleaseBundleExportCommand { + if target == "" { + // Default value as current dir + target += "./" + } + rbe.targetPath = target + return rbe +} + +func createArtifactoryServiceManager(artDetails *config.ServerDetails) (artifactory.ArtifactoryServicesManager, error) { + certsPath, err := coreutils.GetJfrogCertsDir() + if err != nil { + return nil, err + } + artAuth, err := artDetails.CreateArtAuthConfig() + if err != nil { + return nil, err + } + serviceConfig, err := clientConfig.NewConfigBuilder(). + SetServiceDetails(artAuth). + SetCertificatesPath(certsPath). + SetInsecureTls(artDetails.InsecureTls). + SetDryRun(false). + Build() + if err != nil { + return nil, err + } + return artifactory.New(serviceConfig) +} diff --git a/lifecycle/promote.go b/lifecycle/promote.go index 0cf8b64d0..fb078ff36 100644 --- a/lifecycle/promote.go +++ b/lifecycle/promote.go @@ -10,6 +10,7 @@ import ( type ReleaseBundlePromoteCommand struct { releaseBundleCmd + signingKeyName string environment string includeReposPatterns []string excludeReposPatterns []string diff --git a/tests/testdata/npm-workspaces/module1/package.json b/tests/testdata/npm-workspaces/module1/package.json new file mode 100644 index 000000000..c98ade642 --- /dev/null +++ b/tests/testdata/npm-workspaces/module1/package.json @@ -0,0 +1,12 @@ +{ + "name": "module1", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/testdata/npm-workspaces/module2/package.json b/tests/testdata/npm-workspaces/module2/package.json new file mode 100644 index 000000000..d584476e5 --- /dev/null +++ b/tests/testdata/npm-workspaces/module2/package.json @@ -0,0 +1,12 @@ +{ + "name": "module2", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/testdata/npm-workspaces/package.json b/tests/testdata/npm-workspaces/package.json new file mode 100644 index 000000000..59c936a79 --- /dev/null +++ b/tests/testdata/npm-workspaces/package.json @@ -0,0 +1,13 @@ +{ + "name": "npm-pack-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "workspaces": ["module1","module2"] +} diff --git a/utils/config/encryption.go b/utils/config/encryption.go index 3e040c93d..eb4db4445 100644 --- a/utils/config/encryption.go +++ b/utils/config/encryption.go @@ -5,6 +5,7 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" + ioutils "github.com/jfrog/gofrog/io" "io" "os" "strconv" @@ -115,12 +116,7 @@ func getEncryptionKeyFromSecurityConfFile() (key string, err error) { config := viper.New() config.SetConfigType("yaml") f, err := os.Open(secFile) - defer func() { - e := f.Close() - if err == nil { - err = e - } - }() + defer ioutils.Close(f, &err) if err != nil { return "", errorutils.CheckError(err) } diff --git a/utils/config/tokenrefresh.go b/utils/config/tokenrefresh.go index 298a3917d..e8f4da70b 100644 --- a/utils/config/tokenrefresh.go +++ b/utils/config/tokenrefresh.go @@ -1,6 +1,7 @@ package config import ( + "errors" "github.com/jfrog/jfrog-client-go/access" accessservices "github.com/jfrog/jfrog-client-go/access/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -75,10 +76,7 @@ func tokenRefreshHandler(currentAccessToken string, tokenType TokenType) (newAcc unlockFunc, err := lock.CreateLock(lockDirPath) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return @@ -211,10 +209,7 @@ func CreateInitialRefreshableTokensIfNeeded(serverDetails *ServerDetails) (err e unlockFunc, err := lock.CreateLock(lockDirPath) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return diff --git a/utils/coreutils/tableutils.go b/utils/coreutils/tableutils.go index afe507016..cfa3a3eb1 100644 --- a/utils/coreutils/tableutils.go +++ b/utils/coreutils/tableutils.go @@ -2,6 +2,7 @@ package coreutils import ( "bufio" + "errors" "fmt" "math" "os" @@ -136,10 +137,7 @@ func PrintTable(rows interface{}, title string, emptyTableMessage string, printE tableWriter.Style().Options.SeparateRows = true stdoutWriter := bufio.NewWriter(os.Stdout) defer func() { - e := stdoutWriter.Flush() - if err == nil { - err = e - } + err = errors.Join(err, stdoutWriter.Flush()) }() tableWriter.SetOutputMirror(stdoutWriter) tableWriter.Render() diff --git a/utils/coreutils/techutils.go b/utils/coreutils/techutils.go index 0d51b209d..5a24ef707 100644 --- a/utils/coreutils/techutils.go +++ b/utils/coreutils/techutils.go @@ -82,10 +82,11 @@ var technologiesData = map[Technology]TechData{ packageInstallationCommand: "install", }, Pnpm: { - indicators: []string{"pnpm-lock.yaml"}, - exclude: []string{".yarnrc.yml", "yarn.lock", ".yarn"}, - packageDescriptors: []string{"package.json"}, - packageVersionOperator: "@", + indicators: []string{"pnpm-lock.yaml"}, + exclude: []string{".yarnrc.yml", "yarn.lock", ".yarn"}, + packageDescriptors: []string{"package.json"}, + packageVersionOperator: "@", + packageInstallationCommand: "update", }, Yarn: { indicators: []string{".yarnrc.yml", "yarn.lock", ".yarn", ".yarnrc"}, diff --git a/utils/dependencies/utils.go b/utils/dependencies/utils.go index 4f105002c..7be49aa89 100644 --- a/utils/dependencies/utils.go +++ b/utils/dependencies/utils.go @@ -39,8 +39,7 @@ func DownloadExtractor(targetPath, downloadPath string) error { func CreateChecksumFile(targetPath, checksum string) (err error) { out, err := os.Create(targetPath) defer func() { - e := errorutils.CheckError(out.Close()) - err = errors.Join(err, e) + err = errors.Join(err, errorutils.CheckError(out.Close())) }() if errorutils.CheckError(err) != nil { return err diff --git a/utils/java/deptreemanager.go b/utils/java/deptreemanager.go deleted file mode 100644 index e323fad1e..000000000 --- a/utils/java/deptreemanager.go +++ /dev/null @@ -1,124 +0,0 @@ -package java - -import ( - "encoding/json" - "os" - "strings" - - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/xray" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" -) - -const ( - GavPackageTypeIdentifier = "gav://" -) - -func BuildDependencyTree(depTreeParams DepTreeParams, tech coreutils.Technology) ([]*xrayUtils.GraphNode, map[string][]string, error) { - if tech == coreutils.Maven { - return buildMavenDependencyTree(&depTreeParams) - } - return buildGradleDependencyTree(&depTreeParams) -} - -type DepTreeParams struct { - UseWrapper bool - Server *config.ServerDetails - DepsRepo string - IsMavenDepTreeInstalled bool - IsCurationCmd bool - CurationCacheFolder string -} - -type DepTreeManager struct { - server *config.ServerDetails - depsRepo string - useWrapper bool -} - -func NewDepTreeManager(params *DepTreeParams) DepTreeManager { - return DepTreeManager{useWrapper: params.UseWrapper, depsRepo: params.DepsRepo, server: params.Server} -} - -// The structure of a dependency tree of a module in a Gradle/Maven project, as created by the gradle-dep-tree and maven-dep-tree plugins. -type moduleDepTree struct { - Root string `json:"root"` - Nodes map[string]xray.DepTreeNode `json:"nodes"` -} - -// Reads the output files of the gradle-dep-tree and maven-dep-tree plugins and returns them as a slice of GraphNodes. -// It takes the output of the plugin's run (which is a byte representation of a list of paths of the output files, separated by newlines) as input. -func getGraphFromDepTree(outputFilePaths string) (depsGraph []*xrayUtils.GraphNode, uniqueDepsMap map[string][]string, err error) { - modules, err := parseDepTreeFiles(outputFilePaths) - if err != nil { - return - } - uniqueDepsMap = map[string][]string{} - for _, module := range modules { - moduleTree, moduleUniqueDeps := GetModuleTreeAndDependencies(module) - depsGraph = append(depsGraph, moduleTree) - for depToAdd, depTypes := range moduleUniqueDeps { - uniqueDepsMap[depToAdd] = depTypes - } - } - return -} - -// Returns a dependency tree and a flat list of the module's dependencies for the given module -func GetModuleTreeAndDependencies(module *moduleDepTree) (*xrayUtils.GraphNode, map[string][]string) { - moduleTreeMap := make(map[string]xray.DepTreeNode) - moduleDeps := module.Nodes - for depName, dependency := range moduleDeps { - dependencyId := GavPackageTypeIdentifier + depName - var childrenList []string - for _, childName := range dependency.Children { - childId := GavPackageTypeIdentifier + childName - childrenList = append(childrenList, childId) - } - moduleTreeMap[dependencyId] = xray.DepTreeNode{ - Types: dependency.Types, - Children: childrenList, - } - } - return xray.BuildXrayDependencyTree(moduleTreeMap, GavPackageTypeIdentifier+module.Root) -} - -func parseDepTreeFiles(jsonFilePaths string) ([]*moduleDepTree, error) { - outputFilePaths := strings.Split(strings.TrimSpace(jsonFilePaths), "\n") - var modules []*moduleDepTree - for _, path := range outputFilePaths { - results, err := parseDepTreeFile(path) - if err != nil { - return nil, err - } - modules = append(modules, results) - } - return modules, nil -} - -func parseDepTreeFile(path string) (results *moduleDepTree, err error) { - depTreeJson, err := os.ReadFile(strings.TrimSpace(path)) - if errorutils.CheckError(err) != nil { - return - } - results = &moduleDepTree{} - err = errorutils.CheckError(json.Unmarshal(depTreeJson, &results)) - return -} - -func getArtifactoryAuthFromServer(server *config.ServerDetails) (string, string, error) { - username, password, err := server.GetAuthenticationCredentials() - if err != nil { - return "", "", err - } - if username == "" { - return "", "", errorutils.CheckErrorf("a username is required for authenticating with Artifactory") - } - return username, password, nil -} - -func (dtm *DepTreeManager) GetDepsRepo() string { - return dtm.depsRepo -} diff --git a/utils/java/deptreemanager_test.go b/utils/java/deptreemanager_test.go deleted file mode 100644 index 6eb23b3d8..000000000 --- a/utils/java/deptreemanager_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package java - -import ( - "os" - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - "github.com/stretchr/testify/assert" -) - -func TestGetGradleGraphFromDepTree(t *testing.T) { - // Create and change directory to test workspace - tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "gradle")) - defer cleanUp() - assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) - expectedTree := map[string]map[string]string{ - "org.jfrog.example.gradle:shared:1.0": {}, - "org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0": {}, - "org.jfrog.example.gradle:services:1.0": {}, - "org.jfrog.example.gradle:webservice:1.0": { - "junit:junit:4.11": "", - "commons-io:commons-io:1.2": "", - "org.apache.wicket:wicket:1.3.7": "", - "org.jfrog.example.gradle:shared:1.0": "", - "org.jfrog.example.gradle:api:1.0": "", - "commons-lang:commons-lang:2.4": "", - "commons-collections:commons-collections:3.2": "", - }, - "org.jfrog.example.gradle:api:1.0": { - "org.apache.wicket:wicket:1.3.7": "", - "org.jfrog.example.gradle:shared:1.0": "", - "commons-lang:commons-lang:2.4": "", - }, - } - expectedUniqueDeps := []string{ - "junit:junit:4.11", - "org.jfrog.example.gradle:webservice:1.0", - "org.jfrog.example.gradle:api:1.0", - "org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0", - "commons-io:commons-io:1.2", - "org.apache.wicket:wicket:1.3.7", - "org.jfrog.example.gradle:shared:1.0", - "org.jfrog.example.gradle:api:1.0", - "commons-collections:commons-collections:3.2", - "commons-lang:commons-lang:2.4", - "org.hamcrest:hamcrest-core:1.3", - "org.slf4j:slf4j-api:1.4.2", - } - - manager := &gradleDepTreeManager{DepTreeManager{}} - outputFileContent, err := manager.runGradleDepTree() - assert.NoError(t, err) - depTree, uniqueDeps, err := getGraphFromDepTree(outputFileContent) - assert.NoError(t, err) - reflect.DeepEqual(uniqueDeps, expectedUniqueDeps) - - for _, dependency := range depTree { - dependencyId := strings.TrimPrefix(dependency.Id, GavPackageTypeIdentifier) - depChild, exists := expectedTree[dependencyId] - assert.True(t, exists) - assert.Equal(t, len(depChild), len(dependency.Nodes)) - } -} diff --git a/utils/java/gradle.go b/utils/java/gradle.go deleted file mode 100644 index 57ceff8bd..000000000 --- a/utils/java/gradle.go +++ /dev/null @@ -1,199 +0,0 @@ -package java - -import ( - _ "embed" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/jfrog/build-info-go/build" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/log" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" -) - -const ( - remoteDepTreePath = "artifactory/oss-release-local" - gradlew = "gradlew" - gradleDepTreeJarFile = "gradle-dep-tree.jar" - gradleDepTreeInitFile = "gradledeptree.init" - gradleDepTreeOutputFile = "gradledeptree.out" - gradleDepTreeInitScript = `initscript { - repositories { %s - mavenCentral() - } - dependencies { - classpath files('%s') - } -} - -allprojects { - repositories { %s - } - apply plugin: com.jfrog.GradleDepTree -}` - artifactoryRepository = ` - maven { - url "%s/%s" - credentials { - username = '%s' - password = '%s' - } - }` -) - -//go:embed resources/gradle-dep-tree.jar -var gradleDepTreeJar []byte - -type gradleDepTreeManager struct { - DepTreeManager -} - -func buildGradleDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { - manager := &gradleDepTreeManager{DepTreeManager: NewDepTreeManager(params)} - outputFileContent, err := manager.runGradleDepTree() - if err != nil { - return - } - dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFileContent) - return -} - -func (gdt *gradleDepTreeManager) runGradleDepTree() (string, error) { - // Create the script file in the repository - depTreeDir, err := gdt.createDepTreeScriptAndGetDir() - if err != nil { - return "", err - } - defer func() { - err = errors.Join(err, fileutils.RemoveTempDir(depTreeDir)) - }() - - if gdt.useWrapper { - gdt.useWrapper, err = isGradleWrapperExist() - if err != nil { - return "", err - } - } - - output, err := gdt.execGradleDepTree(depTreeDir) - if err != nil { - return "", err - } - return string(output), nil -} - -func (gdt *gradleDepTreeManager) createDepTreeScriptAndGetDir() (tmpDir string, err error) { - tmpDir, err = fileutils.CreateTempDir() - if err != nil { - return - } - var releasesRepo string - releasesRepo, gdt.depsRepo, err = getRemoteRepos(gdt.depsRepo, gdt.server) - if err != nil { - return - } - gradleDepTreeJarPath := filepath.Join(tmpDir, gradleDepTreeJarFile) - if err = errorutils.CheckError(os.WriteFile(gradleDepTreeJarPath, gradleDepTreeJar, 0600)); err != nil { - return - } - gradleDepTreeJarPath = ioutils.DoubleWinPathSeparator(gradleDepTreeJarPath) - - depTreeInitScript := fmt.Sprintf(gradleDepTreeInitScript, releasesRepo, gradleDepTreeJarPath, gdt.depsRepo) - return tmpDir, errorutils.CheckError(os.WriteFile(filepath.Join(tmpDir, gradleDepTreeInitFile), []byte(depTreeInitScript), 0666)) -} - -// getRemoteRepos constructs the sections of Artifactory's remote repositories in the gradle-dep-tree init script. -// depsRemoteRepo - name of the remote repository that proxies the relevant registry, e.g. maven central. -// server - the Artifactory server details on which the repositories reside in. -// Returns the constructed sections. -func getRemoteRepos(depsRepo string, server *config.ServerDetails) (string, string, error) { - constructedReleasesRepo, err := constructReleasesRemoteRepo() - if err != nil { - return "", "", err - } - - constructedDepsRepo, err := getDepTreeArtifactoryRepository(depsRepo, server) - if err != nil { - return "", "", err - } - return constructedReleasesRepo, constructedDepsRepo, nil -} - -func constructReleasesRemoteRepo() (string, error) { - // Try to retrieve the serverID and remote repository that proxies https://releases.jfrog.io, from the environment variable - serverId, repoName, err := coreutils.GetServerIdAndRepo(coreutils.ReleasesRemoteEnv) - if err != nil || serverId == "" || repoName == "" { - return "", err - } - - releasesServer, err := config.GetSpecificConfig(serverId, false, true) - if err != nil { - return "", err - } - - releasesPath := fmt.Sprintf("%s/%s", repoName, remoteDepTreePath) - log.Debug("The `"+gradleDepTreeJarFile+"` will be resolved from", repoName) - return getDepTreeArtifactoryRepository(releasesPath, releasesServer) -} - -func (gdt *gradleDepTreeManager) execGradleDepTree(depTreeDir string) (outputFileContent []byte, err error) { - gradleExecPath, err := build.GetGradleExecPath(gdt.useWrapper) - if err != nil { - err = errorutils.CheckError(err) - return - } - - outputFilePath := filepath.Join(depTreeDir, gradleDepTreeOutputFile) - tasks := []string{ - "clean", - "generateDepTrees", "-I", filepath.Join(depTreeDir, gradleDepTreeInitFile), - "-q", - fmt.Sprintf("-Dcom.jfrog.depsTreeOutputFile=%s", outputFilePath), - "-Dcom.jfrog.includeAllBuildFiles=true"} - log.Info("Running gradle deps tree command:", gradleExecPath, strings.Join(tasks, " ")) - if output, err := exec.Command(gradleExecPath, tasks...).CombinedOutput(); err != nil { - return nil, errorutils.CheckErrorf("error running gradle-dep-tree: %s\n%s", err.Error(), string(output)) - } - defer func() { - err = errors.Join(err, errorutils.CheckError(os.Remove(outputFilePath))) - }() - - outputFileContent, err = os.ReadFile(outputFilePath) - err = errorutils.CheckError(err) - return -} - -func getDepTreeArtifactoryRepository(remoteRepo string, server *config.ServerDetails) (string, error) { - if remoteRepo == "" || server.IsEmpty() { - return "", nil - } - username, password, err := getArtifactoryAuthFromServer(server) - if err != nil { - return "", err - } - - log.Debug("The project dependencies will be resolved from", server.ArtifactoryUrl, "from the", remoteRepo, "repository") - return fmt.Sprintf(artifactoryRepository, - strings.TrimSuffix(server.ArtifactoryUrl, "/"), - remoteRepo, - username, - password), nil -} - -// This function assumes that the Gradle wrapper is in the root directory. -// The --project-dir option of Gradle won't work in this case. -func isGradleWrapperExist() (bool, error) { - wrapperName := gradlew - if coreutils.IsWindows() { - wrapperName += ".bat" - } - return fileutils.IsFileExists(wrapperName, false) -} diff --git a/utils/java/gradle_test.go b/utils/java/gradle_test.go deleted file mode 100644 index 787542fa4..000000000 --- a/utils/java/gradle_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package java - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - testsutils "github.com/jfrog/jfrog-cli-core/v2/utils/config/tests" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - - "github.com/stretchr/testify/assert" -) - -// #nosec G101 -- Dummy token for tests -// jfrog-ignore -const dummyToken = "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA" - -const expectedInitScriptWithRepos = `initscript { - repositories { - mavenCentral() - } - dependencies { - classpath files('%s') - } -} - -allprojects { - repositories { - maven { - url "https://myartifactory.com/artifactory/deps-repo" - credentials { - username = 'admin' - password = '%s' - } - } - } - apply plugin: com.jfrog.GradleDepTree -}` - -func TestGradleTreesWithoutConfig(t *testing.T) { - // Create and change directory to test workspace - tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "gradle")) - defer cleanUp() - assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) - - // Run getModulesDependencyTrees - modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DepTreeParams{}) - if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, uniqueDeps, 12) - assert.Len(t, modulesDependencyTrees, 5) - // Check module - module := tests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.example.gradle:webservice:1.0") - assert.Len(t, module.Nodes, 7) - - // Check direct dependency - directDependency := tests.GetAndAssertNode(t, module.Nodes, "junit:junit:4.11") - assert.Len(t, directDependency.Nodes, 1) - - // Check transitive dependency - tests.GetAndAssertNode(t, directDependency.Nodes, "org.hamcrest:hamcrest-core:1.3") - } -} - -func TestGradleTreesWithConfig(t *testing.T) { - // Create and change directory to test workspace - tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "gradle-example-config")) - defer cleanUp() - assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) - - // Run getModulesDependencyTrees - modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DepTreeParams{UseWrapper: true}) - if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, modulesDependencyTrees, 5) - assert.Len(t, uniqueDeps, 11) - // Check module - module := tests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test.gradle.publish:api:1.0-SNAPSHOT") - assert.Len(t, module.Nodes, 4) - - // Check direct dependency - directDependency := tests.GetAndAssertNode(t, module.Nodes, "commons-lang:commons-lang:2.4") - assert.Len(t, directDependency.Nodes, 1) - - // Check transitive dependency - tests.GetAndAssertNode(t, directDependency.Nodes, "commons-io:commons-io:1.2") - } -} - -func TestIsGradleWrapperExist(t *testing.T) { - // Check Gradle wrapper doesn't exist - isWrapperExist, err := isGradleWrapperExist() - assert.False(t, isWrapperExist) - assert.NoError(t, err) - - // Check Gradle wrapper exist - _, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "gradle")) - defer cleanUp() - isWrapperExist, err = isGradleWrapperExist() - assert.NoError(t, err) - assert.True(t, isWrapperExist) -} - -func TestGetDepTreeArtifactoryRepository(t *testing.T) { - tests := []struct { - name string - remoteRepo string - server *config.ServerDetails - expectedUrl string - expectedErr string - }{ - { - name: "WithAccessToken", - remoteRepo: "my-remote-repo", - server: &config.ServerDetails{ - Url: "https://myartifactory.com", - // jfrog-ignore - AccessToken: dummyToken, - }, - // jfrog-ignore - expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'admin'\n\t\t\t\tpassword = 'eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA'\n\t\t\t}\n\t\t}", - expectedErr: "", - }, - { - name: "WithUsernameAndPassword", - remoteRepo: "my-remote-repo", - server: &config.ServerDetails{ - Url: "https://myartifactory.com", - User: "my-username", - Password: "my-password", - }, - expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'my-username'\n\t\t\t\tpassword = 'my-password'\n\t\t\t}\n\t\t}", - expectedErr: "", - }, - { - name: "MissingCredentials", - remoteRepo: "my-remote-repo", - server: &config.ServerDetails{ - Url: "https://myartifactory.com", - }, - expectedUrl: "", - expectedErr: "either username/password or access token must be set for https://myartifactory.com", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - url, err := getDepTreeArtifactoryRepository(test.remoteRepo, test.server) - if err != nil { - assert.Equal(t, test.expectedErr, err.Error()) - } else { - assert.Equal(t, test.expectedUrl, url) - } - }) - } -} - -func TestCreateDepTreeScript(t *testing.T) { - manager := &gradleDepTreeManager{DepTreeManager: DepTreeManager{}} - tmpDir, err := manager.createDepTreeScriptAndGetDir() - assert.NoError(t, err) - defer func() { - assert.NoError(t, os.Remove(filepath.Join(tmpDir, gradleDepTreeInitFile))) - }() - content, err := os.ReadFile(filepath.Join(tmpDir, gradleDepTreeInitFile)) - assert.NoError(t, err) - gradleDepTreeJarPath := ioutils.DoubleWinPathSeparator(filepath.Join(tmpDir, gradleDepTreeJarFile)) - assert.Equal(t, fmt.Sprintf(gradleDepTreeInitScript, "", gradleDepTreeJarPath, ""), string(content)) -} - -func TestCreateDepTreeScriptWithRepositories(t *testing.T) { - manager := &gradleDepTreeManager{DepTreeManager: DepTreeManager{}} - manager.depsRepo = "deps-repo" - manager.server = &config.ServerDetails{ - Url: "https://myartifactory.com/", - ArtifactoryUrl: "https://myartifactory.com/artifactory", - // jfrog-ignore - AccessToken: dummyToken, - } - tmpDir, err := manager.createDepTreeScriptAndGetDir() - assert.NoError(t, err) - defer func() { - assert.NoError(t, os.Remove(filepath.Join(tmpDir, gradleDepTreeInitFile))) - }() - - content, err := os.ReadFile(filepath.Join(tmpDir, gradleDepTreeInitFile)) - assert.NoError(t, err) - gradleDepTreeJarPath := ioutils.DoubleWinPathSeparator(filepath.Join(tmpDir, gradleDepTreeJarFile)) - // jfrog-ignore - assert.Equal(t, fmt.Sprintf(expectedInitScriptWithRepos, gradleDepTreeJarPath, dummyToken), string(content)) -} - -func TestConstructReleasesRemoteRepo(t *testing.T) { - cleanUp := testsutils.CreateTempEnv(t, false) - serverDetails := &config.ServerDetails{ - ServerId: "test", - ArtifactoryUrl: "https://domain.com/artifactory", - User: "user", - Password: "pass", - } - err := config.SaveServersConf([]*config.ServerDetails{serverDetails}) - assert.NoError(t, err) - defer cleanUp() - testCases := []struct { - envVar string - expectedRepo string - expectedErr error - }{ - {envVar: "", expectedRepo: "", expectedErr: nil}, - {envVar: "test/repo1", expectedRepo: "\n\t\tmaven {\n\t\t\turl \"https://domain.com/artifactory/repo1/artifactory/oss-release-local\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'user'\n\t\t\t\tpassword = 'pass'\n\t\t\t}\n\t\t}", expectedErr: nil}, - {envVar: "notexist/repo1", expectedRepo: "", expectedErr: errors.New("Server ID 'notexist' does not exist.")}, - } - - for _, tc := range testCases { - // Set the environment variable for this test case - func() { - assert.NoError(t, os.Setenv(coreutils.ReleasesRemoteEnv, tc.envVar)) - defer func() { - // Reset the environment variable after each test case - assert.NoError(t, os.Unsetenv(coreutils.ReleasesRemoteEnv)) - }() - actualRepo, actualErr := constructReleasesRemoteRepo() - assert.Equal(t, tc.expectedRepo, actualRepo) - assert.Equal(t, tc.expectedErr, actualErr) - }() - } -} diff --git a/utils/java/mvn.go b/utils/java/mvn.go deleted file mode 100644 index 76514fb11..000000000 --- a/utils/java/mvn.go +++ /dev/null @@ -1,265 +0,0 @@ -package java - -import ( - _ "embed" - "errors" - "fmt" - "net/url" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/log" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" -) - -const ( - mavenDepTreeJarFile = "maven-dep-tree.jar" - mavenDepTreeOutputFile = "mavendeptree.out" - // Changing this version also requires a change in MAVEN_DEP_TREE_VERSION within buildscripts/download_jars.sh - mavenDepTreeVersion = "1.1.0" - settingsXmlFile = "settings.xml" -) - -var mavenConfigPath = filepath.Join(".mvn", "maven.config") - -type MavenDepTreeCmd string - -const ( - Projects MavenDepTreeCmd = "projects" - Tree MavenDepTreeCmd = "tree" -) - -//go:embed resources/settings.xml -var settingsXmlTemplate string - -//go:embed resources/maven-dep-tree.jar -var mavenDepTreeJar []byte - -type MavenDepTreeManager struct { - DepTreeManager - isInstalled bool - // this flag its curation command, it will set dedicated cache and download url. - isCurationCmd bool - // path to the curation dedicated cache - curationCacheFolder string - cmdName MavenDepTreeCmd - settingsXmlPath string -} - -func NewMavenDepTreeManager(params *DepTreeParams, cmdName MavenDepTreeCmd) *MavenDepTreeManager { - depTreeManager := NewDepTreeManager(&DepTreeParams{ - Server: params.Server, - DepsRepo: params.DepsRepo, - }) - return &MavenDepTreeManager{ - DepTreeManager: depTreeManager, - isInstalled: params.IsMavenDepTreeInstalled, - cmdName: cmdName, - isCurationCmd: params.IsCurationCmd, - curationCacheFolder: params.CurationCacheFolder, - } -} - -func buildMavenDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { - manager := NewMavenDepTreeManager(params, Tree) - outputFilePaths, clearMavenDepTreeRun, err := manager.RunMavenDepTree() - if err != nil { - if clearMavenDepTreeRun != nil { - err = errors.Join(err, clearMavenDepTreeRun()) - } - return - } - defer func() { - err = errors.Join(err, clearMavenDepTreeRun()) - }() - dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFilePaths) - return -} - -// Runs maven-dep-tree according to cmdName. Returns the plugin output along with a function pointer to revert the plugin side effects. -// If a non-nil clearMavenDepTreeRun pointer is returnes it means we had no error during the entire function execution -func (mdt *MavenDepTreeManager) RunMavenDepTree() (depTreeOutput string, clearMavenDepTreeRun func() error, err error) { - // depTreeExecDir is a temp directory for all the files that are required for the maven-dep-tree run - depTreeExecDir, clearMavenDepTreeRun, err := mdt.CreateTempDirWithSettingsXmlIfNeeded() - if err != nil { - return - } - if err = mdt.installMavenDepTreePlugin(depTreeExecDir); err != nil { - return - } - - depTreeOutput, err = mdt.execMavenDepTree(depTreeExecDir) - if err != nil { - return - } - return -} - -func (mdt *MavenDepTreeManager) installMavenDepTreePlugin(depTreeExecDir string) error { - if mdt.isInstalled { - return nil - } - mavenDepTreeJarPath := filepath.Join(depTreeExecDir, mavenDepTreeJarFile) - if err := errorutils.CheckError(os.WriteFile(mavenDepTreeJarPath, mavenDepTreeJar, 0666)); err != nil { - return err - } - goals := GetMavenPluginInstallationGoals(mavenDepTreeJarPath) - _, err := mdt.RunMvnCmd(goals) - return err -} - -func GetMavenPluginInstallationGoals(pluginPath string) []string { - return []string{"org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file", "-Dfile=" + pluginPath, "-B"} -} - -func (mdt *MavenDepTreeManager) execMavenDepTree(depTreeExecDir string) (string, error) { - if mdt.cmdName == Tree { - return mdt.runTreeCmd(depTreeExecDir) - } - return mdt.runProjectsCmd() -} - -func (mdt *MavenDepTreeManager) runTreeCmd(depTreeExecDir string) (string, error) { - mavenDepTreePath := filepath.Join(depTreeExecDir, mavenDepTreeOutputFile) - goals := []string{"com.jfrog:maven-dep-tree:" + mavenDepTreeVersion + ":" + string(Tree), "-DdepsTreeOutputFile=" + mavenDepTreePath, "-B"} - if mdt.isCurationCmd { - goals = append(goals, "-Dmaven.repo.local="+mdt.curationCacheFolder) - } - if _, err := mdt.RunMvnCmd(goals); err != nil { - return "", err - } - - mavenDepTreeOutput, err := os.ReadFile(mavenDepTreePath) - if err != nil { - return "", errorutils.CheckError(err) - } - return string(mavenDepTreeOutput), nil -} - -func (mdt *MavenDepTreeManager) runProjectsCmd() (string, error) { - goals := []string{"com.jfrog:maven-dep-tree:" + mavenDepTreeVersion + ":" + string(Projects), "-q"} - output, err := mdt.RunMvnCmd(goals) - if err != nil { - return "", err - } - return string(output), nil -} - -func (mdt *MavenDepTreeManager) RunMvnCmd(goals []string) (cmdOutput []byte, err error) { - restoreMavenConfig, err := removeMavenConfig() - if err != nil { - return - } - - defer func() { - if restoreMavenConfig != nil { - err = errors.Join(err, restoreMavenConfig()) - } - }() - - if mdt.settingsXmlPath != "" { - goals = append(goals, "-s", mdt.settingsXmlPath) - } - - //#nosec G204 - cmdOutput, err = exec.Command("mvn", goals...).CombinedOutput() - if err != nil { - stringOutput := string(cmdOutput) - if len(cmdOutput) > 0 { - log.Info(stringOutput) - } - if msg := mdt.suspectCurationBlockedError(stringOutput); msg != "" { - err = fmt.Errorf("failed running command 'mvn %s\n\n%s", strings.Join(goals, " "), msg) - } else { - err = fmt.Errorf("failed running command 'mvn %s': %s", strings.Join(goals, " "), err.Error()) - } - } - return -} - -func (mdt *MavenDepTreeManager) GetSettingsXmlPath() string { - return mdt.settingsXmlPath -} - -func (mdt *MavenDepTreeManager) SetSettingsXmlPath(settingsXmlPath string) { - mdt.settingsXmlPath = settingsXmlPath -} - -func removeMavenConfig() (func() error, error) { - mavenConfigExists, err := fileutils.IsFileExists(mavenConfigPath, false) - if err != nil { - return nil, err - } - if !mavenConfigExists { - return nil, nil - } - restoreMavenConfig, err := ioutils.BackupFile(mavenConfigPath, "maven.config.bkp") - if err != nil { - return nil, err - } - err = os.Remove(mavenConfigPath) - if err != nil { - err = errorutils.CheckErrorf("failed to remove %s while building the maven dependencies tree. Error received:\n%s", mavenConfigPath, err.Error()) - } - return restoreMavenConfig, err -} - -// Creates a new settings.xml file configured with the provided server and repository from the current MavenDepTreeManager instance. -// The settings.xml will be written to the given path. -func (mdt *MavenDepTreeManager) createSettingsXmlWithConfiguredArtifactory(settingsXmlPath string) error { - username, password, err := getArtifactoryAuthFromServer(mdt.server) - if err != nil { - return err - } - endPoint := mdt.depsRepo - if mdt.isCurationCmd { - endPoint = path.Join("api/curation/audit", endPoint) - } - remoteRepositoryFullPath, err := url.JoinPath(mdt.server.ArtifactoryUrl, endPoint) - if err != nil { - return err - } - mdt.settingsXmlPath = filepath.Join(settingsXmlPath, settingsXmlFile) - settingsXmlContent := fmt.Sprintf(settingsXmlTemplate, username, password, remoteRepositoryFullPath) - - return errorutils.CheckError(os.WriteFile(mdt.settingsXmlPath, []byte(settingsXmlContent), 0600)) -} - -// Creates a temporary directory. -// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temprary directory. -func (mdt *MavenDepTreeManager) CreateTempDirWithSettingsXmlIfNeeded() (tempDirPath string, clearMavenDepTreeRun func() error, err error) { - tempDirPath, err = fileutils.CreateTempDir() - if err != nil { - return - } - - clearMavenDepTreeRun = func() error { return fileutils.RemoveTempDir(tempDirPath) } - - // Create a settings.xml file that sets the dependency resolution from the given server and repository - if mdt.depsRepo != "" { - err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDirPath) - } - if err != nil { - err = errors.Join(err, clearMavenDepTreeRun()) - clearMavenDepTreeRun = nil - } - return -} - -// In case mvn tree fails on 403 or 500 it can be related to packages blocked by curation. -// For this use case to succeed, pass through should be enabled in the curated repos -func (mdt *MavenDepTreeManager) suspectCurationBlockedError(cmdOutput string) (msgToUser string) { - if !mdt.isCurationCmd { - return - } - if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(cmdOutput, "status code: 500") { - msgToUser = "Failed to get dependencies tree for maven project, Please verify pass-through enabled on the curated repos" - } - return msgToUser -} diff --git a/utils/java/mvn_test.go b/utils/java/mvn_test.go deleted file mode 100644 index a527b768b..000000000 --- a/utils/java/mvn_test.go +++ /dev/null @@ -1,380 +0,0 @@ -package java - -import ( - "github.com/jfrog/build-info-go/utils" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/tests" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" - "os" - "path/filepath" - "strings" - "testing" -) - -const ( - //#nosec G101 - dummy token for testing - settingsXmlWithUsernameAndPassword = ` - - - - artifactory - testUser - testPass - - - - - artifactory - https://myartifactory.com/artifactory/testRepo - * - - -` - //#nosec G101 - dummy token for testing - settingsXmlWithUsernameAndPasswordAndCurationDedicatedAPi = ` - - - - artifactory - testUser - testPass - - - - - artifactory - https://myartifactory.com/artifactory/api/curation/audit/testRepo - * - - -` - //#nosec G101 - dummy token for testing - settingsXmlWithUsernameAndToken = ` - - - - artifactory - testUser - eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA - - - - - artifactory - https://myartifactory.com/artifactory/testRepo - * - - -` - //#nosec G101 - dummy token for testing - settingsXmlWithAccessToken = ` - - - - artifactory - admin - eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA - - - - - artifactory - https://myartifactory.com/artifactory/testRepo - * - - -` -) - -func TestMavenTreesMultiModule(t *testing.T) { - // Create and change directory to test workspace - _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example")) - defer cleanUp() - - expectedUniqueDeps := []string{ - GavPackageTypeIdentifier + "javax.mail:mail:1.4", - GavPackageTypeIdentifier + "org.testng:testng:5.9", - GavPackageTypeIdentifier + "javax.servlet:servlet-api:2.5", - GavPackageTypeIdentifier + "org.jfrog.test:multi:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.jfrog.test:multi3:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.jfrog.test:multi2:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "junit:junit:3.8.1", - GavPackageTypeIdentifier + "org.jfrog.test:multi1:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "commons-io:commons-io:1.4", - GavPackageTypeIdentifier + "org.apache.commons:commons-email:1.1", - GavPackageTypeIdentifier + "javax.activation:activation:1.1", - GavPackageTypeIdentifier + "hsqldb:hsqldb:1.8.0.10", - } - // Run getModulesDependencyTrees - modulesDependencyTrees, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) - if assert.NoError(t, err) && assert.NotEmpty(t, modulesDependencyTrees) { - assert.ElementsMatch(t, maps.Keys(uniqueDeps), expectedUniqueDeps, "First is actual, Second is Expected") - // Check root module - multi := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi:3.7-SNAPSHOT") - if assert.NotNil(t, multi) { - assert.Len(t, multi.Nodes, 1) - // Check multi1 with a transitive dependency - multi1 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi1:3.7-SNAPSHOT") - assert.Len(t, multi1.Nodes, 4) - commonsEmail := coreTests.GetAndAssertNode(t, multi1.Nodes, "org.apache.commons:commons-email:1.1") - assert.Len(t, commonsEmail.Nodes, 2) - - // Check multi2 and multi3 - multi2 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi2:3.7-SNAPSHOT") - assert.Len(t, multi2.Nodes, 1) - multi3 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi3:3.7-SNAPSHOT") - assert.Len(t, multi3.Nodes, 4) - } - } -} - -func TestMavenWrapperTrees(t *testing.T) { - // Create and change directory to test workspace - _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example-with-wrapper")) - err := os.Chmod("mvnw", 0700) - defer cleanUp() - assert.NoError(t, err) - expectedUniqueDeps := []string{ - GavPackageTypeIdentifier + "org.jfrog.test:multi1:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.codehaus.plexus:plexus-utils:1.5.1", - GavPackageTypeIdentifier + "org.springframework:spring-beans:2.5.6", - GavPackageTypeIdentifier + "commons-logging:commons-logging:1.1.1", - GavPackageTypeIdentifier + "org.jfrog.test:multi3:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.apache.commons:commons-email:1.1", - GavPackageTypeIdentifier + "org.springframework:spring-aop:2.5.6", - GavPackageTypeIdentifier + "org.springframework:spring-core:2.5.6", - GavPackageTypeIdentifier + "org.jfrog.test:multi:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.jfrog.test:multi2:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.testng:testng:5.9", - GavPackageTypeIdentifier + "hsqldb:hsqldb:1.8.0.10", - GavPackageTypeIdentifier + "junit:junit:3.8.1", - GavPackageTypeIdentifier + "javax.activation:activation:1.1", - GavPackageTypeIdentifier + "javax.mail:mail:1.4", - GavPackageTypeIdentifier + "aopalliance:aopalliance:1.0", - GavPackageTypeIdentifier + "commons-io:commons-io:1.4", - GavPackageTypeIdentifier + "javax.servlet.jsp:jsp-api:2.1", - GavPackageTypeIdentifier + "javax.servlet:servlet-api:2.5", - } - - modulesDependencyTrees, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) - if assert.NoError(t, err) && assert.NotEmpty(t, modulesDependencyTrees) { - assert.ElementsMatch(t, maps.Keys(uniqueDeps), expectedUniqueDeps, "First is actual, Second is Expected") - // Check root module - multi := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi:3.7-SNAPSHOT") - if assert.NotNil(t, multi) { - assert.Len(t, multi.Nodes, 1) - // Check multi1 with a transitive dependency - multi1 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi1:3.7-SNAPSHOT") - assert.Len(t, multi1.Nodes, 7) - commonsEmail := coreTests.GetAndAssertNode(t, multi1.Nodes, "org.apache.commons:commons-email:1.1") - assert.Len(t, commonsEmail.Nodes, 2) - // Check multi2 and multi3 - multi2 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi2:3.7-SNAPSHOT") - assert.Len(t, multi2.Nodes, 1) - multi3 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi3:3.7-SNAPSHOT") - assert.Len(t, multi3.Nodes, 4) - } - } -} - -func TestMavenWrapperTreesTypes(t *testing.T) { - // Create and change directory to test workspace - _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example-with-many-types")) - defer cleanUp() - tree, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) - require.NoError(t, err) - // dependency of pom type - depWithPomType := uniqueDeps["gav://org.webjars:lodash:4.17.21"] - assert.NotEmpty(t, depWithPomType) - assert.Equal(t, depWithPomType[0], "pom") - existInTreePom := false - for _, node := range tree[0].Nodes { - if node.Id == "gav://org.webjars:lodash:4.17.21" { - nodeTypes := *node.Types - assert.Equal(t, nodeTypes[0], "pom") - existInTreePom = true - } - } - assert.True(t, existInTreePom) - - // dependency of jar type - depWithJarType := uniqueDeps["gav://junit:junit:4.11"] - assert.NotEmpty(t, depWithJarType) - assert.Equal(t, depWithJarType[0], "jar") - existInTreeJar := false - for _, node := range tree[0].Nodes { - if node.Id == "gav://junit:junit:4.11" { - nodeTypes := *node.Types - assert.Equal(t, nodeTypes[0], "jar") - existInTreeJar = true - } - } - assert.True(t, existInTreeJar) -} - -func TestDepTreeWithDedicatedCache(t *testing.T) { - // Create and change directory to test workspace - _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example-with-wrapper")) - err := os.Chmod("mvnw", 0700) - defer cleanUp() - assert.NoError(t, err) - tempDir := t.TempDir() - defer assert.NoError(t, utils.RemoveTempDir(tempDir)) - manager := NewMavenDepTreeManager(&DepTreeParams{IsCurationCmd: true, CurationCacheFolder: tempDir}, Tree) - _, err = manager.runTreeCmd(tempDir) - require.NoError(t, err) - // validate one of the jars exist in the dedicated cache for curation - fileExist, err := utils.IsFileExists(filepath.Join(tempDir, "org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar"), false) - require.NoError(t, err) - assert.True(t, fileExist) -} - -func TestGetMavenPluginInstallationArgs(t *testing.T) { - args := GetMavenPluginInstallationGoals("testPlugin") - assert.Equal(t, "org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file", args[0]) - assert.Equal(t, "-Dfile=testPlugin", args[1]) -} - -func TestCreateSettingsXmlWithConfiguredArtifactory(t *testing.T) { - // Test case for successful creation of settings.xml. - mdt := MavenDepTreeManager{ - DepTreeManager: DepTreeManager{ - server: &config.ServerDetails{ - ArtifactoryUrl: "https://myartifactory.com/artifactory", - User: "testUser", - Password: "testPass", - }, - depsRepo: "testRepo", - }, - } - // Create a temporary directory for testing and settings.xml creation - tempDir := t.TempDir() - err := mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) - assert.NoError(t, err) - - // Verify settings.xml file creation with username and password - settingsXmlPath := filepath.Join(tempDir, "settings.xml") - actualContent, err := os.ReadFile(settingsXmlPath) - actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) - assert.NoError(t, err) - assert.Equal(t, settingsXmlWithUsernameAndPassword, string(actualContent)) - - // check curation command write a dedicated api for curation. - mdt.isCurationCmd = true - err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) - require.NoError(t, err) - actualContent, err = os.ReadFile(settingsXmlPath) - actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) - assert.NoError(t, err) - assert.Equal(t, settingsXmlWithUsernameAndPasswordAndCurationDedicatedAPi, string(actualContent)) - mdt.isCurationCmd = false - - mdt.server.Password = "" - // jfrog-ignore - mdt.server.AccessToken = dummyToken - err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) - assert.NoError(t, err) - - // Verify settings.xml file creation with username and access token - actualContent, err = os.ReadFile(settingsXmlPath) - actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) - assert.NoError(t, err) - assert.Equal(t, settingsXmlWithUsernameAndToken, string(actualContent)) - - mdt.server.User = "" - err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) - assert.NoError(t, err) - - // Verify settings.xml file creation with access token only - actualContent, err = os.ReadFile(settingsXmlPath) - actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) - assert.NoError(t, err) - assert.Equal(t, settingsXmlWithAccessToken, string(actualContent)) -} - -func TestRunProjectsCmd(t *testing.T) { - // Create and change directory to test workspace - _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example")) - defer cleanUp() - mvnDepTreeManager := NewMavenDepTreeManager(&DepTreeParams{}, Projects) - output, clearMavenDepTreeRun, err := mvnDepTreeManager.RunMavenDepTree() - assert.NoError(t, err) - assert.NotNil(t, clearMavenDepTreeRun) - - pomPathOccurrences := strings.Count(output, "pomPath") - assert.Equal(t, 4, pomPathOccurrences) - assert.NoError(t, clearMavenDepTreeRun()) -} - -func TestRemoveMavenConfig(t *testing.T) { - tmpDir := t.TempDir() - currentDir, err := os.Getwd() - assert.NoError(t, err) - restoreDir := tests.ChangeDirWithCallback(t, currentDir, tmpDir) - defer restoreDir() - - // No maven.config exists - restoreFunc, err := removeMavenConfig() - assert.Nil(t, restoreFunc) - assert.Nil(t, err) - - // Create maven.config - err = fileutils.CreateDirIfNotExist(".mvn") - assert.NoError(t, err) - file, err := os.Create(mavenConfigPath) - assert.NoError(t, err) - err = file.Close() - assert.NoError(t, err) - restoreFunc, err = removeMavenConfig() - assert.NoError(t, err) - assert.NoFileExists(t, mavenConfigPath) - err = restoreFunc() - assert.NoError(t, err) - assert.FileExists(t, mavenConfigPath) -} - -func TestMavenDepTreeManager_suspectCurationBlockedError(t *testing.T) { - errPrefix := "[ERROR] Failed to execute goal on project my-app: Could not resolve dependencies for project com.mycompany.app:my-app:jar:1.0-SNAPSHOT: Failed to " + - "collect dependencies at junit:junit:jar:3.8.1: Failed to read artifact descriptor for junit:junit:jar:3.8.1: " + - "The following artifacts could not be resolved: junit:junit:pom:3.8.1 (absent): Could not transfer artifact junit:junit:pom:3.8.1 " + - "from/to artifactory (http://test:8046/artifactory/api/curation/audit/maven-remote):" - tests := []struct { - name string - wantMsgToUser string - input string - }{ - { - name: "failed on 403", - wantMsgToUser: "Please verify pass-through enabled on the curated repos", - input: errPrefix + "status code: 403, reason phrase: Forbidden (403)", - }, - { - name: "failed on 500", - wantMsgToUser: "Please verify pass-through enabled on the curated repos", - input: errPrefix + " status code: 500, reason phrase: Internal Server Error (500)", - }, - { - name: "not 403 or 500", - wantMsgToUser: "", - input: errPrefix + " status code: 400, reason phrase: Forbidden (400)", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mdt := &MavenDepTreeManager{} - assert.Contains(t, tt.wantMsgToUser, mdt.suspectCurationBlockedError(tt.input)) - }) - } -} diff --git a/utils/java/resources/gradle-dep-tree.jar b/utils/java/resources/gradle-dep-tree.jar deleted file mode 100644 index b05be1694..000000000 Binary files a/utils/java/resources/gradle-dep-tree.jar and /dev/null differ diff --git a/utils/java/resources/maven-dep-tree.jar b/utils/java/resources/maven-dep-tree.jar deleted file mode 100644 index f8d7ff406..000000000 Binary files a/utils/java/resources/maven-dep-tree.jar and /dev/null differ diff --git a/utils/java/resources/settings.xml b/utils/java/resources/settings.xml deleted file mode 100644 index 9dce691f8..000000000 --- a/utils/java/resources/settings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - artifactory - %s - %s - - - - - artifactory - %s - * - - - \ No newline at end of file diff --git a/utils/plugins/utils.go b/utils/plugins/utils.go index c436cb0e0..e678e17c5 100644 --- a/utils/plugins/utils.go +++ b/utils/plugins/utils.go @@ -2,6 +2,7 @@ package plugins import ( "encoding/json" + "errors" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/lock" cliLog "github.com/jfrog/jfrog-cli-core/v2/utils/log" @@ -61,10 +62,7 @@ func readPluginsConfigAndConvertV0tToV1IfNeeded() (err error) { unlockFunc, err = lock.CreateLock(lockDirPath) // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. defer func() { - e := unlockFunc() - if err == nil { - err = e - } + err = errors.Join(err, unlockFunc()) }() if err != nil { return