You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	feat: improve vulnerability reporting via GitHub issues (#3924)
* feat: improve vulnerability reporting via GitHub issues * feat: update reports * chore: add tls cert links * only write log on error * chore: update formatting * chore: update handling of direct dependencies * chore: fix linting issue * chore: minor updates
This commit is contained in:
		| @@ -2,6 +2,9 @@ package cmd | ||||
|  | ||||
| import ( | ||||
| 	"archive/zip" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"encoding/xml" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| @@ -13,9 +16,6 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"encoding/json" | ||||
| 	"encoding/xml" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/checkmarx" | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	piperHttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| @@ -26,67 +26,96 @@ import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/toolrecord" | ||||
| 	"github.com/bmatcuk/doublestar" | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| type checkmarxExecuteScanUtils interface { | ||||
| 	CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error | ||||
| 	FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) | ||||
| 	Stat(name string) (os.FileInfo, error) | ||||
| 	Open(name string) (*os.File, error) | ||||
| 	WriteFile(filename string, data []byte, perm os.FileMode) error | ||||
| 	PathMatch(pattern, name string) (bool, error) | ||||
| 	GetWorkspace() string | ||||
| 	GetIssueService() *github.IssuesService | ||||
| 	GetSearchService() *github.SearchService | ||||
| } | ||||
|  | ||||
| type checkmarxExecuteScanUtilsBundle struct { | ||||
| 	workspace string | ||||
| 	issues    *github.IssuesService | ||||
| 	search    *github.SearchService | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) { | ||||
| 	return doublestar.PathMatch(pattern, name) | ||||
| } | ||||
|  | ||||
| func (b checkmarxExecuteScanUtilsBundle) GetWorkspace() string { | ||||
| 	return b.workspace | ||||
| func (c *checkmarxExecuteScanUtilsBundle) GetWorkspace() string { | ||||
| 	return c.workspace | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error { | ||||
| 	return ioutil.WriteFile(filename, data, perm) | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) { | ||||
| 	return zip.FileInfoHeader(fi) | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) { | ||||
| 	return os.Stat(name) | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) Open(name string) (*os.File, error) { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) Open(name string) (*os.File, error) { | ||||
| 	return os.Open(name) | ||||
| } | ||||
|  | ||||
| func (checkmarxExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| func (c *checkmarxExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	return piperGithub.CreateIssue(ghCreateIssueOptions) | ||||
| } | ||||
|  | ||||
| func (c *checkmarxExecuteScanUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return c.issues | ||||
| } | ||||
|  | ||||
| func (c *checkmarxExecuteScanUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return c.search | ||||
| } | ||||
|  | ||||
| func newCheckmarxExecuteScanUtilsBundle(workspace string, client *github.Client) checkmarxExecuteScanUtils { | ||||
| 	utils := checkmarxExecuteScanUtilsBundle{ | ||||
| 		workspace: workspace, | ||||
| 	} | ||||
| 	if client != nil { | ||||
| 		utils.issues = client.Issues | ||||
| 		utils.search = client.Search | ||||
| 	} | ||||
| 	return &utils | ||||
| } | ||||
|  | ||||
| func checkmarxExecuteScan(config checkmarxExecuteScanOptions, _ *telemetry.CustomData, influx *checkmarxExecuteScanInflux) { | ||||
| 	client := &piperHttp.Client{} | ||||
| 	options := piperHttp.ClientOptions{MaxRetries: config.MaxRetries} | ||||
| 	client.SetOptions(options) | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, ghClient, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Warning("Failed to get GitHub client") | ||||
| 	} | ||||
| 	sys, err := checkmarx.NewSystemInstance(client, config.ServerURL, config.Username, config.Password) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatalf("Failed to create Checkmarx client talking to URL %v", config.ServerURL) | ||||
| 	} | ||||
| 	influx.step_data.fields.checkmarx = false | ||||
| 	utils := checkmarxExecuteScanUtilsBundle{workspace: "./"} | ||||
| 	if err := runScan(config, sys, influx, utils); err != nil { | ||||
| 	utils := newCheckmarxExecuteScanUtilsBundle("./", ghClient) | ||||
| 	if err := runScan(ctx, config, sys, influx, utils); err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to execute Checkmarx scan.") | ||||
| 	} | ||||
| 	influx.step_data.fields.checkmarx = true | ||||
| } | ||||
|  | ||||
| func runScan(config checkmarxExecuteScanOptions, sys checkmarx.System, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| func runScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| 	teamID := config.TeamID | ||||
| 	if len(teamID) == 0 { | ||||
| 		readTeamID, err := loadTeamIDByTeamName(config, sys, teamID) | ||||
| @@ -114,7 +143,7 @@ func runScan(config checkmarxExecuteScanOptions, sys checkmarx.System, influx *c | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = uploadAndScan(config, sys, project, influx, utils) | ||||
| 	err = uploadAndScan(ctx, config, sys, project, influx, utils) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "scan, upload, and result validation returned an error") | ||||
| 	} | ||||
| @@ -240,13 +269,13 @@ func zipWorkspaceFiles(filterPattern string, utils checkmarxExecuteScanUtils) (* | ||||
| 	return zipFile, nil | ||||
| } | ||||
|  | ||||
| func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| func uploadAndScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| 	previousScans, err := sys.GetScans(project.ID) | ||||
| 	if err != nil && config.VerifyOnly { | ||||
| 		log.Entry().Warnf("Cannot load scans for project %v, verification only mode aborted", project.Name) | ||||
| 	} | ||||
| 	if len(previousScans) > 0 && config.VerifyOnly { | ||||
| 		err := verifyCxProjectCompliance(config, sys, previousScans[0].ID, influx, utils) | ||||
| 		err := verifyCxProjectCompliance(ctx, config, sys, previousScans[0].ID, influx, utils) | ||||
| 		if err != nil { | ||||
| 			log.SetErrorCategory(log.ErrorCompliance) | ||||
| 			return errors.Wrapf(err, "project %v not compliant", project.Name) | ||||
| @@ -280,12 +309,12 @@ func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, pro | ||||
| 			incremental = false | ||||
| 		} | ||||
|  | ||||
| 		return triggerScan(config, sys, project, incremental, influx, utils) | ||||
| 		return triggerScan(ctx, config, sys, project, incremental, influx, utils) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, incremental bool, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| func triggerScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, incremental bool, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| 	scan, err := sys.ScanProject(project.ID, incremental, true, !config.AvoidDuplicateProjectScans) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "cannot scan project %v", project.Name) | ||||
| @@ -298,10 +327,10 @@ func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, proje | ||||
| 	} | ||||
|  | ||||
| 	log.Entry().Debugln("Scan finished") | ||||
| 	return verifyCxProjectCompliance(config, sys, scan.ID, influx, utils) | ||||
| 	return verifyCxProjectCompliance(ctx, config, sys, scan.ID, influx, utils) | ||||
| } | ||||
|  | ||||
| func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| func verifyCxProjectCompliance(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { | ||||
| 	var reports []piperutils.Path | ||||
| 	if config.GeneratePdfReport { | ||||
| 		pdfReportName := createReportName(utils.GetWorkspace(), "CxSASTReport_%v.pdf") | ||||
| @@ -354,11 +383,7 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx | ||||
| 		// add JSON report to archiving list | ||||
| 		reports = append(reports, paths...) | ||||
| 	} | ||||
|  | ||||
| 	links := []piperutils.Path{{Target: results["DeepLink"].(string), Name: "Checkmarx Web UI"}} | ||||
| 	piperutils.PersistReportsAndLinks("checkmarxExecuteScan", utils.GetWorkspace(), reports, links) | ||||
|  | ||||
| 	reportToInflux(results, influx) | ||||
|  | ||||
| 	insecure := false | ||||
| 	var insecureResults []string | ||||
| @@ -370,8 +395,14 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx | ||||
|  | ||||
| 		if insecure && config.CreateResultIssue && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { | ||||
| 			log.Entry().Debug("Creating/updating GitHub issue with check results") | ||||
| 			err := reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils) | ||||
| 			if err != nil { | ||||
| 			gh := reporting.GitHub{ | ||||
| 				Owner:         &config.Owner, | ||||
| 				Repository:    &config.Repository, | ||||
| 				Assignees:     &config.Assignees, | ||||
| 				IssueService:  utils.GetIssueService(), | ||||
| 				SearchService: utils.GetSearchService(), | ||||
| 			} | ||||
| 			if err := gh.UploadSingleReport(ctx, scanReport); err != nil { | ||||
| 				return fmt.Errorf("failed to upload scan results into GitHub: %w", err) | ||||
| 			} | ||||
| 		} | ||||
| @@ -385,6 +416,9 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	piperutils.PersistReportsAndLinks("checkmarxExecuteScan", utils.GetWorkspace(), reports, links) | ||||
| 	reportToInflux(results, influx) | ||||
|  | ||||
| 	if insecure { | ||||
| 		if config.VulnerabilityThresholdResult == "FAILURE" { | ||||
| 			log.SetErrorCategory(log.ErrorCompliance) | ||||
| @@ -496,7 +530,7 @@ func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scanID i | ||||
| 		return errors.Wrap(err, "failed to download the report") | ||||
| 	} | ||||
| 	log.Entry().Debugf("Saving report to file %v...", reportFileName) | ||||
| 	return utils.WriteFile(reportFileName, report, 0700) | ||||
| 	return utils.WriteFile(reportFileName, report, 0o700) | ||||
| } | ||||
|  | ||||
| func enforceThresholds(config checkmarxExecuteScanOptions, results map[string]interface{}) (bool, []string, []string) { | ||||
| @@ -696,7 +730,7 @@ func getDetailedResults(sys checkmarx.System, reportFileName string, scanID int, | ||||
| 		return resultMap, errors.Wrap(err, "failed to download xml report") | ||||
| 	} | ||||
| 	if len(data) > 0 { | ||||
| 		err = utils.WriteFile(reportFileName, data, 0700) | ||||
| 		err = utils.WriteFile(reportFileName, data, 0o700) | ||||
| 		if err != nil { | ||||
| 			return resultMap, errors.Wrap(err, "failed to write file") | ||||
| 		} | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package cmd | ||||
| import ( | ||||
| 	"archive/zip" | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| @@ -16,8 +17,9 @@ import ( | ||||
| 	"github.com/bmatcuk/doublestar" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/checkmarx" | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| type fileInfo struct { | ||||
| @@ -29,23 +31,28 @@ type fileInfo struct { | ||||
| 	syss    interface{} // underlying data source (can return nil) | ||||
| } | ||||
|  | ||||
| func (fi fileInfo) IsDir() bool { | ||||
| 	return fi.dir | ||||
| func (f *fileInfo) IsDir() bool { | ||||
| 	return f.dir | ||||
| } | ||||
| func (fi fileInfo) Name() string { | ||||
| 	return fi.nam | ||||
|  | ||||
| func (f *fileInfo) Name() string { | ||||
| 	return f.nam | ||||
| } | ||||
| func (fi fileInfo) Size() int64 { | ||||
| 	return fi.siz | ||||
|  | ||||
| func (f *fileInfo) Size() int64 { | ||||
| 	return f.siz | ||||
| } | ||||
| func (fi fileInfo) ModTime() time.Time { | ||||
| 	return fi.modtime | ||||
|  | ||||
| func (f *fileInfo) ModTime() time.Time { | ||||
| 	return f.modtime | ||||
| } | ||||
| func (fi fileInfo) Mode() os.FileMode { | ||||
| 	return fi.mod | ||||
|  | ||||
| func (f *fileInfo) Mode() os.FileMode { | ||||
| 	return f.mod | ||||
| } | ||||
| func (fi fileInfo) Sys() interface{} { | ||||
| 	return fi.syss | ||||
|  | ||||
| func (f *fileInfo) Sys() interface{} { | ||||
| 	return f.syss | ||||
| } | ||||
|  | ||||
| type systemMock struct { | ||||
| @@ -65,12 +72,15 @@ func (sys *systemMock) FilterPresetByName(_ []checkmarx.Preset, presetName strin | ||||
| 	} | ||||
| 	return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) FilterPresetByID([]checkmarx.Preset, int) checkmarx.Preset { | ||||
| 	return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) FilterProjectByName([]checkmarx.Project, string) checkmarx.Project { | ||||
| 	return checkmarx.Project{ID: 1, Name: "Test", TeamID: "16", IsPublic: true} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetProjectByID(projectID int) (checkmarx.Project, error) { | ||||
| 	if projectID == 17 { | ||||
| 		return checkmarx.Project{ID: 17, Name: "Test_PR-17", TeamID: "16", IsPublic: true}, nil | ||||
| @@ -91,12 +101,14 @@ func (sys *systemMock) GetProjectsByNameAndTeam(projectName, teamID string) ([]c | ||||
| 	sys.previousPName = projectName | ||||
| 	return []checkmarx.Project{}, fmt.Errorf("no project error") | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) FilterTeamByName(_ []checkmarx.Team, teamName string) (checkmarx.Team, error) { | ||||
| 	if teamName == "OpenSource/Cracks/16" { | ||||
| 		return checkmarx.Team{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, nil | ||||
| 	} | ||||
| 	return checkmarx.Team{ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) FilterTeamByID(_ []checkmarx.Team, teamID json.RawMessage) checkmarx.Team { | ||||
| 	teamIDBytes, _ := teamID.MarshalJSON() | ||||
| 	if bytes.Equal(teamIDBytes, []byte(`"16"`)) { | ||||
| @@ -104,56 +116,72 @@ func (sys *systemMock) FilterTeamByID(_ []checkmarx.Team, teamID json.RawMessage | ||||
| 	} | ||||
| 	return checkmarx.Team{ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) DownloadReport(int) ([]byte, error) { | ||||
| 	return sys.response.([]byte), nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetReportStatus(int) (checkmarx.ReportStatusResponse, error) { | ||||
| 	return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) RequestNewReport(int, string) (checkmarx.Report, error) { | ||||
| 	return checkmarx.Report{ReportID: 17}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetResults(int) checkmarx.ResultsStatistics { | ||||
| 	return checkmarx.ResultsStatistics{} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetScans(int) ([]checkmarx.ScanStatus, error) { | ||||
| 	return []checkmarx.ScanStatus{{IsIncremental: true}, {IsIncremental: true}, {IsIncremental: true}, {IsIncremental: false}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetScanStatusAndDetail(int) (string, checkmarx.ScanStatusDetail) { | ||||
| 	return "Finished", checkmarx.ScanStatusDetail{Stage: "Step 1 of 25", Step: "Scan something"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) ScanProject(_ int, isIncrementalV, isPublicV, forceScanV bool) (checkmarx.Scan, error) { | ||||
| 	sys.isIncremental = isIncrementalV | ||||
| 	sys.isPublic = isPublicV | ||||
| 	sys.forceScan = forceScanV | ||||
| 	return checkmarx.Scan{ID: 16}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) UpdateProjectConfiguration(int, int, string) error { | ||||
| 	sys.updateProjectConfigurationCalled = true | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) UpdateProjectExcludeSettings(int, string, string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) UploadProjectSourceCode(int, string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) CreateProject(string, string) (checkmarx.ProjectCreateResult, error) { | ||||
| 	return checkmarx.ProjectCreateResult{ID: 20}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) CreateBranch(int, string) int { | ||||
| 	return 18 | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetShortDescription(int, int) (checkmarx.ShortDescription, error) { | ||||
| 	return checkmarx.ShortDescription{Text: "dummyText"}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetPresets() []checkmarx.Preset { | ||||
| 	sys.getPresetsCalled = true | ||||
| 	return []checkmarx.Preset{{ID: 10078, Name: "SAP Java Default", OwnerName: "16"}, {ID: 10048, Name: "SAP JS Default", OwnerName: "16"}, {ID: 16, Name: "CX_Default", OwnerName: "16"}} | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetProjects() ([]checkmarx.Project, error) { | ||||
| 	return []checkmarx.Project{{ID: 15, Name: "OtherTest", TeamID: "16"}, {ID: 1, Name: "Test", TeamID: "16"}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMock) GetTeams() []checkmarx.Team { | ||||
| 	return []checkmarx.Team{{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, {ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"}} | ||||
| } | ||||
| @@ -169,45 +197,59 @@ type systemMockForExistingProject struct { | ||||
| func (sys *systemMockForExistingProject) FilterPresetByName([]checkmarx.Preset, string) checkmarx.Preset { | ||||
| 	return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) FilterPresetByID([]checkmarx.Preset, int) checkmarx.Preset { | ||||
| 	return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) FilterProjectByName([]checkmarx.Project, string) checkmarx.Project { | ||||
| 	return checkmarx.Project{ID: 1, Name: "TestExisting", TeamID: "16", IsPublic: true} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetProjectByID(int) (checkmarx.Project, error) { | ||||
| 	return checkmarx.Project{}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetProjectsByNameAndTeam(projectName, teamID string) ([]checkmarx.Project, error) { | ||||
| 	return []checkmarx.Project{{ID: 19, Name: projectName, TeamID: teamID, IsPublic: true}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) FilterTeamByName([]checkmarx.Team, string) (checkmarx.Team, error) { | ||||
| 	return checkmarx.Team{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) FilterTeamByID([]checkmarx.Team, json.RawMessage) checkmarx.Team { | ||||
| 	return checkmarx.Team{ID: json.RawMessage(`"15"`), FullName: "OpenSource/Cracks/15"} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) DownloadReport(int) ([]byte, error) { | ||||
| 	return sys.response.([]byte), nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetReportStatus(int) (checkmarx.ReportStatusResponse, error) { | ||||
| 	return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) RequestNewReport(int, string) (checkmarx.Report, error) { | ||||
| 	return checkmarx.Report{ReportID: 17}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetResults(int) checkmarx.ResultsStatistics { | ||||
| 	return checkmarx.ResultsStatistics{} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetScans(int) ([]checkmarx.ScanStatus, error) { | ||||
| 	return []checkmarx.ScanStatus{{IsIncremental: true}, {IsIncremental: true}, {IsIncremental: true}, {IsIncremental: false}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetShortDescription(int, int) (checkmarx.ShortDescription, error) { | ||||
| 	return checkmarx.ShortDescription{Text: "dummyText"}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetScanStatusAndDetail(int) (string, checkmarx.ScanStatusDetail) { | ||||
| 	return "Finished", checkmarx.ScanStatusDetail{Stage: "", Step: ""} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) ScanProject(_ int, isIncrementalV, isPublicV, forceScanV bool) (checkmarx.Scan, error) { | ||||
| 	sys.scanProjectCalled = true | ||||
| 	sys.isIncremental = isIncrementalV | ||||
| @@ -215,27 +257,35 @@ func (sys *systemMockForExistingProject) ScanProject(_ int, isIncrementalV, isPu | ||||
| 	sys.forceScan = forceScanV | ||||
| 	return checkmarx.Scan{ID: 16}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) UpdateProjectConfiguration(int, int, string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) UpdateProjectExcludeSettings(int, string, string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) UploadProjectSourceCode(int, string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) CreateProject(string, string) (checkmarx.ProjectCreateResult, error) { | ||||
| 	return checkmarx.ProjectCreateResult{}, fmt.Errorf("create project error") | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) CreateBranch(int, string) int { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetPresets() []checkmarx.Preset { | ||||
| 	return []checkmarx.Preset{{ID: 10078, Name: "SAP_Java_Default", OwnerName: "16"}, {ID: 10048, Name: "SAP_JS_Default", OwnerName: "16"}} | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetProjects() ([]checkmarx.Project, error) { | ||||
| 	return []checkmarx.Project{{ID: 1, Name: "TestExisting", TeamID: "16"}}, nil | ||||
| } | ||||
|  | ||||
| func (sys *systemMockForExistingProject) GetTeams() []checkmarx.Team { | ||||
| 	return []checkmarx.Team{{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, {ID: json.RawMessage(`"15"`), FullName: "OpenSource/Cracks/15"}} | ||||
| } | ||||
| @@ -247,56 +297,56 @@ type checkmarxExecuteScanUtilsMock struct { | ||||
| 	errorOnWriteFile      bool | ||||
| 	errorOnPathMatch      bool | ||||
| 	workspace             string | ||||
| 	ghCreateIssueError    error | ||||
| } | ||||
|  | ||||
| func newCheckmarxExecuteScanUtilsMock() checkmarxExecuteScanUtilsMock { | ||||
| 	return checkmarxExecuteScanUtilsMock{} | ||||
| func newCheckmarxExecuteScanUtilsMock() *checkmarxExecuteScanUtilsMock { | ||||
| 	return &checkmarxExecuteScanUtilsMock{} | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) GetWorkspace() string { | ||||
| func (c *checkmarxExecuteScanUtilsMock) GetWorkspace() string { | ||||
| 	return c.workspace | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) PathMatch(pattern, name string) (bool, error) { | ||||
| func (c *checkmarxExecuteScanUtilsMock) PathMatch(pattern, name string) (bool, error) { | ||||
| 	if c.errorOnPathMatch { | ||||
| 		return false, fmt.Errorf("error on PathMatch") | ||||
| 	} | ||||
| 	return doublestar.PathMatch(pattern, name) | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) WriteFile(filename string, data []byte, perm os.FileMode) error { | ||||
| func (c *checkmarxExecuteScanUtilsMock) WriteFile(filename string, data []byte, perm os.FileMode) error { | ||||
| 	if c.errorOnWriteFile { | ||||
| 		return fmt.Errorf("error on WriteFile") | ||||
| 	} | ||||
| 	return ioutil.WriteFile(filename, data, perm) | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) { | ||||
| func (c *checkmarxExecuteScanUtilsMock) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) { | ||||
| 	if c.errorOnFileInfoHeader { | ||||
| 		return nil, fmt.Errorf("error on FileInfoHeader") | ||||
| 	} | ||||
| 	return zip.FileInfoHeader(fi) | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) Stat(name string) (os.FileInfo, error) { | ||||
| func (c *checkmarxExecuteScanUtilsMock) Stat(name string) (os.FileInfo, error) { | ||||
| 	if c.errorOnStat { | ||||
| 		return nil, fmt.Errorf("error on Stat") | ||||
| 	} | ||||
| 	return os.Stat(name) | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) Open(name string) (*os.File, error) { | ||||
| func (c *checkmarxExecuteScanUtilsMock) Open(name string) (*os.File, error) { | ||||
| 	if c.errorOnOpen { | ||||
| 		return nil, fmt.Errorf("error on Open") | ||||
| 	} | ||||
| 	return os.Open(name) | ||||
| } | ||||
|  | ||||
| func (c checkmarxExecuteScanUtilsMock) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	if c.ghCreateIssueError != nil { | ||||
| 		return c.ghCreateIssueError | ||||
| 	} | ||||
| func (c *checkmarxExecuteScanUtilsMock) GetIssueService() *github.IssuesService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *checkmarxExecuteScanUtilsMock) GetSearchService() *github.SearchService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -318,7 +368,7 @@ func TestFilterFileGlob(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range tt { | ||||
| 		result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, v.input, v.fInfo, newCheckmarxExecuteScanUtilsMock()) | ||||
| 		result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, v.input, &v.fInfo, newCheckmarxExecuteScanUtilsMock()) | ||||
| 		assert.Equal(t, v.expected, result, fmt.Sprintf("wrong result for run %v", k)) | ||||
| 		assert.NoError(t, err, "no error expected in run %v", k) | ||||
| 	} | ||||
| @@ -330,8 +380,8 @@ func TestFilterFileGlob_errorOnPathMatch(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.errorOnPathMatch = true | ||||
|  | ||||
| 	result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, filepath.Join("a", "b", "c"), fileInfo{}, utilsMock) | ||||
| 	assert.Equal(t, false, result, fmt.Sprintf("wrong result")) | ||||
| 	result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, filepath.Join("a", "b", "c"), &fileInfo{}, utilsMock) | ||||
| 	assert.Equal(t, false, result, "wrong result") | ||||
| 	assert.EqualError(t, err, "Pattern **/node_modules/** could not get executed: error on PathMatch") | ||||
| } | ||||
|  | ||||
| @@ -342,17 +392,17 @@ func TestZipFolder(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		dir := t.TempDir() | ||||
|  | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd.yaml"), []byte("abcd.yaml"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd.yaml"), []byte("abcd.yaml"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0700) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		var zipFileMock bytes.Buffer | ||||
| @@ -361,7 +411,7 @@ func TestZipFolder(t *testing.T) { | ||||
|  | ||||
| 		zipString := zipFileMock.String() | ||||
|  | ||||
| 		//assert.Equal(t, 724, zipFileMock.Len(), "Expected length of 724, but got %v", zipFileMock.Len()) | ||||
| 		// assert.Equal(t, 724, zipFileMock.Len(), "Expected length of 724, but got %v", zipFileMock.Len()) | ||||
| 		assert.True(t, strings.Contains(zipString, "abcd.go"), "Expected 'abcd.go' contained") | ||||
| 		assert.True(t, strings.Contains(zipString, filepath.Join("somepath", "abcd.txt")), "Expected 'somepath/abcd.txt' contained") | ||||
| 		assert.False(t, strings.Contains(zipString, "abcd_test.go"), "Not expected 'abcd_test.go' contained") | ||||
| @@ -373,15 +423,15 @@ func TestZipFolder(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		dir := t.TempDir() | ||||
|  | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0700) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		var zipFileMock bytes.Buffer | ||||
| @@ -396,15 +446,15 @@ func TestZipFolder(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		dir := t.TempDir() | ||||
|  | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0700) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		var zipFileMock bytes.Buffer | ||||
| @@ -419,15 +469,15 @@ func TestZipFolder(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		dir := t.TempDir() | ||||
|  | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 		err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0700) | ||||
| 		err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700) | ||||
| 		err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		var zipFileMock bytes.Buffer | ||||
| @@ -507,11 +557,12 @@ func TestGetDetailedResults(t *testing.T) { | ||||
|  | ||||
| func TestRunScan(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -520,7 +571,7 @@ func TestRunScan(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| 	assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value") | ||||
| 	assert.Equal(t, true, sys.isPublic, "isPublic has wrong value") | ||||
| @@ -530,11 +581,12 @@ func TestRunScan(t *testing.T) { | ||||
|  | ||||
| func TestRunScan_nonNumeralPreset(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -543,17 +595,18 @@ func TestRunScan_nonNumeralPreset(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| } | ||||
|  | ||||
| func TestRunOptimizedScan(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{IsOptimizedAndScheduled: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "1", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -562,7 +615,7 @@ func TestRunOptimizedScan(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| 	assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value") | ||||
| 	assert.Equal(t, true, sys.isPublic, "isPublic has wrong value") | ||||
| @@ -593,6 +646,7 @@ func TestSetPresetForProjectWithNameProvided(t *testing.T) { | ||||
|  | ||||
| func TestVerifyOnly(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{VerifyOnly: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| @@ -603,13 +657,14 @@ func TestVerifyOnly(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err := runScan(options, sys, &influx, utilsMock) | ||||
| 	err := runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| 	assert.Equal(t, false, sys.scanProjectCalled, "ScanProject was invoked but shouldn't") | ||||
| } | ||||
|  | ||||
| func TestVerifyOnly_errorOnWriteFileDoesNotBlock(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{VerifyOnly: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| @@ -621,17 +676,18 @@ func TestVerifyOnly_errorOnWriteFileDoesNotBlock(t *testing.T) { | ||||
| 	utilsMock.workspace = workspace | ||||
| 	utilsMock.errorOnWriteFile = true | ||||
|  | ||||
| 	err := runScan(options, sys, &influx, utilsMock) | ||||
| 	err := runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.EqualError(t, err, "scan, upload, and result validation returned an error: project TestExisting not compliant: failed to get detailed results: failed to write file: error on WriteFile") | ||||
| } | ||||
|  | ||||
| func TestRunScanWOtherCycle(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true} | ||||
| 	options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -640,7 +696,7 @@ func TestRunScanWOtherCycle(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| 	assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") | ||||
| 	assert.Equal(t, true, sys.isPublic, "isPublic has wrong value") | ||||
| @@ -649,6 +705,7 @@ func TestRunScanWOtherCycle(t *testing.T) { | ||||
|  | ||||
| func TestRunScanErrorInZip(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true} | ||||
| 	options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| @@ -660,17 +717,18 @@ func TestRunScanErrorInZip(t *testing.T) { | ||||
| 	utilsMock.workspace = workspace | ||||
| 	utilsMock.errorOnFileInfoHeader = true | ||||
|  | ||||
| 	err := runScan(options, sys, &influx, utilsMock) | ||||
| 	err := runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.EqualError(t, err, "scan, upload, and result validation returned an error: failed to zip workspace files: failed to compact folder: error on FileInfoHeader") | ||||
| } | ||||
|  | ||||
| func TestRunScanForPullRequest(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)} | ||||
| 	options := checkmarxExecuteScanOptions{PullRequestName: "PR-19", ProjectName: "Test", VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true, AvoidDuplicateProjectScans: false} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -679,7 +737,7 @@ func TestRunScanForPullRequest(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") | ||||
| 	assert.Equal(t, true, sys.isPublic, "isPublic has wrong value") | ||||
| 	assert.Equal(t, true, sys.forceScan, "forceScan has wrong value") | ||||
| @@ -687,11 +745,12 @@ func TestRunScanForPullRequest(t *testing.T) { | ||||
|  | ||||
| func TestRunScanForPullRequestProjectNew(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true} | ||||
| 	options := checkmarxExecuteScanOptions{PullRequestName: "PR-17", ProjectName: "Test", AvoidDuplicateProjectScans: true, VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -700,7 +759,7 @@ func TestRunScanForPullRequestProjectNew(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "Unexpected error caught") | ||||
| 	assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") | ||||
| 	assert.Equal(t, true, sys.isPublic, "isPublic has wrong value") | ||||
| @@ -709,11 +768,12 @@ func TestRunScanForPullRequestProjectNew(t *testing.T) { | ||||
|  | ||||
| func TestRunScanForPullRequestProjectNew_nonNumeralPreset(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true} | ||||
| 	options := checkmarxExecuteScanOptions{PullRequestName: "PR-17", ProjectName: "Test", AvoidDuplicateProjectScans: true, VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -722,12 +782,13 @@ func TestRunScanForPullRequestProjectNew_nonNumeralPreset(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.NoError(t, err, "error occurred but none expected") | ||||
| } | ||||
|  | ||||
| func TestRunScanHighViolationPercentage(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?> | ||||
| 	<CxXMLResults InitiatorName="admin" Owner="admin" ScanId="1000005" ProjectId="2" ProjectName="Project 1" TeamFullPathOnReportDate="CxServer" DeepLink="http://WIN2K12-TEMP/CxWebClient/ViewerMain.aspx?scanid=1000005&projectid=2" ScanStart="Sunday, December 3, 2017 4:50:34 PM" Preset="Checkmarx Default" ScanTime="00h:03m:18s" LinesOfCodeScanned="6838" FilesScanned="34" ReportCreationTime="Sunday, December 3, 2017 6:13:45 PM" Team="CxServer" CheckmarxVersion="8.6.0" ScanComments="" ScanType="Incremental" SourceOrigin="LocalPath" Visibility="Public"> | ||||
| @@ -751,7 +812,7 @@ func TestRunScanHighViolationPercentage(t *testing.T) { | ||||
| 	</CxXMLResults>`)} | ||||
| 	options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdHigh: 100, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -760,12 +821,13 @@ func TestRunScanHighViolationPercentage(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.Contains(t, fmt.Sprint(err), "the project is not compliant", "Expected different error") | ||||
| } | ||||
|  | ||||
| func TestRunScanHighViolationAbsolute(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?> | ||||
| 		<CxXMLResults InitiatorName="admin" Owner="admin" ScanId="1000005" ProjectId="2" ProjectName="Project 1" TeamFullPathOnReportDate="CxServer" DeepLink="http://WIN2K12-TEMP/CxWebClient/ViewerMain.aspx?scanid=1000005&projectid=2" ScanStart="Sunday, December 3, 2017 4:50:34 PM" Preset="Checkmarx Default" ScanTime="00h:03m:18s" LinesOfCodeScanned="6838" FilesScanned="34" ReportCreationTime="Sunday, December 3, 2017 6:13:45 PM" Team="CxServer" CheckmarxVersion="8.6.0" ScanComments="" ScanType="Incremental" SourceOrigin="LocalPath" Visibility="Public"> | ||||
| @@ -789,7 +851,7 @@ func TestRunScanHighViolationAbsolute(t *testing.T) { | ||||
| 		</CxXMLResults>`)} | ||||
| 	options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdLow: 1, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} | ||||
| 	workspace := t.TempDir() | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700) | ||||
| 	err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700) | ||||
| 	assert.NoError(t, err) | ||||
| 	options.FilterPattern = "**/abcd.go" | ||||
|  | ||||
| @@ -798,7 +860,7 @@ func TestRunScanHighViolationAbsolute(t *testing.T) { | ||||
| 	utilsMock := newCheckmarxExecuteScanUtilsMock() | ||||
| 	utilsMock.workspace = workspace | ||||
|  | ||||
| 	err = runScan(options, sys, &influx, utilsMock) | ||||
| 	err = runScan(ctx, options, sys, &influx, utilsMock) | ||||
| 	assert.Contains(t, fmt.Sprint(err), "the project is not compliant", "Expected different error") | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| @@ -25,6 +26,8 @@ import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperutils" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/SAP/jenkins-library/pkg/toolrecord" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| type detectUtils interface { | ||||
| @@ -41,25 +44,31 @@ type detectUtils interface { | ||||
|  | ||||
| 	DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error | ||||
|  | ||||
| 	CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error | ||||
| 	GetIssueService() *github.IssuesService | ||||
| 	GetSearchService() *github.SearchService | ||||
| } | ||||
|  | ||||
| type detectUtilsBundle struct { | ||||
| 	*command.Command | ||||
| 	*piperutils.Files | ||||
| 	*piperhttp.Client | ||||
| 	issues *github.IssuesService | ||||
| 	search *github.SearchService | ||||
| } | ||||
|  | ||||
| // CreateIssue supplies capability for GitHub issue creation | ||||
| func (d *detectUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	return piperGithub.CreateIssue(ghCreateIssueOptions) | ||||
| func (d *detectUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return d.issues | ||||
| } | ||||
|  | ||||
| func (d *detectUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return d.search | ||||
| } | ||||
|  | ||||
| type blackduckSystem struct { | ||||
| 	Client bd.Client | ||||
| } | ||||
|  | ||||
| func newDetectUtils() detectUtils { | ||||
| func newDetectUtils(client *github.Client) detectUtils { | ||||
| 	utils := detectUtilsBundle{ | ||||
| 		Command: &command.Command{ | ||||
| 			ErrorCategoryMapping: map[string][]string{ | ||||
| @@ -89,6 +98,10 @@ func newDetectUtils() detectUtils { | ||||
| 		Files:  &piperutils.Files{}, | ||||
| 		Client: &piperhttp.Client{}, | ||||
| 	} | ||||
| 	if client != nil { | ||||
| 		utils.issues = client.Issues | ||||
| 		utils.search = client.Search | ||||
| 	} | ||||
| 	utils.Stdout(log.Writer()) | ||||
| 	utils.Stderr(log.Writer()) | ||||
| 	return &utils | ||||
| @@ -103,10 +116,13 @@ func newBlackduckSystem(config detectExecuteScanOptions) *blackduckSystem { | ||||
|  | ||||
| func detectExecuteScan(config detectExecuteScanOptions, _ *telemetry.CustomData, influx *detectExecuteScanInflux) { | ||||
| 	influx.step_data.fields.detect = false | ||||
| 	utils := newDetectUtils() | ||||
| 	err := runDetect(config, utils, influx) | ||||
|  | ||||
| 	ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", config.CustomTLSCertificateLinks) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Warning("Failed to get GitHub client") | ||||
| 	} | ||||
| 	utils := newDetectUtils(client) | ||||
| 	if err := runDetect(ctx, config, utils, influx); err != nil { | ||||
| 		log.Entry(). | ||||
| 			WithError(err). | ||||
| 			Fatal("failed to execute detect scan") | ||||
| @@ -115,7 +131,7 @@ func detectExecuteScan(config detectExecuteScanOptions, _ *telemetry.CustomData, | ||||
| 	influx.step_data.fields.detect = true | ||||
| } | ||||
|  | ||||
| func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detectExecuteScanInflux) error { | ||||
| func runDetect(ctx context.Context, config detectExecuteScanOptions, utils detectUtils, influx *detectExecuteScanInflux) error { | ||||
| 	// detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck | ||||
| 	err := getDetectScript(config, utils) | ||||
| 	if err != nil { | ||||
| @@ -127,7 +143,7 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec | ||||
| 			log.Entry().Warnf("failed to delete 'detect.sh' script: %v", err) | ||||
| 		} | ||||
| 	}() | ||||
| 	err = utils.Chmod("detect.sh", 0700) | ||||
| 	err = utils.Chmod("detect.sh", 0o700) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -158,7 +174,7 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec | ||||
|  | ||||
| 	err = utils.RunShell("/bin/bash", script) | ||||
| 	blackduckSystem := newBlackduckSystem(config) | ||||
| 	reportingErr := postScanChecksAndReporting(config, influx, utils, blackduckSystem) | ||||
| 	reportingErr := postScanChecksAndReporting(ctx, config, influx, utils, blackduckSystem) | ||||
| 	if reportingErr != nil { | ||||
| 		if strings.Contains(reportingErr.Error(), "License Policy Violations found") { | ||||
| 			log.Entry().Errorf("License Policy Violations found") | ||||
| @@ -194,8 +210,8 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec | ||||
| func mapErrorCategory(exitCodeKey int) { | ||||
| 	switch exitCodeKey { | ||||
| 	case 0: | ||||
| 		//In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category | ||||
| 		//hence this method doesnt need to set an error category or go to 'default' case | ||||
| 		// In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category | ||||
| 		// hence this method doesnt need to set an error category or go to 'default' case | ||||
| 		break | ||||
| 	case 1: | ||||
| 		log.SetErrorCategory(log.ErrorInfrastructure) | ||||
| @@ -230,7 +246,6 @@ func mapErrorCategory(exitCodeKey int) { | ||||
|  | ||||
| // Exit codes/error code mapping | ||||
| func exitCodeMapping(exitCodeKey int) string { | ||||
|  | ||||
| 	exitCodes := map[int]string{ | ||||
| 		0:   "Detect Scan completed successfully", | ||||
| 		1:   "FAILURE_BLACKDUCK_CONNECTIVITY => Detect was unable to connect to Black Duck. Check your configuration and connection.", | ||||
| @@ -282,8 +297,8 @@ func getDetectScript(config detectExecuteScanOptions, utils detectUtils) error { | ||||
|  | ||||
| func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectUtils) ([]string, error) { | ||||
| 	detectVersionName := getVersionName(config) | ||||
| 	//Split on spaces, the scanPropeties, so that each property is available as a single string | ||||
| 	//instead of all properties being part of a single string | ||||
| 	// Split on spaces, the scanPropeties, so that each property is available as a single string | ||||
| 	// instead of all properties being part of a single string | ||||
| 	config.ScanProperties = piperutils.SplitAndTrim(config.ScanProperties, " ") | ||||
|  | ||||
| 	if config.ScanOnChanges { | ||||
| @@ -297,7 +312,7 @@ func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectU | ||||
| 		} | ||||
| 		config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=false") | ||||
| 	} else { | ||||
| 		//When unmap is set to false, any occurances of unmap=true from scanProperties must be removed | ||||
| 		// When unmap is set to false, any occurances of unmap=true from scanProperties must be removed | ||||
| 		config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=true") | ||||
| 	} | ||||
|  | ||||
| @@ -476,7 +491,7 @@ func isMajorVulnerability(v bd.Vulnerability) bool { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func postScanChecksAndReporting(config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error { | ||||
| func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error { | ||||
| 	errorsOccured := []string{} | ||||
| 	vulns, _, err := getVulnsAndComponents(config, influx, sys) | ||||
| 	if err != nil { | ||||
| @@ -487,8 +502,14 @@ func postScanChecksAndReporting(config detectExecuteScanOptions, influx *detectE | ||||
| 		log.Entry().Debugf("Creating result issues for %v alert(s)", len(vulns.Items)) | ||||
| 		issueDetails := make([]reporting.IssueDetail, len(vulns.Items)) | ||||
| 		piperutils.CopyAtoB(vulns.Items, issueDetails) | ||||
| 		err = reporting.UploadMultipleReportsToGithub(&issueDetails, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, config.CustomTLSCertificateLinks, utils) | ||||
| 		if err != nil { | ||||
| 		gh := reporting.GitHub{ | ||||
| 			Owner:         &config.Owner, | ||||
| 			Repository:    &config.Repository, | ||||
| 			Assignees:     &config.Assignees, | ||||
| 			IssueService:  utils.GetIssueService(), | ||||
| 			SearchService: utils.GetSearchService(), | ||||
| 		} | ||||
| 		if err := gh.UploadMultipleReports(ctx, &issueDetails); err != nil { | ||||
| 			errorsOccured = append(errorsOccured, fmt.Sprint(err)) | ||||
| 		} | ||||
| 	} | ||||
| @@ -624,7 +645,7 @@ func writePolicyStatusReports(scanReport reporting.ScanReport, config detectExec | ||||
|  | ||||
| 	htmlReport, _ := scanReport.ToHTML() | ||||
| 	htmlReportPath := "piper_detect_policy_violation_report.html" | ||||
| 	if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil { | ||||
| 	if err := utils.FileWrite(htmlReportPath, htmlReport, 0o666); err != nil { | ||||
| 		log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 		return reportPaths, errors.Wrapf(err, "failed to write html report") | ||||
| 	} | ||||
| @@ -632,12 +653,12 @@ func writePolicyStatusReports(scanReport reporting.ScanReport, config detectExec | ||||
|  | ||||
| 	jsonReport, _ := scanReport.ToJSON() | ||||
| 	if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists { | ||||
| 		err := utils.MkdirAll(reporting.StepReportDirectory, 0777) | ||||
| 		err := utils.MkdirAll(reporting.StepReportDirectory, 0o777) | ||||
| 		if err != nil { | ||||
| 			return reportPaths, errors.Wrap(err, "failed to create reporting directory") | ||||
| 		} | ||||
| 	} | ||||
| 	if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_policy_%v.json", fmt.Sprintf("%v", time.Now()))), jsonReport, 0666); err != nil { | ||||
| 	if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_policy_%v.json", fmt.Sprintf("%v", time.Now()))), jsonReport, 0o666); err != nil { | ||||
| 		return reportPaths, errors.Wrapf(err, "failed to write json report") | ||||
| 	} | ||||
|  | ||||
| @@ -673,7 +694,7 @@ func writeIpPolicyJson(config detectExecuteScanOptions, utils detectUtils, paths | ||||
| 		return fmt.Errorf("failed to marshal policy violation data: %w", err), violationCount | ||||
| 	} | ||||
|  | ||||
| 	err = utils.FileWrite("blackduck-ip.json", violationContent, 0666) | ||||
| 	err = utils.FileWrite("blackduck-ip.json", violationContent, 0o666) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to write policy violation report: %w", err), violationCount | ||||
| 	} | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| @@ -15,6 +16,7 @@ import ( | ||||
| 	piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -26,6 +28,14 @@ type detectTestUtilsBundle struct { | ||||
| 	customEnv []string | ||||
| } | ||||
|  | ||||
| func (d *detectTestUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (d *detectTestUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type httpMockClient struct { | ||||
| 	responseBodyForURL map[string]string | ||||
| 	errorMessageForURL map[string]string | ||||
| @@ -164,7 +174,6 @@ func (c *detectTestUtilsBundle) RunExecutable(string, ...string) error { | ||||
| } | ||||
|  | ||||
| func (c *detectTestUtilsBundle) SetOptions(piperhttp.ClientOptions) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (c *detectTestUtilsBundle) GetOsEnv() []string { | ||||
| @@ -172,7 +181,6 @@ func (c *detectTestUtilsBundle) GetOsEnv() []string { | ||||
| } | ||||
|  | ||||
| func (c *detectTestUtilsBundle) DownloadFile(url, filename string, _ http.Header, _ []*http.Cookie) error { | ||||
|  | ||||
| 	if c.expectedError != nil { | ||||
| 		return c.expectedError | ||||
| 	} | ||||
| @@ -200,9 +208,10 @@ func TestRunDetect(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		ctx := context.Background() | ||||
| 		utilsMock := newDetectTestUtilsBundle() | ||||
| 		utilsMock.AddFile("detect.sh", []byte("")) | ||||
| 		err := runDetect(detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{}) | ||||
| 		err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{}) | ||||
|  | ||||
| 		assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect7.sh"], "detect.sh") | ||||
| 		assert.True(t, utilsMock.HasRemovedFile("detect.sh")) | ||||
| @@ -215,12 +224,13 @@ func TestRunDetect(t *testing.T) { | ||||
|  | ||||
| 	t.Run("success case detect 6", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		ctx := context.Background() | ||||
| 		utilsMock := newDetectTestUtilsBundle() | ||||
| 		utilsMock.AddFile("detect.sh", []byte("")) | ||||
| 		options := detectExecuteScanOptions{ | ||||
| 			CustomEnvironmentVariables: []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"}, | ||||
| 		} | ||||
| 		err := runDetect(options, utilsMock, &detectExecuteScanInflux{}) | ||||
| 		err := runDetect(ctx, options, utilsMock, &detectExecuteScanInflux{}) | ||||
|  | ||||
| 		assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect.sh"], "detect.sh") | ||||
| 		assert.True(t, utilsMock.HasRemovedFile("detect.sh")) | ||||
| @@ -233,10 +243,11 @@ func TestRunDetect(t *testing.T) { | ||||
|  | ||||
| 	t.Run("success case detect 6 from OS env", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		ctx := context.Background() | ||||
| 		utilsMock := newDetectTestUtilsBundle() | ||||
| 		utilsMock.AddFile("detect.sh", []byte("")) | ||||
| 		utilsMock.customEnv = []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"} | ||||
| 		err := runDetect(detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{}) | ||||
| 		err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{}) | ||||
|  | ||||
| 		assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect.sh"], "detect.sh") | ||||
| 		assert.True(t, utilsMock.HasRemovedFile("detect.sh")) | ||||
| @@ -249,11 +260,12 @@ func TestRunDetect(t *testing.T) { | ||||
|  | ||||
| 	t.Run("failure case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		ctx := context.Background() | ||||
| 		utilsMock := newDetectTestUtilsBundle() | ||||
| 		utilsMock.ShouldFailOnCommand = map[string]error{"./detect.sh --blackduck.url= --blackduck.api.token= \"--detect.project.name=''\" \"--detect.project.version.name=''\" \"--detect.code.location.name=''\" --detect.source.path='.'": fmt.Errorf("")} | ||||
| 		utilsMock.ExitCode = 3 | ||||
| 		utilsMock.AddFile("detect.sh", []byte("")) | ||||
| 		err := runDetect(detectExecuteScanOptions{FailOnSevereVulnerabilities: true}, utilsMock, &detectExecuteScanInflux{}) | ||||
| 		err := runDetect(ctx, detectExecuteScanOptions{FailOnSevereVulnerabilities: true}, utilsMock, &detectExecuteScanInflux{}) | ||||
| 		assert.Equal(t, utilsMock.ExitCode, 3) | ||||
| 		assert.Contains(t, err.Error(), "FAILURE_POLICY_VIOLATION => Detect found policy violations.") | ||||
| 		assert.True(t, utilsMock.HasRemovedFile("detect.sh")) | ||||
| @@ -261,10 +273,11 @@ func TestRunDetect(t *testing.T) { | ||||
|  | ||||
| 	t.Run("maven parameters", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		ctx := context.Background() | ||||
| 		utilsMock := newDetectTestUtilsBundle() | ||||
| 		utilsMock.CurrentDir = "root_folder" | ||||
| 		utilsMock.AddFile("detect.sh", []byte("")) | ||||
| 		err := runDetect(detectExecuteScanOptions{ | ||||
| 		err := runDetect(ctx, detectExecuteScanOptions{ | ||||
| 			M2Path:              ".pipeline/local_repo", | ||||
| 			ProjectSettingsFile: "project-settings.xml", | ||||
| 			GlobalSettingsFile:  "global-settings.xml", | ||||
| @@ -597,7 +610,6 @@ func TestAddDetectArgs(t *testing.T) { | ||||
|  | ||||
| // Testing exit code mapping method | ||||
| func TestExitCodeMapping(t *testing.T) { | ||||
|  | ||||
| 	cases := []struct { | ||||
| 		exitCode int | ||||
| 		expected string | ||||
| @@ -617,10 +629,11 @@ func TestExitCodeMapping(t *testing.T) { | ||||
| func TestPostScanChecksAndReporting(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("Reporting after scan", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := detectExecuteScanOptions{Token: "token", ServerURL: "https://my.blackduck.system", ProjectName: "SHC-PiperTest", Version: "", CustomScanVersion: "1.0"} | ||||
| 		utils := newDetectTestUtilsBundle() | ||||
| 		sys := newBlackduckMockSystem(config) | ||||
| 		err := postScanChecksAndReporting(config, &detectExecuteScanInflux{}, utils, &sys) | ||||
| 		err := postScanChecksAndReporting(ctx, config, &detectExecuteScanInflux{}, utils, &sys) | ||||
|  | ||||
| 		assert.EqualError(t, err, "License Policy Violations found") | ||||
| 		content, err := utils.FileRead("blackduck-ip.json") | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import ( | ||||
|  | ||||
| 	"github.com/bmatcuk/doublestar" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/google/uuid" | ||||
|  | ||||
| 	"github.com/piper-validation/fortify-client-go/models" | ||||
| @@ -59,13 +59,16 @@ type fortifyUtils interface { | ||||
|  | ||||
| 	SetDir(d string) | ||||
| 	GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) | ||||
| 	CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error | ||||
| 	GetIssueService() *github.IssuesService | ||||
| 	GetSearchService() *github.SearchService | ||||
| } | ||||
|  | ||||
| type fortifyUtilsBundle struct { | ||||
| 	*command.Command | ||||
| 	*piperutils.Files | ||||
| 	*piperhttp.Client | ||||
| 	issues *github.IssuesService | ||||
| 	search *github.SearchService | ||||
| } | ||||
|  | ||||
| func (f *fortifyUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) { | ||||
| @@ -76,27 +79,46 @@ func (f *fortifyUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.Creat | ||||
| 	return piperGithub.CreateIssue(ghCreateIssueOptions) | ||||
| } | ||||
|  | ||||
| func newFortifyUtilsBundle() fortifyUtils { | ||||
| func (f *fortifyUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return f.issues | ||||
| } | ||||
|  | ||||
| func (f *fortifyUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return f.search | ||||
| } | ||||
|  | ||||
| func newFortifyUtilsBundle(client *github.Client) fortifyUtils { | ||||
| 	utils := fortifyUtilsBundle{ | ||||
| 		Command: &command.Command{}, | ||||
| 		Files:   &piperutils.Files{}, | ||||
| 		Client:  &piperhttp.Client{}, | ||||
| 	} | ||||
| 	if client != nil { | ||||
| 		utils.issues = client.Issues | ||||
| 		utils.search = client.Search | ||||
| 	} | ||||
| 	utils.Stdout(log.Writer()) | ||||
| 	utils.Stderr(log.Writer()) | ||||
| 	return &utils | ||||
| } | ||||
|  | ||||
| const checkString = "<---CHECK FORTIFY---" | ||||
| const classpathFileName = "fortify-execute-scan-cp.txt" | ||||
| const ( | ||||
| 	checkString       = "<---CHECK FORTIFY---" | ||||
| 	classpathFileName = "fortify-execute-scan-cp.txt" | ||||
| ) | ||||
|  | ||||
| func fortifyExecuteScan(config fortifyExecuteScanOptions, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux) { | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Warning("Failed to get GitHub client") | ||||
| 	} | ||||
| 	auditStatus := map[string]string{} | ||||
| 	sys := fortify.NewSystemInstance(config.ServerURL, config.APIEndpoint, config.AuthToken, time.Minute*15) | ||||
| 	utils := newFortifyUtilsBundle() | ||||
| 	utils := newFortifyUtilsBundle(client) | ||||
|  | ||||
| 	influx.step_data.fields.fortify = false | ||||
| 	reports, err := runFortifyScan(config, sys, utils, telemetryData, influx, auditStatus) | ||||
| 	reports, err := runFortifyScan(ctx, config, sys, utils, telemetryData, influx, auditStatus) | ||||
| 	piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Fortify scan and check failed") | ||||
| @@ -120,7 +142,7 @@ func determineArtifact(config fortifyExecuteScanOptions, utils fortifyUtils) (ve | ||||
| 	return artifact, nil | ||||
| } | ||||
|  | ||||
| func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils fortifyUtils, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) { | ||||
| func runFortifyScan(ctx context.Context, config fortifyExecuteScanOptions, sys fortify.System, utils fortifyUtils, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) { | ||||
| 	var reports []piperutils.Path | ||||
| 	log.Entry().Debugf("Running Fortify scan against SSC at %v", config.ServerURL) | ||||
|  | ||||
| @@ -207,7 +229,7 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils | ||||
|  | ||||
| 	if config.VerifyOnly { | ||||
| 		log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) | ||||
| 		err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) | ||||
| 		paths, err := verifyFFProjectCompliance(ctx, config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) | ||||
| 		reports = append(reports, paths...) | ||||
| 		return reports, err | ||||
| 	} | ||||
| @@ -218,7 +240,9 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils | ||||
| 	// Create sourceanalyzer command based on configuration | ||||
| 	buildID := uuid.New().String() | ||||
| 	utils.SetDir(config.ModulePath) | ||||
| 	os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm) | ||||
| 	if err := os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm); err != nil { | ||||
| 		log.Entry().WithError(err).Error("failed to create directory") | ||||
| 	} | ||||
|  | ||||
| 	if config.UpdateRulePack { | ||||
| 		err := utils.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-url", config.ServerURL) | ||||
| @@ -282,7 +306,7 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils | ||||
| 	} | ||||
|  | ||||
| 	log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) | ||||
| 	err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) | ||||
| 	paths, err := verifyFFProjectCompliance(ctx, config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) | ||||
| 	reports = append(reports, paths...) | ||||
| 	return reports, err | ||||
| } | ||||
| @@ -293,31 +317,35 @@ func classifyErrorOnLookup(err error) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUtils, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (error, []piperutils.Path) { | ||||
| func verifyFFProjectCompliance(ctx context.Context, config fortifyExecuteScanOptions, utils fortifyUtils, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) { | ||||
| 	reports := []piperutils.Path{} | ||||
| 	// Generate report | ||||
| 	if config.Reporting { | ||||
| 		resultURL := []byte(fmt.Sprintf("%v/html/ssc/version/%v/fix/null/", config.ServerURL, projectVersion.ID)) | ||||
| 		ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0700) | ||||
| 		if err := ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0o700); err != nil { | ||||
| 			log.Entry().WithError(err).Error("failed to write file") | ||||
| 		} | ||||
|  | ||||
| 		data, err := generateAndDownloadQGateReport(config, sys, project, projectVersion) | ||||
| 		if err != nil { | ||||
| 			return err, reports | ||||
| 			return reports, err | ||||
| 		} | ||||
| 		if err := ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0o700); err != nil { | ||||
| 			log.Entry().WithError(err).Warning("failed to write file") | ||||
| 		} | ||||
| 		ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0700) | ||||
| 	} | ||||
|  | ||||
| 	// Perform audit compliance checks | ||||
| 	issueFilterSelectorSet, err := sys.GetIssueFilterSelectorOfProjectVersionByName(projectVersion.ID, []string{"Analysis", "Folder", "Category"}, nil) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID), reports | ||||
| 		return reports, errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID) | ||||
| 	} | ||||
| 	log.Entry().Debugf("initial filter selector set: %v", issueFilterSelectorSet) | ||||
|  | ||||
| 	spotChecksCountByCategory := []fortify.SpotChecksAuditCount{} | ||||
| 	numberOfViolations, issueGroups, err := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus, &spotChecksCountByCategory) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to analyze unaudited issues"), reports | ||||
| 		return reports, errors.Wrap(err, "failed to analyze unaudited issues") | ||||
| 	} | ||||
| 	numberOfSuspiciousExploitable, issueGroupsSuspiciousExploitable := analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) | ||||
| 	numberOfViolations += numberOfSuspiciousExploitable | ||||
| @@ -335,7 +363,7 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUt | ||||
| 	scanReport := fortify.CreateCustomReport(fortifyReportingData, issueGroups) | ||||
| 	paths, err := fortify.WriteCustomReports(scanReport) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to write custom reports"), reports | ||||
| 		return reports, errors.Wrap(err, "failed to write custom reports") | ||||
| 	} | ||||
| 	reports = append(reports, paths...) | ||||
|  | ||||
| @@ -343,24 +371,30 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUt | ||||
| 	log.Entry().Debugf("%v, %v, %v, %v, %v, %v", config.CreateResultIssue, numberOfViolations > 0, len(config.GithubToken) > 0, len(config.GithubAPIURL) > 0, len(config.Owner) > 0, len(config.Repository) > 0) | ||||
| 	if config.CreateResultIssue && numberOfViolations > 0 && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { | ||||
| 		log.Entry().Debug("Creating/updating GitHub issue with scan results") | ||||
| 		err = reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrap(err, "failed to upload scan results into GitHub"), reports | ||||
| 		gh := reporting.GitHub{ | ||||
| 			Owner:         &config.Owner, | ||||
| 			Repository:    &config.Repository, | ||||
| 			Assignees:     &config.Assignees, | ||||
| 			IssueService:  utils.GetIssueService(), | ||||
| 			SearchService: utils.GetSearchService(), | ||||
| 		} | ||||
| 		if err := gh.UploadSingleReport(ctx, scanReport); err != nil { | ||||
| 			return reports, fmt.Errorf("failed to upload scan results into GitHub: %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	jsonReport := fortify.CreateJSONReport(fortifyReportingData, spotChecksCountByCategory, config.ServerURL) | ||||
| 	paths, err = fortify.WriteJSONReport(jsonReport) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to write json report"), reports | ||||
| 		return reports, errors.Wrap(err, "failed to write json report") | ||||
| 	} | ||||
| 	reports = append(reports, paths...) | ||||
|  | ||||
| 	if numberOfViolations > 0 { | ||||
| 		log.SetErrorCategory(log.ErrorCompliance) | ||||
| 		return errors.New("fortify scan failed, the project is not compliant. For details check the archived report"), reports | ||||
| 		return reports, errors.New("fortify scan failed, the project is not compliant. For details check the archived report") | ||||
| 	} | ||||
| 	return nil, reports | ||||
| 	return reports, nil | ||||
| } | ||||
|  | ||||
| func prepareReportData(influx *fortifyExecuteScanInflux) fortify.FortifyReportData { | ||||
| @@ -531,6 +565,9 @@ func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify. | ||||
| 	log.Entry().Info("Analyzing suspicious and exploitable issues") | ||||
| 	reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Analysis"}, []string{}) | ||||
| 	fetchedGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Errorf("failed to get project issues") | ||||
| 	} | ||||
|  | ||||
| 	suspiciousCount := 0 | ||||
| 	exploitableCount := 0 | ||||
| @@ -776,7 +813,8 @@ func generateMavenFortifyDefines(config *fortifyExecuteScanOptions, file string) | ||||
| 		"-DincludeScope=compile", | ||||
| 		"-DskipTests", | ||||
| 		"-Dmaven.javadoc.skip=true", | ||||
| 		"--fail-at-end"} | ||||
| 		"--fail-at-end", | ||||
| 	} | ||||
|  | ||||
| 	if len(config.BuildDescriptorExcludeList) > 0 { | ||||
| 		// From the documentation, these are file paths to a module's pom.xml. | ||||
| @@ -855,7 +893,7 @@ func removeDuplicates(contents, separator string) string { | ||||
| } | ||||
|  | ||||
| func triggerFortifyScan(config fortifyExecuteScanOptions, utils fortifyUtils, buildID, buildLabel, buildProject string) error { | ||||
| 	var err error = nil | ||||
| 	var err error | ||||
| 	// Do special Python related prep | ||||
| 	pipVersion := "pip3" | ||||
| 	if config.PythonVersion != "python3" { | ||||
| @@ -899,10 +937,14 @@ func triggerFortifyScan(config fortifyExecuteScanOptions, utils fortifyUtils, bu | ||||
| 			context := map[string]string{} | ||||
| 			cmdTemplate := []string{pipVersion, "install", "--user", "-r", config.PythonRequirementsFile} | ||||
| 			cmdTemplate = append(cmdTemplate, tokenize(config.PythonRequirementsInstallSuffix)...) | ||||
| 			executeTemplatedCommand(utils, cmdTemplate, context) | ||||
| 			if err := executeTemplatedCommand(utils, cmdTemplate, context); err != nil { | ||||
| 				log.Entry().WithError(err).Error("failed to execute template command") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		executeTemplatedCommand(utils, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion}) | ||||
| 		if err := executeTemplatedCommand(utils, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion}); err != nil { | ||||
| 			log.Entry().WithError(err).Error("failed to execute template command") | ||||
| 		} | ||||
|  | ||||
| 		config.Translate, err = populatePipTranslate(&config, classpath) | ||||
| 		if err != nil { | ||||
| @@ -1001,7 +1043,7 @@ func handleSingleTranslate(config *fortifyExecuteScanOptions, command fortifyUti | ||||
| } | ||||
|  | ||||
| func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildID, buildLabel, buildProject string) error { | ||||
| 	var scanOptions = []string{ | ||||
| 	scanOptions := []string{ | ||||
| 		"-verbose", | ||||
| 		"-64", | ||||
| 		"-b", | ||||
| @@ -1013,9 +1055,7 @@ func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildI | ||||
| 		scanOptions = append(scanOptions, "-quick") | ||||
| 	} | ||||
| 	if len(config.AdditionalScanParameters) > 0 { | ||||
| 		for _, scanParameter := range config.AdditionalScanParameters { | ||||
| 			scanOptions = append(scanOptions, scanParameter) | ||||
| 		} | ||||
| 		scanOptions = append(scanOptions, config.AdditionalScanParameters...) | ||||
| 	} | ||||
| 	if len(buildLabel) > 0 { | ||||
| 		scanOptions = append(scanOptions, "-build-label", buildLabel) | ||||
| @@ -1034,7 +1074,7 @@ func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildI | ||||
|  | ||||
| func determinePullRequestMerge(config fortifyExecuteScanOptions) (string, string) { | ||||
| 	author := "" | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{}) | ||||
| 	if err == nil && ctx != nil && client != nil { | ||||
| 		prID, author, err := determinePullRequestMergeGithub(ctx, config, client.PullRequests) | ||||
|   | ||||
| @@ -22,10 +22,9 @@ import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperutils" | ||||
| 	"github.com/SAP/jenkins-library/pkg/versioning" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	"github.com/piper-validation/fortify-client-go/models" | ||||
| ) | ||||
|  | ||||
| @@ -35,26 +34,24 @@ type fortifyTestUtilsBundle struct { | ||||
| 	*execRunnerMock | ||||
| 	*mock.FilesMock | ||||
| 	getArtifactShouldFail bool | ||||
| 	ghCreateIssueOptions  *piperGithub.CreateIssueOptions | ||||
| 	ghCreateIssueError    error | ||||
| } | ||||
|  | ||||
| func (f fortifyTestUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { | ||||
| func (f *fortifyTestUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { | ||||
| 	panic("not expected to be called in tests") | ||||
| } | ||||
|  | ||||
| func (f fortifyTestUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) { | ||||
| func (f *fortifyTestUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) { | ||||
| 	if f.getArtifactShouldFail { | ||||
| 		return nil, fmt.Errorf("build tool '%v' not supported", buildTool) | ||||
| 	} | ||||
| 	return artifactMock{Coordinates: newCoordinatesMock()}, nil | ||||
| } | ||||
|  | ||||
| func (f fortifyTestUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	if f.ghCreateIssueError != nil { | ||||
| 		return f.ghCreateIssueError | ||||
| 	} | ||||
| 	f.ghCreateIssueOptions = ghCreateIssueOptions | ||||
| func (f *fortifyTestUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (cf *fortifyTestUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -77,16 +74,20 @@ func newCoordinatesMock() versioning.Coordinates { | ||||
| 		Version:    "1.0.0", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a artifactMock) VersioningScheme() string { | ||||
| 	return "full" | ||||
| } | ||||
|  | ||||
| func (a artifactMock) GetVersion() (string, error) { | ||||
| 	return a.Coordinates.Version, nil | ||||
| } | ||||
|  | ||||
| func (a artifactMock) SetVersion(v string) error { | ||||
| 	a.Coordinates.Version = v | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a artifactMock) GetCoordinates() (versioning.Coordinates, error) { | ||||
| 	return a.Coordinates, nil | ||||
| } | ||||
| @@ -100,21 +101,27 @@ type fortifyMock struct { | ||||
| func (f *fortifyMock) GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) { | ||||
| 	return &models.Project{Name: &name, ID: 64}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) { | ||||
| 	return &models.ProjectVersion{ID: id, Name: &name, Project: &models.Project{Name: &projectName}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) { | ||||
| 	return []*models.Attribute{}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) { | ||||
| 	return attributes, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) { | ||||
| 	return &models.ProjectVersion{ID: 4711, Name: &projectVersionName, Project: &models.Project{Name: &projectName}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) { | ||||
| 	return &models.ProjectVersion{ID: 4712, Name: &pullRequestName, Project: masterProjectVersion.Project}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) { | ||||
| 	return version, nil | ||||
| } | ||||
| @@ -122,19 +129,24 @@ func (f *fortifyMock) CreateProjectVersion(version *models.ProjectVersion) (*mod | ||||
| func (f *fortifyMock) ProjectVersionCopyFromPartial(sourceID, targetID int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) ProjectVersionCopyCurrentState(sourceID, targetID int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) ProjectVersionCopyPermissions(sourceID, targetID int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) CommitProjectVersion(id int64) (*models.ProjectVersion, error) { | ||||
| 	name := "Committed" | ||||
| 	return &models.ProjectVersion{ID: id, Name: &name}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) { | ||||
| 	switch id { | ||||
| 	case 4711: | ||||
| @@ -198,12 +210,15 @@ func (f *fortifyMock) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact | ||||
| 		return []*models.Artifact{}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) { | ||||
| 	return &models.FilterSet{}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) { | ||||
| 	return &models.IssueFilterSelectorSet{}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector { | ||||
| 	if issueFilterSelectorSet.FilterBySet != nil { | ||||
| 		for _, filter := range issueFilterSelectorSet.FilterBySet { | ||||
| @@ -214,6 +229,7 @@ func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.I | ||||
| 	} | ||||
| 	return &models.IssueFilterSelector{DisplayName: name} | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) { | ||||
| 	if filter == "ET1:abcd" { | ||||
| 		group := "HTTP Verb tampering" | ||||
| @@ -260,13 +276,16 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64 | ||||
| 		{ID: &group3, CleanName: &group3, TotalCount: &total3, AuditedCount: &audited3}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet { | ||||
| 	return issueFilterSelectorSet | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) { | ||||
| 	suppressed := int32(6) | ||||
| 	return []*models.IssueStatistics{{SuppressedCount: &suppressed}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) { | ||||
| 	if !f.Successive { | ||||
| 		f.Successive = true | ||||
| @@ -275,31 +294,38 @@ func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTem | ||||
| 	f.Successive = false | ||||
| 	return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetReportDetails(id int64) (*models.SavedReport, error) { | ||||
| 	return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) { | ||||
| 	exploitable := "Exploitable" | ||||
| 	friority := "High" | ||||
| 	hascomments := true | ||||
| 	return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}, {ID: 1112, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) { | ||||
| 	exploitable := "Exploitable" | ||||
| 	friority := "High" | ||||
| 	hascomments := true | ||||
| 	return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) { | ||||
| 	comment := "Dummy" | ||||
| 	return []*models.IssueAuditComment{{Comment: &comment}}, nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) UploadResultFile(endpoint, file string, projectVersionID int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) DownloadReportFile(endpoint string, reportID int64) ([]byte, error) { | ||||
| 	return []byte("abcd"), nil | ||||
| } | ||||
|  | ||||
| func (f *fortifyMock) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) { | ||||
| 	return []byte("defg"), nil | ||||
| } | ||||
| @@ -364,6 +390,7 @@ func (er *execRunnerMock) Stdout(out io.Writer) { | ||||
| func (er *execRunnerMock) Stderr(err io.Writer) { | ||||
| 	er.currentExecution().errWriter = err | ||||
| } | ||||
|  | ||||
| func (er *execRunnerMock) RunExecutable(e string, p ...string) error { | ||||
| 	er.numExecutions++ | ||||
| 	er.currentExecution().executable = e | ||||
| @@ -383,7 +410,7 @@ func (er *execRunnerMock) RunExecutable(e string, p ...string) error { | ||||
| 		} | ||||
| 	} else if e == "mvn" { | ||||
| 		path := strings.ReplaceAll(p[2], "-Dmdep.outputFile=", "") | ||||
| 		err := ioutil.WriteFile(path, []byte(classpathMaven), 0644) | ||||
| 		err := ioutil.WriteFile(path, []byte(classpathMaven), 0o644) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -397,7 +424,7 @@ func TestDetermineArtifact(t *testing.T) { | ||||
| 		utilsMock := newFortifyTestUtilsBundle() | ||||
| 		utilsMock.getArtifactShouldFail = true | ||||
|  | ||||
| 		_, err := determineArtifact(fortifyExecuteScanOptions{}, utilsMock) | ||||
| 		_, err := determineArtifact(fortifyExecuteScanOptions{}, &utilsMock) | ||||
| 		assert.EqualError(t, err, "Unable to get artifact from descriptor : build tool '' not supported") | ||||
| 	}) | ||||
| } | ||||
| @@ -406,7 +433,6 @@ func TestExecutions(t *testing.T) { | ||||
| 	type parameterTestData struct { | ||||
| 		nameOfRun             string | ||||
| 		config                fortifyExecuteScanOptions | ||||
| 		expectedError         string | ||||
| 		expectedReportsLength int | ||||
| 		expectedReports       []string | ||||
| 	} | ||||
| @@ -433,11 +459,12 @@ func TestExecutions(t *testing.T) { | ||||
|  | ||||
| 	for _, data := range testData { | ||||
| 		t.Run(data.nameOfRun, func(t *testing.T) { | ||||
| 			ctx := context.Background() | ||||
| 			ff := fortifyMock{} | ||||
| 			utils := newFortifyTestUtilsBundle() | ||||
| 			influx := fortifyExecuteScanInflux{} | ||||
| 			auditStatus := map[string]string{} | ||||
| 			reports, _ := runFortifyScan(data.config, &ff, utils, nil, &influx, auditStatus) | ||||
| 			reports, _ := runFortifyScan(ctx, data.config, &ff, &utils, nil, &influx, auditStatus) | ||||
| 			if len(data.expectedReports) != data.expectedReportsLength { | ||||
| 				assert.Fail(t, fmt.Sprintf("Wrong number of reports detected, expected %v, actual %v", data.expectedReportsLength, len(data.expectedReports))) | ||||
| 			} | ||||
| @@ -578,7 +605,8 @@ func TestTriggerFortifyScan(t *testing.T) { | ||||
| 			BuildDescriptorFile:      "./pom.xml", | ||||
| 			AdditionalScanParameters: []string{"-Dtest=property"}, | ||||
| 			Memory:                   "-Xmx4G -Xms2G", | ||||
| 			Src:                      []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}} | ||||
| 			Src:                      []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}, | ||||
| 		} | ||||
| 		triggerFortifyScan(config, &utils, "test", "testLabel", "my.group-myartifact") | ||||
|  | ||||
| 		assert.Equal(t, 3, utils.numExecutions) | ||||
| @@ -686,8 +714,10 @@ func TestGenerateAndDownloadQGateReport(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| var defaultPollingDelay = 10 * time.Second | ||||
| var defaultPollingTimeout = 0 * time.Minute | ||||
| var ( | ||||
| 	defaultPollingDelay   = 10 * time.Second | ||||
| 	defaultPollingTimeout = 0 * time.Minute | ||||
| ) | ||||
|  | ||||
| func verifyScanResultsFinishedUploadingDefaults(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64) error { | ||||
| 	return verifyScanResultsFinishedUploading(config, sys, projectVersionID, "", &models.FilterSet{}, | ||||
| @@ -957,7 +987,6 @@ func TestPopulateMavenTranslate(t *testing.T) { | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, `[{"classpath":""}]`, translate) | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestPopulatePipTranslate(t *testing.T) { | ||||
| @@ -997,7 +1026,8 @@ func TestPopulatePipTranslate(t *testing.T) { | ||||
| 		config := fortifyExecuteScanOptions{ | ||||
| 			Translate:            `[{"pythonPath":""}]`, | ||||
| 			Src:                  []string{"./**/*"}, | ||||
| 			PythonAdditionalPath: []string{"./lib", "."}} | ||||
| 			PythonAdditionalPath: []string{"./lib", "."}, | ||||
| 		} | ||||
| 		translate, err := populatePipTranslate(&config, "ignored/path") | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, `[{"pythonPath":""}]`, translate, "Expected different parameters") | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| @@ -19,7 +19,7 @@ type gitHubBranchProtectionRepositoriesService interface { | ||||
| } | ||||
|  | ||||
| func githubCheckBranchProtection(config githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData) { | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to get GitHub client") | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -110,5 +110,4 @@ func TestRunGithubCheckBranchProtection(t *testing.T) { | ||||
| 		err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "not enough mandatory reviewers") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| @@ -16,7 +16,7 @@ type githubIssueCommentService interface { | ||||
| } | ||||
|  | ||||
| func githubCommentIssue(config githubCommentIssueOptions, telemetryData *telemetry.CustomData) { | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to get GitHub client") | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| @@ -20,7 +20,7 @@ type githubIssueService interface { | ||||
| } | ||||
|  | ||||
| func githubCreatePullRequest(config githubCreatePullRequestOptions, telemetryData *telemetry.CustomData) { | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to get GitHub client") | ||||
| @@ -33,7 +33,6 @@ func githubCreatePullRequest(config githubCreatePullRequestOptions, telemetryDat | ||||
| } | ||||
|  | ||||
| func runGithubCreatePullRequest(ctx context.Context, config *githubCreatePullRequestOptions, ghPRService githubPRService, ghIssueService githubIssueService) error { | ||||
|  | ||||
| 	prRequest := github.NewPullRequest{ | ||||
| 		Title: &config.Title, | ||||
| 		Head:  &config.Head, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -107,7 +107,6 @@ func TestRunGithubCreatePullRequest(t *testing.T) { | ||||
|  | ||||
| 		err := runGithubCreatePullRequest(ctx, &myGithubPROptions, &ghPRService, &ghIssueService) | ||||
| 		assert.EqualError(t, err, "Error occurred when creating pull request: Authentication failed", "Wrong error returned") | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Edit error", func(t *testing.T) { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| @@ -30,7 +30,7 @@ type githubIssueClient interface { | ||||
| } | ||||
|  | ||||
| func githubPublishRelease(config githubPublishReleaseOptions, telemetryData *telemetry.CustomData) { | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, config.UploadURL, []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to get GitHub client.") | ||||
| @@ -43,13 +43,12 @@ func githubPublishRelease(config githubPublishReleaseOptions, telemetryData *tel | ||||
| } | ||||
|  | ||||
| func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient, ghIssueClient githubIssueClient) error { | ||||
|  | ||||
| 	var publishedAt github.Timestamp | ||||
|  | ||||
| 	lastRelease, resp, err := ghRepoClient.GetLatestRelease(ctx, config.Owner, config.Repository) | ||||
| 	if err != nil { | ||||
| 		if resp != nil && resp.StatusCode == 404 { | ||||
| 			//no previous release found -> first release | ||||
| 			// no previous release found -> first release | ||||
| 			config.AddDeltaToLastRelease = false | ||||
| 			log.Entry().Debug("This is the first release.") | ||||
| 		} else { | ||||
| @@ -59,7 +58,7 @@ func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOp | ||||
| 	publishedAt = lastRelease.GetPublishedAt() | ||||
| 	log.Entry().Debugf("Previous GitHub release published: '%v'", publishedAt) | ||||
|  | ||||
| 	//updating assets only supported on latest release | ||||
| 	// updating assets only supported on latest release | ||||
| 	if len(config.AssetPath) > 0 && config.Version == "latest" { | ||||
| 		return uploadReleaseAsset(ctx, lastRelease.GetID(), config, ghRepoClient) | ||||
| 	} else if len(config.AssetPathList) > 0 && config.Version == "latest" { | ||||
| @@ -147,7 +146,7 @@ func getClosedIssuesText(ctx context.Context, publishedAt github.Timestamp, conf | ||||
| func getReleaseDeltaText(config *githubPublishReleaseOptions, lastRelease *github.RepositoryRelease) string { | ||||
| 	releaseDeltaText := "" | ||||
|  | ||||
| 	//add delta link to previous release | ||||
| 	// add delta link to previous release | ||||
| 	releaseDeltaText += "\n**Changes**\n" | ||||
| 	releaseDeltaText += fmt.Sprintf( | ||||
| 		"[%v...%v](%v/%v/%v/compare/%v...%v)\n", | ||||
| @@ -174,7 +173,6 @@ func uploadReleaseAssetList(ctx context.Context, releaseID int64, config *github | ||||
| } | ||||
|  | ||||
| func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient) error { | ||||
|  | ||||
| 	assets, _, err := ghRepoClient.ListReleaseAssets(ctx, config.Owner, config.Repository, releaseID, &github.ListOptions{}) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "Failed to get list of release assets.") | ||||
| @@ -187,7 +185,7 @@ func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPubl | ||||
| 		} | ||||
| 	} | ||||
| 	if assetID != 0 { | ||||
| 		//asset needs to be deleted first since API does not allow for replacement | ||||
| 		// asset needs to be deleted first since API does not allow for replacement | ||||
| 		_, err := ghRepoClient.DeleteReleaseAsset(ctx, config.Owner, config.Repository, assetID) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrap(err, "Failed to delete release asset.") | ||||
| @@ -227,7 +225,7 @@ func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPubl | ||||
| } | ||||
|  | ||||
| func isExcluded(issue *github.Issue, excludeLabels []string) bool { | ||||
| 	//issue.Labels[0].GetName() | ||||
| 	// issue.Labels[0].GetName() | ||||
| 	for _, ex := range excludeLabels { | ||||
| 		for _, l := range issue.Labels { | ||||
| 			if ex == l.GetName() { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/cmd/mocks" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/mock" | ||||
| ) | ||||
| @@ -151,7 +151,7 @@ func TestRunGithubPublishRelease(t *testing.T) { | ||||
|  | ||||
| 	t.Run("Success - subsequent releases & with body", func(t *testing.T) { | ||||
| 		lastTag := "1.0" | ||||
| 		lastPublishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)} | ||||
| 		lastPublishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)} | ||||
| 		ghRepoClient := ghRCMock{ | ||||
| 			createErr: nil, | ||||
| 			latestRelease: &github.RepositoryRelease{ | ||||
| @@ -263,7 +263,7 @@ func TestRunGithubPublishRelease(t *testing.T) { | ||||
|  | ||||
| func TestGetClosedIssuesText(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	publishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)} | ||||
| 	publishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)} | ||||
|  | ||||
| 	t.Run("No issues", func(t *testing.T) { | ||||
| 		ghIssueClient := ghICMock{} | ||||
| @@ -278,7 +278,7 @@ func TestGetClosedIssuesText(t *testing.T) { | ||||
|  | ||||
| 	t.Run("All issues", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		publishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)} | ||||
| 		publishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)} | ||||
|  | ||||
| 		prHTMLURL := []string{"https://github.com/TEST/test/pull/1", "https://github.com/TEST/test/pull/2"} | ||||
| 		prTitle := []string{"Pull1", "Pull2"} | ||||
| @@ -311,7 +311,6 @@ func TestGetClosedIssuesText(t *testing.T) { | ||||
| 		assert.Equal(t, "asc", ghIssueClient.options.Direction, "Sort direction not properly passed") | ||||
| 		assert.Equal(t, publishedAt.Time, ghIssueClient.options.Since, "PublishedAt not properly passed") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGetReleaseDeltaText(t *testing.T) { | ||||
| @@ -459,7 +458,6 @@ func TestUploadReleaseAssetList(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestIsExcluded(t *testing.T) { | ||||
|  | ||||
| 	l1 := "label1" | ||||
| 	l2 := "label2" | ||||
|  | ||||
| @@ -480,5 +478,4 @@ func TestIsExcluded(t *testing.T) { | ||||
| 	for k, v := range tt { | ||||
| 		assert.Equal(t, v.expected, isExcluded(v.issue, v.excludeLabels), fmt.Sprintf("Run %v failed", k)) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| @@ -19,7 +19,7 @@ type gitHubCommitStatusRepositoriesService interface { | ||||
| } | ||||
|  | ||||
| func githubSetCommitStatus(config githubSetCommitStatusOptions, telemetryData *telemetry.CustomData) { | ||||
| 	//TODO provide parameter for trusted certs | ||||
| 	// TODO provide parameter for trusted certs | ||||
| 	ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{}) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Failed to get GitHub client") | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ package mocks | ||||
| import ( | ||||
| 	context "context" | ||||
|  | ||||
| 	github "github.com/google/go-github/v32/github" | ||||
| 	github "github.com/google/go-github/v45/github" | ||||
| 	mock "github.com/stretchr/testify/mock" | ||||
|  | ||||
| 	os "os" | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| @@ -24,6 +25,8 @@ import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/versioning" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/xuri/excelize/v2" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| // ScanOptions is just used to make the lines less long | ||||
| @@ -48,12 +51,10 @@ type whitesource interface { | ||||
| type whitesourceUtils interface { | ||||
| 	ws.Utils | ||||
| 	piperutils.FileUtils | ||||
| 	GetArtifactCoordinates(buildTool, buildDescriptorFile string, | ||||
| 		options *versioning.Options) (versioning.Coordinates, error) | ||||
|  | ||||
| 	CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error | ||||
|  | ||||
| 	GetArtifactCoordinates(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Coordinates, error) | ||||
| 	Now() time.Time | ||||
| 	GetIssueService() *github.IssuesService | ||||
| 	GetSearchService() *github.SearchService | ||||
| } | ||||
|  | ||||
| type whitesourceUtilsBundle struct { | ||||
| @@ -61,11 +62,8 @@ type whitesourceUtilsBundle struct { | ||||
| 	*command.Command | ||||
| 	*piperutils.Files | ||||
| 	npmExecutor npm.Executor | ||||
| } | ||||
|  | ||||
| // CreateIssue supplies capability for GitHub issue creation | ||||
| func (w *whitesourceUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	return piperGithub.CreateIssue(ghCreateIssueOptions) | ||||
| 	issues      *github.IssuesService | ||||
| 	search      *github.SearchService | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsBundle) FileOpen(name string, flag int, perm os.FileMode) (ws.File, error) { | ||||
| @@ -103,12 +101,24 @@ func (w *whitesourceUtilsBundle) Now() time.Time { | ||||
| 	return time.Now() | ||||
| } | ||||
|  | ||||
| func newWhitesourceUtils(config *ScanOptions) *whitesourceUtilsBundle { | ||||
| func (w *whitesourceUtilsBundle) GetIssueService() *github.IssuesService { | ||||
| 	return w.issues | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsBundle) GetSearchService() *github.SearchService { | ||||
| 	return w.search | ||||
| } | ||||
|  | ||||
| func newWhitesourceUtils(config *ScanOptions, client *github.Client) *whitesourceUtilsBundle { | ||||
| 	utils := whitesourceUtilsBundle{ | ||||
| 		Client:  &piperhttp.Client{}, | ||||
| 		Command: &command.Command{}, | ||||
| 		Files:   &piperutils.Files{}, | ||||
| 	} | ||||
| 	if client != nil { | ||||
| 		utils.issues = client.Issues | ||||
| 		utils.search = client.Search | ||||
| 	} | ||||
| 	// Reroute cmd output to logging framework | ||||
| 	utils.Stdout(log.Writer()) | ||||
| 	utils.Stderr(log.Writer()) | ||||
| @@ -125,18 +135,21 @@ func newWhitesourceScan(config *ScanOptions) *ws.Scan { | ||||
| } | ||||
|  | ||||
| func whitesourceExecuteScan(config ScanOptions, _ *telemetry.CustomData, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) { | ||||
| 	utils := newWhitesourceUtils(&config) | ||||
| 	ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", config.CustomTLSCertificateLinks) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Warning("Failed to get GitHub client") | ||||
| 	} | ||||
| 	utils := newWhitesourceUtils(&config, client) | ||||
| 	scan := newWhitesourceScan(&config) | ||||
| 	sys := ws.NewSystem(config.ServiceURL, config.OrgToken, config.UserToken, time.Duration(config.Timeout)*time.Second) | ||||
| 	influx.step_data.fields.whitesource = false | ||||
| 	err := runWhitesourceExecuteScan(&config, scan, utils, sys, commonPipelineEnvironment, influx) | ||||
| 	if err != nil { | ||||
| 	if err := runWhitesourceExecuteScan(ctx, &config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("step execution failed") | ||||
| 	} | ||||
| 	influx.step_data.fields.whitesource = true | ||||
| } | ||||
|  | ||||
| func runWhitesourceExecuteScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error { | ||||
| func runWhitesourceExecuteScan(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error { | ||||
| 	if err := resolveAggregateProjectName(config, scan, sys); err != nil { | ||||
| 		return errors.Wrapf(err, "failed to resolve and aggregate project name") | ||||
| 	} | ||||
| @@ -160,14 +173,14 @@ func runWhitesourceExecuteScan(config *ScanOptions, scan *ws.Scan, utils whiteso | ||||
| 			return errors.Wrapf(err, "failed to aggregate version wide vulnerabilities") | ||||
| 		} | ||||
| 	} else { | ||||
| 		if err := runWhitesourceScan(config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil { | ||||
| 		if err := runWhitesourceScan(ctx, config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil { | ||||
| 			return errors.Wrapf(err, "failed to execute WhiteSource scan") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error { | ||||
| func runWhitesourceScan(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error { | ||||
| 	// Download Docker image for container scan | ||||
| 	// ToDo: move it to improve testability | ||||
| 	if config.BuildTool == "docker" { | ||||
| @@ -212,7 +225,7 @@ func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUti | ||||
| 	} | ||||
| 	log.Entry().Info("-----------------------------------------------------") | ||||
|  | ||||
| 	paths, err := checkAndReportScanResults(config, scan, utils, sys, influx) | ||||
| 	paths, err := checkAndReportScanResults(ctx, config, scan, utils, sys, influx) | ||||
| 	piperutils.PersistReportsAndLinks("whitesourceExecuteScan", "", paths, nil) | ||||
| 	persistScannedProjects(config, scan, commonPipelineEnvironment) | ||||
| 	if err != nil { | ||||
| @@ -221,7 +234,7 @@ func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUti | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) { | ||||
| func checkAndReportScanResults(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) { | ||||
| 	reportPaths := []piperutils.Path{} | ||||
| 	if !config.Reporting && !config.SecurityVulnerabilities { | ||||
| 		return reportPaths, nil | ||||
| @@ -251,7 +264,7 @@ func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whiteso | ||||
| 	reportPaths = append(reportPaths, rPath) | ||||
|  | ||||
| 	if config.SecurityVulnerabilities { | ||||
| 		rPaths, err := checkSecurityViolations(config, scan, sys, utils, influx) | ||||
| 		rPaths, err := checkSecurityViolations(ctx, config, scan, sys, utils, influx) | ||||
| 		reportPaths = append(reportPaths, rPaths...) | ||||
| 		if err != nil { | ||||
| 			checkErrors = append(checkErrors, fmt.Sprint(err)) | ||||
| @@ -538,7 +551,7 @@ func checkPolicyViolations(config *ScanOptions, scan *ws.Scan, sys whitesource, | ||||
| 	return policyReport, nil | ||||
| } | ||||
|  | ||||
| func checkSecurityViolations(config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) { | ||||
| func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) { | ||||
| 	var reportPaths []piperutils.Path | ||||
| 	// Check for security vulnerabilities and fail the build if cvssSeverityLimit threshold is crossed | ||||
| 	// convert config.CvssSeverityLimit to float64 | ||||
| @@ -574,8 +587,14 @@ func checkSecurityViolations(config *ScanOptions, scan *ws.Scan, sys whitesource | ||||
| 			log.Entry().Debugf("Creating result issues for %v alert(s)", vulnerabilitiesCount) | ||||
| 			issueDetails := make([]reporting.IssueDetail, len(allAlerts)) | ||||
| 			piperutils.CopyAtoB(allAlerts, issueDetails) | ||||
| 			err = reporting.UploadMultipleReportsToGithub(&issueDetails, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, config.CustomTLSCertificateLinks, utils) | ||||
| 			if err != nil { | ||||
| 			gh := reporting.GitHub{ | ||||
| 				Owner:         &config.Owner, | ||||
| 				Repository:    &config.Repository, | ||||
| 				Assignees:     &config.Assignees, | ||||
| 				IssueService:  utils.GetIssueService(), | ||||
| 				SearchService: utils.GetSearchService(), | ||||
| 			} | ||||
| 			if err := gh.UploadMultipleReports(ctx, &issueDetails); err != nil { | ||||
| 				errorsOccured = append(errorsOccured, fmt.Sprint(err)) | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperenv" | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperutils" | ||||
| @@ -14,6 +14,8 @@ import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/versioning" | ||||
| 	ws "github.com/SAP/jenkins-library/pkg/whitesource" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| type whitesourceUtilsMock struct { | ||||
| @@ -25,17 +27,14 @@ type whitesourceUtilsMock struct { | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptorFile string, | ||||
| 	options *versioning.Options) (versioning.Coordinates, error) { | ||||
| 	options *versioning.Options, | ||||
| ) (versioning.Coordinates, error) { | ||||
| 	w.usedBuildTool = buildTool | ||||
| 	w.usedBuildDescriptorFile = buildDescriptorFile | ||||
| 	w.usedOptions = *options | ||||
| 	return w.coordinates, nil | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsMock) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| const wsTimeNow = "2010-05-10 00:15:42" | ||||
|  | ||||
| func (w *whitesourceUtilsMock) Now() time.Time { | ||||
| @@ -43,6 +42,14 @@ func (w *whitesourceUtilsMock) Now() time.Time { | ||||
| 	return now | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsMock) GetIssueService() *github.IssuesService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (w *whitesourceUtilsMock) GetSearchService() *github.SearchService { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func newWhitesourceUtilsMock() *whitesourceUtilsMock { | ||||
| 	return &whitesourceUtilsMock{ | ||||
| 		ScanUtilsMock: &ws.ScanUtilsMock{ | ||||
| @@ -60,7 +67,7 @@ func newWhitesourceUtilsMock() *whitesourceUtilsMock { | ||||
| func TestNewWhitesourceUtils(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	config := ScanOptions{} | ||||
| 	utils := newWhitesourceUtils(&config) | ||||
| 	utils := newWhitesourceUtils(&config, &github.Client{Issues: &github.IssuesService{}, Search: &github.SearchService{}}) | ||||
|  | ||||
| 	assert.NotNil(t, utils.Client) | ||||
| 	assert.NotNil(t, utils.Command) | ||||
| @@ -70,6 +77,7 @@ func TestNewWhitesourceUtils(t *testing.T) { | ||||
| func TestRunWhitesourceExecuteScan(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("fails for invalid configured project token", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := ScanOptions{ | ||||
| 			BuildDescriptorFile: "my-mta.yml", | ||||
| @@ -86,13 +94,14 @@ func TestRunWhitesourceExecuteScan(t *testing.T) { | ||||
| 		cpe := whitesourceExecuteScanCommonPipelineEnvironment{} | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		err := runWhitesourceExecuteScan(&config, scan, utilsMock, systemMock, &cpe, &influx) | ||||
| 		err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) | ||||
| 		// assert | ||||
| 		assert.EqualError(t, err, "failed to resolve and aggregate project name: failed to get project by token: no project with token 'no-such-project-token' found in Whitesource") | ||||
| 		assert.Equal(t, "", config.ProjectName) | ||||
| 		assert.Equal(t, "", scan.AggregateProjectName) | ||||
| 	}) | ||||
| 	t.Run("retrieves aggregate project name by configured token", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := ScanOptions{ | ||||
| 			BuildDescriptorFile:       "my-mta.yml", | ||||
| @@ -113,7 +122,7 @@ func TestRunWhitesourceExecuteScan(t *testing.T) { | ||||
| 		cpe := whitesourceExecuteScanCommonPipelineEnvironment{} | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		err := runWhitesourceExecuteScan(&config, scan, utilsMock, systemMock, &cpe, &influx) | ||||
| 		err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) | ||||
| 		// assert | ||||
| 		assert.NoError(t, err) | ||||
| 		// Retrieved project name is stored in scan.AggregateProjectName, but not in config.ProjectName | ||||
| @@ -137,6 +146,7 @@ func TestRunWhitesourceExecuteScan(t *testing.T) { | ||||
| func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("no reports requested", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := &ScanOptions{ | ||||
| 			ProductToken: "mock-product-token", | ||||
| @@ -148,7 +158,7 @@ func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 		system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		_, err := checkAndReportScanResults(config, scan, utils, system, &influx) | ||||
| 		_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) | ||||
| 		// assert | ||||
| 		assert.NoError(t, err) | ||||
| 		vPath := filepath.Join(ws.ReportsDirectory, "mock-project-vulnerability-report.txt") | ||||
| @@ -157,6 +167,7 @@ func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 		assert.False(t, utils.HasWrittenFile(rPath)) | ||||
| 	}) | ||||
| 	t.Run("check vulnerabilities - invalid limit", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := &ScanOptions{ | ||||
| 			SecurityVulnerabilities: true, | ||||
| @@ -167,11 +178,12 @@ func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 		system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		_, err := checkAndReportScanResults(config, scan, utils, system, &influx) | ||||
| 		_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) | ||||
| 		// assert | ||||
| 		assert.EqualError(t, err, "failed to parse parameter cvssSeverityLimit (invalid) as floating point number: strconv.ParseFloat: parsing \"invalid\": invalid syntax") | ||||
| 	}) | ||||
| 	t.Run("check vulnerabilities - limit not hit", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := &ScanOptions{ | ||||
| 			ProductToken:            "mock-product-token", | ||||
| @@ -185,11 +197,12 @@ func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 		system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		_, err := checkAndReportScanResults(config, scan, utils, system, &influx) | ||||
| 		_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) | ||||
| 		// assert | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
| 	t.Run("check vulnerabilities - limit exceeded", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		// init | ||||
| 		config := &ScanOptions{ | ||||
| 			ProductToken:                "mock-product-token", | ||||
| @@ -205,7 +218,7 @@ func TestCheckAndReportScanResults(t *testing.T) { | ||||
| 		system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
| 		// test | ||||
| 		_, err := checkAndReportScanResults(config, scan, utils, system, &influx) | ||||
| 		_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) | ||||
| 		// assert | ||||
| 		assert.EqualError(t, err, "1 Open Source Software Security vulnerabilities with CVSS score greater or equal to 4.0 detected in project mock-project - 1") | ||||
| 	}) | ||||
| @@ -389,7 +402,6 @@ func TestCheckPolicyViolations(t *testing.T) { | ||||
|  | ||||
| 		exists, err := utilsMock.FileExists(filepath.Join(reporting.StepReportDirectory, "whitesourceExecuteScan_ip_2d3120020f3f46393a54575a7f6f5675ad536721.json")) | ||||
| 		assert.True(t, exists) | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success - no reports", func(t *testing.T) { | ||||
| @@ -496,6 +508,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	t.Run("success - non-aggregated", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := ScanOptions{ | ||||
| 			CvssSeverityLimit: "7", | ||||
| 		} | ||||
| @@ -510,7 +523,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 		utilsMock := newWhitesourceUtilsMock() | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
|  | ||||
| 		reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx) | ||||
| 		reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) | ||||
| 		assert.NoError(t, err) | ||||
| 		fileContent, err := utilsMock.FileRead(reportPaths[0].Target) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -518,6 +531,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success - aggregated", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := ScanOptions{ | ||||
| 			CvssSeverityLimit: "7", | ||||
| 			ProjectToken:      "theProjectToken", | ||||
| @@ -530,24 +544,25 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 		utilsMock := newWhitesourceUtilsMock() | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
|  | ||||
| 		reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx) | ||||
| 		reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, 0, len(reportPaths)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error - wrong limit", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := ScanOptions{CvssSeverityLimit: "x"} | ||||
| 		scan := newWhitesourceScan(&config) | ||||
| 		systemMock := ws.NewSystemMock("ignored") | ||||
| 		utilsMock := newWhitesourceUtilsMock() | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
|  | ||||
| 		_, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx) | ||||
| 		_, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to parse parameter cvssSeverityLimit") | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error - non-aggregated", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := ScanOptions{ | ||||
| 			CvssSeverityLimit:           "5", | ||||
| 			FailOnSevereVulnerabilities: true, | ||||
| @@ -563,7 +578,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 		utilsMock := newWhitesourceUtilsMock() | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
|  | ||||
| 		reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx) | ||||
| 		reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities") | ||||
| 		fileContent, err := utilsMock.FileRead(reportPaths[0].Target) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -571,6 +586,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error - aggregated", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		config := ScanOptions{ | ||||
| 			CvssSeverityLimit:           "5", | ||||
| 			ProjectToken:                "theProjectToken", | ||||
| @@ -584,7 +600,7 @@ func TestCheckSecurityViolations(t *testing.T) { | ||||
| 		utilsMock := newWhitesourceUtilsMock() | ||||
| 		influx := whitesourceExecuteScanInflux{} | ||||
|  | ||||
| 		reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx) | ||||
| 		reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities") | ||||
| 		assert.Equal(t, 0, len(reportPaths)) | ||||
| 	}) | ||||
| @@ -626,7 +642,6 @@ func TestCheckProjectSecurityViolations(t *testing.T) { | ||||
| 		_, _, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &influx) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to retrieve project alerts from WhiteSource") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestAggregateVersionWideLibraries(t *testing.T) { | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -25,9 +25,9 @@ require ( | ||||
| 	github.com/go-playground/locales v0.14.0 | ||||
| 	github.com/go-playground/universal-translator v0.18.0 | ||||
| 	github.com/go-playground/validator/v10 v10.9.0 | ||||
| 	github.com/google/go-cmp v0.5.7 | ||||
| 	github.com/google/go-cmp v0.5.8 | ||||
| 	github.com/google/go-containerregistry v0.6.0 | ||||
| 	github.com/google/go-github/v32 v32.1.0 | ||||
| 	github.com/google/go-github/v45 v45.2.0 | ||||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/hashicorp/go-retryablehttp v0.7.0 | ||||
| 	github.com/hashicorp/vault v1.9.3 | ||||
|   | ||||
							
								
								
									
										7
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								go.sum
									
									
									
									
									
								
							| @@ -921,15 +921,16 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= | ||||
| github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= | ||||
| github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= | ||||
| github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= | ||||
| github.com/google/go-containerregistry v0.5.2-0.20210604130445-3bfab55f3bd9/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q= | ||||
| github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4= | ||||
| github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= | ||||
| github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= | ||||
| github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= | ||||
| github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= | ||||
| github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= | ||||
| github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= | ||||
| github.com/google/go-metrics-stackdriver v0.2.0 h1:rbs2sxHAPn2OtUj9JdR/Gij1YKGl0BTVD0augB+HEjE= | ||||
| github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0= | ||||
| github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	"github.com/SAP/jenkins-library/pkg/reporting" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| @@ -94,28 +95,45 @@ type VulnerabilityWithRemediation struct { | ||||
|  | ||||
| // Title returns the issue title representation of the contents | ||||
| func (v Vulnerability) Title() string { | ||||
| 	return fmt.Sprintf("%v/%v/%v/%v-%v", "SECURITY_VULNERABILITY", v.VulnerabilityWithRemediation.Severity, v.VulnerabilityName, v.Name, v.Version) | ||||
| 	return fmt.Sprintf("Security Vulnerability %v %v", v.VulnerabilityName, v.Name) | ||||
| } | ||||
|  | ||||
| // ToMarkdown returns the markdown representation of the contents | ||||
| func (v Vulnerability) ToMarkdown() ([]byte, error) { | ||||
| 	return []byte(fmt.Sprintf( | ||||
| 		`**Vulnerability %v** | ||||
| | Severity | Base (NVD) Score | Temporal Score | Package | Installed Version | Description | Fix Resolution | Link | | ||||
| | --- | --- | --- | --- | --- | --- | --- | --- | | ||||
| |%v|%v|%v|%v|%v|%v|%v|[%v](%v)| | ||||
| `, | ||||
| 		v.VulnerabilityWithRemediation.VulnerabilityName, | ||||
| 		v.VulnerabilityWithRemediation.Severity, | ||||
| 		v.VulnerabilityWithRemediation.BaseScore, | ||||
| 		v.VulnerabilityWithRemediation.OverallScore, | ||||
| 		v.Name, | ||||
| 		v.Version, | ||||
| 		v.Description, | ||||
| 		"", | ||||
| 		"", | ||||
| 		"", | ||||
| 	)), nil | ||||
| 	vul := reporting.VulnerabilityReport{ | ||||
| 		ArtifactID: v.Name, | ||||
|  | ||||
| 		// no information available about branch and commit, yet | ||||
| 		Branch:   "", | ||||
| 		CommitID: "", | ||||
|  | ||||
| 		Description: v.Description, | ||||
|  | ||||
| 		// no information available about direct/indirect dependency, yet | ||||
| 		//DirectDependency:  ... , | ||||
|  | ||||
| 		// no information available about footer, yet | ||||
| 		Footer: "", | ||||
|  | ||||
| 		// no information available about group, yet | ||||
| 		Group: "", | ||||
|  | ||||
| 		// no information available about pipeline name and link, publish date and resolution yet | ||||
| 		PipelineName: "", | ||||
| 		PipelineLink: "", | ||||
| 		PublishDate:  "", | ||||
| 		Resolution:   "", | ||||
|  | ||||
| 		Score:    float64(v.VulnerabilityWithRemediation.BaseScore), | ||||
| 		Severity: v.VulnerabilityWithRemediation.Severity, | ||||
| 		Version:  v.Version, | ||||
|  | ||||
| 		// no vulnerability link available, yet | ||||
| 		VulnerabilityLink: "", | ||||
| 		VulnerabilityName: v.VulnerabilityName, | ||||
| 	} | ||||
|  | ||||
| 	return vul.ToMarkdown() | ||||
| } | ||||
|  | ||||
| // ToTxt returns the textual representation of the contents | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package github | ||||
|  | ||||
| import ( | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| @@ -31,7 +32,7 @@ func FetchCommitStatistics(options *FetchCommitOptions) (FetchCommitResult, erro | ||||
| 		return FetchCommitResult{}, errors.Wrap(err, "failed to get GitHub client") | ||||
| 	} | ||||
| 	// fetch commit by SAH | ||||
| 	result, _, err := client.Repositories.GetCommit(ctx, options.Owner, options.Repository, options.SHA) | ||||
| 	result, _, err := client.Repositories.GetCommit(ctx, options.Owner, options.Repository, options.SHA, &github.ListOptions{}) | ||||
| 	if err != nil { | ||||
| 		return FetchCommitResult{}, errors.Wrap(err, "failed to get GitHub commit") | ||||
| 	} | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import ( | ||||
|  | ||||
| 	piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
| @@ -38,7 +38,7 @@ type CreateIssueOptions struct { | ||||
| 	TrustedCerts   []string `json:"trustedCerts,omitempty"` | ||||
| } | ||||
|  | ||||
| //NewClient creates a new GitHub client using an OAuth token for authentication | ||||
| // NewClient creates a new GitHub client using an OAuth token for authentication | ||||
| func NewClient(token, apiURL, uploadURL string, trustedCerts []string) (context.Context, *github.Client, error) { | ||||
| 	httpClient := piperhttp.Client{} | ||||
| 	httpClient.SetOptions(piperhttp.ClientOptions{ | ||||
| @@ -83,7 +83,6 @@ func CreateIssue(ghCreateIssueOptions *CreateIssueOptions) error { | ||||
| } | ||||
|  | ||||
| func createIssueLocal(ctx context.Context, ghCreateIssueOptions *CreateIssueOptions, ghCreateIssueService githubCreateIssueService, ghSearchIssuesService githubSearchIssuesService, ghCreateCommentService githubCreateCommentService) error { | ||||
|  | ||||
| 	issue := github.IssueRequest{ | ||||
| 		Title: &ghCreateIssueOptions.Title, | ||||
| 	} | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
| 	"regexp" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/google/go-github/v32/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -47,7 +47,6 @@ type ghSearchIssuesMock struct { | ||||
| } | ||||
|  | ||||
| func (g *ghSearchIssuesMock) Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) { | ||||
|  | ||||
| 	regex := regexp.MustCompile(`.*in:title (?P<Title>(.*))`) | ||||
| 	matches := regex.FindStringSubmatch(query) | ||||
|  | ||||
|   | ||||
| @@ -1,62 +1,108 @@ | ||||
| package reporting | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| ) | ||||
|  | ||||
| type Uploader interface { | ||||
| 	CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error | ||||
| type githubIssueService interface { | ||||
| 	Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error) | ||||
| 	CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) | ||||
| 	Edit(ctx context.Context, owner string, repo string, number int, issue *github.IssueRequest) (*github.Issue, *github.Response, error) | ||||
| } | ||||
|  | ||||
| // UploadSingleReportToGithub uploads a single report to GitHub | ||||
| func UploadSingleReportToGithub(scanReport IssueDetail, token, APIURL, owner, repository string, assignees []string, uploader Uploader) error { | ||||
| type githubSearchService interface { | ||||
| 	Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) | ||||
| } | ||||
|  | ||||
| // GitHub contains metadata for reporting towards GitHub | ||||
| type GitHub struct { | ||||
| 	Owner         *string | ||||
| 	Repository    *string | ||||
| 	Assignees     *[]string | ||||
| 	IssueService  githubIssueService | ||||
| 	SearchService githubSearchService | ||||
| } | ||||
|  | ||||
| // UploadSingleReport uploads a single report to GitHub | ||||
| func (g *GitHub) UploadSingleReport(ctx context.Context, scanReport IssueDetail) error { | ||||
| 	// JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub | ||||
| 	// ignore JSON errors since structure is in our hands | ||||
| 	title := scanReport.Title() | ||||
| 	markdownReport, _ := scanReport.ToMarkdown() | ||||
| 	options := piperGithub.CreateIssueOptions{ | ||||
| 		Token:          token, | ||||
| 		APIURL:         APIURL, | ||||
| 		Owner:          owner, | ||||
| 		Repository:     repository, | ||||
| 		Title:          scanReport.Title(), | ||||
| 		Body:           markdownReport, | ||||
| 		Assignees:      assignees, | ||||
| 		UpdateExisting: true, | ||||
|  | ||||
| 	log.Entry().Debugf("Creating/updating GitHub issue with title %v in org %v and repo %v", title, g.Owner, g.Repository) | ||||
| 	if err := g.createIssueOrUpdateIssueComment(ctx, title, string(markdownReport)); err != nil { | ||||
| 		return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", title, err) | ||||
| 	} | ||||
| 	err := uploader.CreateIssue(&options) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UploadMultipleReports uploads a number of reports to GitHub, one per IssueDetail to create transparency | ||||
| func (g *GitHub) UploadMultipleReports(ctx context.Context, scanReports *[]IssueDetail) error { | ||||
| 	for _, scanReport := range *scanReports { | ||||
| 		if err := g.UploadSingleReport(ctx, scanReport); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *GitHub) createIssueOrUpdateIssueComment(ctx context.Context, title, issueContent string) error { | ||||
| 	// check if issue is existing | ||||
| 	issueNumber, issueBody, err := g.findExistingIssue(ctx, title) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", scanReport.Title(), err) | ||||
| 		return fmt.Errorf("error when looking up issue: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if issueNumber == 0 { | ||||
| 		// issue not existing need to create it | ||||
| 		issue := github.IssueRequest{Title: &title, Body: &issueContent, Assignees: g.Assignees} | ||||
| 		if _, _, err := g.IssueService.Create(ctx, *g.Owner, *g.Repository, &issue); err != nil { | ||||
| 			return fmt.Errorf("failed to create issue: %w", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// let's compare and only update in case an update is required | ||||
| 	if issueContent != issueBody { | ||||
| 		// update of issue required | ||||
| 		issueRequest := github.IssueRequest{Body: &issueContent} | ||||
| 		if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, issueNumber, &issueRequest); err != nil { | ||||
| 			return fmt.Errorf("failed to edit issue: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		// now add a small comment that the issue content has been updated | ||||
| 		updateText := "issue content has been updated" | ||||
| 		updateComment := github.IssueComment{Body: &updateText} | ||||
| 		if _, _, err := g.IssueService.CreateComment(ctx, *g.Owner, *g.Repository, issueNumber, &updateComment); err != nil { | ||||
| 			return fmt.Errorf("failed to create comment: %w", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UploadMultipleReportsToGithub uploads a number of reports to GitHub, one per IssueDetail to create transparency | ||||
| func UploadMultipleReportsToGithub(scanReports *[]IssueDetail, token, APIURL, owner, repository string, assignees, trustedCerts []string, uploader Uploader) error { | ||||
| 	for i := 0; i < len(*scanReports); i++ { | ||||
| 		vuln := (*scanReports)[i] | ||||
| 		title := vuln.Title() | ||||
| 		markdownReport, _ := vuln.ToMarkdown() | ||||
| 		options := piperGithub.CreateIssueOptions{ | ||||
| 			Token:          token, | ||||
| 			APIURL:         APIURL, | ||||
| 			Owner:          owner, | ||||
| 			Repository:     repository, | ||||
| 			Title:          title, | ||||
| 			Body:           markdownReport, | ||||
| 			Assignees:      assignees, | ||||
| 			UpdateExisting: true, | ||||
| 			TrustedCerts:   trustedCerts, | ||||
| 		} | ||||
|  | ||||
| 		log.Entry().Debugf("Creating/updating GitHub issue(s) with title %v in org %v and repo %v", title, owner, repository) | ||||
| 		err := uploader.CreateIssue(&options) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", vuln.Title(), err) | ||||
| func (g *GitHub) findExistingIssue(ctx context.Context, title string) (int, string, error) { | ||||
| 	queryString := fmt.Sprintf("is:issue repo:%v/%v in:title %v", *g.Owner, *g.Repository, title) | ||||
| 	searchResult, _, err := g.SearchService.Issues(ctx, queryString, nil) | ||||
| 	if err != nil { | ||||
| 		return 0, "", fmt.Errorf("error occurred when looking for existing issue: %w", err) | ||||
| 	} | ||||
| 	for _, i := range searchResult.Issues { | ||||
| 		if i != nil && *i.Title == title { | ||||
| 			if i.GetState() == "closed" { | ||||
| 				// reopen issue | ||||
| 				open := "open" | ||||
| 				ir := github.IssueRequest{State: &open} | ||||
| 				if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, i.GetNumber(), &ir); err != nil { | ||||
| 					return i.GetNumber(), "", fmt.Errorf("failed to re-open issue: %w", err) | ||||
| 				} | ||||
| 			} | ||||
| 			return i.GetNumber(), i.GetBody(), nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| 	return 0, "", nil | ||||
| } | ||||
|   | ||||
| @@ -1,148 +1,440 @@ | ||||
| package reporting | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	piperGithub "github.com/SAP/jenkins-library/pkg/github" | ||||
| 	"github.com/google/go-github/v45/github" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| type mockUploader struct { | ||||
| 	issueOptions *piperGithub.CreateIssueOptions | ||||
| 	uploadError  error | ||||
| type scanReportlMock struct { | ||||
| 	markdown       []byte | ||||
| 	text           string | ||||
| 	title          string | ||||
| 	failToMarkdown bool | ||||
| } | ||||
|  | ||||
| func (m *mockUploader) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { | ||||
| 	m.issueOptions = ghCreateIssueOptions | ||||
| 	return m.uploadError | ||||
| func (s *scanReportlMock) Title() string { | ||||
| 	return s.title | ||||
| } | ||||
|  | ||||
| type issueDetailMock struct { | ||||
| 	vulnerabilityType       string | ||||
| 	vulnerabilityName       string | ||||
| 	libraryName             string | ||||
| 	vulnerabilitySeverity   string | ||||
| 	vulnerabilityScore      float64 | ||||
| 	vulnerabilityCVSS3Score float64 | ||||
| func (s *scanReportlMock) ToMarkdown() ([]byte, error) { | ||||
| 	if s.failToMarkdown { | ||||
| 		return s.markdown, fmt.Errorf("toMarkdown failure") | ||||
| 	} | ||||
| 	return s.markdown, nil | ||||
| } | ||||
|  | ||||
| func (idm issueDetailMock) Title() string { | ||||
| 	return fmt.Sprintf("%v/%v/%v", idm.vulnerabilityType, idm.vulnerabilityName, idm.libraryName) | ||||
| func (s *scanReportlMock) ToTxt() string { | ||||
| 	return s.text | ||||
| } | ||||
|  | ||||
| func (idm issueDetailMock) ToMarkdown() ([]byte, error) { | ||||
| 	return []byte(fmt.Sprintf(`**Vulnerability %v** | ||||
| | Severity | Package | Installed Version | Description | Fix Resolution | Link | | ||||
| | --- | --- | --- | --- | --- | --- | | ||||
| |%v|%v|%v|%v|%v|[%v](%v)| | ||||
| `, idm.vulnerabilityName, idm.vulnerabilitySeverity, idm.libraryName, "", "", "", "", "")), nil | ||||
| type ghServicesMock struct { | ||||
| 	issues               []*github.Issue | ||||
| 	createError          error | ||||
| 	comment              *github.IssueComment | ||||
| 	createCommmentNumber int | ||||
| 	createCommentError   error | ||||
| 	editError            error | ||||
| 	editNumber           int | ||||
| 	editRequest          *github.IssueRequest | ||||
| 	searchError          error | ||||
| 	searchOpts           *github.SearchOptions | ||||
| 	searchQuery          string | ||||
| 	searchResult         []*github.Issue | ||||
| } | ||||
|  | ||||
| func (idm issueDetailMock) ToTxt() string { | ||||
| 	return fmt.Sprintf(`Vulnerability %v | ||||
| Severity: %v | ||||
| Package: %v | ||||
| Installed Version: %v | ||||
| Description: %v | ||||
| Fix Resolution: %v | ||||
| Link: %v | ||||
| `, idm.vulnerabilityName, idm.vulnerabilitySeverity, idm.libraryName, "", "", "", "") | ||||
| func (g *ghServicesMock) Create(ctx context.Context, owner string, repo string, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) { | ||||
| 	if g.issues == nil { | ||||
| 		g.issues = []*github.Issue{} | ||||
| 	} | ||||
| 	number := len(g.issues) + 1 | ||||
| 	id := int64(number) | ||||
| 	assignees := []*github.User{} | ||||
| 	if issueRequest.Assignees != nil { | ||||
| 		for _, userName := range *issueRequest.Assignees { | ||||
| 			// cannot use userName directly since variable is re-used in the loop and thus name in assignees would be pointing to final value only. | ||||
| 			user := userName | ||||
| 			assignees = append(assignees, &github.User{Name: &user}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	theIssue := github.Issue{ | ||||
| 		ID:        &id, | ||||
| 		Number:    &number, | ||||
| 		Title:     issueRequest.Title, | ||||
| 		Body:      issueRequest.Body, | ||||
| 		Assignees: assignees, | ||||
| 	} | ||||
| 	g.issues = append(g.issues, &theIssue) | ||||
| 	if g.createError != nil { | ||||
| 		return &theIssue, &github.Response{}, g.createError | ||||
| 	} | ||||
| 	return &theIssue, &github.Response{}, nil | ||||
| } | ||||
|  | ||||
| func TestUploadSingleReportToGithub(t *testing.T) { | ||||
| func (g *ghServicesMock) CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { | ||||
| 	g.createCommmentNumber = number | ||||
| 	g.comment = comment | ||||
| 	if g.createCommentError != nil { | ||||
| 		return &github.IssueComment{}, &github.Response{}, g.createCommentError | ||||
| 	} | ||||
| 	return &github.IssueComment{}, &github.Response{}, nil | ||||
| } | ||||
|  | ||||
| func (g *ghServicesMock) Edit(ctx context.Context, owner string, repo string, number int, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) { | ||||
| 	g.editNumber = number | ||||
| 	g.editRequest = issueRequest | ||||
| 	if g.editError != nil { | ||||
| 		return &github.Issue{}, &github.Response{}, g.editError | ||||
| 	} | ||||
| 	return &github.Issue{}, &github.Response{}, nil | ||||
| } | ||||
|  | ||||
| func (g *ghServicesMock) Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) { | ||||
| 	g.searchOpts = opts | ||||
| 	g.searchQuery = query | ||||
|  | ||||
| 	if g.searchError != nil { | ||||
| 		return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, g.searchError | ||||
| 	} | ||||
| 	return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, nil | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	owner      string = "testOwner" | ||||
| 	repository string = "testRepository" | ||||
| ) | ||||
|  | ||||
| func TestUploadSingleReport(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		testUploader := mockUploader{} | ||||
| 		testData := struct { | ||||
| 			scanReport ScanReport | ||||
| 			token      string | ||||
| 			apiurl     string | ||||
| 			owner      string | ||||
| 			repository string | ||||
| 			assignees  []string | ||||
| 			uploader   Uploader | ||||
| 		}{ | ||||
| 			scanReport: ScanReport{ReportTitle: "testReportTitle"}, | ||||
| 			token:      "testToken", | ||||
| 			apiurl:     "testApiUrl", | ||||
| 			owner:      "testOwner", | ||||
| 			repository: "testRepository", | ||||
| 			assignees:  []string{"testAssignee1", "testAssignee2"}, | ||||
| 			uploader:   &testUploader, | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{} | ||||
| 		s := scanReportlMock{title: "The Title", markdown: []byte("# The Markdown")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		err := UploadSingleReportToGithub(testData.scanReport, testData.token, testData.apiurl, testData.owner, testData.repository, testData.assignees, testData.uploader) | ||||
| 		err := gh.UploadSingleReport(ctx, &s) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		assert.Equal(t, testData.token, testUploader.issueOptions.Token) | ||||
| 		assert.Equal(t, testData.apiurl, testUploader.issueOptions.APIURL) | ||||
| 		assert.Equal(t, testData.owner, testUploader.issueOptions.Owner) | ||||
| 		assert.Equal(t, testData.repository, testUploader.issueOptions.Repository) | ||||
| 		assert.Equal(t, testData.scanReport.ReportTitle, testUploader.issueOptions.Title) | ||||
| 		assert.Contains(t, string(testUploader.issueOptions.Body), "testReportTitle") | ||||
| 		assert.Equal(t, testData.assignees, testUploader.issueOptions.Assignees) | ||||
| 		assert.True(t, testUploader.issueOptions.UpdateExisting) | ||||
| 		assert.Equal(t, s.title, ghMock.issues[0].GetTitle()) | ||||
| 		assert.Equal(t, string(s.markdown), ghMock.issues[0].GetBody()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		testUploader := mockUploader{uploadError: fmt.Errorf("upload failed")} | ||||
| 		var report IssueDetail | ||||
| 		report = ScanReport{} | ||||
| 		err := UploadSingleReportToGithub(report, "", "", "", "", []string{}, &testUploader) | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{createError: fmt.Errorf("create failed")} | ||||
| 		s := scanReportlMock{title: "The Title"} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		assert.Contains(t, fmt.Sprint(err), "upload failed") | ||||
| 		err := gh.UploadSingleReport(ctx, &s) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to upload results for 'The Title' into GitHub issue: failed to create issue: create failed") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestUploadMultipleReportsToGithub(t *testing.T) { | ||||
| func TestUploadMultipleReports(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		testUploader := mockUploader{} | ||||
| 		testData := struct { | ||||
| 			reports    []IssueDetail | ||||
| 			token      string | ||||
| 			apiurl     string | ||||
| 			owner      string | ||||
| 			repository string | ||||
| 			assignees  []string | ||||
| 			uploader   Uploader | ||||
| 		}{ | ||||
| 			reports:    []IssueDetail{issueDetailMock{vulnerabilityType: "SECURITY_VULNERABILITY", libraryName: "test-component", vulnerabilityName: "CVE-2022001", vulnerabilitySeverity: "MEDIUM", vulnerabilityScore: 5.3}}, | ||||
| 			token:      "testToken", | ||||
| 			apiurl:     "testApiUrl", | ||||
| 			owner:      "testOwner", | ||||
| 			repository: "testRepository", | ||||
| 			assignees:  []string{"testAssignee1", "testAssignee2"}, | ||||
| 			uploader:   &testUploader, | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{} | ||||
| 		s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")} | ||||
| 		s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")} | ||||
| 		s := []IssueDetail{&s1, &s2} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		err := UploadMultipleReportsToGithub(&testData.reports, testData.token, testData.apiurl, testData.owner, testData.repository, testData.assignees, []string{}, testData.uploader) | ||||
| 		err := gh.UploadMultipleReports(ctx, &s) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		assert.Equal(t, testData.token, testUploader.issueOptions.Token) | ||||
| 		assert.Equal(t, testData.apiurl, testUploader.issueOptions.APIURL) | ||||
| 		assert.Equal(t, testData.owner, testUploader.issueOptions.Owner) | ||||
| 		assert.Equal(t, testData.repository, testUploader.issueOptions.Repository) | ||||
| 		assert.Equal(t, testData.reports[0].Title(), testUploader.issueOptions.Title) | ||||
| 		assert.Contains(t, string(testUploader.issueOptions.Body), "CVE-2022001") | ||||
| 		assert.Equal(t, testData.assignees, testUploader.issueOptions.Assignees) | ||||
| 		assert.True(t, testUploader.issueOptions.UpdateExisting) | ||||
| 		assert.Equal(t, s1.title, ghMock.issues[0].GetTitle()) | ||||
| 		assert.Equal(t, string(s1.markdown), ghMock.issues[0].GetBody()) | ||||
| 		assert.Equal(t, s2.title, ghMock.issues[1].GetTitle()) | ||||
| 		assert.Equal(t, string(s2.markdown), ghMock.issues[1].GetBody()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		testUploader := mockUploader{uploadError: fmt.Errorf("upload failed")} | ||||
| 		reports := []IssueDetail{issueDetailMock{vulnerabilityType: "SECURITY_VULNERABILITY", libraryName: "test-component", vulnerabilityName: "CVE-2022001", vulnerabilitySeverity: "MEDIUM", vulnerabilityScore: 5.3}} | ||||
| 		err := UploadMultipleReportsToGithub(&reports, "", "", "", "", []string{}, []string{}, &testUploader) | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{createError: fmt.Errorf("create failed")} | ||||
| 		s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")} | ||||
| 		s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")} | ||||
| 		s := []IssueDetail{&s1, &s2} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		assert.Contains(t, fmt.Sprint(err), "upload failed") | ||||
| 		err := gh.UploadMultipleReports(ctx, &s) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to upload results for 'The Title 1' into GitHub issue: failed to create issue: create failed") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCreateIssueOrUpdateIssueComment(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	title := "The Title" | ||||
| 	assignees := []string{"assignee1", "assignee2"} | ||||
|  | ||||
| 	t.Run("success case - new issue", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			Assignees:     &assignees, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, title, ghMock.issues[0].GetTitle()) | ||||
| 		assert.Equal(t, markdown, ghMock.issues[0].GetBody()) | ||||
| 		assert.Equal(t, assignees[0], ghMock.issues[0].Assignees[0].GetName()) | ||||
| 		assert.Equal(t, assignees[1], ghMock.issues[0].Assignees[1].GetName()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - update issue", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "open" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, number, ghMock.editNumber) | ||||
| 		assert.Equal(t, markdown, ghMock.editRequest.GetBody()) | ||||
| 		assert.Equal(t, number, ghMock.createCommmentNumber) | ||||
| 		assert.Equal(t, "issue content has been updated", ghMock.comment.GetBody()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - no update", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "open" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "the body of the issue" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Nil(t, ghMock.editRequest) | ||||
| 		assert.Nil(t, ghMock.comment) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - lookup failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
|  | ||||
| 		assert.EqualError(t, err, "error when looking up issue: error occurred when looking for existing issue: search failed") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - issue creation failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{createError: fmt.Errorf("creation failed")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to create issue: creation failed") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - issue editing failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "open" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, editError: fmt.Errorf("edit failed")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
| 		assert.EqualError(t, err, "failed to edit issue: edit failed") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - edit comment creation failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "open" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, createCommentError: fmt.Errorf("comment failed")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
| 		markdown := "# The Markdown" | ||||
|  | ||||
| 		err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) | ||||
| 		assert.EqualError(t, err, "failed to create comment: comment failed") | ||||
|  | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestFindExistingIssue(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	t.Run("success case - issue found", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "open" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		i, b, err := gh.findExistingIssue(ctx, title) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, fmt.Sprintf("is:issue repo:%v/%v in:title %v", *gh.Owner, *gh.Repository, title), ghMock.searchQuery) | ||||
| 		assert.Equal(t, 1, i) | ||||
| 		assert.Equal(t, body, b) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - issue found, reopen", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "closed" | ||||
| 		title := "The Title" | ||||
| 		body := "the body of the issue" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} | ||||
| 		ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		i, b, err := gh.findExistingIssue(ctx, "The Title") | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, 1, ghMock.editNumber) | ||||
| 		assert.Equal(t, "open", ghMock.editRequest.GetState()) | ||||
| 		assert.Equal(t, 1, i) | ||||
| 		assert.Equal(t, body, b) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - no issue found", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		i, body, err := gh.findExistingIssue(ctx, "The Title") | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, 0, i) | ||||
| 		assert.Equal(t, "", body) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - search failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		i, _, err := gh.findExistingIssue(ctx, "The Title") | ||||
|  | ||||
| 		assert.EqualError(t, err, "error occurred when looking for existing issue: search failed") | ||||
| 		assert.Equal(t, 0, i) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - reopen failed", func(t *testing.T) { | ||||
| 		ctx := context.Background() | ||||
| 		number := 1 | ||||
| 		state := "closed" | ||||
| 		title := "The Title" | ||||
| 		issue := github.Issue{Number: &number, State: &state, Title: &title} | ||||
| 		ghMock := ghServicesMock{editError: fmt.Errorf("reopen failed"), searchResult: []*github.Issue{&issue}} | ||||
| 		gh := GitHub{ | ||||
| 			Owner:         &owner, | ||||
| 			Repository:    &repository, | ||||
| 			IssueService:  &ghMock, | ||||
| 			SearchService: &ghMock, | ||||
| 		} | ||||
|  | ||||
| 		_, _, err := gh.findExistingIssue(ctx, "The Title") | ||||
| 		assert.EqualError(t, err, "failed to re-open issue: reopen failed") | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,8 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/text/cases" | ||||
| 	"golang.org/x/text/language" | ||||
| ) | ||||
|  | ||||
| // IssueDetail represents any content that can be transformed into the body of a GitHub issue | ||||
| @@ -17,6 +19,86 @@ type IssueDetail interface { | ||||
| 	ToTxt() string | ||||
| } | ||||
|  | ||||
| // VulnerabilityReport represents metadata for a report on a vulnerability | ||||
| type VulnerabilityReport struct { | ||||
| 	ArtifactID        string | ||||
| 	Branch            string | ||||
| 	CommitID          string | ||||
| 	Description       string | ||||
| 	DirectDependency  string | ||||
| 	Footer            string | ||||
| 	Group             string | ||||
| 	PipelineName      string | ||||
| 	PipelineLink      string | ||||
| 	PublishDate       string | ||||
| 	Resolution        string | ||||
| 	Score             float64 | ||||
| 	Severity          string | ||||
| 	Version           string | ||||
| 	VulnerabilityLink string | ||||
| 	VulnerabilityName string | ||||
| } | ||||
|  | ||||
| const vulnerabilityMdTemplate string = `# {{title .Severity }} ({{ .Score }}) Vulnerability {{ .VulnerabilityName }} - {{ .ArtifactID }} | ||||
|  | ||||
| **Vulnerability link:** [{{ .VulnerabilityLink }}]({{ .VulnerabilityLink }}) | ||||
|  | ||||
| ## Fix | ||||
|  | ||||
| **{{ .Resolution }}** | ||||
|  | ||||
| ## Context | ||||
|  | ||||
| {{if .PipelineLink -}} | ||||
| ### Pipeline | ||||
|  | ||||
| Pipeline run: [{{ .PipelineName }}]({{ .PipelineLink }}) | ||||
| {{- end}} | ||||
|  | ||||
| ### Detected in | ||||
|  | ||||
| {{if .Branch}}**Branch:** {{ .Branch }}{{- end}} | ||||
| {{if .CommitID}}**CommitId:** {{ .CommitID }}{{- end}} | ||||
| {{if .DirectDependency}}**Dependency:** {{if (eq .DirectDependency "true")}}direct{{ else }}indirect{{ end }}{{- end}} | ||||
| {{if .ArtifactID}}**ArtifactId:** {{ .ArtifactID }}{{- end}} | ||||
| {{if .Group}}**Group:** {{ .Group }}{{- end}} | ||||
| {{if .Version}}**Version:** {{ .Version }}{{- end}} | ||||
| {{if .PublishDate}}**Publishing date:** {{.PublishDate }}{{- end}} | ||||
|  | ||||
| ## Description | ||||
|  | ||||
| {{ .Description }} | ||||
|  | ||||
| --- | ||||
|  | ||||
| {{.Footer}} | ||||
| ` | ||||
|  | ||||
| // ToMarkdown creates a vulnerability in markdown format which can be used in GitHub issues | ||||
| func (v *VulnerabilityReport) ToMarkdown() ([]byte, error) { | ||||
| 	funcMap := template.FuncMap{ | ||||
| 		"date": func(t time.Time) string { | ||||
| 			return t.Format("2006-01-02") | ||||
| 		}, | ||||
| 		"title": func(s string) string { | ||||
| 			caser := cases.Title(language.AmericanEnglish) | ||||
| 			return caser.String(s) | ||||
| 		}, | ||||
| 	} | ||||
| 	md := []byte{} | ||||
| 	tmpl, err := template.New("report").Funcs(funcMap).Parse(vulnerabilityMdTemplate) | ||||
| 	if err != nil { | ||||
| 		return md, fmt.Errorf("failed to create  markdown issue template: %w", err) | ||||
| 	} | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	err = tmpl.Execute(buf, v) | ||||
| 	if err != nil { | ||||
| 		return md, fmt.Errorf("failed to execute markdown issue template: %w", err) | ||||
| 	} | ||||
| 	md = buf.Bytes() | ||||
| 	return md, nil | ||||
| } | ||||
|  | ||||
| // ScanReport defines the elements of a scan report used by various scan steps | ||||
| type ScanReport struct { | ||||
| 	StepName       string          `json:"stepName"` | ||||
| @@ -92,7 +174,7 @@ func (s *ScanReport) AddSubHeader(header, details string) { | ||||
| 	s.Subheaders = append(s.Subheaders, Subheader{Description: header, Details: details}) | ||||
| } | ||||
|  | ||||
| //StepReportDirectory specifies the default directory for markdown reports which can later be collected by step pipelineCreateSummary | ||||
| // StepReportDirectory specifies the default directory for markdown reports which can later be collected by step pipelineCreateSummary | ||||
| const StepReportDirectory = ".pipeline/stepReports" | ||||
|  | ||||
| // ToJSON returns the report in JSON format | ||||
|   | ||||
| @@ -1,12 +1,53 @@ | ||||
| package reporting | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestVulToMarkdown(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("success - empty", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		vulReport := VulnerabilityReport{} | ||||
| 		_, err := vulReport.ToMarkdown() | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success - filled", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		vulReport := VulnerabilityReport{ | ||||
| 			ArtifactID:        "theArtifact", | ||||
| 			Branch:            "main", | ||||
| 			CommitID:          "acb123", | ||||
| 			Description:       "This is the test description.", | ||||
| 			DirectDependency:  "true", | ||||
| 			Footer:            "This is the test footer", | ||||
| 			Group:             "the.group", | ||||
| 			PipelineName:      "thePipelineName", | ||||
| 			PipelineLink:      "https://the.link.to.the.pipeline", | ||||
| 			PublishDate:       "2022-06-30", | ||||
| 			Resolution:        "This is the test resolution.", | ||||
| 			Score:             7.8, | ||||
| 			Severity:          "high", | ||||
| 			Version:           "1.2.3", | ||||
| 			VulnerabilityLink: "https://the.link/to/the/vulnerability", | ||||
| 			VulnerabilityName: "CVE-Test-001", | ||||
| 		} | ||||
| 		goldenFilePath := filepath.Join("testdata", "markdownVulnerability.golden") | ||||
| 		expected, err := ioutil.ReadFile(goldenFilePath) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		res, err := vulReport.ToMarkdown() | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, string(expected), string(res)) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestToHTML(t *testing.T) { | ||||
| 	t.Run("empty table", func(t *testing.T) { | ||||
| 		report := ScanReport{ | ||||
|   | ||||
							
								
								
									
										31
									
								
								pkg/reporting/testdata/markdownVulnerability.golden
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								pkg/reporting/testdata/markdownVulnerability.golden
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # High (7.8) Vulnerability CVE-Test-001 - theArtifact | ||||
|  | ||||
| **Vulnerability link:** [https://the.link/to/the/vulnerability](https://the.link/to/the/vulnerability) | ||||
|  | ||||
| ## Fix | ||||
|  | ||||
| **This is the test resolution.** | ||||
|  | ||||
| ## Context | ||||
|  | ||||
| ### Pipeline | ||||
|  | ||||
| Pipeline run: [thePipelineName](https://the.link.to.the.pipeline) | ||||
|  | ||||
| ### Detected in | ||||
|  | ||||
| **Branch:** main | ||||
| **CommitId:** acb123 | ||||
| **Dependency:** direct | ||||
| **ArtifactId:** theArtifact | ||||
| **Group:** the.group | ||||
| **Version:** 1.2.3 | ||||
| **Publishing date:** 2022-06-30 | ||||
|  | ||||
| ## Description | ||||
|  | ||||
| This is the test description. | ||||
|  | ||||
| --- | ||||
|  | ||||
| This is the test footer | ||||
| @@ -10,6 +10,7 @@ import ( | ||||
|  | ||||
| 	piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/reporting" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| @@ -57,7 +58,10 @@ type Alert struct { | ||||
|  | ||||
| // Title returns the issue title representation of the contents | ||||
| func (a Alert) Title() string { | ||||
| 	return fmt.Sprintf("%v/%v/%v/%v", a.Type, consolidate(a.Vulnerability.Severity, a.Vulnerability.CVSS3Severity, a.Vulnerability.Score, a.Vulnerability.CVSS3Score), a.Vulnerability.Name, a.Library.ArtifactID) | ||||
| 	if a.Type == "SECURITY_VULNERABILITY" { | ||||
| 		return fmt.Sprintf("Security Vulnerability %v %v", a.Vulnerability.Name, a.Library.ArtifactID) | ||||
| 	} | ||||
| 	return fmt.Sprintf("%v %v %v ", a.Type, a.Vulnerability.Name, a.Library.ArtifactID) | ||||
| } | ||||
|  | ||||
| func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score float64) string { | ||||
| @@ -92,23 +96,30 @@ func (a Alert) ToMarkdown() ([]byte, error) { | ||||
| 	if score == 0 { | ||||
| 		score = a.Vulnerability.Score | ||||
| 	} | ||||
| 	return []byte(fmt.Sprintf( | ||||
| 		`**Vulnerability %v** | ||||
| | Severity | Base (NVD) Score | Temporal Score | Package | Installed Version | Description | Fix Resolution | Link | | ||||
| | --- | --- | --- | --- | --- | --- | --- | --- | | ||||
| |%v|%v|%v|%v|%v|%v|%v|[%v](%v)| | ||||
| `, | ||||
| 		a.Vulnerability.Name, | ||||
| 		a.Vulnerability.Severity, | ||||
| 		score, | ||||
| 		score, | ||||
| 		a.Library.ArtifactID, | ||||
| 		a.Library.Version, | ||||
| 		a.Vulnerability.Description, | ||||
| 		a.Vulnerability.TopFix.FixResolution, | ||||
| 		a.Vulnerability.Name, | ||||
| 		a.Vulnerability.URL, | ||||
| 	)), nil | ||||
|  | ||||
| 	vul := reporting.VulnerabilityReport{ | ||||
| 		ArtifactID: a.Library.ArtifactID, | ||||
| 		// no information available about branch and commit, yet | ||||
| 		Branch:           "", | ||||
| 		CommitID:         "", | ||||
| 		Description:      a.Vulnerability.Description, | ||||
| 		DirectDependency: fmt.Sprint(a.DirectDependency), | ||||
| 		// no information available about footer, yet | ||||
| 		Footer: "", | ||||
| 		Group:  a.Library.GroupID, | ||||
| 		// no information available about pipeline name and link, yet | ||||
| 		PipelineName:      "", | ||||
| 		PipelineLink:      "", | ||||
| 		PublishDate:       a.Vulnerability.PublishDate, | ||||
| 		Resolution:        a.Vulnerability.TopFix.FixResolution, | ||||
| 		Score:             score, | ||||
| 		Severity:          consolidate(a.Vulnerability.Severity, a.Vulnerability.CVSS3Severity, a.Vulnerability.Score, a.Vulnerability.CVSS3Score), | ||||
| 		Version:           a.Library.Version, | ||||
| 		VulnerabilityLink: a.Vulnerability.URL, | ||||
| 		VulnerabilityName: a.Vulnerability.Name, | ||||
| 	} | ||||
|  | ||||
| 	return vul.ToMarkdown() | ||||
| } | ||||
|  | ||||
| // ToTxt returns the textual representation of the contents | ||||
|   | ||||
		Reference in New Issue
	
	Block a user