diff --git a/commands/audit/sca/common.go b/commands/audit/sca/common.go index 92ccf435..82c7b3fc 100644 --- a/commands/audit/sca/common.go +++ b/commands/audit/sca/common.go @@ -22,7 +22,7 @@ import ( var DefaultExcludePatterns = []string{"*.git*", "*node_modules*", "*target*", "*venv*", "*test*"} -var curationErrorMsgToUserTemplate = "Failed to retrieve the dependencies tree for the %s project. Please contact your " + +var CurationErrorMsgToUserTemplate = "Failed to retrieve the dependencies tree for the %s project. Please contact your " + "Artifactory administrator to verify pass-through for Curation audit is enabled for your project" func GetExcludePattern(params utils.AuditParams) string { @@ -180,11 +180,15 @@ func SuspectCurationBlockedError(isCurationCmd bool, tech techutils.Technology, case techutils.Maven: if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") || strings.Contains(cmdOutput, "status code: 500") { - msgToUser = fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven) + msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven) } case techutils.Pip: if strings.Contains(strings.ToLower(cmdOutput), "http error 403") { - msgToUser = fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Pip) + msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Pip) + } + case techutils.Go: + if strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") { + msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Go) } } return diff --git a/commands/audit/sca/common_test.go b/commands/audit/sca/common_test.go index be8b42fc..f9da86b4 100644 --- a/commands/audit/sca/common_test.go +++ b/commands/audit/sca/common_test.go @@ -280,6 +280,7 @@ func TestSuspectCurationBlockedError(t *testing.T) { mvnOutput1 := "status code: 403, reason phrase: Forbidden (403)" mvnOutput2 := "status code: 500, reason phrase: Server Error (500)" pipOutput := "because of HTTP error 403 Client Error: Forbidden for url" + goOutput := "Failed running Go command: 403 Forbidden" tests := []struct { name string @@ -293,21 +294,21 @@ func TestSuspectCurationBlockedError(t *testing.T) { isCurationCmd: true, tech: techutils.Maven, output: mvnOutput1, - expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven), + expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven), }, { name: "mvn 500 error", isCurationCmd: true, tech: techutils.Maven, output: mvnOutput2, - expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven), + expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven), }, { name: "pip 403 error", isCurationCmd: true, - tech: techutils.Maven, + tech: techutils.Pip, output: pipOutput, - expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Pip), + expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Pip), }, { name: "pip not pass through error", @@ -322,7 +323,14 @@ func TestSuspectCurationBlockedError(t *testing.T) { output: "http error 401", }, { - name: "nota supported tech", + name: "golang 403 error", + isCurationCmd: true, + tech: techutils.Go, + output: goOutput, + expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Go), + }, + { + name: "not a supported tech", isCurationCmd: true, tech: coreutils.CI, output: pipOutput, @@ -330,7 +338,7 @@ func TestSuspectCurationBlockedError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - SuspectCurationBlockedError(tt.isCurationCmd, tt.tech, tt.output) + assert.Equal(t, SuspectCurationBlockedError(tt.isCurationCmd, tt.tech, tt.output), tt.expect) }) } } diff --git a/commands/audit/sca/go/golang.go b/commands/audit/sca/go/golang.go index 41528985..38d95b94 100644 --- a/commands/audit/sca/go/golang.go +++ b/commands/audit/sca/go/golang.go @@ -1,13 +1,16 @@ package _go import ( + "errors" "fmt" biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/datastructures" goartifactoryutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/golang" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/techutils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "strings" ) @@ -28,20 +31,36 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils. err = fmt.Errorf("failed while getting server details: %s", err.Error()) return } + goProxyParams := goutils.GoProxyUrlParams{Direct: true} + // in case of curation command, we set an alternative cache folder when building go dep tree, + // also, it's not using the "direct" option, artifacts should be resolved only from the configured repo. + if params.IsCurationCmd() { + goProxyParams.EndpointPrefix = coreutils.CurationPassThroughApi + goProxyParams.Direct = false + projCacheDir, errCacheFolder := utils.GetCurationCacheFolderByTech(techutils.Go) + if errCacheFolder != nil { + err = errCacheFolder + return + } + if err = goartifactoryutils.SetGoModCache(projCacheDir); err != nil { + return + } + } remoteGoRepo := params.DepsRepo() if remoteGoRepo != "" { - if err = goartifactoryutils.SetArtifactoryAsResolutionServer(server, remoteGoRepo); err != nil { + if err = goartifactoryutils.SetArtifactoryAsResolutionServer(server, remoteGoRepo, goProxyParams); err != nil { return } } + // Calculate go dependencies graph - dependenciesGraph, err := goutils.GetDependenciesGraph(currentDir) + dependenciesGraph, err := utils.GetDependenciesGraph(currentDir) if err != nil || len(dependenciesGraph) == 0 { return } // Calculate go dependencies list - dependenciesList, err := goutils.GetDependenciesList(currentDir) + dependenciesList, err := utils.GetDependenciesList(currentDir, handleCurationGoError) if err != nil { return } @@ -58,16 +77,37 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils. uniqueDepsSet := datastructures.MakeSet[string]() populateGoDependencyTree(rootNode, dependenciesGraph, dependenciesList, uniqueDepsSet) + // In case of curation command, go version is not relevant as it can't be resolved from go repo + if !params.IsCurationCmd() { + if gotErr := addGoVersionToTree(rootNode, uniqueDepsSet); gotErr != nil { + err = gotErr + return + } + } + + dependencyTree = []*xrayUtils.GraphNode{rootNode} + uniqueDeps = uniqueDepsSet.ToSlice() + return +} + +func addGoVersionToTree(rootNode *xrayUtils.GraphNode, uniqueDepsSet *datastructures.Set[string]) error { goVersionDependency, err := getGoVersionAsDependency() if err != nil { - return + return err } rootNode.Nodes = append(rootNode.Nodes, goVersionDependency) uniqueDepsSet.Add(goVersionDependency.Id) + return err +} - dependencyTree = []*xrayUtils.GraphNode{rootNode} - uniqueDeps = uniqueDepsSet.ToSlice() - return +func handleCurationGoError(err error) (bool, error) { + if err == nil { + return false, nil + } + if msgToUser := sca.SuspectCurationBlockedError(true, techutils.Go, err.Error()); msgToUser != "" { + return true, errors.New(msgToUser) + } + return false, nil } func populateGoDependencyTree(currNode *xrayUtils.GraphNode, dependenciesGraph map[string][]string, dependenciesList map[string]bool, uniqueDepsSet *datastructures.Set[string]) { diff --git a/commands/audit/sca/go/gloang_test.go b/commands/audit/sca/go/golang_test.go similarity index 76% rename from commands/audit/sca/go/gloang_test.go rename to commands/audit/sca/go/golang_test.go index 9ba70cb0..933e0e50 100644 --- a/commands/audit/sca/go/gloang_test.go +++ b/commands/audit/sca/go/golang_test.go @@ -1,6 +1,9 @@ package _go import ( + "errors" + "fmt" + "github.com/jfrog/jfrog-cli-security/utils/techutils" "os" "path/filepath" "strings" @@ -77,3 +80,32 @@ func removeTxtSuffix(txtFileName string) error { // go.sum.txt >> go.sum return fileutils.MoveFile(txtFileName, strings.TrimSuffix(txtFileName, ".txt")) } + +func Test_handleCurationGoError(t *testing.T) { + + tests := []struct { + name string + err error + expectedError error + }{ + { + name: "curation error 403", + err: errors.New("package download failed due to 403 forbidden test failure"), + expectedError: fmt.Errorf(sca.CurationErrorMsgToUserTemplate, techutils.Go), + }, + { + name: "not curation error 500", + err: errors.New("package download failed due to 500 internal server error test failure"), + }, + { + name: "no error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := handleCurationGoError(tt.err) + assert.Equal(t, tt.expectedError, err) + assert.Equal(t, tt.expectedError != nil, got) + }) + } +} diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 36774979..40e7bcac 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -182,8 +182,8 @@ func getDirectDependenciesFromTree(dependencyTrees []*xrayCmdUtils.GraphNode) [] } func getCurationCacheByTech(tech techutils.Technology) (string, error) { - if tech == techutils.Maven { - return xrayutils.GetCurationMavenCacheFolder() + if tech == techutils.Maven || tech == techutils.Go { + return xrayutils.GetCurationCacheFolderByTech(tech) } return "", nil } diff --git a/commands/curation/curationaudit.go b/commands/curation/curationaudit.go index 0aa22791..82b54693 100644 --- a/commands/curation/curationaudit.go +++ b/commands/curation/curationaudit.go @@ -54,6 +54,7 @@ const ( TotalConcurrentRequests = 10 MinArtiPassThroughSupport = "7.82.0" + MinArtiGolangSupport = "7.87.0" MinXrayPassTHroughSupport = "3.92.0" ) @@ -62,20 +63,23 @@ var CurationOutputFormats = []string{string(outFormat.Table), string(outFormat.J var supportedTech = map[techutils.Technology]func(ca *CurationAuditCommand) (bool, error){ techutils.Npm: func(ca *CurationAuditCommand) (bool, error) { return true, nil }, techutils.Pip: func(ca *CurationAuditCommand) (bool, error) { - return ca.checkSupportByVersionOrEnv(techutils.Pip, utils.CurationPipSupport) + return ca.checkSupportByVersionOrEnv(techutils.Pip, utils.CurationSupportFlag, MinArtiPassThroughSupport) }, techutils.Maven: func(ca *CurationAuditCommand) (bool, error) { - return ca.checkSupportByVersionOrEnv(techutils.Maven, utils.CurationMavenSupport) + return ca.checkSupportByVersionOrEnv(techutils.Maven, utils.CurationSupportFlag, MinArtiPassThroughSupport) + }, + techutils.Go: func(ca *CurationAuditCommand) (bool, error) { + return ca.checkSupportByVersionOrEnv(techutils.Go, utils.CurationSupportFlag, MinArtiGolangSupport) }, } -func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Technology, envName string) (bool, error) { +func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Technology, envName string, minArtiVersion string) (bool, error) { if flag, err := clientutils.GetBoolEnvValue(envName, false); flag { return true, nil } else if err != nil { log.Error(err) } - rtVersion, serverDetails, err := ca.getRtVersionAndServiceDetails(tech) + artiVersion, serverDetails, err := ca.getRtVersionAndServiceDetails(tech) if err != nil { return false, err } @@ -86,7 +90,7 @@ func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Techno } xrayVersionErr := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, MinXrayPassTHroughSupport) - rtVersionErr := clientutils.ValidateMinimumVersion(clientutils.Artifactory, rtVersion, MinArtiPassThroughSupport) + rtVersionErr := clientutils.ValidateMinimumVersion(clientutils.Artifactory, artiVersion, minArtiVersion) if xrayVersionErr != nil || rtVersionErr != nil { return false, errors.Join(xrayVersionErr, rtVersionErr) } @@ -304,6 +308,7 @@ func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map return err } rootNode := depTreeResult.FullDepTrees[0] + // we don't pass artiUrl and repo as we don't want to download the package, only to get the name and version. _, projectName, projectScope, projectVersion := getUrlNameAndVersionByTech(tech, rootNode, nil, "", "") if projectName == "" { workPath, err := os.Getwd() @@ -312,9 +317,12 @@ func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map } projectName = filepath.Base(workPath) } - + fullProjectName := projectName + if projectVersion != "" { + fullProjectName += ":" + projectVersion + } if ca.Progress() != nil { - ca.Progress().SetHeadlineMsg(fmt.Sprintf("Fetch curation status for %s graph with %v nodes project name: %s:%s", tech.ToFormal(), len(depTreeResult.FlatTree.Nodes)-1, projectName, projectVersion)) + ca.Progress().SetHeadlineMsg(fmt.Sprintf("Fetch curation status for %s graph with %v nodes project name: %s", tech.ToFormal(), len(depTreeResult.FlatTree.Nodes)-1, fullProjectName)) } if projectScope != "" { projectName = projectScope + "/" + projectName @@ -626,7 +634,8 @@ func getUrlNameAndVersionByTech(tech techutils.Technology, node *xrayUtils.Graph case techutils.Pip: downloadUrls, name, version = getPythonNameVersion(node.Id, downloadUrlsMap) return - + case techutils.Go: + return getGoNameScopeAndVersion(node.Id, artiUrl, repo) } return } @@ -648,7 +657,21 @@ func getPythonNameVersion(id string, downloadUrlsMap map[string]string) (downloa return } -// input- id: gav://org.apache.tomcat.embed:tomcat-embed-jasper:8.0.33 +// input - id: go://github.com/kennygrant/sanitize:v1.2.4 +// input - repo: go +// output: downloadUrl: /api/go/go/github.com/kennygrant/sanitize/@v/v1.2.4.zip +func getGoNameScopeAndVersion(id, artiUrl, repo string) (downloadUrls []string, name, scope, version string) { + id = strings.TrimPrefix(id, techutils.Go.String()+"://") + nameVersion := strings.Split(id, ":") + name = nameVersion[0] + if len(nameVersion) > 1 { + version = nameVersion[1] + } + url := strings.TrimSuffix(artiUrl, "/") + "/api/go/" + repo + "/" + name + "/@v/" + version + ".zip" + return []string{url}, name, "", version +} + +// input(with classifier) - id: gav://org.apache.tomcat.embed:tomcat-embed-jasper:8.0.33-jdk15 // input - repo: libs-release // output - downloadUrl: /libs-release/org/apache/tomcat/embed/tomcat-embed-jasper/8.0.33/tomcat-embed-jasper-8.0.33-jdk15.jar func getMavenNameScopeAndVersion(id, artiUrl, repo string, node *xrayUtils.GraphNode) (downloadUrls []string, name, scope, version string) { diff --git a/commands/curation/curationaudit_test.go b/commands/curation/curationaudit_test.go index c8447948..42c5980e 100644 --- a/commands/curation/curationaudit_test.go +++ b/commands/curation/curationaudit_test.go @@ -408,42 +408,54 @@ func TestDoCurationAudit(t *testing.T) { tests := getTestCasesForDoCurationAudit() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Set configuration for test currentDir, err := os.Getwd() assert.NoError(t, err) configurationDir := tt.pathToTest callback := clienttestutils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, filepath.Join(currentDir, configurationDir)) defer callback() - callbackMaven := clienttestutils.SetEnvWithCallbackAndAssert(t, utils.CurationMavenSupport, "true") - defer callbackMaven() - callbackPip := clienttestutils.SetEnvWithCallbackAndAssert(t, utils.CurationPipSupport, "true") - defer callbackPip() + callbackCurationFlag := clienttestutils.SetEnvWithCallbackAndAssert(t, utils.CurationSupportFlag, "true") + defer callbackCurationFlag() + // Golang option to disable the use of the checksum database + callbackNoSum := clienttestutils.SetEnvWithCallbackAndAssert(t, "GOSUMDB", "off") + defer callbackNoSum() + + // Create Mock server and write configuration file mockServer, config := curationServer(t, tt.expectedBuildRequest, tt.expectedRequest, tt.requestToFail, tt.requestToError, tt.serveResources) defer mockServer.Close() - configFilePath := WriteServerDetailsConfigFileBytes(t, config.ArtifactoryUrl, configurationDir) + configFilePath := WriteServerDetailsConfigFileBytes(t, config.ArtifactoryUrl, configurationDir, tt.createServerWithoutCreds) defer func() { assert.NoError(t, fileutils.RemoveTempDir(configFilePath)) }() - curationCmd := NewCurationAuditCommand() - curationCmd.SetIsCurationCmd(true) - curationCmd.parallelRequests = 3 - curationCmd.SetIgnoreConfigFile(tt.shouldIgnoreConfigFile) rootDir, err := os.Getwd() assert.NoError(t, err) - // Set the working dir for npm project. - require.NoError(t, err) + + // Run pre-test command if tt.preTestExec != "" { callbackPreTest := clienttestutils.ChangeDirWithCallback(t, rootDir, tt.pathToPreTest) output, err := exec.Command(tt.preTestExec, tt.funcToGetGoals(t)...).CombinedOutput() assert.NoErrorf(t, err, string(output)) callbackPreTest() } + + // Set the working dir for project. callback3 := clienttestutils.ChangeDirWithCallback(t, rootDir, strings.TrimSuffix(tt.pathToTest, string(os.PathSeparator)+".jfrog")) defer func() { cacheFolder, err := utils.GetCurationCacheFolder() require.NoError(t, err) - assert.NoError(t, fileutils.RemoveTempDir(cacheFolder)) + err = fileutils.RemoveTempDir(cacheFolder) + if err != nil { + // in some package manager the cache folder can be deleted only by root, in this case, test continue without failing + assert.ErrorIs(t, err, os.ErrPermission) + } callback3() }() + + // Create audit command, and run it + curationCmd := NewCurationAuditCommand() + curationCmd.SetIsCurationCmd(true) + curationCmd.parallelRequests = 3 + curationCmd.SetIgnoreConfigFile(tt.shouldIgnoreConfigFile) results := map[string][]*PackageStatus{} if tt.requestToError == nil { assert.NoError(t, curationCmd.doCurateAudit(results)) @@ -461,6 +473,7 @@ func TestDoCurationAudit(t *testing.T) { assert.NoError(t, tt.cleanDependencies()) } }() + // Add the mock server to the expected blocked message url for key := range tt.expectedResp { for index := range tt.expectedResp[key] { @@ -479,24 +492,81 @@ func TestDoCurationAudit(t *testing.T) { } type testCase struct { - name string - pathToTest string - pathToPreTest string - preTestExec string - serveResources map[string]string - funcToGetGoals func(t *testing.T) []string - shouldIgnoreConfigFile bool - expectedBuildRequest map[string]bool - expectedRequest map[string]bool - requestToFail map[string]bool - expectedResp map[string][]*PackageStatus - requestToError map[string]bool - expectedError string - cleanDependencies func() error + name string + pathToTest string + pathToPreTest string + preTestExec string + serveResources map[string]string + funcToGetGoals func(t *testing.T) []string + shouldIgnoreConfigFile bool + expectedBuildRequest map[string]bool + expectedRequest map[string]bool + requestToFail map[string]bool + expectedResp map[string][]*PackageStatus + requestToError map[string]bool + expectedError string + cleanDependencies func() error + createServerWithoutCreds bool } func getTestCasesForDoCurationAudit() []testCase { tests := []testCase{ + { + name: "go tree - one blocked package", + pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "go", "curation-project", ".jfrog"), + createServerWithoutCreds: true, + serveResources: map[string]string{ + "v1.5.2.mod": filepath.Join("resources", "quote-v1.5.2.mod"), + "v1.5.2.zip": filepath.Join("resources", "quote-v1.5.2.zip"), + "v1.5.2.info": filepath.Join("resources", "quote-v1.5.2.info"), + "v1.3.0.mod": filepath.Join("resources", "sampler-v1.3.0.mod"), + "v1.3.0.zip": filepath.Join("resources", "sampler-v1.3.0.zip"), + "v1.3.0.info": filepath.Join("resources", "sampler-v1.3.0.info"), + "v0.0.0-20170915032832-14c0d48ead0c.mod": filepath.Join("resources", "text-v0.0.0-20170915032832-14c0d48ead0c.mod"), + "v0.0.0-20170915032832-14c0d48ead0c.zip": filepath.Join("resources", "text-v0.0.0-20170915032832-14c0d48ead0c.zip"), + "v0.0.0-20170915032832-14c0d48ead0c.info": filepath.Join("resources", "text-v0.0.0-20170915032832-14c0d48ead0c.info"), + }, + requestToFail: map[string]bool{ + "/api/go/go-virtual/rsc.io/sampler/@v/v1.3.0.zip": false, + }, + expectedResp: map[string][]*PackageStatus{ + "github.com/you/hello": {{ + Action: "blocked", + ParentName: "rsc.io/quote", + ParentVersion: "v1.5.2", + BlockedPackageUrl: "/api/go/go-virtual/rsc.io/sampler/@v/v1.3.0.zip", + PackageName: "rsc.io/sampler", + PackageVersion: "v1.3.0", + BlockingReason: "Policy violations", + DepRelation: "indirect", + PkgType: "go", + Policy: []Policy{ + { + Policy: "pol1", + Condition: "cond1", + }, + }, + }, + { + Action: "blocked", + ParentName: "rsc.io/sampler", + ParentVersion: "v1.3.0", + BlockedPackageUrl: "/api/go/go-virtual/rsc.io/sampler/@v/v1.3.0.zip", + PackageName: "rsc.io/sampler", + PackageVersion: "v1.3.0", + BlockingReason: "Policy violations", + DepRelation: "direct", + PkgType: "go", + Policy: []Policy{ + { + Policy: "pol1", + Condition: "cond1", + }, + }, + }, + }, + }, + }, { name: "python tree - one blocked package", pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "python", "pip", "pip-curation", ".jfrog"), @@ -541,7 +611,7 @@ func getTestCasesForDoCurationAudit() []testCase { assert.NoError(t, err) // set the cache to test project dir, in order to fill its cache with dependencies callbackPreTest := clienttestutils.ChangeDirWithCallback(t, rootDir, filepath.Join("..", "test")) - curationCache, err := utils.GetCurationMavenCacheFolder() + curationCache, err := utils.GetCurationCacheFolderByTech(techutils.Maven) callbackPreTest() require.NoError(t, err) return []string{"com.jfrog:maven-dep-tree:tree", "-DdepsTreeOutputFile=output", "-Dmaven.repo.local=" + curationCache} @@ -700,13 +770,18 @@ func curationServer(t *testing.T, expectedBuildRequest map[string]bool, expected return serverMock, config } -func WriteServerDetailsConfigFileBytes(t *testing.T, url string, configPath string) string { +func WriteServerDetailsConfigFileBytes(t *testing.T, url string, configPath string, withoutCreds bool) string { + var username, password string + if !withoutCreds { + username = "admin" + password = "password" + } serverDetails := config.ConfigV5{ Servers: []*config.ServerDetails{ { - User: "admin", - Password: "password", ServerId: "test", + User: username, + Password: password, Url: url, ArtifactoryUrl: url, }, @@ -720,3 +795,33 @@ func WriteServerDetailsConfigFileBytes(t *testing.T, url string, configPath stri assert.NoError(t, os.WriteFile(confFilePath, detailsByte, 0644)) return confFilePath } + +func Test_getGoNameScopeAndVersion(t *testing.T) { + tests := []struct { + name string + compId string + rtUrl string + downloadUrls []string + repo string + compName string + version string + }{ + { + name: "valid go component id", + compId: "go://github.com/kennygrant/sanitize:v1.2.4", + rtUrl: "http://test/artifactory", + repo: "test", + downloadUrls: []string{"http://test/artifactory/api/go/test/github.com/kennygrant/sanitize/@v/v1.2.4.zip"}, + compName: "github.com/kennygrant/sanitize", + version: "v1.2.4", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotDownloadUrls, gotName, _, gotVersion := getGoNameScopeAndVersion(tt.compId, tt.rtUrl, tt.repo) + assert.Equal(t, tt.downloadUrls, gotDownloadUrls) + assert.Equal(t, tt.compName, gotName) + assert.Equal(t, tt.version, gotVersion) + }) + } +} diff --git a/go.mod b/go.mod index 1f4892e6..866c4892 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.22.3 require ( github.com/gookit/color v1.5.4 - github.com/jfrog/build-info-go v1.9.27 + github.com/jfrog/build-info-go v1.9.29 github.com/jfrog/gofrog v1.7.2 github.com/jfrog/jfrog-apps-config v1.0.1 - github.com/jfrog/jfrog-cli-core/v2 v2.53.0 - github.com/jfrog/jfrog-client-go v1.40.2 + github.com/jfrog/jfrog-cli-core/v2 v2.53.1 + github.com/jfrog/jfrog-client-go v1.41.0 github.com/magiconair/properties v1.8.7 github.com/owenrumney/go-sarif/v2 v2.3.0 github.com/stretchr/testify v1.9.0 @@ -98,8 +98,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240603153234-c15cde9842c7 +//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240530101935-539b5837ce04 +//replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev -// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev +//replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index d3373250..e18474ab 100644 --- a/go.sum +++ b/go.sum @@ -96,16 +96,16 @@ github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+ github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= 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.9.27 h1:7RWJcajqtNNbGHuYkgOLUIG7mmRKF0yxC7mvYAbdVlU= -github.com/jfrog/build-info-go v1.9.27/go.mod h1:8T7/ajM9aGshvgpwCtXwIFpyF/R6CEn4W+/FLryNXWw= +github.com/jfrog/build-info-go v1.9.29 h1:3vJ+kbk9PpU6wjisXi9c4qISNpYkISh/NmB5mq1ZlSY= +github.com/jfrog/build-info-go v1.9.29/go.mod h1:AzFJlN/yKfKuKcSBaGy5nNmKN1xzx6+XcRWAswCTLTA= github.com/jfrog/gofrog v1.7.2 h1:VkAaA/9tmbw27IqgUOmaZWnO6ATUqL3vRzDnsROKATw= github.com/jfrog/gofrog v1.7.2/go.mod h1:WJFk88SR9Sr9mKl1bQBig7DmSdXiBGKV3WhL9O6jL9w= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240603153234-c15cde9842c7 h1:pWVjLJ4kwm9jn0hEPWABtQTXT77phbvLGR7icTNvtOk= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240603153234-c15cde9842c7/go.mod h1:ckNDje4Ydeo7DPS4kiripqSZ7xF7mVE5Gca3uJ5vTik= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240530101935-539b5837ce04 h1:ERLE/L7YPr6aCUTeAnE8SXU5VOZHd5/XK16rM1TEpts= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240530101935-539b5837ce04/go.mod h1:37RR4pYgXZM4w7tywyfRu8t2wagt0qf5wBtpDILWBsk= +github.com/jfrog/jfrog-cli-core/v2 v2.53.1 h1:odwPJlrUVw7yKIYctVIn7/8YW/Ynwq4vvsmrXOzAAa8= +github.com/jfrog/jfrog-cli-core/v2 v2.53.1/go.mod h1:4iTSevmlThM1Aw5NAY4WyVxim5US4SkrmxHSHFimaqk= +github.com/jfrog/jfrog-client-go v1.41.0 h1:g5OTFvreOVQ6U/5LUXFJfA3Bc+AZCo2PO/EzCLxLbLE= +github.com/jfrog/jfrog-client-go v1.41.0/go.mod h1:AN+/mT2DIBE4oRZicJojqND2BEKLfA7f73i5rT3Lfcc= 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= diff --git a/tests/testdata/projects/package-managers/go/curation-project/.jfrog/projects/go.yaml b/tests/testdata/projects/package-managers/go/curation-project/.jfrog/projects/go.yaml new file mode 100644 index 00000000..7f16d533 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/.jfrog/projects/go.yaml @@ -0,0 +1,5 @@ +version: 1 +type: go +resolver: + repo: go-virtual + serverId: test \ No newline at end of file diff --git a/tests/testdata/projects/package-managers/go/curation-project/go.mod b/tests/testdata/projects/package-managers/go/curation-project/go.mod new file mode 100644 index 00000000..309e9d79 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/go.mod @@ -0,0 +1,9 @@ +module github.com/you/hello + +go 1.20 + +require rsc.io/quote v1.5.2 + +require ( + rsc.io/sampler v1.3.0 // indirect +) diff --git a/tests/testdata/projects/package-managers/go/curation-project/hello.go b/tests/testdata/projects/package-managers/go/curation-project/hello.go new file mode 100644 index 00000000..0a29866c --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/hello.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "rsc.io/quote" +) + +func main() { + fmt.Println(quote.Hello()) +} diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.info b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.info new file mode 100644 index 00000000..95db50a7 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.info @@ -0,0 +1 @@ +{"Version":"v1.5.2","Time":"2019-04-11T23:18:29Z"} \ No newline at end of file diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.mod b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.mod new file mode 100644 index 00000000..5585b91d --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.mod @@ -0,0 +1,3 @@ +module "rsc.io/quote" + +require "rsc.io/sampler" v1.3.0 diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.zip b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.zip new file mode 100644 index 00000000..279c9dea Binary files /dev/null and b/tests/testdata/projects/package-managers/go/curation-project/resources/quote-v1.5.2.zip differ diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.info b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.info new file mode 100644 index 00000000..926a7345 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.info @@ -0,0 +1 @@ +{"Version":"v1.3.0","Time":"2019-04-11T18:32:53Z"} \ No newline at end of file diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.mod b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.mod new file mode 100644 index 00000000..7e006849 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.mod @@ -0,0 +1,3 @@ +module "rsc.io/sampler" + +require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.zip b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.zip new file mode 100644 index 00000000..56af1de6 Binary files /dev/null and b/tests/testdata/projects/package-managers/go/curation-project/resources/sampler-v1.3.0.zip differ diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.info b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.info new file mode 100644 index 00000000..90aa0672 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.info @@ -0,0 +1 @@ +{"Version":"v0.0.0-20170915032832-14c0d48ead0c","Time":"2019-04-11T18:33:09Z"} \ No newline at end of file diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.mod b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.mod new file mode 100644 index 00000000..f8a27b65 --- /dev/null +++ b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.mod @@ -0,0 +1 @@ +module golang.org/x/text diff --git a/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.zip b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.zip new file mode 100644 index 00000000..2943802d Binary files /dev/null and b/tests/testdata/projects/package-managers/go/curation-project/resources/text-v0.0.0-20170915032832-14c0d48ead0c.zip differ diff --git a/utils/golang.go b/utils/golang.go new file mode 100644 index 00000000..ec4d424a --- /dev/null +++ b/utils/golang.go @@ -0,0 +1,23 @@ +package utils + +import ( + "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +func GetDependenciesList(projectDir string, errorFunc utils.HandleErrorFunc) (map[string]bool, error) { + deps, err := utils.GetDependenciesList(projectDir, log.Logger, errorFunc) + if err != nil { + return nil, errorutils.CheckError(err) + } + return deps, nil +} + +func GetDependenciesGraph(projectDir string) (map[string][]string, error) { + deps, err := utils.GetDependenciesGraph(projectDir, log.Logger) + if err != nil { + return nil, errorutils.CheckError(err) + } + return deps, nil +} diff --git a/utils/paths.go b/utils/paths.go index e0462150..918d1b9b 100644 --- a/utils/paths.go +++ b/utils/paths.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/utils/techutils" "os" "path/filepath" ) @@ -15,8 +16,7 @@ const ( CurationsDir = "JFROG_CLI_CURATION_DIR" // #nosec G101 -- Not credentials. - CurationMavenSupport = "JFROG_CLI_CURATION_MAVEN" - CurationPipSupport = "JFROG_CLI_CURATION_PIP" + CurationSupportFlag = "JFROG_CLI_CURATION" ) func getJfrogCurationFolder() (string, error) { @@ -39,11 +39,21 @@ func GetCurationCacheFolder() (string, error) { return filepath.Join(curationFolder, "cache"), nil } -func GetCurationMavenCacheFolder() (projectDir string, err error) { +func GetCurationCacheFolderByTech(tech techutils.Technology) (projectDir string, err error) { + pathHash, errFromHash := getProjectPathHash() + if errFromHash != nil { + err = errFromHash + return + } curationFolder, err := GetCurationCacheFolder() if err != nil { return "", err } + projectDir = filepath.Join(curationFolder, tech.String(), pathHash) + return +} + +func getProjectPathHash() (string, error) { workingDir, err := os.Getwd() if err != nil { return "", err @@ -54,8 +64,7 @@ func GetCurationMavenCacheFolder() (projectDir string, err error) { if err != nil { return "", err } - projectDir = filepath.Join(curationFolder, "maven", hex.EncodeToString(hasher.Sum(nil))) - return + return hex.EncodeToString(hasher.Sum(nil)), nil } func GetCurationPipCacheFolder() (string, error) {