From af2a01c06413fb0aa5fa64ecb4584d728f63dffa Mon Sep 17 00:00:00 2001 From: Sven Merk <33895725+nevskrem@users.noreply.github.com> Date: Mon, 25 May 2020 19:48:59 +0200 Subject: [PATCH] Fortify implementation in golang (#1428) --- .gitignore | 1 + cmd/artifactPrepareVersion_generated.go | 6 +- cmd/artifactPrepareVersion_test.go | 5 + cmd/checkmarxExecuteScan_generated.go | 10 +- cmd/detectExecuteScan_generated.go | 6 +- cmd/fortifyExecuteScan.go | 690 +++++++++ cmd/fortifyExecuteScan_generated.go | 630 ++++++++ cmd/fortifyExecuteScan_generated_test.go | 16 + cmd/fortifyExecuteScan_test.go | 612 ++++++++ cmd/gctsCreateRepository_generated.go | 2 +- cmd/githubCreatePullRequest_generated.go | 4 +- cmd/githubPublishRelease_generated.go | 8 +- cmd/karmaExecuteTests_generated.go | 6 +- cmd/kubernetesDeploy_generated.go | 6 +- cmd/mavenBuild_generated.go | 2 +- cmd/mtaBuild_generated.go | 10 +- cmd/nexusUpload_generated.go | 2 +- cmd/npmExecuteScripts_generated.go | 2 +- cmd/piper.go | 1 + cmd/protecodeExecuteScan_generated.go | 8 +- cmd/sonarExecuteScan_generated.go | 8 +- cmd/xsDeploy_generated.go | 6 +- .../docs/steps/fortifyExecuteScan.md | 8 + go.mod | 12 +- go.sum | 135 +- pkg/checkmarx/checkmarx_test.go | 4 + pkg/fortify/fortify.go | 741 +++++++++ pkg/fortify/fortify_test.go | 1330 +++++++++++++++++ pkg/generator/helper/helper.go | 70 +- pkg/generator/helper/helper_test.go | 4 +- .../custom_step_code_generated.golden | 2 +- .../step_code_generated.golden | 2 +- pkg/http/http.go | 203 ++- pkg/piperutils/slices.go | 10 + pkg/piperutils/templateUtils.go | 30 + pkg/piperutils/templateUtils_test.go | 46 + pkg/piperutils/testdata/2_setup.py | 22 + pkg/piperutils/testdata/VERSION.TXT | 1 + pkg/piperutils/testdata/setup.py | 22 + pkg/piperutils/testdata/test2_pom.xml | 15 + pkg/piperutils/testdata/test_pom.xml | 9 + pkg/versioning/descriptorUtils.go | 47 + pkg/versioning/descriptorUtils_test.go | 92 ++ pkg/versioning/docker.go | 5 + pkg/versioning/inifile.go | 5 + pkg/versioning/jsonfile.go | 5 + pkg/versioning/maven.go | 64 + pkg/versioning/pip.go | 146 ++ pkg/versioning/pip_test.go | 3 + pkg/versioning/versionfile.go | 5 + pkg/versioning/versioning.go | 12 +- pkg/versioning/versioning_test.go | 4 +- pkg/versioning/yamlfile.go | 5 + resources/metadata/fortify.yaml | 472 ++++++ test/groovy/CommonStepsTest.groovy | 1 + vars/fortifyExecuteScan.groovy | 17 + 56 files changed, 5461 insertions(+), 129 deletions(-) create mode 100644 cmd/fortifyExecuteScan.go create mode 100644 cmd/fortifyExecuteScan_generated.go create mode 100644 cmd/fortifyExecuteScan_generated_test.go create mode 100644 cmd/fortifyExecuteScan_test.go create mode 100644 documentation/docs/steps/fortifyExecuteScan.md create mode 100644 pkg/fortify/fortify.go create mode 100644 pkg/fortify/fortify_test.go create mode 100644 pkg/piperutils/templateUtils.go create mode 100644 pkg/piperutils/templateUtils_test.go create mode 100644 pkg/piperutils/testdata/2_setup.py create mode 100644 pkg/piperutils/testdata/VERSION.TXT create mode 100644 pkg/piperutils/testdata/setup.py create mode 100644 pkg/piperutils/testdata/test2_pom.xml create mode 100644 pkg/piperutils/testdata/test_pom.xml create mode 100644 pkg/versioning/descriptorUtils.go create mode 100644 pkg/versioning/descriptorUtils_test.go create mode 100644 pkg/versioning/pip.go create mode 100644 pkg/versioning/pip_test.go create mode 100644 resources/metadata/fortify.yaml create mode 100644 vars/fortifyExecuteScan.groovy diff --git a/.gitignore b/.gitignore index 0b721994a..b7ed14398 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ consumer-test/**/workspace /piper.exe .factorypath +debug.test /cache/protecode *errorDetails.json diff --git a/cmd/artifactPrepareVersion_generated.go b/cmd/artifactPrepareVersion_generated.go index 3f5f3eabb..2dd5b519a 100644 --- a/cmd/artifactPrepareVersion_generated.go +++ b/cmd/artifactPrepareVersion_generated.go @@ -189,7 +189,7 @@ Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: 0 { + fortifyProjectVersion = config.PullRequestName + projectVersion, err := sys.LookupOrCreateProjectVersionDetailsForPullRequest(project.ID, projectVersion, fortifyProjectVersion) + if err != nil { + return fmt.Errorf("Failed to lookup / create project version for pull request %v: %w", fortifyProjectVersion, err) + } + log.Entry().Debugf("Looked up / created project version with ID %v for PR %v", projectVersion.ID, fortifyProjectVersion) + } else { + prID := determinePullRequestMerge(config) + if len(prID) > 0 { + log.Entry().Debugf("Determined PR ID '%v' for merge check", prID) + pullRequestProjectName := fmt.Sprintf("PR-%v", prID) + err = sys.MergeProjectVersionStateOfPRIntoMaster(config.FprDownloadEndpoint, config.FprUploadEndpoint, project.ID, projectVersion.ID, pullRequestProjectName) + if err != nil { + return fmt.Errorf("Failed to merge project version state for pull request %v into project version %v of project %v: %w", pullRequestProjectName, fortifyProjectVersion, project.ID, err) + } + } + } + + log.Entry().Debugf("Scanning and uploading to project %v with version %v and projectVersionId %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) + buildLabel := fmt.Sprintf("%v/repos/%v/%v/commits/%v", config.GithubAPIURL, config.Owner, config.Repository, config.CommitID) + + // Create sourceanalyzer command based on configuration + buildID := uuid.New().String() + command.SetDir(config.ModulePath) + os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm) + + if config.UpdateRulePack { + err := command.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-url", config.ServerURL) + if err != nil { + log.Entry().WithError(err).WithField("serverUrl", config.ServerURL).Fatal("Failed to update rule pack") + } + err = command.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-showInstalledRules") + if err != nil { + log.Entry().WithError(err).WithField("serverUrl", config.ServerURL).Fatal("Failed to fetch details of installed rule pack") + } + } + + triggerFortifyScan(config, command, buildID, buildLabel) + + var reports []piperutils.Path + reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/fortify-scan.*", config.ModulePath)}) + reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/*.fpr", config.ModulePath)}) + + var message string + if config.UploadResults { + log.Entry().Debug("Uploading results") + resultFilePath := fmt.Sprintf("%vtarget/result.fpr", config.ModulePath) + err = sys.UploadResultFile(config.FprUploadEndpoint, resultFilePath, projectVersion.ID) + message = fmt.Sprintf("Failed to upload result file %v to Fortify SSC at %v", resultFilePath, config.ServerURL) + } else { + log.Entry().Debug("Generating XML report") + xmlReportName := "fortify_result.xml" + err = command.RunExecutable("ReportGenerator", "-format", "xml", "-f", xmlReportName, "-source", fmt.Sprintf("%vtarget/result.fpr", config.ModulePath)) + message = fmt.Sprintf("Failed to generate XML report %v", xmlReportName) + if err != nil { + reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vfortify_result.xml", config.ModulePath)}) + } + } + piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil) + if err != nil { + return fmt.Errorf(message+": %w", err) + } + + log.Entry().Debugf("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) + filterSet, err := sys.GetFilterSetOfProjectVersionByTitle(projectVersion.ID, config.FilterSetTitle) + if filterSet == nil || err != nil { + return fmt.Errorf("Failed to load filter set with title %v", config.FilterSetTitle) + } + + // Ensure latest FPR is processed + err = verifyScanResultsFinishedUploading(config, sys, projectVersion.ID, buildLabel, filterSet, 0) + if err != nil { + return err + } + + // Generate report + if config.Reporting { + resultURL := []byte(fmt.Sprintf("https://fortify.tools.sap/ssc/html/ssc/version/%v/fix/null/", projectVersion.ID)) + ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0700) + + data, err := generateAndDownloadQGateReport(config, sys, project, projectVersion) + if err != nil { + return err + } + 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 { + log.Entry().WithError(err).Fatalf("Failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID) + } + log.Entry().Debugf("initial filter selector set: %v", issueFilterSelectorSet) + numberOfViolations := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) + numberOfViolations += analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) + + log.Entry().Infof("Counted %v violations, details: %v", numberOfViolations, auditStatus) + + influx.fortify_data.fields.projectName = fortifyProjectName + influx.fortify_data.fields.projectVersion = fortifyProjectVersion + influx.fortify_data.fields.violations = fmt.Sprintf("%v", numberOfViolations) + if numberOfViolations > 0 { + return errors.New("fortify scan failed, the project is not compliant. For details check the archived report") + } + return nil +} + +func analyseUnauditedIssues(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) int { + log.Entry().Info("Analyzing unaudited issues") + reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Folder"}, nil) + fetchedIssueGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) + if err != nil { + log.Entry().WithError(err).Fatalf("Failed to fetch project version issue groups with filter set %v and selector %v for project version ID %v", filterSet, issueFilterSelectorSet, projectVersion.ID) + } + overallViolations := 0 + for _, issueGroup := range fetchedIssueGroups { + overallViolations += getIssueDeltaFor(config, sys, issueGroup, projectVersion.ID, filterSet, issueFilterSelectorSet, influx, auditStatus) + } + return overallViolations +} + +func getIssueDeltaFor(config fortifyExecuteScanOptions, sys fortify.System, issueGroup *models.ProjectVersionIssueGroup, projectVersionID int64, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) int { + totalMinusAuditedDelta := 0 + group := "" + total := 0 + audited := 0 + if issueGroup != nil { + group = *issueGroup.ID + total = int(*issueGroup.TotalCount) + audited = int(*issueGroup.AuditedCount) + } + groupTotalMinusAuditedDelta := total - audited + if groupTotalMinusAuditedDelta > 0 { + reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Folder", "Analysis"}, []string{group}) + folderSelector := sys.GetFilterSetByDisplayName(reducedFilterSelectorSet, "Folder") + if folderSelector == nil { + log.Entry().Fatal("folder selector not found") + } + analysisSelector := sys.GetFilterSetByDisplayName(reducedFilterSelectorSet, "Analysis") + + auditStatus[group] = fmt.Sprintf("%v total : %v audited", total, audited) + + if strings.Contains(config.MustAuditIssueGroups, group) { + totalMinusAuditedDelta += groupTotalMinusAuditedDelta + if group == "Corporate Security Requirements" { + influx.fortify_data.fields.corporateTotal = fmt.Sprintf("%v", total) + influx.fortify_data.fields.corporateAudited = fmt.Sprintf("%v", audited) + } + if group == "Audit All" { + influx.fortify_data.fields.auditAllTotal = fmt.Sprintf("%v", total) + influx.fortify_data.fields.auditAllAudited = fmt.Sprintf("%v", audited) + } + log.Entry().Errorf("[projectVersionId %v]: Unaudited %v detected, count %v", projectVersionID, group, totalMinusAuditedDelta) + logIssueURL(config, projectVersionID, folderSelector, analysisSelector) + } + + if strings.Contains(config.SpotAuditIssueGroups, group) { + log.Entry().Infof("Analyzing %v", config.SpotAuditIssueGroups) + filter := fmt.Sprintf("%v:%v", folderSelector.EntityType, folderSelector.SelectorOptions[0].Value) + fetchedIssueGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersionID, filter, filterSet.GUID, sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Category"}, nil)) + if err != nil { + log.Entry().WithError(err).Fatalf("Failed to fetch project version issue groups with filter %v, filter set %v and selector %v for project version ID %v", filter, filterSet, issueFilterSelectorSet, projectVersionID) + } + totalMinusAuditedDelta += getSpotIssueCount(config, sys, fetchedIssueGroups, projectVersionID, filterSet, reducedFilterSelectorSet, influx, auditStatus) + } + } + return totalMinusAuditedDelta +} + +func getSpotIssueCount(config fortifyExecuteScanOptions, sys fortify.System, spotCheckCategories []*models.ProjectVersionIssueGroup, projectVersionID int64, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) int { + overallDelta := 0 + overallIssues := 0 + overallIssuesAudited := 0 + for _, issueGroup := range spotCheckCategories { + group := "" + total := 0 + audited := 0 + if issueGroup != nil { + group = *issueGroup.ID + total = int(*issueGroup.TotalCount) + audited = int(*issueGroup.AuditedCount) + } + flagOutput := "" + + if ((total <= config.SpotCheckMinimum || config.SpotCheckMinimum < 0) && audited != total) || (total > config.SpotCheckMinimum && audited < config.SpotCheckMinimum) { + currentDelta := config.SpotCheckMinimum - audited + if config.SpotCheckMinimum < 0 || config.SpotCheckMinimum > total { + currentDelta = total - audited + } + if currentDelta > 0 { + filterSelectorFolder := sys.GetFilterSetByDisplayName(issueFilterSelectorSet, "Folder") + filterSelectorAnalysis := sys.GetFilterSetByDisplayName(issueFilterSelectorSet, "Analysis") + overallDelta += currentDelta + log.Entry().Errorf("[projectVersionId %v]: %v unaudited spot check issues detected in group %v", projectVersionID, currentDelta, group) + logIssueURL(config, projectVersionID, filterSelectorFolder, filterSelectorAnalysis) + flagOutput = checkString + } + } + + overallIssues += total + overallIssuesAudited += audited + + auditStatus[group] = fmt.Sprintf("%v total : %v audited %v", total, audited, flagOutput) + } + + influx.fortify_data.fields.spotChecksTotal = fmt.Sprintf("%v", overallIssues) + influx.fortify_data.fields.spotChecksAudited = fmt.Sprintf("%v", overallIssuesAudited) + influx.fortify_data.fields.spotChecksGap = fmt.Sprintf("%v", overallDelta) + + return overallDelta +} + +func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) int { + log.Entry().Info("Analyzing suspicious and exploitable issues") + reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Analysis"}, []string{}) + fetchedGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) + + suspiciousCount := 0 + exploitableCount := 0 + for _, issueGroup := range fetchedGroups { + if *issueGroup.ID == "3" { + suspiciousCount = int(*issueGroup.TotalCount) + } else if *issueGroup.ID == "4" { + exploitableCount = int(*issueGroup.TotalCount) + } + } + + result := 0 + if (suspiciousCount > 0 && config.ConsiderSuspicious) || exploitableCount > 0 { + result = result + suspiciousCount + exploitableCount + log.Entry().Errorf("[projectVersionId %v]: %v suspicious and %v exploitable issues detected", projectVersion.ID, suspiciousCount, exploitableCount) + log.Entry().Errorf("%v/html/ssc/index.jsp#!/version/%v/fix?issueGrouping=%v_%v&issueFilters=%v_%v", config.ServerURL, projectVersion.ID, reducedFilterSelectorSet.GroupBySet[0].EntityType, reducedFilterSelectorSet.GroupBySet[0].Value, reducedFilterSelectorSet.FilterBySet[0].EntityType, reducedFilterSelectorSet.FilterBySet[0].Value) + } + issueStatistics, err := sys.GetIssueStatisticsOfProjectVersion(projectVersion.ID) + if err != nil { + log.Entry().WithError(err).Errorf("Failed to fetch project version statistics for project version ID %v", projectVersion.ID) + } + auditStatus["Suspicious"] = fmt.Sprintf("%v", suspiciousCount) + auditStatus["Exploitable"] = fmt.Sprintf("%v", exploitableCount) + suppressedCount := *issueStatistics[0].SuppressedCount + if suppressedCount > 0 { + auditStatus["Suppressed"] = fmt.Sprintf("WARNING: Detected %v suppressed issues which could violate audit compliance!!!", suppressedCount) + } + influx.fortify_data.fields.suspicious = fmt.Sprintf("%v", suspiciousCount) + influx.fortify_data.fields.exploitable = fmt.Sprintf("%v", exploitableCount) + influx.fortify_data.fields.suppressed = fmt.Sprintf("%v", suppressedCount) + + return result +} + +func logIssueURL(config fortifyExecuteScanOptions, projectVersionID int64, folderSelector, analysisSelector *models.IssueFilterSelector) { + url := fmt.Sprintf("%v/html/ssc/index.jsp#!/version/%v/fix", config.ServerURL, projectVersionID) + if len(folderSelector.SelectorOptions) > 0 { + url += fmt.Sprintf("?issueFilters=%v_%v:%v", + folderSelector.EntityType, + folderSelector.Value, + folderSelector.SelectorOptions[0].Value) + } else { + log.Entry().Debugf("no 'filter by set' array entries") + } + if analysisSelector != nil { + url += fmt.Sprintf("&issueFilters=%v_%v:", + analysisSelector.EntityType, + analysisSelector.Value) + } else { + log.Entry().Debugf("no second entry in 'filter by set' array") + } + log.Entry().Error(url) +} + +func generateAndDownloadQGateReport(config fortifyExecuteScanOptions, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion) ([]byte, error) { + log.Entry().Infof("Generating report with template ID %v", config.ReportTemplateID) + report, err := sys.GenerateQGateReport(project.ID, projectVersion.ID, int64(config.ReportTemplateID), *project.Name, *projectVersion.Name, config.ReportType) + if err != nil { + log.Entry().WithError(err).Fatal("Failed to generate Q-Gate report") + } + log.Entry().Debugf("Triggered report generation of report ID %v", report.ID) + status := report.Status + for status != "Complete" && status != "Error Processing" { + time.Sleep(10 * time.Second) + report, err = sys.GetReportDetails(report.ID) + if err != nil { + return []byte{}, fmt.Errorf("Failed to fetch Q-Gate report generation status: %w", err) + } + status = report.Status + } + data, err := sys.DownloadReportFile(config.ReportDownloadEndpoint, projectVersion.ID) + if err != nil { + return []byte{}, fmt.Errorf("Failed to download Q-Gate Report: %w", err) + } + return data, nil +} + +func checkArtifactStatus(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64, buildLabel string, filterSet *models.FilterSet, artifact *models.Artifact, numInvokes int) error { + numInvokes++ + if "PROCESSING" == artifact.Status || "SCHED_PROCESSING" == artifact.Status { + if numInvokes >= (config.PollingMinutes * 6) { + return fmt.Errorf("Terminating after %v minutes since artifact for Project Version %v is still in status %v", config.PollingMinutes, projectVersionID, artifact.Status) + } + log.Entry().Infof("Most recent artifact uploaded on %v of Project Version %v is still in status %v...", artifact.UploadDate, projectVersionID, artifact.Status) + time.Sleep(10 * time.Second) + return verifyScanResultsFinishedUploading(config, sys, projectVersionID, buildLabel, filterSet, numInvokes) + } + if "REQUIRE_AUTH" == artifact.Status { + // verify no manual issue approval needed + return fmt.Errorf("There are artifacts that require manual approval for Project Version %v\n%v/html/ssc/index.jsp#!/version/%v/artifacts?filterSet=%v", projectVersionID, config.ServerURL, projectVersionID, filterSet.GUID) + } + if "ERROR_PROCESSING" == artifact.Status { + return fmt.Errorf("There are artifacts that failed processing for Project Version %v\n%v/html/ssc/index.jsp#!/version/%v/artifacts?filterSet=%v", projectVersionID, config.ServerURL, projectVersionID, filterSet.GUID) + } + return nil +} + +func verifyScanResultsFinishedUploading(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64, buildLabel string, filterSet *models.FilterSet, numInvokes int) error { + log.Entry().Debug("Verifying scan results have finished uploading and processing") + var artifacts []*models.Artifact + var relatedUpload *models.Artifact + for relatedUpload == nil { + artifacts, err := sys.GetArtifactsOfProjectVersion(projectVersionID) + log.Entry().Debugf("Received %v artifacts for project version ID %v", len(artifacts), projectVersionID) + if err != nil { + log.Entry().WithError(err).Fatalf("Failed to fetch artifacts of project version ID %v", projectVersionID) + } + if len(artifacts) > 0 { + latest := artifacts[0] + if err := checkArtifactStatus(config, sys, projectVersionID, buildLabel, filterSet, latest, numInvokes); err != nil { + return err + } + notFound := true + for _, artifact := range artifacts { + if len(buildLabel) > 0 && artifact.Embed != nil && artifact.Embed.Scans != nil && len(artifact.Embed.Scans) > 0 { + scan := artifact.Embed.Scans[0] + if notFound && scan != nil && strings.HasSuffix(scan.BuildLabel, buildLabel) { + relatedUpload = artifact + notFound = false + } + } + } + } else { + return fmt.Errorf("No uploaded artifacts for assessment detected for project version with ID %v", projectVersionID) + } + if relatedUpload == nil { + log.Entry().Warn("Unable to identify artifact based on the build label, will consider most recent artifact as related to the scan") + relatedUpload = artifacts[0] + } + } + + differenceInSeconds := calculateTimeDifferenceToLastUpload(relatedUpload.UploadDate, projectVersionID) + // Use the absolute value for checking the time difference + if differenceInSeconds > float64(60*config.DeltaMinutes) { + return errors.New("No recent upload detected on Project Version") + } + warn := false + for _, upload := range artifacts { + if upload.Status == "ERROR_PROCESSING" { + warn = true + } + } + if warn { + log.Entry().Warn("Previous uploads detected that failed processing, please ensure that your scans are properly configured") + } + return nil +} + +func calculateTimeDifferenceToLastUpload(uploadDate models.Iso8601MilliDateTime, projectVersionID int64) float64 { + log.Entry().Infof("Last upload on project version %v happened on %v", projectVersionID, uploadDate) + uploadDateAsTime := time.Time(uploadDate) + duration := time.Since(uploadDateAsTime) + log.Entry().Debugf("Difference duration is %v", duration) + absoluteSeconds := math.Abs(duration.Seconds()) + log.Entry().Infof("Difference since %v in seconds is %v", uploadDateAsTime, absoluteSeconds) + return absoluteSeconds +} + +func executeTemplatedCommand(command execRunner, cmdTemplate []string, context map[string]string) { + for index, cmdTemplatePart := range cmdTemplate { + result, err := piperutils.ExecuteTemplate(cmdTemplatePart, context) + if err != nil { + log.Entry().WithError(err).Fatalf("Failed to transform template for command fragment: %v", cmdTemplatePart) + } + cmdTemplate[index] = result + } + err := command.RunExecutable(cmdTemplate[0], cmdTemplate[1:]...) + if err != nil { + log.Entry().WithError(err).WithField("command", cmdTemplate).Fatal("Failed to execute command") + } +} + +func autoresolvePipClasspath(executable string, parameters []string, file string, command execRunner) string { + // redirect stdout and create cp file from command output + outfile, err := os.Create(file) + if err != nil { + log.Entry().WithError(err).Fatal("Failed to create classpath file") + } + defer outfile.Close() + command.Stdout(outfile) + err = command.RunExecutable(executable, parameters...) + if err != nil { + log.Entry().WithError(err).WithField("command", fmt.Sprintf("%v with parameters %v", executable, parameters)).Fatal("Failed to run classpath autodetection command") + } + command.Stdout(log.Entry().Writer()) + return readClasspathFile(file) +} + +func autoresolveMavenClasspath(pomFilePath, file string, command execRunner) string { + executeOptions := maven.ExecuteOptions{ + PomPath: pomFilePath, + Goals: []string{"dependency:build-classpath"}, + Defines: []string{fmt.Sprintf("-Dmdep.outputFile=%v", file), "-DincludeScope=compile"}, + ReturnStdout: false, + } + _, err := maven.Execute(&executeOptions, command) + if err != nil { + log.Entry().WithError(err).Warn("failed to determine classpath using Maven") + } + return readClasspathFile(file) +} + +func readClasspathFile(file string) string { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Entry().WithError(err).Warnf("failed to read classpath from file '%v'", file) + } + return strings.TrimSpace(string(data)) +} + +func triggerFortifyScan(config fortifyExecuteScanOptions, command execRunner, buildID, buildLabel string) { + // Do special Python related prep + pipVersion := "pip3" + if config.PythonVersion != "python3" { + pipVersion = "pip2" + } + + classpath := "" + if config.BuildTool == "maven" { + if config.AutodetectClasspath { + classpath = autoresolveMavenClasspath(config.BuildDescriptorFile, classpathFileName, command) + } + if len(config.Translate) == 0 { + translate := `[{"classpath":"` + translate += classpath + translate += `","src":"**/*.xml **/*.html **/*.jsp **/*.js src/main/resources/**/* src/main/java/**/*"}]` + config.Translate = translate + } + } + if config.BuildTool == "pip" { + if config.AutodetectClasspath { + classpath = autoresolvePipClasspath(config.PythonVersion, []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, classpathFileName, command) + } + // install the dev dependencies + if len(config.PythonRequirementsFile) > 0 { + context := map[string]string{} + cmdTemplate := []string{pipVersion, "install", "--user", "-r", config.PythonRequirementsFile} + cmdTemplate = append(cmdTemplate, tokenize(config.PythonRequirementsInstallSuffix)...) + executeTemplatedCommand(command, cmdTemplate, context) + } + + executeTemplatedCommand(command, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion}) + + if len(config.Translate) == 0 { + translate := `[{"pythonPath":"` + translate += classpath + translate += ";" + translate += config.PythonAdditionalPath + translate += `","pythonIncludes":"` + translate += config.PythonIncludes + translate += `","pythonExcludes":"` + translate += strings.ReplaceAll(config.PythonExcludes, "-exclude ", "") + translate += `"}]` + config.Translate = translate + } + } + + translateProject(&config, command, buildID, classpath) + + scanProject(&config, command, buildID, buildLabel) +} + +func translateProject(config *fortifyExecuteScanOptions, command execRunner, buildID, classpath string) { + var translateList []map[string]string + json.Unmarshal([]byte(config.Translate), &translateList) + log.Entry().Debugf("Translating with options: %v", translateList) + for _, translate := range translateList { + if len(classpath) > 0 { + translate["autoClasspath"] = classpath + } + handleSingleTranslate(config, command, buildID, translate) + } +} + +func handleSingleTranslate(config *fortifyExecuteScanOptions, command execRunner, buildID string, t map[string]string) { + if t != nil { + log.Entry().Debugf("Handling translate config %v", t) + translateOptions := []string{ + "-verbose", + "-64", + "-b", + buildID, + } + translateOptions = append(translateOptions, tokenize(config.Memory)...) + translateOptions = appendToOptions(config, translateOptions, t) + log.Entry().Debugf("Running sourceanalyzer translate command with options %v", translateOptions) + err := command.RunExecutable("sourceanalyzer", translateOptions...) + if err != nil { + log.Entry().WithError(err).WithField("translateOptions", translateOptions).Fatal("failed to execute sourceanalyzer translate command") + } + } else { + log.Entry().Debug("Skipping translate with nil value") + } +} + +func scanProject(config *fortifyExecuteScanOptions, command execRunner, buildID, buildLabel string) { + var scanOptions = []string{ + "-verbose", + "-64", + "-b", + buildID, + "-scan", + } + scanOptions = append(scanOptions, tokenize(config.Memory)...) + if config.QuickScan { + scanOptions = append(scanOptions, "-quick") + } + if len(buildLabel) > 0 { + scanOptions = append(scanOptions, "-build-label", buildLabel) + } + scanOptions = append(scanOptions, "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr") + + err := command.RunExecutable("sourceanalyzer", scanOptions...) + if err != nil { + log.Entry().WithError(err).WithField("scanOptions", scanOptions).Fatal("failed to execute sourceanalyzer scan command") + } +} + +func determinePullRequestMerge(config fortifyExecuteScanOptions) string { + ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "") + if err == nil { + result, err := determinePullRequestMergeGithub(ctx, config, client.PullRequests) + if err != nil { + log.Entry().WithError(err).Warn("Failed to get PR metadata via GitHub client") + } else { + return result + } + } + + log.Entry().Infof("Trying to determine PR ID in commit message: %v", config.CommitMessage) + r, _ := regexp.Compile(config.PullRequestMessageRegex) + matches := r.FindSubmatch([]byte(config.CommitMessage)) + if matches != nil && len(matches) > 1 { + return string(matches[config.PullRequestMessageRegexGroup]) + } + return "" +} + +func determinePullRequestMergeGithub(ctx context.Context, config fortifyExecuteScanOptions, pullRequestServiceInstance pullRequestService) (string, error) { + options := github.PullRequestListOptions{State: "closed", Sort: "updated", Direction: "desc"} + prList, _, err := pullRequestServiceInstance.ListPullRequestsWithCommit(ctx, config.Owner, config.Repository, config.CommitID, &options) + if err == nil && len(prList) > 0 { + return fmt.Sprintf("%v", prList[0].GetNumber()), nil + } + return "", err +} + +func appendToOptions(config *fortifyExecuteScanOptions, options []string, t map[string]string) []string { + if config.BuildTool == "windows" { + if len(t["aspnetcore"]) > 0 { + options = append(options, "-aspnetcore") + } + if len(t["dotNetCoreVersion"]) > 0 { + options = append(options, "-dotnet-core-version", t["dotNetCoreVersion"]) + } + if len(t["exclude"]) > 0 { + options = append(options, "-exclude", t["exclude"]) + } + if len(t["libDirs"]) > 0 { + options = append(options, "-libdirs", t["libDirs"]) + } + return append(options, tokenize(t["src"])...) + } + if config.BuildTool == "maven" { + if len(t["autoClasspath"]) > 0 { + options = append(options, "-cp", t["autoClasspath"]) + } else if len(t["classpath"]) > 0 { + options = append(options, "-cp", t["classpath"]) + } + if len(t["extdirs"]) > 0 { + options = append(options, "-extdirs", t["extdirs"]) + } + if len(t["javaBuildDir"]) > 0 { + options = append(options, "-java-build-dir", t["javaBuildDir"]) + } + if len(t["source"]) > 0 { + options = append(options, "-source", t["source"]) + } + if len(t["jdk"]) > 0 { + options = append(options, "-jdk", t["jdk"]) + } + if len(t["sourcepath"]) > 0 { + options = append(options, "-sourcepath", t["sourcepath"]) + } + return append(options, tokenize(t["src"])...) + } + if config.BuildTool == "pip" { + if len(t["autoClasspath"]) > 0 { + options = append(options, "-python-path", t["autoClasspath"]) + } else if len(t["pythonPath"]) > 0 { + options = append(options, "-python-path", t["pythonPath"]) + } + if len(t["djangoTemplatDirs"]) > 0 { + options = append(options, "-django-template-dirs", t["djangoTemplatDirs"]) + } + if len(t["pythonExcludes"]) > 0 { + options = append(options, "-exclude", t["pythonExcludes"]) + } + return append(options, t["pythonIncludes"]) + } + return options +} diff --git a/cmd/fortifyExecuteScan_generated.go b/cmd/fortifyExecuteScan_generated.go new file mode 100644 index 000000000..b735a4bc9 --- /dev/null +++ b/cmd/fortifyExecuteScan_generated.go @@ -0,0 +1,630 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperenv" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/spf13/cobra" +) + +type fortifyExecuteScanOptions struct { + AuthToken string `json:"authToken,omitempty"` + GithubToken string `json:"githubToken,omitempty"` + AutoCreate bool `json:"autoCreate,omitempty"` + MvnCustomArgs string `json:"mvnCustomArgs,omitempty"` + ModulePath string `json:"modulePath,omitempty"` + PythonRequirementsFile string `json:"pythonRequirementsFile,omitempty"` + AutodetectClasspath bool `json:"autodetectClasspath,omitempty"` + MustAuditIssueGroups string `json:"mustAuditIssueGroups,omitempty"` + SpotAuditIssueGroups string `json:"spotAuditIssueGroups,omitempty"` + PythonRequirementsInstallSuffix string `json:"pythonRequirementsInstallSuffix,omitempty"` + PythonVersion string `json:"pythonVersion,omitempty"` + UploadResults bool `json:"uploadResults,omitempty"` + BuildDescriptorFile string `json:"buildDescriptorFile,omitempty"` + CommitID string `json:"commitId,omitempty"` + CommitMessage string `json:"commitMessage,omitempty"` + GithubAPIURL string `json:"githubApiUrl,omitempty"` + Owner string `json:"owner,omitempty"` + Repository string `json:"repository,omitempty"` + Memory string `json:"memory,omitempty"` + UpdateRulePack bool `json:"updateRulePack,omitempty"` + PythonExcludes string `json:"pythonExcludes,omitempty"` + ReportDownloadEndpoint string `json:"reportDownloadEndpoint,omitempty"` + PollingMinutes int `json:"pollingMinutes,omitempty"` + QuickScan bool `json:"quickScan,omitempty"` + Translate string `json:"translate,omitempty"` + APIEndpoint string `json:"apiEndpoint,omitempty"` + ReportType string `json:"reportType,omitempty"` + PythonAdditionalPath string `json:"pythonAdditionalPath,omitempty"` + ArtifactURL string `json:"artifactUrl,omitempty"` + ConsiderSuspicious bool `json:"considerSuspicious,omitempty"` + FprUploadEndpoint string `json:"fprUploadEndpoint,omitempty"` + ProjectName string `json:"projectName,omitempty"` + PythonIncludes string `json:"pythonIncludes,omitempty"` + Reporting bool `json:"reporting,omitempty"` + ServerURL string `json:"serverUrl,omitempty"` + BuildDescriptorExcludeList string `json:"buildDescriptorExcludeList,omitempty"` + PullRequestMessageRegexGroup int `json:"pullRequestMessageRegexGroup,omitempty"` + DeltaMinutes int `json:"deltaMinutes,omitempty"` + SpotCheckMinimum int `json:"spotCheckMinimum,omitempty"` + FprDownloadEndpoint string `json:"fprDownloadEndpoint,omitempty"` + DefaultVersioningModel string `json:"defaultVersioningModel,omitempty"` + PythonInstallCommand string `json:"pythonInstallCommand,omitempty"` + ReportTemplateID int `json:"reportTemplateId,omitempty"` + FilterSetTitle string `json:"filterSetTitle,omitempty"` + PullRequestName string `json:"pullRequestName,omitempty"` + PullRequestMessageRegex string `json:"pullRequestMessageRegex,omitempty"` + BuildTool string `json:"buildTool,omitempty"` +} + +type fortifyExecuteScanInflux struct { + fortify_data struct { + fields struct { + projectName string + projectVersion string + violations string + corporateTotal string + corporateAudited string + auditAllTotal string + auditAllAudited string + spotChecksTotal string + spotChecksAudited string + spotChecksGap string + suspicious string + exploitable string + suppressed string + } + tags struct { + } + } +} + +func (i *fortifyExecuteScanInflux) persist(path, resourceName string) { + measurementContent := []struct { + measurement string + valType string + name string + value string + }{ + {valType: config.InfluxField, measurement: "fortify_data", name: "projectName", value: i.fortify_data.fields.projectName}, + {valType: config.InfluxField, measurement: "fortify_data", name: "projectVersion", value: i.fortify_data.fields.projectVersion}, + {valType: config.InfluxField, measurement: "fortify_data", name: "violations", value: i.fortify_data.fields.violations}, + {valType: config.InfluxField, measurement: "fortify_data", name: "corporateTotal", value: i.fortify_data.fields.corporateTotal}, + {valType: config.InfluxField, measurement: "fortify_data", name: "corporateAudited", value: i.fortify_data.fields.corporateAudited}, + {valType: config.InfluxField, measurement: "fortify_data", name: "auditAllTotal", value: i.fortify_data.fields.auditAllTotal}, + {valType: config.InfluxField, measurement: "fortify_data", name: "auditAllAudited", value: i.fortify_data.fields.auditAllAudited}, + {valType: config.InfluxField, measurement: "fortify_data", name: "spotChecksTotal", value: i.fortify_data.fields.spotChecksTotal}, + {valType: config.InfluxField, measurement: "fortify_data", name: "spotChecksAudited", value: i.fortify_data.fields.spotChecksAudited}, + {valType: config.InfluxField, measurement: "fortify_data", name: "spotChecksGap", value: i.fortify_data.fields.spotChecksGap}, + {valType: config.InfluxField, measurement: "fortify_data", name: "suspicious", value: i.fortify_data.fields.suspicious}, + {valType: config.InfluxField, measurement: "fortify_data", name: "exploitable", value: i.fortify_data.fields.exploitable}, + {valType: config.InfluxField, measurement: "fortify_data", name: "suppressed", value: i.fortify_data.fields.suppressed}, + } + + errCount := 0 + for _, metric := range measurementContent { + err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value) + if err != nil { + log.Entry().WithError(err).Error("Error persisting influx environment.") + errCount++ + } + } + if errCount > 0 { + log.Entry().Fatal("failed to persist Influx environment") + } +} + +// FortifyExecuteScanCommand This BETA step executes a Fortify scan on the specified project to perform static code analysis and check the source code for security flaws. +func FortifyExecuteScanCommand() *cobra.Command { + const STEP_NAME = "fortifyExecuteScan" + + metadata := fortifyExecuteScanMetadata() + var stepConfig fortifyExecuteScanOptions + var startTime time.Time + var influx fortifyExecuteScanInflux + + var createFortifyExecuteScanCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "This BETA step executes a Fortify scan on the specified project to perform static code analysis and check the source code for security flaws.", + Long: `This step executes a Fortify scan on the specified project to perform static code analysis and check the source code for security flaws. + +The Fortify step triggers a scan locally on your Jenkins within a docker container so finally you have to supply a docker image with a Fortify SCA +and Java plus Maven or alternatively Python installed into it for being able to perform any scans. + +DISCLAIMER: The step has not yet been tested on a wide variaty of projects, and is therefore considered of BETA quality.`, + PreRunE: func(cmd *cobra.Command, args []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + return err + } + log.RegisterSecret(stepConfig.AuthToken) + log.RegisterSecret(stepConfig.GithubToken) + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + influx.persist(GeneralConfig.EnvRootPath, "influx") + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetry.Send(&telemetryData) + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + fortifyExecuteScan(stepConfig, &telemetryData, &influx) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addFortifyExecuteScanFlags(createFortifyExecuteScanCmd, &stepConfig) + return createFortifyExecuteScanCmd +} + +func addFortifyExecuteScanFlags(cmd *cobra.Command, stepConfig *fortifyExecuteScanOptions) { + cmd.Flags().StringVar(&stepConfig.AuthToken, "authToken", os.Getenv("PIPER_authToken"), "The FortifyToken to use for authentication") + cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line") + cmd.Flags().BoolVar(&stepConfig.AutoCreate, "autoCreate", false, "Whether Fortify project and project version shall be implicitly auto created in case they cannot be found in the backend") + cmd.Flags().StringVar(&stepConfig.MvnCustomArgs, "mvnCustomArgs", ``, "Allows providing additional Maven command line parameters") + cmd.Flags().StringVar(&stepConfig.ModulePath, "modulePath", `./`, "Allows providing the path for the module to scan") + cmd.Flags().StringVar(&stepConfig.PythonRequirementsFile, "pythonRequirementsFile", os.Getenv("PIPER_pythonRequirementsFile"), "The requirements file used in `buildTool: 'pip'` to populate the build environment with the necessary dependencies") + cmd.Flags().BoolVar(&stepConfig.AutodetectClasspath, "autodetectClasspath", true, "Whether the classpath is automatically determined via build tool i.e. maven or pip or not at all") + cmd.Flags().StringVar(&stepConfig.MustAuditIssueGroups, "mustAuditIssueGroups", `Corporate Security Requirements, Audit All`, "Comma separated list of issue groups that must be audited completely") + cmd.Flags().StringVar(&stepConfig.SpotAuditIssueGroups, "spotAuditIssueGroups", `Spot Checks of Each Category`, "Comma separated list of issue groups that are spot checked and for which `spotCheckMinimum` audited issues are enforced") + cmd.Flags().StringVar(&stepConfig.PythonRequirementsInstallSuffix, "pythonRequirementsInstallSuffix", os.Getenv("PIPER_pythonRequirementsInstallSuffix"), "The suffix for the command used to install the requirements file in `buildTool: 'pip'` to populate the build environment with the necessary dependencies") + cmd.Flags().StringVar(&stepConfig.PythonVersion, "pythonVersion", `python3`, "Python version to be used in `buildTool: 'pip'`") + cmd.Flags().BoolVar(&stepConfig.UploadResults, "uploadResults", true, "Whether results shall be uploaded or not") + cmd.Flags().StringVar(&stepConfig.BuildDescriptorFile, "buildDescriptorFile", os.Getenv("PIPER_buildDescriptorFile"), "Path to the build descriptor file addressing the module/folder to be scanned. Defaults are for buildTool=`maven`: `./pom.xml`, buildTool=`pip`: `./setup.py`.") + cmd.Flags().StringVar(&stepConfig.CommitID, "commitId", os.Getenv("PIPER_commitId"), "Set the Git commit ID for identifying artifacts throughout the scan.") + cmd.Flags().StringVar(&stepConfig.CommitMessage, "commitMessage", os.Getenv("PIPER_commitMessage"), "Set the Git commit message for identifying pull request merges throughout the scan.") + cmd.Flags().StringVar(&stepConfig.GithubAPIURL, "githubApiUrl", `https://api.github.com`, "Set the GitHub API url.") + cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.") + cmd.Flags().StringVar(&stepConfig.Memory, "memory", `-Xmx4G -Xms512M`, "The amount of memory granted to the translate/scan executions") + cmd.Flags().BoolVar(&stepConfig.UpdateRulePack, "updateRulePack", true, "Whether the rule pack shall be updated and pulled from Fortify SSC before scanning or not") + cmd.Flags().StringVar(&stepConfig.PythonExcludes, "pythonExcludes", `-exclude ./**/tests/**/*;./**/setup.py`, "The excludes pattern used in `buildTool: 'pip'` for excluding specific .py files i.e. tests") + cmd.Flags().StringVar(&stepConfig.ReportDownloadEndpoint, "reportDownloadEndpoint", `/transfer/reportDownload.html`, "Fortify SSC endpoint for Report downloads") + cmd.Flags().IntVar(&stepConfig.PollingMinutes, "pollingMinutes", 30, "The number of minutes for which an uploaded FPR artifact's status is being polled to finish queuing/processing, if exceeded polling will be stopped and an error will be thrown") + cmd.Flags().BoolVar(&stepConfig.QuickScan, "quickScan", false, "Whether a quick scan should be performed, please consult the related Fortify documentation on JAM on the impact of this setting") + cmd.Flags().StringVar(&stepConfig.Translate, "translate", os.Getenv("PIPER_translate"), "JSON string of list of maps with required key `'src'`, and optional keys `'exclude'`, `'libDirs'`, `'aspnetcore'`, and `'dotNetCoreVersion'`") + cmd.Flags().StringVar(&stepConfig.APIEndpoint, "apiEndpoint", `/api/v1`, "Fortify SSC endpoint used for uploading the scan results and checking the audit state") + cmd.Flags().StringVar(&stepConfig.ReportType, "reportType", `PDF`, "The type of report to be generated") + cmd.Flags().StringVar(&stepConfig.PythonAdditionalPath, "pythonAdditionalPath", `./lib;.`, "The addional path which can be used in `buildTool: 'pip'` for customization purposes") + cmd.Flags().StringVar(&stepConfig.ArtifactURL, "artifactUrl", os.Getenv("PIPER_artifactUrl"), "Path/Url pointing to an additional artifact repository for resolution of additional artifacts during the build") + cmd.Flags().BoolVar(&stepConfig.ConsiderSuspicious, "considerSuspicious", true, "Whether suspicious issues should trigger the check to fail or not") + cmd.Flags().StringVar(&stepConfig.FprUploadEndpoint, "fprUploadEndpoint", `/upload/resultFileUpload.html`, "Fortify SSC endpoint for FPR uploads") + cmd.Flags().StringVar(&stepConfig.ProjectName, "projectName", `{{list .GroupID .ArtifactID | join "-" | trimAll "-"}}`, "The project used for reporting results in SSC") + cmd.Flags().StringVar(&stepConfig.PythonIncludes, "pythonIncludes", `./**/*`, "The includes pattern used in `buildTool: 'pip'` for including .py files") + cmd.Flags().BoolVar(&stepConfig.Reporting, "reporting", false, "Influences whether a report is generated or not") + cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", os.Getenv("PIPER_serverUrl"), "Fortify SSC Url to be used for accessing the APIs") + cmd.Flags().StringVar(&stepConfig.BuildDescriptorExcludeList, "buildDescriptorExcludeList", `[]`, "Build descriptor files to exclude modules from being scanned") + cmd.Flags().IntVar(&stepConfig.PullRequestMessageRegexGroup, "pullRequestMessageRegexGroup", 1, "The group number for extracting the pull request id in `pullRequestMessageRegex`") + cmd.Flags().IntVar(&stepConfig.DeltaMinutes, "deltaMinutes", 5, "The number of minutes for which an uploaded FPR artifact is considered to be recent and healthy, if exceeded an error will be thrown") + cmd.Flags().IntVar(&stepConfig.SpotCheckMinimum, "spotCheckMinimum", 1, "The minimum number of issues that must be audited per category in the `Spot Checks of each Category` folder to avoid an error being thrown") + cmd.Flags().StringVar(&stepConfig.FprDownloadEndpoint, "fprDownloadEndpoint", `/download/currentStateFprDownload.html`, "Fortify SSC endpoint for FPR downloads") + cmd.Flags().StringVar(&stepConfig.DefaultVersioningModel, "defaultVersioningModel", `major`, "The default project versioning model used in case `projectVersion` parameter is empty for creating the version based on the build descriptor version to report results in SSC, can be one of `'major'`, `'major-minor'`, `'semantic'`, `'full'`") + cmd.Flags().StringVar(&stepConfig.PythonInstallCommand, "pythonInstallCommand", `{{.Pip}} install --user .`, "Additional install command that can be run when `buildTool: 'pip'` is used which allows further customizing the execution environment of the scan") + cmd.Flags().IntVar(&stepConfig.ReportTemplateID, "reportTemplateId", 18, "Report template ID to be used for generating the Fortify report") + cmd.Flags().StringVar(&stepConfig.FilterSetTitle, "filterSetTitle", `SAP`, "Title of the filter set to use for analysing the results") + cmd.Flags().StringVar(&stepConfig.PullRequestName, "pullRequestName", os.Getenv("PIPER_pullRequestName"), "The name of the pull request branch which will trigger creation of a new version in Fortify SSC based on the master branch version") + cmd.Flags().StringVar(&stepConfig.PullRequestMessageRegex, "pullRequestMessageRegex", `.*Merge pull request #(\\d+) from.*`, "Regex used to identify the PR-XXX reference within the merge commit message") + cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", `maven`, "Scan type used for the step which can be `'maven'`, `'pip'`") + + cmd.MarkFlagRequired("authToken") +} + +// retrieve step metadata +func fortifyExecuteScanMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "fortifyExecuteScan", + Aliases: []config.Alias{}, + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Parameters: []config.StepParameters{ + { + Name: "authToken", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "githubToken", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "autoCreate", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "mvnCustomArgs", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "modulePath", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonRequirementsFile", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "autodetectClasspath", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "mustAuditIssueGroups", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "spotAuditIssueGroups", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonRequirementsInstallSuffix", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonVersion", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "uploadResults", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "buildDescriptorFile", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "commitId", + ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "git/commitId"}}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "commitMessage", + ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "git/commitMessage"}}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "githubApiUrl", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "owner", + ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "github/owner"}}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "githubOrg"}}, + }, + { + Name: "repository", + ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "github/repository"}}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "githubRepo"}}, + }, + { + Name: "memory", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "updateRulePack", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonExcludes", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "reportDownloadEndpoint", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyReportDownloadEndpoint"}}, + }, + { + Name: "pollingMinutes", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "quickScan", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "translate", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "apiEndpoint", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyApiEndpoint"}}, + }, + { + Name: "reportType", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonAdditionalPath", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "artifactUrl", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "considerSuspicious", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "fprUploadEndpoint", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyFprUploadEndpoint"}}, + }, + { + Name: "projectName", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyProjectName"}}, + }, + { + Name: "pythonIncludes", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "reporting", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "serverUrl", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyServerUrl"}, {Name: "sscUrl"}}, + }, + { + Name: "buildDescriptorExcludeList", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pullRequestMessageRegexGroup", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "deltaMinutes", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "spotCheckMinimum", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "fprDownloadEndpoint", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "fortifyFprDownloadEndpoint"}}, + }, + { + Name: "defaultVersioningModel", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pythonInstallCommand", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "reportTemplateId", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "filterSetTitle", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pullRequestName", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "pullRequestMessageRegex", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "buildTool", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/fortifyExecuteScan_generated_test.go b/cmd/fortifyExecuteScan_generated_test.go new file mode 100644 index 000000000..071c2b593 --- /dev/null +++ b/cmd/fortifyExecuteScan_generated_test.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFortifyExecuteScanCommand(t *testing.T) { + + testCmd := FortifyExecuteScanCommand() + + // only high level testing performed - details are tested in step generation procudure + assert.Equal(t, "fortifyExecuteScan", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/fortifyExecuteScan_test.go b/cmd/fortifyExecuteScan_test.go new file mode 100644 index 000000000..004c2efb4 --- /dev/null +++ b/cmd/fortifyExecuteScan_test.go @@ -0,0 +1,612 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/google/go-github/v28/github" + "github.com/stretchr/testify/assert" + + "github.com/piper-validation/fortify-client-go/models" +) + +type fortifyMock struct { + Successive bool +} + +func (f *fortifyMock) GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) { + return &models.Project{Name: &name}, 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 +} + +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) { + if id == 4711 { + return []*models.Artifact{{Status: "PROCESSED", UploadDate: models.Iso8601MilliDateTime(time.Now().UTC())}}, nil + } + if id == 4712 { + return []*models.Artifact{{Status: "ERROR_PROCESSING", UploadDate: models.Iso8601MilliDateTime(time.Now().UTC())}}, nil + } + if id == 4713 { + return []*models.Artifact{{Status: "REQUIRE_AUTH", UploadDate: models.Iso8601MilliDateTime(time.Now().UTC())}}, nil + } + if id == 4714 { + return []*models.Artifact{{Status: "PROCESSING", UploadDate: models.Iso8601MilliDateTime(time.Now().UTC())}}, nil + } + if id == 4715 { + return []*models.Artifact{{Status: "PROCESSED", Embed: &models.EmbeddedScans{[]*models.Scan{{BuildLabel: "/commit/test"}}}, UploadDate: models.Iso8601MilliDateTime(time.Now().UTC())}}, nil + } + 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 { + if filter.DisplayName == name { + return filter + } + } + } + return nil +} +func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) { + if filter == "ET1:abcd" { + group := "HTTP Verb tampering" + total := int32(4) + audited := int32(3) + group2 := "Password in code" + total2 := int32(4) + audited2 := int32(4) + group3 := "Memory leak" + total3 := int32(5) + audited3 := int32(4) + return []*models.ProjectVersionIssueGroup{ + {ID: &group, TotalCount: &total, AuditedCount: &audited}, + {ID: &group2, TotalCount: &total2, AuditedCount: &audited2}, + {ID: &group3, TotalCount: &total3, AuditedCount: &audited3}, + }, nil + } + if issueFilterSelectorSet != nil && issueFilterSelectorSet.FilterBySet[0].GUID == "3" { + group := "3" + total := int32(4) + audited := int32(0) + group2 := "4" + total2 := int32(5) + audited2 := int32(0) + return []*models.ProjectVersionIssueGroup{ + {ID: &group, TotalCount: &total, AuditedCount: &audited}, + {ID: &group2, TotalCount: &total2, AuditedCount: &audited2}, + }, nil + } + group := "Audit All" + total := int32(15) + audited := int32(12) + group2 := "Corporate Security Requirements" + total2 := int32(20) + audited2 := int32(11) + group3 := "Spot Checks of Each Category" + total3 := int32(5) + audited3 := int32(4) + return []*models.ProjectVersionIssueGroup{ + {ID: &group, TotalCount: &total, AuditedCount: &audited}, + {ID: &group2, TotalCount: &total2, AuditedCount: &audited2}, + {ID: &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 + return &models.SavedReport{Status: "Processing"}, nil + } + f.Successive = false + return &models.SavedReport{Status: "Complete"}, nil +} +func (f *fortifyMock) GetReportDetails(id int64) (*models.SavedReport, error) { + return &models.SavedReport{Status: "Complete"}, nil +} +func (f *fortifyMock) UploadResultFile(endpoint, file string, projectVersionID int64) error { + return nil +} +func (f *fortifyMock) DownloadReportFile(endpoint string, projectVersionID int64) ([]byte, error) { + return []byte("abcd"), nil +} +func (f *fortifyMock) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) { + return []byte("defg"), nil +} + +type pullRequestServiceMock struct{} + +func (prService pullRequestServiceMock) ListPullRequestsWithCommit(ctx context.Context, owner, repo, sha string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) { + if owner == "A" { + result := 17 + return []*github.PullRequest{{Number: &result}}, &github.Response{}, nil + } else if owner == "C" { + return []*github.PullRequest{}, &github.Response{}, errors.New("Test error") + } + return []*github.PullRequest{}, &github.Response{}, nil +} + +type execRunnerMock struct { + numExecutions int + current *execution + executions []*execution +} + +type execution struct { + dirValue string + envValue []string + outWriter io.Writer + errWriter io.Writer + executable string + parameters []string +} + +func (er *execRunnerMock) newExecution() *execution { + newExecution := &execution{} + er.executions = append(er.executions, newExecution) + return newExecution +} + +func (er *execRunnerMock) currentExecution() *execution { + if nil == er.current { + er.numExecutions = 0 + er.current = er.newExecution() + } + return er.current +} + +func (er *execRunnerMock) SetDir(d string) { + er.currentExecution().dirValue = d +} + +func (er *execRunnerMock) SetEnv(e []string) { + er.currentExecution().envValue = e +} + +func (er *execRunnerMock) Stdout(out io.Writer) { + er.currentExecution().outWriter = out +} + +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 + er.currentExecution().parameters = p + classpathPip := "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib" + classpathMaven := "some.jar;someother.jar" + if e == "python2" { + er.currentExecution().outWriter.Write([]byte(classpathPip)) + } else if e == "mvn" { + path := strings.ReplaceAll(p[2], "-Dmdep.outputFile=", "") + err := ioutil.WriteFile(path, []byte(classpathMaven), 0644) + if err != nil { + return err + } + } + er.current = er.newExecution() + return nil +} + +func TestParametersAreValidated(t *testing.T) { + type parameterTestData struct { + nameOfRun string + config fortifyExecuteScanOptions + expectedError string + } + + testData := []parameterTestData{ + { + nameOfRun: "all parameters empty", + config: fortifyExecuteScanOptions{}, + expectedError: "unable to get artifact from descriptor : build tool '' not supported", + }, + } + + for _, data := range testData { + t.Run(data.nameOfRun, func(t *testing.T) { + ff := fortifyMock{} + runner := execRunnerMock{} + influx := fortifyExecuteScanInflux{} + auditStatus := map[string]string{} + err := runFortifyScan(data.config, &ff, &runner, nil, &influx, auditStatus) + assert.EqualError(t, err, data.expectedError) + }) + } +} + +func TestAnalyseSuspiciousExploitable(t *testing.T) { + config := fortifyExecuteScanOptions{SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"} + ff := fortifyMock{} + influx := fortifyExecuteScanInflux{} + name := "test" + selectorGUID := "3" + selectorName := "Analysis" + selectorEntityType := "CUSTOMTAG" + projectVersion := models.ProjectVersion{ID: 4711, Name: &name} + auditStatus := map[string]string{} + selectorSet := models.IssueFilterSelectorSet{ + FilterBySet: []*models.IssueFilterSelector{ + { + GUID: selectorGUID, + DisplayName: selectorName, + EntityType: selectorEntityType, + }, + }, + GroupBySet: []*models.IssueSelector{ + { + GUID: &selectorGUID, + DisplayName: &selectorName, + EntityType: &selectorEntityType, + }, + }, + } + issues := analyseSuspiciousExploitable(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) + assert.Equal(t, 9, issues) + + assert.Equal(t, "4", influx.fortify_data.fields.suspicious) + assert.Equal(t, "5", influx.fortify_data.fields.exploitable) + assert.Equal(t, "6", influx.fortify_data.fields.suppressed) +} + +func TestAnalyseUnauditedIssues(t *testing.T) { + config := fortifyExecuteScanOptions{SpotCheckMinimum: 4, MustAuditIssueGroups: "Audit All, Corporate Security Requirements", SpotAuditIssueGroups: "Spot Checks of Each Category"} + ff := fortifyMock{} + influx := fortifyExecuteScanInflux{} + name := "test" + projectVersion := models.ProjectVersion{ID: 4711, Name: &name} + auditStatus := map[string]string{} + selectorSet := models.IssueFilterSelectorSet{ + FilterBySet: []*models.IssueFilterSelector{ + { + GUID: "1", + DisplayName: "Folder", + EntityType: "ET1", + SelectorOptions: []*models.SelectorOption{ + { + Value: "abcd", + }, + }, + }, + { + GUID: "2", + DisplayName: "Category", + EntityType: "ET2", + SelectorOptions: []*models.SelectorOption{ + { + Value: "abcd", + }, + }, + }, + { + GUID: "3", + DisplayName: "Analysis", + EntityType: "ET3", + SelectorOptions: []*models.SelectorOption{ + { + Value: "abcd", + }, + }, + }, + }, + } + issues := analyseUnauditedIssues(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) + assert.Equal(t, 13, issues) + + assert.Equal(t, "15", influx.fortify_data.fields.auditAllTotal) + assert.Equal(t, "12", influx.fortify_data.fields.auditAllAudited) + assert.Equal(t, "20", influx.fortify_data.fields.corporateTotal) + assert.Equal(t, "11", influx.fortify_data.fields.corporateAudited) + assert.Equal(t, "13", influx.fortify_data.fields.spotChecksTotal) + assert.Equal(t, "11", influx.fortify_data.fields.spotChecksAudited) + assert.Equal(t, "1", influx.fortify_data.fields.spotChecksGap) +} + +func TestTriggerFortifyScan(t *testing.T) { + t.Run("maven", func(t *testing.T) { + dir, err := ioutil.TempDir("", "test trigger fortify scan") + if err != nil { + t.Fatal("Failed to create temporary directory") + } + oldCWD, _ := os.Getwd() + _ = os.Chdir(dir) + // clean up tmp dir + defer func() { + _ = os.Chdir(oldCWD) + _ = os.RemoveAll(dir) + }() + + runner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "maven", AutodetectClasspath: true, BuildDescriptorFile: "./pom.xml", Memory: "-Xmx4G -Xms2G"} + triggerFortifyScan(config, &runner, "test", "testLabel") + + assert.Equal(t, 3, runner.numExecutions) + + assert.Equal(t, "mvn", runner.executions[0].executable) + assert.Equal(t, []string{"--file", "./pom.xml", "-Dmdep.outputFile=cp.txt", "-DincludeScope=compile", "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn", "--batch-mode", "dependency:build-classpath"}, runner.executions[0].parameters) + + assert.Equal(t, "sourceanalyzer", runner.executions[1].executable) + assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-Xmx4G", "-Xms2G", "-cp", "some.jar;someother.jar", "**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}, runner.executions[1].parameters) + + assert.Equal(t, "sourceanalyzer", runner.executions[2].executable) + assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-scan", "-Xmx4G", "-Xms2G", "-build-label", "testLabel", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, runner.executions[2].parameters) + }) + + t.Run("pip", func(t *testing.T) { + dir, err := ioutil.TempDir("", "test trigger fortify scan") + if err != nil { + t.Fatal("Failed to create temporary directory") + } + oldCWD, _ := os.Getwd() + _ = os.Chdir(dir) + // clean up tmp dir + defer func() { + _ = os.Chdir(oldCWD) + _ = os.RemoveAll(dir) + }() + + runner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "pip", PythonVersion: "python2", AutodetectClasspath: true, BuildDescriptorFile: "./setup.py", PythonRequirementsFile: "./requirements.txt", PythonInstallCommand: "pip2 install --user", Memory: "-Xmx4G -Xms2G"} + triggerFortifyScan(config, &runner, "test", "testLabel") + + assert.Equal(t, 5, runner.numExecutions) + + assert.Equal(t, "python2", runner.executions[0].executable) + assert.Equal(t, []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, runner.executions[0].parameters) + + assert.Equal(t, "pip2", runner.executions[1].executable) + assert.Equal(t, []string{"install", "--user", "-r", "./requirements.txt", ""}, runner.executions[1].parameters) + + assert.Equal(t, "pip2", runner.executions[2].executable) + assert.Equal(t, []string{"install", "--user"}, runner.executions[2].parameters) + + assert.Equal(t, "sourceanalyzer", runner.executions[3].executable) + assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-Xmx4G", "-Xms2G", "-python-path", "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib", ""}, runner.executions[3].parameters) + + assert.Equal(t, "sourceanalyzer", runner.executions[4].executable) + assert.Equal(t, []string{"-verbose", "-64", "-b", "test", "-scan", "-Xmx4G", "-Xms2G", "-build-label", "testLabel", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, runner.executions[4].parameters) + }) +} + +func TestGenerateAndDownloadQGateReport(t *testing.T) { + ffMock := fortifyMock{Successive: false} + config := fortifyExecuteScanOptions{ReportTemplateID: 18, ReportType: "PDF"} + name := "test" + projectVersion := models.ProjectVersion{ID: 4711, Name: &name} + project := models.Project{ID: 815, Name: &name} + projectVersion.Project = &project + + t.Run("success", func(t *testing.T) { + data, err := generateAndDownloadQGateReport(config, &ffMock, &project, &projectVersion) + assert.NoError(t, err) + assert.Equal(t, []byte("abcd"), data) + }) +} + +func TestVerifyScanResultsFinishedUploading(t *testing.T) { + ffMock := fortifyMock{Successive: false} + + t.Run("error no recent upload detected", func(t *testing.T) { + config := fortifyExecuteScanOptions{DeltaMinutes: -1} + err := verifyScanResultsFinishedUploading(config, &ffMock, 4711, "", &models.FilterSet{}, 0) + assert.EqualError(t, err, "No recent upload detected on Project Version") + }) + + config := fortifyExecuteScanOptions{DeltaMinutes: 20} + t.Run("success", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4711, "", &models.FilterSet{}, 0) + assert.NoError(t, err) + }) + + t.Run("error processing", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4712, "", &models.FilterSet{}, 0) + assert.EqualError(t, err, "There are artifacts that failed processing for Project Version 4712\n/html/ssc/index.jsp#!/version/4712/artifacts?filterSet=") + }) + + t.Run("error required auth", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4713, "", &models.FilterSet{}, 0) + assert.EqualError(t, err, "There are artifacts that require manual approval for Project Version 4713\n/html/ssc/index.jsp#!/version/4713/artifacts?filterSet=") + }) + + t.Run("error polling timeout", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4714, "", &models.FilterSet{}, 1) + assert.EqualError(t, err, "Terminating after 0 minutes since artifact for Project Version 4714 is still in status PROCESSING") + }) + + t.Run("success build label", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4715, "/commit/test", &models.FilterSet{}, 0) + assert.NoError(t, err) + }) + + t.Run("error no artifacts", func(t *testing.T) { + err := verifyScanResultsFinishedUploading(config, &ffMock, 4716, "", &models.FilterSet{}, 0) + assert.EqualError(t, err, "No uploaded artifacts for assessment detected for project version with ID 4716") + }) +} + +func TestCalculateTimeDifferenceToLastUpload(t *testing.T) { + diffSeconds := calculateTimeDifferenceToLastUpload(models.Iso8601MilliDateTime(time.Now().UTC()), 1234) + + assert.Equal(t, true, diffSeconds < 1) +} + +func TestExecuteTemplatedCommand(t *testing.T) { + runner := execRunnerMock{} + template := []string{"{{.Executable}}", "-c", "{{.Param}}"} + context := map[string]string{"Executable": "test.cmd", "Param": "abcd"} + executeTemplatedCommand(&runner, template, context) + + assert.Equal(t, "test.cmd", runner.executions[0].executable) + assert.Equal(t, []string{"-c", "abcd"}, runner.executions[0].parameters) +} + +func TestDeterminePullRequestMerge(t *testing.T) { + config := fortifyExecuteScanOptions{CommitMessage: "Merge pull request #2462 from branch f-test", PullRequestMessageRegex: `(?m).*Merge pull request #(\d+) from.*`, PullRequestMessageRegexGroup: 1} + + t.Run("success", func(t *testing.T) { + match := determinePullRequestMerge(config) + assert.Equal(t, "2462", match, "Expected different result") + }) + + t.Run("no match", func(t *testing.T) { + config.CommitMessage = "Some test commit" + match := determinePullRequestMerge(config) + assert.Equal(t, "", match, "Expected different result") + }) +} + +func TestDeterminePullRequestMergeGithub(t *testing.T) { + prServiceMock := pullRequestServiceMock{} + + t.Run("success", func(t *testing.T) { + match, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "A"}, prServiceMock) + assert.NoError(t, err) + assert.Equal(t, "17", match, "Expected different result") + }) + + t.Run("no match", func(t *testing.T) { + match, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "B"}, prServiceMock) + assert.NoError(t, err) + assert.Equal(t, "", match, "Expected different result") + }) + + t.Run("error", func(t *testing.T) { + match, err := determinePullRequestMergeGithub(nil, fortifyExecuteScanOptions{Owner: "C"}, prServiceMock) + assert.EqualError(t, err, "Test error") + assert.Equal(t, "", match, "Expected different result") + }) +} + +func TestTranslateProject(t *testing.T) { + t.Run("python", func(t *testing.T) { + execRunner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "pip", Memory: "-Xmx4G", Translate: `[{"pythonPath":"./some/path","pythonIncludes":"./**/*","pythonExcludes":"./tests/**/*"}]`} + translateProject(&config, &execRunner, "/commit/7267658798797", "") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx4G", "-python-path", "./some/path", "-exclude", "./tests/**/*", "./**/*"}, execRunner.executions[0].parameters, "Expected different parameters") + }) + + t.Run("asp", func(t *testing.T) { + execRunner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "windows", Memory: "-Xmx6G", Translate: `[{"aspnetcore":"true","dotNetCoreVersion":"3.5","exclude":"./tests/**/*","libDirs":"tmp/","src":"./**/*"}]`} + translateProject(&config, &execRunner, "/commit/7267658798797", "") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx6G", "-aspnetcore", "-dotnet-core-version", "3.5", "-exclude", "./tests/**/*", "-libdirs", "tmp/", "./**/*"}, execRunner.executions[0].parameters, "Expected different parameters") + }) + + t.Run("java", func(t *testing.T) { + execRunner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "maven", Memory: "-Xmx2G", Translate: `[{"classpath":"./classes/*.jar","extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}]`} + translateProject(&config, &execRunner, "/commit/7267658798797", "") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx2G", "-cp", "./classes/*.jar", "-extdirs", "tmp/", "-source", "1.8", "-jdk", "1.8.0-21", "-sourcepath", "src/ext/", "./**/*"}, execRunner.executions[0].parameters, "Expected different parameters") + }) + + t.Run("auto classpath", func(t *testing.T) { + execRunner := execRunnerMock{} + config := fortifyExecuteScanOptions{BuildTool: "maven", Memory: "-Xmx2G", Translate: `[{"classpath":"./classes/*.jar", "extdirs":"tmp/","jdk":"1.8.0-21","source":"1.8","sourcepath":"src/ext/","src":"./**/*"}]`} + translateProject(&config, &execRunner, "/commit/7267658798797", "./WEB-INF/lib/*.jar") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-Xmx2G", "-cp", "./WEB-INF/lib/*.jar", "-extdirs", "tmp/", "-source", "1.8", "-jdk", "1.8.0-21", "-sourcepath", "src/ext/", "./**/*"}, execRunner.executions[0].parameters, "Expected different parameters") + }) +} + +func TestScanProject(t *testing.T) { + config := fortifyExecuteScanOptions{Memory: "-Xmx4G"} + + t.Run("normal", func(t *testing.T) { + execRunner := execRunnerMock{} + scanProject(&config, &execRunner, "/commit/7267658798797", "label") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-scan", "-Xmx4G", "-build-label", "label", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, execRunner.executions[0].parameters, "Expected different parameters") + }) + + t.Run("quick", func(t *testing.T) { + execRunner := execRunnerMock{} + config.QuickScan = true + scanProject(&config, &execRunner, "/commit/7267658798797", "") + assert.Equal(t, "sourceanalyzer", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-verbose", "-64", "-b", "/commit/7267658798797", "-scan", "-Xmx4G", "-quick", "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr"}, execRunner.executions[0].parameters, "Expected different parameters") + }) +} + +func TestAutoresolveClasspath(t *testing.T) { + t.Run("success pip", func(t *testing.T) { + execRunner := execRunnerMock{} + dir, err := ioutil.TempDir("", "classpath") + assert.NoError(t, err, "Unexpected error detected") + defer os.RemoveAll(dir) + file := filepath.Join(dir, "cp.txt") + + result := autoresolvePipClasspath("python2", []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, file, &execRunner) + assert.Equal(t, "python2", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"-c", "import sys;p=sys.path;p.remove('');print(';'.join(p))"}, execRunner.executions[0].parameters, "Expected different parameters") + assert.Equal(t, "/usr/lib/python35.zip;/usr/lib/python3.5;/usr/lib/python3.5/plat-x86_64-linux-gnu;/usr/lib/python3.5/lib-dynload;/home/piper/.local/lib/python3.5/site-packages;/usr/local/lib/python3.5/dist-packages;/usr/lib/python3/dist-packages;./lib", result, "Expected different result") + }) + + t.Run("success maven", func(t *testing.T) { + execRunner := execRunnerMock{} + dir, err := ioutil.TempDir("", "classpath") + assert.NoError(t, err, "Unexpected error detected") + defer os.RemoveAll(dir) + file := filepath.Join(dir, "cp.txt") + + result := autoresolveMavenClasspath("pom.xml", file, &execRunner) + assert.Equal(t, "mvn", execRunner.executions[0].executable, "Expected different executable") + assert.Equal(t, []string{"--file", "pom.xml", fmt.Sprintf("-Dmdep.outputFile=%v", file), "-DincludeScope=compile", "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn", "--batch-mode", "dependency:build-classpath"}, execRunner.executions[0].parameters, "Expected different parameters") + assert.Equal(t, "some.jar;someother.jar", result, "Expected different result") + }) +} diff --git a/cmd/gctsCreateRepository_generated.go b/cmd/gctsCreateRepository_generated.go index abf6f4739..6e44e8efd 100644 --- a/cmd/gctsCreateRepository_generated.go +++ b/cmd/gctsCreateRepository_generated.go @@ -89,7 +89,7 @@ func addGctsCreateRepositoryFlags(cmd *cobra.Command, stepConfig *gctsCreateRepo cmd.Flags().StringVar(&stepConfig.RemoteRepositoryURL, "remoteRepositoryURL", os.Getenv("PIPER_remoteRepositoryURL"), "URL of the corresponding remote repository") cmd.Flags().StringVar(&stepConfig.Role, "role", os.Getenv("PIPER_role"), "Role of the local repository. Choose between 'TARGET' and 'SOURCE'. Local repositories with a TARGET role will NOT be able to be the source of code changes") cmd.Flags().StringVar(&stepConfig.VSID, "vSID", os.Getenv("PIPER_vSID"), "Virtual SID of the local repository. The vSID corresponds to the transport route that delivers content to the remote Git repository") - cmd.Flags().StringVar(&stepConfig.Type, "type", "GIT", "Type of the used source code management tool") + cmd.Flags().StringVar(&stepConfig.Type, "type", `GIT`, "Type of the used source code management tool") cmd.MarkFlagRequired("username") cmd.MarkFlagRequired("password") diff --git a/cmd/githubCreatePullRequest_generated.go b/cmd/githubCreatePullRequest_generated.go index ab6ac8598..1c5da8731 100644 --- a/cmd/githubCreatePullRequest_generated.go +++ b/cmd/githubCreatePullRequest_generated.go @@ -87,11 +87,11 @@ func addGithubCreatePullRequestFlags(cmd *cobra.Command, stepConfig *githubCreat cmd.Flags().StringSliceVar(&stepConfig.Assignees, "assignees", []string{}, "Login names of users to which the PR should be assigned to.") cmd.Flags().StringVar(&stepConfig.Base, "base", os.Getenv("PIPER_base"), "The name of the branch you want the changes pulled into.") cmd.Flags().StringVar(&stepConfig.Body, "body", os.Getenv("PIPER_body"), "The description text of the pull request in markdown format.") - cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", "https://api.github.com", "Set the GitHub API url.") + cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API url.") cmd.Flags().StringVar(&stepConfig.Head, "head", os.Getenv("PIPER_head"), "The name of the branch where your changes are implemented.") cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.") cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.") - cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", "https://github.com", "GitHub server url for end-user access.") + cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", `https://github.com`, "GitHub server url for end-user access.") cmd.Flags().StringVar(&stepConfig.Title, "title", os.Getenv("PIPER_title"), "Title of the pull request.") cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line") cmd.Flags().StringSliceVar(&stepConfig.Labels, "labels", []string{}, "Labels to be added to the pull request.") diff --git a/cmd/githubPublishRelease_generated.go b/cmd/githubPublishRelease_generated.go index 11876afa6..074ac4757 100644 --- a/cmd/githubPublishRelease_generated.go +++ b/cmd/githubPublishRelease_generated.go @@ -96,17 +96,17 @@ The result looks like func addGithubPublishReleaseFlags(cmd *cobra.Command, stepConfig *githubPublishReleaseOptions) { cmd.Flags().BoolVar(&stepConfig.AddClosedIssues, "addClosedIssues", false, "If set to `true`, closed issues and merged pull-requests since the last release will added below the `releaseBodyHeader`") cmd.Flags().BoolVar(&stepConfig.AddDeltaToLastRelease, "addDeltaToLastRelease", false, "If set to `true`, a link will be added to the relese information that brings up all commits since the last release.") - cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", "https://api.github.com", "Set the GitHub API url.") + cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API url.") cmd.Flags().StringVar(&stepConfig.AssetPath, "assetPath", os.Getenv("PIPER_assetPath"), "Path to a release asset which should be uploaded to the list of release assets.") - cmd.Flags().StringVar(&stepConfig.Commitish, "commitish", "master", "Target git commitish for the release") + cmd.Flags().StringVar(&stepConfig.Commitish, "commitish", `master`, "Target git commitish for the release") cmd.Flags().StringSliceVar(&stepConfig.ExcludeLabels, "excludeLabels", []string{}, "Allows to exclude issues with dedicated list of labels.") cmd.Flags().StringSliceVar(&stepConfig.Labels, "labels", []string{}, "Labels to include in issue search.") cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.") cmd.Flags().StringVar(&stepConfig.ReleaseBodyHeader, "releaseBodyHeader", os.Getenv("PIPER_releaseBodyHeader"), "Content which will appear for the release.") cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.") - cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", "https://github.com", "GitHub server url for end-user access.") + cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", `https://github.com`, "GitHub server url for end-user access.") cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line") - cmd.Flags().StringVar(&stepConfig.UploadURL, "uploadUrl", "https://uploads.github.com", "Set the GitHub API url.") + cmd.Flags().StringVar(&stepConfig.UploadURL, "uploadUrl", `https://uploads.github.com`, "Set the GitHub API url.") cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Define the version number which will be written as tag as well as release name.") cmd.MarkFlagRequired("apiUrl") diff --git a/cmd/karmaExecuteTests_generated.go b/cmd/karmaExecuteTests_generated.go index 021944dc0..0664baa1f 100644 --- a/cmd/karmaExecuteTests_generated.go +++ b/cmd/karmaExecuteTests_generated.go @@ -83,9 +83,9 @@ In the Docker network, the containers can be referenced by the values provided i } func addKarmaExecuteTestsFlags(cmd *cobra.Command, stepConfig *karmaExecuteTestsOptions) { - cmd.Flags().StringVar(&stepConfig.InstallCommand, "installCommand", "npm install --quiet", "The command that is executed to install the test tool.") - cmd.Flags().StringVar(&stepConfig.ModulePath, "modulePath", ".", "Define the path of the module to execute tests on.") - cmd.Flags().StringVar(&stepConfig.RunCommand, "runCommand", "npm run karma", "The command that is executed to start the tests.") + cmd.Flags().StringVar(&stepConfig.InstallCommand, "installCommand", `npm install --quiet`, "The command that is executed to install the test tool.") + cmd.Flags().StringVar(&stepConfig.ModulePath, "modulePath", `.`, "Define the path of the module to execute tests on.") + cmd.Flags().StringVar(&stepConfig.RunCommand, "runCommand", `npm run karma`, "The command that is executed to start the tests.") cmd.MarkFlagRequired("installCommand") cmd.MarkFlagRequired("modulePath") diff --git a/cmd/kubernetesDeploy_generated.go b/cmd/kubernetesDeploy_generated.go index 8ce55a454..0c11a228b 100644 --- a/cmd/kubernetesDeploy_generated.go +++ b/cmd/kubernetesDeploy_generated.go @@ -117,17 +117,17 @@ func addKubernetesDeployFlags(cmd *cobra.Command, stepConfig *kubernetesDeployOp cmd.Flags().StringVar(&stepConfig.ContainerRegistryPassword, "containerRegistryPassword", os.Getenv("PIPER_containerRegistryPassword"), "Password for container registry access - typically provided by the CI/CD environment.") cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "http(s) url of the Container registry.") cmd.Flags().StringVar(&stepConfig.ContainerRegistryUser, "containerRegistryUser", os.Getenv("PIPER_containerRegistryUser"), "Username for container registry access - typically provided by the CI/CD environment.") - cmd.Flags().StringVar(&stepConfig.ContainerRegistrySecret, "containerRegistrySecret", "regsecret", "Name of the container registry secret used for pulling containers from the registry.") + cmd.Flags().StringVar(&stepConfig.ContainerRegistrySecret, "containerRegistrySecret", `regsecret`, "Name of the container registry secret used for pulling containers from the registry.") cmd.Flags().BoolVar(&stepConfig.CreateDockerRegistrySecret, "createDockerRegistrySecret", false, "Toggle to turn on Regsecret creation with a \"deployTool:kubectl\" deployment.") cmd.Flags().StringVar(&stepConfig.DeploymentName, "deploymentName", os.Getenv("PIPER_deploymentName"), "Defines the name of the deployment.") - cmd.Flags().StringVar(&stepConfig.DeployTool, "deployTool", "kubectl", "Defines the tool which should be used for deployment.") + cmd.Flags().StringVar(&stepConfig.DeployTool, "deployTool", `kubectl`, "Defines the tool which should be used for deployment.") cmd.Flags().IntVar(&stepConfig.HelmDeployWaitSeconds, "helmDeployWaitSeconds", 300, "Number of seconds before helm deploy returns.") cmd.Flags().StringVar(&stepConfig.Image, "image", os.Getenv("PIPER_image"), "Full name of the image to be deployed.") cmd.Flags().StringSliceVar(&stepConfig.IngressHosts, "ingressHosts", []string{}, "List of ingress hosts to be exposed via helm deployment.") cmd.Flags().StringVar(&stepConfig.KubeConfig, "kubeConfig", os.Getenv("PIPER_kubeConfig"), "Defines the path to the \"kubeconfig\" file.") cmd.Flags().StringVar(&stepConfig.KubeContext, "kubeContext", os.Getenv("PIPER_kubeContext"), "Defines the context to use from the \"kubeconfig\" file.") cmd.Flags().StringVar(&stepConfig.KubeToken, "kubeToken", os.Getenv("PIPER_kubeToken"), "Contains the id_token used by kubectl for authentication. Consider using kubeConfig parameter instead.") - cmd.Flags().StringVar(&stepConfig.Namespace, "namespace", "default", "Defines the target Kubernetes namespace for the deployment.") + cmd.Flags().StringVar(&stepConfig.Namespace, "namespace", `default`, "Defines the target Kubernetes namespace for the deployment.") cmd.Flags().StringVar(&stepConfig.TillerNamespace, "tillerNamespace", os.Getenv("PIPER_tillerNamespace"), "Defines optional tiller namespace for deployments using helm.") cmd.MarkFlagRequired("chartPath") diff --git a/cmd/mavenBuild_generated.go b/cmd/mavenBuild_generated.go index 7a2cd38c3..5a16223df 100644 --- a/cmd/mavenBuild_generated.go +++ b/cmd/mavenBuild_generated.go @@ -79,7 +79,7 @@ supports ci friendly versioning by flattening the pom before installing.`, } func addMavenBuildFlags(cmd *cobra.Command, stepConfig *mavenBuildOptions) { - cmd.Flags().StringVar(&stepConfig.PomPath, "pomPath", "pom.xml", "Path to the pom file which should be installed including all children.") + cmd.Flags().StringVar(&stepConfig.PomPath, "pomPath", `pom.xml`, "Path to the pom file which should be installed including all children.") cmd.Flags().BoolVar(&stepConfig.Flatten, "flatten", true, "Defines if the pom files should be flattened to support ci friendly maven versioning.") cmd.Flags().BoolVar(&stepConfig.Verify, "verify", false, "Instead of installing the artifact only the verify lifecycle phase is executed.") cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Path to the mvn settings file that should be used as project settings file.") diff --git a/cmd/mtaBuild_generated.go b/cmd/mtaBuild_generated.go index 6d99c21a2..b291074e3 100644 --- a/cmd/mtaBuild_generated.go +++ b/cmd/mtaBuild_generated.go @@ -111,15 +111,15 @@ func MtaBuildCommand() *cobra.Command { } func addMtaBuildFlags(cmd *cobra.Command, stepConfig *mtaBuildOptions) { - cmd.Flags().StringVar(&stepConfig.BuildTarget, "buildTarget", "NEO", "mtaBuildTool 'classic' only: The target platform to which the mtar can be deployed. Valid values: 'CF', 'NEO', 'XSA'.") - cmd.Flags().StringVar(&stepConfig.MtaBuildTool, "mtaBuildTool", "cloudMbt", "Tool to use when building the MTA. Valid values: 'classic', 'cloudMbt'.") + cmd.Flags().StringVar(&stepConfig.BuildTarget, "buildTarget", `NEO`, "mtaBuildTool 'classic' only: The target platform to which the mtar can be deployed. Valid values: 'CF', 'NEO', 'XSA'.") + cmd.Flags().StringVar(&stepConfig.MtaBuildTool, "mtaBuildTool", `cloudMbt`, "Tool to use when building the MTA. Valid values: 'classic', 'cloudMbt'.") cmd.Flags().StringVar(&stepConfig.MtarName, "mtarName", os.Getenv("PIPER_mtarName"), "The name of the generated mtar file including its extension.") - cmd.Flags().StringVar(&stepConfig.MtaJarLocation, "mtaJarLocation", "/opt/sap/mta/lib/mta.jar", "mtaBuildTool 'classic' only: The location of the SAP Multitarget Application Archive Builder jar file, including file name and extension. If you run on Docker, this must match the location of the jar file in the container as well.") + cmd.Flags().StringVar(&stepConfig.MtaJarLocation, "mtaJarLocation", `/opt/sap/mta/lib/mta.jar`, "mtaBuildTool 'classic' only: The location of the SAP Multitarget Application Archive Builder jar file, including file name and extension. If you run on Docker, this must match the location of the jar file in the container as well.") cmd.Flags().StringVar(&stepConfig.Extensions, "extensions", os.Getenv("PIPER_extensions"), "The path to the extension descriptor file.") - cmd.Flags().StringVar(&stepConfig.Platform, "platform", "CF", "mtaBuildTool 'cloudMbt' only: The target platform to which the mtar can be deployed.") + cmd.Flags().StringVar(&stepConfig.Platform, "platform", `CF`, "mtaBuildTool 'cloudMbt' only: The target platform to which the mtar can be deployed.") cmd.Flags().StringVar(&stepConfig.ApplicationName, "applicationName", os.Getenv("PIPER_applicationName"), "The name of the application which is being built. If the parameter has been provided and no `mta.yaml` exists, the `mta.yaml` will be automatically generated using this parameter and the information (`name` and `version`) from 'package.json` before the actual build starts.") cmd.Flags().StringVar(&stepConfig.DefaultNpmRegistry, "defaultNpmRegistry", os.Getenv("PIPER_defaultNpmRegistry"), "Url to the npm registry that should be used for installing npm dependencies.") - cmd.Flags().StringVar(&stepConfig.SapNpmRegistry, "sapNpmRegistry", "https://npm.sap.com", "Url to the sap npm registry that should be used for installing npm dependencies prefixed with @sap.") + cmd.Flags().StringVar(&stepConfig.SapNpmRegistry, "sapNpmRegistry", `https://npm.sap.com`, "Url to the sap npm registry that should be used for installing npm dependencies prefixed with @sap.") cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Path or url to the mvn settings file that should be used as project settings file.") cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Path or url to the mvn settings file that should be used as global settings file") diff --git a/cmd/nexusUpload_generated.go b/cmd/nexusUpload_generated.go index c1abe0fc1..6b6a059e8 100644 --- a/cmd/nexusUpload_generated.go +++ b/cmd/nexusUpload_generated.go @@ -99,7 +99,7 @@ If an image for mavenExecute is configured, and npm packages are to be published } func addNexusUploadFlags(cmd *cobra.Command, stepConfig *nexusUploadOptions) { - cmd.Flags().StringVar(&stepConfig.Version, "version", "nexus3", "The Nexus Repository Manager version. Currently supported are 'nexus2' and 'nexus3'.") + cmd.Flags().StringVar(&stepConfig.Version, "version", `nexus3`, "The Nexus Repository Manager version. Currently supported are 'nexus2' and 'nexus3'.") cmd.Flags().StringVar(&stepConfig.Url, "url", os.Getenv("PIPER_url"), "URL of the nexus. The scheme part of the URL will not be considered, because only http is supported.") cmd.Flags().StringVar(&stepConfig.MavenRepository, "mavenRepository", os.Getenv("PIPER_mavenRepository"), "Name of the nexus repository for Maven and MTA deployments. If this is not provided, Maven and MTA deployment is implicitly disabled.") cmd.Flags().StringVar(&stepConfig.NpmRepository, "npmRepository", os.Getenv("PIPER_npmRepository"), "Name of the nexus repository for npm deployments. If this is not provided, npm deployment is implicitly disabled.") diff --git a/cmd/npmExecuteScripts_generated.go b/cmd/npmExecuteScripts_generated.go index de1e92e4a..c82820da9 100644 --- a/cmd/npmExecuteScripts_generated.go +++ b/cmd/npmExecuteScripts_generated.go @@ -77,7 +77,7 @@ func addNpmExecuteScriptsFlags(cmd *cobra.Command, stepConfig *npmExecuteScripts cmd.Flags().BoolVar(&stepConfig.Install, "install", false, "Run npm install or similar commands depending on the project structure.") cmd.Flags().StringSliceVar(&stepConfig.RunScripts, "runScripts", []string{}, "List of additional run scripts to execute from package.json.") cmd.Flags().StringVar(&stepConfig.DefaultNpmRegistry, "defaultNpmRegistry", os.Getenv("PIPER_defaultNpmRegistry"), "URL of the npm registry to use. Defaults to https://registry.npmjs.org/") - cmd.Flags().StringVar(&stepConfig.SapNpmRegistry, "sapNpmRegistry", "https://npm.sap.com", "The default npm registry URL to be used as the remote mirror for the SAP npm packages.") + cmd.Flags().StringVar(&stepConfig.SapNpmRegistry, "sapNpmRegistry", `https://npm.sap.com`, "The default npm registry URL to be used as the remote mirror for the SAP npm packages.") } diff --git a/cmd/piper.go b/cmd/piper.go index 47bba1868..53daa92cd 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -74,6 +74,7 @@ func Execute() { rootCmd.AddCommand(CloudFoundryDeleteServiceCommand()) rootCmd.AddCommand(AbapEnvironmentPullGitRepoCommand()) rootCmd.AddCommand(CheckmarxExecuteScanCommand()) + rootCmd.AddCommand(FortifyExecuteScanCommand()) rootCmd.AddCommand(MtaBuildCommand()) rootCmd.AddCommand(ProtecodeExecuteScanCommand()) rootCmd.AddCommand(MavenExecuteCommand()) diff --git a/cmd/protecodeExecuteScan_generated.go b/cmd/protecodeExecuteScan_generated.go index 270922ec8..c1fff6f1d 100644 --- a/cmd/protecodeExecuteScan_generated.go +++ b/cmd/protecodeExecuteScan_generated.go @@ -140,17 +140,17 @@ func ProtecodeExecuteScanCommand() *cobra.Command { } func addProtecodeExecuteScanFlags(cmd *cobra.Command, stepConfig *protecodeExecuteScanOptions) { - cmd.Flags().StringVar(&stepConfig.ExcludeCVEs, "excludeCVEs", "[]", "DEPRECATED: Do use triaging within the Protecode UI instead") + cmd.Flags().StringVar(&stepConfig.ExcludeCVEs, "excludeCVEs", `[]`, "DEPRECATED: Do use triaging within the Protecode UI instead") cmd.Flags().BoolVar(&stepConfig.FailOnSevereVulnerabilities, "failOnSevereVulnerabilities", true, "Whether to fail the job on severe vulnerabilties or not") cmd.Flags().StringVar(&stepConfig.ScanImage, "scanImage", os.Getenv("PIPER_scanImage"), "The reference to the docker image to scan with Protecode") cmd.Flags().StringVar(&stepConfig.DockerRegistryURL, "dockerRegistryUrl", os.Getenv("PIPER_dockerRegistryUrl"), "The reference to the docker registry to scan with Protecode") - cmd.Flags().StringVar(&stepConfig.CleanupMode, "cleanupMode", "binary", "Decides which parts are removed from the Protecode backend after the scan") + cmd.Flags().StringVar(&stepConfig.CleanupMode, "cleanupMode", `binary`, "Decides which parts are removed from the Protecode backend after the scan") cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "The path to the file from local workspace to scan with Protecode") cmd.Flags().BoolVar(&stepConfig.IncludeLayers, "includeLayers", false, "Flag if the docker layers should be included") cmd.Flags().BoolVar(&stepConfig.AddSideBarLink, "addSideBarLink", true, "Whether to create a side bar link pointing to the report produced by Protecode or not") - cmd.Flags().StringVar(&stepConfig.TimeoutMinutes, "timeoutMinutes", "60", "The timeout to wait for the scan to finish") + cmd.Flags().StringVar(&stepConfig.TimeoutMinutes, "timeoutMinutes", `60`, "The timeout to wait for the scan to finish") cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", os.Getenv("PIPER_serverUrl"), "The URL to the Protecode backend") - cmd.Flags().StringVar(&stepConfig.ReportFileName, "reportFileName", "protecode_report.pdf", "The file name of the report to be created") + cmd.Flags().StringVar(&stepConfig.ReportFileName, "reportFileName", `protecode_report.pdf`, "The file name of the report to be created") cmd.Flags().StringVar(&stepConfig.FetchURL, "fetchUrl", os.Getenv("PIPER_fetchUrl"), "The URL to fetch the file to scan with Protecode which must be accessible via public HTTP GET request") cmd.Flags().StringVar(&stepConfig.Group, "group", os.Getenv("PIPER_group"), "The Protecode group ID of your team") cmd.Flags().BoolVar(&stepConfig.ReuseExisting, "reuseExisting", false, "Whether to reuse an existing product instead of creating a new one") diff --git a/cmd/sonarExecuteScan_generated.go b/cmd/sonarExecuteScan_generated.go index eabd2cc5a..780c04e42 100644 --- a/cmd/sonarExecuteScan_generated.go +++ b/cmd/sonarExecuteScan_generated.go @@ -128,25 +128,25 @@ func SonarExecuteScanCommand() *cobra.Command { } func addSonarExecuteScanFlags(cmd *cobra.Command, stepConfig *sonarExecuteScanOptions) { - cmd.Flags().StringVar(&stepConfig.Instance, "instance", "SonarCloud", "Jenkins only: The name of the SonarQube instance defined in the Jenkins settings. DEPRECATED: use host parameter instead") + cmd.Flags().StringVar(&stepConfig.Instance, "instance", `SonarCloud`, "Jenkins only: The name of the SonarQube instance defined in the Jenkins settings. DEPRECATED: use host parameter instead") cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "The URL to the Sonar backend.") cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "Token used to authenticate with the Sonar Server.") cmd.Flags().StringVar(&stepConfig.Organization, "organization", os.Getenv("PIPER_organization"), "SonarCloud.io only: Organization that the project will be assigned to in SonarCloud.io.") cmd.Flags().StringVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", os.Getenv("PIPER_customTlsCertificateLinks"), "List of comma-separated download links to custom TLS certificates. This is required to ensure trusted connections to instances with custom certificates.") - cmd.Flags().StringVar(&stepConfig.SonarScannerDownloadURL, "sonarScannerDownloadUrl", "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.3.0.2102-linux.zip", "URL to the sonar-scanner-cli archive.") + cmd.Flags().StringVar(&stepConfig.SonarScannerDownloadURL, "sonarScannerDownloadUrl", `https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.3.0.2102-linux.zip`, "URL to the sonar-scanner-cli archive.") cmd.Flags().StringVar(&stepConfig.ProjectVersion, "projectVersion", os.Getenv("PIPER_projectVersion"), "The project version that is reported to SonarQube.") cmd.Flags().StringSliceVar(&stepConfig.Options, "options", []string{}, "A list of options which are passed to the sonar-scanner.") cmd.Flags().StringVar(&stepConfig.BranchName, "branchName", os.Getenv("PIPER_branchName"), "Non-Pull-Request only: Name of the SonarQube branch that should be used to report findings to.") cmd.Flags().StringVar(&stepConfig.ChangeID, "changeId", os.Getenv("PIPER_changeId"), "Pull-Request only: The id of the pull-request.") cmd.Flags().StringVar(&stepConfig.ChangeBranch, "changeBranch", os.Getenv("PIPER_changeBranch"), "Pull-Request only: The name of the pull-request branch.") cmd.Flags().StringVar(&stepConfig.ChangeTarget, "changeTarget", os.Getenv("PIPER_changeTarget"), "Pull-Request only: The name of the base branch.") - cmd.Flags().StringVar(&stepConfig.PullRequestProvider, "pullRequestProvider", "GitHub", "Pull-Request only: The scm provider.") + cmd.Flags().StringVar(&stepConfig.PullRequestProvider, "pullRequestProvider", `GitHub`, "Pull-Request only: The scm provider.") cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Pull-Request only: The owner of the scm repository.") cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Pull-Request only: The scm repository.") cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "Pull-Request only: Token for Github to set status on the Pull-Request.") cmd.Flags().BoolVar(&stepConfig.DisableInlineComments, "disableInlineComments", false, "Pull-Request only: Disables the pull-request decoration with inline comments. DEPRECATED: only supported in SonarQube < 7.2") cmd.Flags().BoolVar(&stepConfig.LegacyPRHandling, "legacyPRHandling", false, "Pull-Request only: Activates the pull-request handling using the [GitHub Plugin](https://docs.sonarqube.org/display/PLUG/GitHub+Plugin). DEPRECATED: only supported in SonarQube < 7.2") - cmd.Flags().StringVar(&stepConfig.GithubAPIURL, "githubApiUrl", "https://api.github.com", "Pull-Request only: The URL to the Github API. see [GitHub plugin docs](https://docs.sonarqube.org/display/PLUG/GitHub+Plugin#GitHubPlugin-Usage) DEPRECATED: only supported in SonarQube < 7.2") + cmd.Flags().StringVar(&stepConfig.GithubAPIURL, "githubApiUrl", `https://api.github.com`, "Pull-Request only: The URL to the Github API. see [GitHub plugin docs](https://docs.sonarqube.org/display/PLUG/GitHub+Plugin#GitHubPlugin-Usage) DEPRECATED: only supported in SonarQube < 7.2") } diff --git a/cmd/xsDeploy_generated.go b/cmd/xsDeploy_generated.go index 133fd9e9b..d3f398a80 100644 --- a/cmd/xsDeploy_generated.go +++ b/cmd/xsDeploy_generated.go @@ -116,10 +116,10 @@ func XsDeployCommand() *cobra.Command { func addXsDeployFlags(cmd *cobra.Command, stepConfig *xsDeployOptions) { cmd.Flags().StringVar(&stepConfig.DeployOpts, "deployOpts", os.Getenv("PIPER_deployOpts"), "Additional options appended to the deploy command. Only needed for sophisticated cases. When provided it is the duty of the provider to ensure proper quoting / escaping.") - cmd.Flags().StringVar(&stepConfig.OperationIDLogPattern, "operationIdLogPattern", "^.*xs bg-deploy -i (.*) -a.*$", "Regex pattern for retrieving the ID of the operation from the xs log.") + cmd.Flags().StringVar(&stepConfig.OperationIDLogPattern, "operationIdLogPattern", `^.*xs bg-deploy -i (.*) -a.*$`, "Regex pattern for retrieving the ID of the operation from the xs log.") cmd.Flags().StringVar(&stepConfig.MtaPath, "mtaPath", os.Getenv("PIPER_mtaPath"), "Path to deployable") - cmd.Flags().StringVar(&stepConfig.Action, "action", "NONE", "Used for finalizing the blue-green deployment.") - cmd.Flags().StringVar(&stepConfig.Mode, "mode", "DEPLOY", "Controls if there is a standard deployment or a blue green deployment. Values: 'DEPLOY', 'BG_DEPLOY'") + cmd.Flags().StringVar(&stepConfig.Action, "action", `NONE`, "Used for finalizing the blue-green deployment.") + cmd.Flags().StringVar(&stepConfig.Mode, "mode", `DEPLOY`, "Controls if there is a standard deployment or a blue green deployment. Values: 'DEPLOY', 'BG_DEPLOY'") cmd.Flags().StringVar(&stepConfig.OperationID, "operationId", os.Getenv("PIPER_operationId"), "The operation ID. Used in case of bg-deploy in order to resume or abort a previously started deployment.") cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", os.Getenv("PIPER_apiUrl"), "The api url (e.g. https://example.org:12345") cmd.Flags().StringVar(&stepConfig.User, "user", os.Getenv("PIPER_user"), "User") diff --git a/documentation/docs/steps/fortifyExecuteScan.md b/documentation/docs/steps/fortifyExecuteScan.md new file mode 100644 index 000000000..3aa5019ea --- /dev/null +++ b/documentation/docs/steps/fortifyExecuteScan.md @@ -0,0 +1,8 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## ${docGenParameters} + +## ${docGenConfiguration} + diff --git a/go.mod b/go.mod index 3c93b79f2..bcd68dee0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,9 @@ go 1.13 require ( github.com/GoogleContainerTools/container-diff v0.15.0 github.com/Jeffail/gabs/v2 v2.5.0 + github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/bmatcuk/doublestar v1.2.4 github.com/containerd/containerd v1.3.4 // indirect @@ -12,13 +15,20 @@ require ( github.com/getsentry/sentry-go v0.6.0 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.0.0 + github.com/go-openapi/runtime v0.19.11 + github.com/go-openapi/strfmt v0.19.4 github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.4.0 // indirect github.com/google/go-cmp v0.4.0 - github.com/google/go-containerregistry v0.0.0-20200413145205-82d30a103c0a + github.com/google/go-containerregistry v0.0.0-20200131185320-aec8da010de2 github.com/google/go-github/v28 v28.1.1 github.com/google/uuid v1.1.1 + github.com/huandu/xstrings v1.3.0 // indirect + github.com/imdario/mergo v0.3.9 // indirect github.com/magiconair/properties v1.8.0 + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/motemen/go-nuts v0.0.0-20190725124253-1d2432db96b0 + github.com/piper-validation/fortify-client-go v0.0.0-20200206215926-532b5b150d22 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.5.0 github.com/spf13/cobra v1.0.0 diff --git a/go.sum b/go.sum index a7c4bd5b4..fd9dfe349 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,12 @@ github.com/Jeffail/gabs/v2 v2.5.0 h1:ERXffrksCEPjKVDWbZDBcOwrpXctXfeFGXxOQh1umOE github.com/Jeffail/gabs/v2 v2.5.0/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= @@ -42,20 +48,28 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -162,6 +176,8 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -178,17 +194,67 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3 h1:7MGZI1ibQDLasvAz8HuhvYk9eNJbJkCOXWsSjjMS+Zc= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.11 h1:6J11dQiIV+BOLlMbk2YmM8RvGaOU38syeqy62qhh3W8= +github.com/go-openapi/runtime v0.19.11/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4 h1:eRvaqAhpL0IL6Trh5fDsGnGhiXndzHFuA05w6sXH6/g= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.6 h1:WsKw9J1WzYBVxWRYwLqEk3325RL6G0SSWksuamkk6q0= +github.com/go-openapi/validate v0.19.6/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -197,6 +263,7 @@ github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= @@ -232,8 +299,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-containerregistry v0.0.0-20200413145205-82d30a103c0a h1:XNvUa41C0oOgPmTmZKrWKhOCCFPLnOwLfm+pWU75sNs= -github.com/google/go-containerregistry v0.0.0-20200413145205-82d30a103c0a/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA= +github.com/google/go-containerregistry v0.0.0-20200131185320-aec8da010de2 h1:/z0FoA29APs30PljxT6GoZQekF5c1cYhow2osFsj1XU= +github.com/google/go-containerregistry v0.0.0-20200131185320-aec8da010de2/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -242,6 +309,7 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -272,7 +340,11 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo= +github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -327,8 +399,11 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -344,9 +419,14 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -355,6 +435,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/motemen/go-nuts v0.0.0-20190725124253-1d2432db96b0 h1:CnSVrlMNAZMWI1+uH6ldpXRv2pe7t50IQX448EJrJhw= +github.com/motemen/go-nuts v0.0.0-20190725124253-1d2432db96b0/go.mod h1:vfh/NPxHgDwggXit20W1llPsXcz39xJ7I8vo7kVrOCk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -388,10 +470,13 @@ github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/piper-validation/fortify-client-go v0.0.0-20200206215926-532b5b150d22 h1:xSbcGENeXvuG+tu4suCmsr+Vm+p3peYNgJDDxUBeJa8= +github.com/piper-validation/fortify-client-go v0.0.0-20200206215926-532b5b150d22/go.mod h1:EZkdCgngw/tInYdidqDQlRIXvyM1fSbqn/vx83YNCcw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -416,6 +501,7 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -464,6 +550,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/testcontainers/testcontainers-go v0.4.0 h1:BpzZG0/I4s4oAVrkYhf9K3R0AKLvhE0bX1kVSqyUVx8= github.com/testcontainers/testcontainers-go v0.4.0/go.mod h1:BXwe1JilTOLT8cmVyPMDbIw7e+8UCGeAhxjBwguG5wQ= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -477,7 +565,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= +github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= @@ -494,6 +583,10 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -505,11 +598,14 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -522,17 +618,21 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -572,6 +672,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -605,6 +706,7 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -615,9 +717,12 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200115165105-de0b1760071a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -664,6 +769,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= @@ -695,14 +801,15 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= -k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= -k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= -k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= -k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= -k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= -k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= -k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= +k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -710,7 +817,7 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= +k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= diff --git a/pkg/checkmarx/checkmarx_test.go b/pkg/checkmarx/checkmarx_test.go index b3bc46451..cbc4b95d7 100644 --- a/pkg/checkmarx/checkmarx_test.go +++ b/pkg/checkmarx/checkmarx_test.go @@ -3,6 +3,7 @@ package checkmarx import ( "bytes" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -53,6 +54,9 @@ func (sm *senderMock) UploadRequest(method, url, file, fieldName string, header sm.header = header return &http.Response{StatusCode: sm.httpStatusCode, Body: ioutil.NopCloser(bytes.NewReader([]byte(sm.responseBody)))}, nil } +func (sm *senderMock) Upload(_ piperHttp.UploadRequestData) (*http.Response, error) { + return &http.Response{}, fmt.Errorf("not implemented") +} func (sm *senderMock) SetOptions(opts piperHttp.ClientOptions) { sm.token = opts.Token } diff --git a/pkg/fortify/fortify.go b/pkg/fortify/fortify.go new file mode 100644 index 000000000..6d29dda14 --- /dev/null +++ b/pkg/fortify/fortify.go @@ -0,0 +1,741 @@ +package fortify + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + "time" + + ff "github.com/piper-validation/fortify-client-go/fortify" + "github.com/piper-validation/fortify-client-go/fortify/artifact_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/attribute_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/auth_entity_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/file_token_controller" + "github.com/piper-validation/fortify-client-go/fortify/filter_set_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/issue_group_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/issue_selector_set_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/issue_statistics_of_project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/project_controller" + "github.com/piper-validation/fortify-client-go/fortify/project_version_controller" + "github.com/piper-validation/fortify-client-go/fortify/project_version_of_project_controller" + "github.com/piper-validation/fortify-client-go/fortify/saved_report_controller" + "github.com/piper-validation/fortify-client-go/models" + + piperHttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperutils" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// System is the interface abstraction of a specific SystemInstance +type System interface { + GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) + GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) + GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) + SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) + CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) + LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) + CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) + ProjectVersionCopyFromPartial(sourceID, targetID int64) error + ProjectVersionCopyCurrentState(sourceID, targetID int64) error + ProjectVersionCopyPermissions(sourceID, targetID int64) error + CommitProjectVersion(id int64) (*models.ProjectVersion, error) + MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error + GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) + GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) + GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) + GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector + GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) + ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet + GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) + GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) + GetReportDetails(id int64) (*models.SavedReport, error) + UploadResultFile(endpoint, file string, projectVersionID int64) error + DownloadReportFile(endpoint string, projectVersionID int64) ([]byte, error) + DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) +} + +// SystemInstance is the specific instance +type SystemInstance struct { + timeout time.Duration + token string + serverURL string + client *ff.Fortify + httpClient *piperHttp.Client + logger *logrus.Entry +} + +// NewSystemInstance - creates an returns a new SystemInstance +func NewSystemInstance(serverURL, apiEndpoint, authToken string, timeout time.Duration) *SystemInstance { + format := strfmt.Default + dateTimeFormat := models.Iso8601MilliDateTime{} + format.Add("datetime", &dateTimeFormat, models.IsDateTime) + clientInstance := ff.NewHTTPClientWithConfig(format, createTransportConfig(serverURL, apiEndpoint)) + httpClientInstance := &piperHttp.Client{} + httpClientOptions := piperHttp.ClientOptions{Token: "FortifyToken " + authToken, TransportTimeout: timeout} + httpClientInstance.SetOptions(httpClientOptions) + + return NewSystemInstanceForClient(clientInstance, httpClientInstance, serverURL, authToken, timeout) +} + +func createTransportConfig(serverURL, apiEndpoint string) *ff.TransportConfig { + scheme, host := splitSchemeAndHost(serverURL) + host, hostEndpoint := splitHostAndEndpoint(host) + return &ff.TransportConfig{ + Host: host, + Schemes: []string{scheme}, + BasePath: fmt.Sprintf("%v/%v", hostEndpoint, apiEndpoint)} +} + +func splitSchemeAndHost(url string) (scheme, host string) { + schemeEnd := strings.Index(url, "://") + if schemeEnd >= 0 { + scheme = url[0:schemeEnd] + host = url[schemeEnd+3:] + } else { + scheme = "https" + host = url + } + return +} + +func splitHostAndEndpoint(urlWithoutScheme string) (host, endpoint string) { + hostEnd := strings.Index(urlWithoutScheme, "/") + if hostEnd >= 0 { + host = urlWithoutScheme[0:hostEnd] + endpoint = urlWithoutScheme[hostEnd+1:] + } else { + host = urlWithoutScheme + endpoint = "" + } + return +} + +// NewSystemInstanceForClient - creates a new SystemInstance +func NewSystemInstanceForClient(clientInstance *ff.Fortify, httpClientInstance *piperHttp.Client, serverURL, authToken string, requestTimeout time.Duration) *SystemInstance { + return &SystemInstance{ + timeout: requestTimeout, + token: authToken, + serverURL: serverURL, + client: clientInstance, + httpClient: httpClientInstance, + logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/fortify"), + } +} + +// AuthenticateRequest authenticates the request +func (sys *SystemInstance) AuthenticateRequest(req runtime.ClientRequest, formats strfmt.Registry) error { + req.SetHeaderParam("Authorization", fmt.Sprintf("FortifyToken %v", sys.token)) + return nil +} + +// GetProjectByName returns the project identified by the name provided +// autoCreate and projectVersion parameters only used if autoCreate=true +func (sys *SystemInstance) GetProjectByName(projectName string, autoCreate bool, projectVersionName string) (*models.Project, error) { + nameParam := fmt.Sprintf("name=%v", projectName) + fullText := true + params := &project_controller.ListProjectParams{Q: &nameParam, Fulltextsearch: &fullText} + params.WithTimeout(sys.timeout) + result, err := sys.client.ProjectController.ListProject(params, sys) + if err != nil { + return nil, err + } + for _, project := range result.GetPayload().Data { + if *project.Name == projectName { + return project, nil + } + } + + // Project with specified name was NOT found, check if autoCreate flag is set, if not stop otherwise create it automatically + if !autoCreate { + return nil, fmt.Errorf("Project with name %v not found in backend and automatic creation not enabled", projectName) + } + + log.Entry().Debugf("No projects found with name: %v auto-creating one now...", projectName) + projectVersion, err := sys.CreateProjectVersionIfNotExist(projectName, projectVersionName, "Created by Go script") + if err != nil { + return nil, fmt.Errorf("failed to auto-create new project: %w", err) + } + log.Entry().Debugf("Finished creating project: %v", projectVersion) + return projectVersion.Project, nil +} + +// GetProjectVersionDetailsByProjectIDAndVersionName returns the project version details of the project version identified by the id and project versionname +// projectName parameter is only used if autoCreate=true +func (sys *SystemInstance) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, versionName string, autoCreate bool, projectName string) (*models.ProjectVersion, error) { + nameParam := fmt.Sprintf("name=%v", versionName) + fullText := true + params := &project_version_of_project_controller.ListProjectVersionOfProjectParams{ParentID: id, Q: &nameParam, Fulltextsearch: &fullText} + params.WithTimeout(sys.timeout) + result, err := sys.client.ProjectVersionOfProjectController.ListProjectVersionOfProject(params, sys) + if err != nil { + return nil, err + } + for _, projectVersion := range result.GetPayload().Data { + if *projectVersion.Name == versionName { + return projectVersion, nil + } + } + // projectVersion not found for specified project id and name, check if autoCreate is enabled + if !autoCreate { + return nil, errors.New(fmt.Sprintf("Project version with name %v not found in project with ID %v and automatic creation not enabled", versionName, id)) + } + + log.Entry().Debugf("Could not find project version with name %v under project %v auto-creating one now...", versionName, projectName) + version, err := sys.CreateProjectVersionIfNotExist(projectName, versionName, "Created by Go script") + if err != nil { + return nil, errors.Wrapf(err, "failed to auto-create project version: %v for project %v", versionName, projectName) + } + log.Entry().Debugf("Successfully created project version %v for project %v", versionName, projectName) + return version, nil +} + +// CreateProjectVersionIfNotExist creates a new ProjectVersion if it does not already exist. +// If the projectName also does not exist, it will create that as well. +func (sys *SystemInstance) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) { + var projectID int64 = 0 + // check if project with projectName exists + projectResp, err := sys.GetProjectByName(projectName, false, "") + if err == nil { + // project already exists, all we need to do is append a new ProjectVersion to it + // save the project id for later + projectID = projectResp.ID + } + + issueTemplateID := "4c5799c9-1940-4abe-b57a-3bcad88eb041" + active := true + committed := true + projectVersionDto := &models.ProjectVersion{ + Name: &projectVersionName, + Description: &description, + IssueTemplateID: &issueTemplateID, + Active: &active, + Committed: &committed, + Project: &models.Project{ID: projectID}, + } + + if projectVersionDto.Project.ID == 0 { // project does not exist, set one up + projectVersionDto.Project = &models.Project{ + Name: &projectName, + Description: description, + IssueTemplateID: &issueTemplateID, + } + } + projectVersion, err := sys.CreateProjectVersion(projectVersionDto) + if err != nil { + return nil, errors.Wrapf(err, "Failed to create new project version %v for projectName %v", projectVersionName, projectName) + } + _, err = sys.CommitProjectVersion(projectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to commit project version %v: %v", projectVersion.ID, err) + } + return projectVersion, nil +} + +// LookupOrCreateProjectVersionDetailsForPullRequest looks up a project version for pull requests or creates it from scratch +func (sys *SystemInstance) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) { + projectVersion, _ := sys.GetProjectVersionDetailsByProjectIDAndVersionName(projectID, pullRequestName, false, "") + if nil != projectVersion { + return projectVersion, nil + } + + newVersion := &models.ProjectVersion{} + newVersion.Name = &pullRequestName + newVersion.Description = masterProjectVersion.Description + newVersion.Active = masterProjectVersion.Active + newVersion.Committed = masterProjectVersion.Committed + newVersion.Project = &models.Project{} + newVersion.Project.Name = masterProjectVersion.Project.Name + newVersion.Project.Description = masterProjectVersion.Project.Description + newVersion.Project.ID = masterProjectVersion.Project.ID + newVersion.IssueTemplateID = masterProjectVersion.IssueTemplateID + + projectVersion, err := sys.CreateProjectVersion(newVersion) + if err != nil { + return nil, errors.Wrapf(err, "Failed to create new project version for pull request %v", pullRequestName) + } + attributes, err := sys.GetProjectVersionAttributesByProjectVersionID(masterProjectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to load project version attributes for master project version %v", masterProjectVersion.ID) + } + for _, attribute := range attributes { + attribute.ID = 0 + } + _, err = sys.SetProjectVersionAttributesByProjectVersionID(projectVersion.ID, attributes) + if err != nil { + return nil, errors.Wrapf(err, "Failed to update project version attributes for pull request project version %v", projectVersion.ID) + } + err = sys.ProjectVersionCopyFromPartial(masterProjectVersion.ID, projectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to copy from partial of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) + } + _, err = sys.CommitProjectVersion(projectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to commit project version %v: %v", projectVersion.ID, err) + } + err = sys.ProjectVersionCopyCurrentState(masterProjectVersion.ID, projectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to copy current state of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) + } + err = sys.ProjectVersionCopyPermissions(masterProjectVersion.ID, projectVersion.ID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to copy permissions of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) + } + return projectVersion, nil +} + +// GetProjectVersionAttributesByProjectVersionID returns the project version attributes of the project version identified by the id +func (sys *SystemInstance) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) { + params := &attribute_of_project_version_controller.ListAttributeOfProjectVersionParams{ParentID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.AttributeOfProjectVersionController.ListAttributeOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// SetProjectVersionAttributesByProjectVersionID sets the project version attributes of the project version identified by the id +func (sys *SystemInstance) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) { + params := &attribute_of_project_version_controller.UpdateCollectionAttributeOfProjectVersionParams{ParentID: id, Data: attributes} + params.WithTimeout(sys.timeout) + result, err := sys.client.AttributeOfProjectVersionController.UpdateCollectionAttributeOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// CreateProjectVersion creates the project version with the provided details +func (sys *SystemInstance) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) { + params := &project_version_controller.CreateProjectVersionParams{Resource: version} + params.WithTimeout(sys.timeout) + result, err := sys.client.ProjectVersionController.CreateProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// ProjectVersionCopyFromPartial copies parts of the source project version to the target project version identified by their ids +func (sys *SystemInstance) ProjectVersionCopyFromPartial(sourceID, targetID int64) error { + enable := true + settings := models.ProjectVersionCopyPartialRequest{ + ProjectVersionID: &targetID, + PreviousProjectVersionID: &sourceID, + CopyAnalysisProcessingRules: &enable, + CopyBugTrackerConfiguration: &enable, + CopyCurrentStateFpr: &enable, + CopyCustomTags: &enable, + } + params := &project_version_controller.CopyProjectVersionParams{Resource: &settings} + params.WithTimeout(sys.timeout) + _, err := sys.client.ProjectVersionController.CopyProjectVersion(params, sys) + if err != nil { + return err + } + return nil +} + +// ProjectVersionCopyCurrentState copies the project version state of sourceID into the new project version addressed by targetID +func (sys *SystemInstance) ProjectVersionCopyCurrentState(sourceID, targetID int64) error { + enable := true + settings := models.ProjectVersionCopyCurrentStateRequest{ + ProjectVersionID: &targetID, + PreviousProjectVersionID: &sourceID, + CopyCurrentStateFpr: &enable, + } + params := &project_version_controller.CopyCurrentStateForProjectVersionParams{Resource: &settings} + params.WithTimeout(sys.timeout) + _, err := sys.client.ProjectVersionController.CopyCurrentStateForProjectVersion(params, sys) + if err != nil { + return err + } + return nil +} + +func (sys *SystemInstance) getAuthEntityOfProjectVersion(id int64) ([]*models.AuthenticationEntity, error) { + embed := "roles" + params := &auth_entity_of_project_version_controller.ListAuthEntityOfProjectVersionParams{Embed: &embed, ParentID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.AuthEntityOfProjectVersionController.ListAuthEntityOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +func (sys *SystemInstance) updateCollectionAuthEntityOfProjectVersion(id int64, data []*models.AuthenticationEntity) error { + params := &auth_entity_of_project_version_controller.UpdateCollectionAuthEntityOfProjectVersionParams{ParentID: id, Data: data} + params.WithTimeout(sys.timeout) + _, err := sys.client.AuthEntityOfProjectVersionController.UpdateCollectionAuthEntityOfProjectVersion(params, sys) + if err != nil { + return err + } + return nil +} + +// ProjectVersionCopyPermissions copies the authentication entity of the project version addressed by sourceID to the one of targetID +func (sys *SystemInstance) ProjectVersionCopyPermissions(sourceID, targetID int64) error { + result, err := sys.getAuthEntityOfProjectVersion(sourceID) + if err != nil { + return err + } + err = sys.updateCollectionAuthEntityOfProjectVersion(targetID, result) + if err != nil { + return err + } + return nil +} + +func (sys *SystemInstance) updateProjectVersionDetails(id int64, details *models.ProjectVersion) (*models.ProjectVersion, error) { + params := &project_version_controller.UpdateProjectVersionParams{ID: id, Resource: details} + params.WithTimeout(sys.timeout) + result, err := sys.client.ProjectVersionController.UpdateProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// CommitProjectVersion commits the project version with the provided id +func (sys *SystemInstance) CommitProjectVersion(id int64) (*models.ProjectVersion, error) { + enabled := true + update := models.ProjectVersion{Committed: &enabled} + return sys.updateProjectVersionDetails(id, &update) +} + +func (sys *SystemInstance) inactivateProjectVersion(id int64) (*models.ProjectVersion, error) { + enabled := true + disabled := false + update := models.ProjectVersion{Committed: &enabled, Active: &disabled} + return sys.updateProjectVersionDetails(id, &update) +} + +// GetArtifactsOfProjectVersion returns the list of artifacts related to the project version addressed with id +func (sys *SystemInstance) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) { + scans := "scans" + params := &artifact_of_project_version_controller.ListArtifactOfProjectVersionParams{ParentID: id, Embed: &scans} + params.WithTimeout(sys.timeout) + result, err := sys.client.ArtifactOfProjectVersionController.ListArtifactOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// MergeProjectVersionStateOfPRIntoMaster merges the PR project version's fpr result file into the master project version +func (sys *SystemInstance) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error { + log.Entry().Debugf("Looking up project version with name '%v' to merge audit status into master version", pullRequestName) + prProjectVersion, _ := sys.GetProjectVersionDetailsByProjectIDAndVersionName(masterProjectID, pullRequestName, false, "") + if nil != prProjectVersion { + log.Entry().Debugf("Found project version with ID '%v', starting transfer", prProjectVersion.ID) + data, err := sys.DownloadResultFile(downloadEndpoint, prProjectVersion.ID) + if err != nil { + return errors.Wrapf(err, "Failed to download current state FPR of PR project version %v", prProjectVersion.ID) + } + err = sys.uploadResultFileContent(uploadEndpoint, "prMergeTransfer.fpr", bytes.NewReader(data), masterProjectVersionID) + if err != nil { + return errors.Wrapf(err, "Failed to upload PR project version state to master project version %v", masterProjectVersionID) + } + _, err = sys.inactivateProjectVersion(prProjectVersion.ID) + if err != nil { + log.Entry().Warnf("Failed to inactivate merged PR project version %v", prProjectVersion.ID) + } + } else { + log.Entry().Debug("No related project version found in SSC") + } + return nil +} + +// GetFilterSetOfProjectVersionByTitle returns the filter set with the given title related to the project version addressed with id, if no title is provided the default filter set will be returned +func (sys *SystemInstance) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) { + params := &filter_set_of_project_version_controller.ListFilterSetOfProjectVersionParams{ParentID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.FilterSetOfProjectVersionController.ListFilterSetOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + var defaultFilterSet *models.FilterSet + for _, filterSet := range result.GetPayload().Data { + if len(title) > 0 && filterSet.Title == title { + return filterSet, nil + } + if filterSet.DefaultFilterSet { + defaultFilterSet = filterSet + } + } + if len(title) > 0 { + log.Entry().Warnf("Failed to load filter set with title '%v', falling back to default filter set", title) + } + if nil != defaultFilterSet { + return defaultFilterSet, nil + } + return nil, fmt.Errorf("Failed to identify requested filter set and default filter") +} + +// GetIssueFilterSelectorOfProjectVersionByName returns the groupings with the given names related to the project version addressed with id +func (sys *SystemInstance) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) { + params := &issue_selector_set_of_project_version_controller.GetIssueSelectorSetOfProjectVersionParams{ParentID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.IssueSelectorSetOfProjectVersionController.GetIssueSelectorSetOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return sys.ReduceIssueFilterSelectorSet(result.GetPayload().Data, names, options), nil +} + +// ReduceIssueFilterSelectorSet filters the set to the relevant filter display names +func (sys *SystemInstance) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet { + groupingList := []*models.IssueSelector{} + if issueFilterSelectorSet.GroupBySet != nil { + for _, group := range issueFilterSelectorSet.GroupBySet { + if piperutils.ContainsString(names, *group.DisplayName) { + log.Entry().Debugf("adding new grouping '%v' to reduced list", *group.DisplayName) + groupingList = append(groupingList, group) + } + } + } + filterList := []*models.IssueFilterSelector{} + if issueFilterSelectorSet.FilterBySet != nil { + for _, filter := range issueFilterSelectorSet.FilterBySet { + if piperutils.ContainsString(names, filter.DisplayName) { + newFilter := &models.IssueFilterSelector{} + newFilter.DisplayName = filter.DisplayName + newFilter.Description = filter.Description + newFilter.EntityType = filter.EntityType + newFilter.FilterSelectorType = filter.FilterSelectorType + newFilter.GUID = filter.GUID + newFilter.Value = filter.Value + newFilter.SelectorOptions = []*models.SelectorOption{} + for _, option := range filter.SelectorOptions { + if (nil != options && piperutils.ContainsString(options, option.DisplayName)) || options == nil || len(options) == 0 { + log.Entry().Debugf("adding selector option '%v' to list for filter selector '%v'", option.DisplayName, newFilter.DisplayName) + newFilter.SelectorOptions = append(newFilter.SelectorOptions, option) + } + } + log.Entry().Debugf("adding new filter '%v' to reduced list with selector options '%v'", newFilter.DisplayName, newFilter.SelectorOptions) + filterList = append(filterList, newFilter) + } + } + } + return &models.IssueFilterSelectorSet{GroupBySet: groupingList, FilterBySet: filterList} +} + +//GetFilterSetByDisplayName returns the set identified by the provided name or nil +func (sys *SystemInstance) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector { + if issueFilterSelectorSet.FilterBySet != nil { + for _, filter := range issueFilterSelectorSet.FilterBySet { + if filter.DisplayName == name { + return filter + } + } + } + return nil +} + +func (sys *SystemInstance) getIssuesOfProjectVersion(id int64, filter, filterset, groupingtype string) ([]*models.ProjectVersionIssueGroup, error) { + enable := true + params := &issue_group_of_project_version_controller.ListIssueGroupOfProjectVersionParams{ParentID: id, Showsuppressed: &enable, Filterset: &filterset, Groupingtype: &groupingtype} + params.WithTimeout(sys.timeout) + if len(filter) > 0 { + params.WithFilter(&filter) + } + result, err := sys.client.IssueGroupOfProjectVersionController.ListIssueGroupOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// GetProjectIssuesByIDAndFilterSetGroupedBySelector returns issues of the project version addressed with id filtered with the respective set and grouped by the issue filter selector grouping +func (sys *SystemInstance) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) { + groupingTypeGUID := "" + if issueFilterSelectorSet != nil { + groupingTypeGUID = *issueFilterSelectorSet.GroupBySet[0].GUID + } + + result, err := sys.getIssuesOfProjectVersion(id, filter, filterSetGUID, groupingTypeGUID) + if err != nil { + return nil, err + } + return result, nil +} + +// GetIssueStatisticsOfProjectVersion returns the issue statistics related to the project version addressed with id +func (sys *SystemInstance) GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) { + params := &issue_statistics_of_project_version_controller.ListIssueStatisticsOfProjectVersionParams{ParentID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.IssueStatisticsOfProjectVersionController.ListIssueStatisticsOfProjectVersion(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// GenerateQGateReport returns the issue statistics related to the project version addressed with id +func (sys *SystemInstance) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) { + paramIdentifier := "projectVersionId" + paramType := "SINGLE_PROJECT" + paramName := "Q-gate-report" + reportType := "PORTFOLIO" + inputReportParameters := []*models.InputReportParameter{&models.InputReportParameter{Name: ¶mName, Identifier: ¶mIdentifier, ParamValue: projectVersionID, Type: ¶mType}} + reportProjectVersions := []*models.ReportProjectVersion{&models.ReportProjectVersion{ID: projectVersionID, Name: projectVersionName}} + reportProjects := []*models.ReportProject{&models.ReportProject{ID: projectID, Name: projectName, Versions: reportProjectVersions}} + report := models.SavedReport{Name: fmt.Sprintf("FortifyReport: %v:%v", projectName, projectVersionName), Type: &reportType, ReportDefinitionID: &reportTemplateID, Format: &reportFormat, Projects: reportProjects, InputReportParameters: inputReportParameters} + params := &saved_report_controller.CreateSavedReportParams{Resource: &report} + params.WithTimeout(sys.timeout) + result, err := sys.client.SavedReportController.CreateSavedReport(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +// GetReportDetails returns the details of the report addressed with id +func (sys *SystemInstance) GetReportDetails(id int64) (*models.SavedReport, error) { + params := &saved_report_controller.ReadSavedReportParams{ID: id} + params.WithTimeout(sys.timeout) + result, err := sys.client.SavedReportController.ReadSavedReport(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +func (sys *SystemInstance) invalidateFileTokens() error { + log.Entry().Debug("invalidating file tokens") + params := &file_token_controller.MultiDeleteFileTokenParams{} + params.WithTimeout(sys.timeout) + _, err := sys.client.FileTokenController.MultiDeleteFileToken(params, sys) + return err +} + +func (sys *SystemInstance) getFileToken(tokenType string) (*models.FileToken, error) { + token := models.FileToken{FileTokenType: &tokenType} + params := &file_token_controller.CreateFileTokenParams{Resource: &token} + params.WithTimeout(sys.timeout) + result, err := sys.client.FileTokenController.CreateFileToken(params, sys) + if err != nil { + return nil, err + } + return result.GetPayload().Data, nil +} + +func (sys *SystemInstance) getFileUploadToken() (*models.FileToken, error) { + log.Entry().Debug("fetching upload token") + return sys.getFileToken("UPLOAD") +} + +func (sys *SystemInstance) getFileDownloadToken() (*models.FileToken, error) { + log.Entry().Debug("fetching download token") + return sys.getFileToken("DOWNLOAD") +} + +func (sys *SystemInstance) getReportFileToken() (*models.FileToken, error) { + log.Entry().Debug("fetching report download token") + return sys.getFileToken("REPORT_FILE") +} + +// UploadResultFile uploads a fpr file to the fortify backend +func (sys *SystemInstance) UploadResultFile(endpoint, file string, projectVersionID int64) error { + fileHandle, err := os.Open(file) + if err != nil { + return errors.Wrapf(err, "Unable to locate file %v", file) + } + defer fileHandle.Close() + + return sys.uploadResultFileContent(endpoint, file, fileHandle, projectVersionID) +} + +func (sys *SystemInstance) uploadResultFileContent(endpoint, file string, fileContent io.Reader, projectVersionID int64) error { + token, err := sys.getFileUploadToken() + if err != nil { + return err + } + defer sys.invalidateFileTokens() + + header := http.Header{} + header.Add("Cache-Control", "no-cache, no-store, must-revalidate") + header.Add("Pragma", "no-cache") + + formFields := map[string]string{} + formFields["entityId"] = fmt.Sprintf("%v", projectVersionID) + + _, err = sys.httpClient.Upload(piperHttp.UploadRequestData{ + Method: http.MethodPost, + URL: fmt.Sprintf("%v%v?mat=%v", sys.serverURL, endpoint, token.Token), + File: file, + FileFieldName: "file", + FormFields: formFields, + FileContent: fileContent, + Header: header, + }) + return err +} + +// DownloadFile downloads a file from Fortify backend +func (sys *SystemInstance) downloadFile(endpoint, method, acceptType, downloadToken string, projectVersionID int64) ([]byte, error) { + header := http.Header{} + header.Add("Cache-Control", "no-cache, no-store, must-revalidate") + header.Add("Pragma", "no-cache") + header.Add("Accept", acceptType) + header.Add("Content-Type", "application/form-data") + body := url.Values{ + "id": {fmt.Sprintf("%v", projectVersionID)}, + "mat": {downloadToken}, + } + var response *http.Response + var err error + if method == http.MethodGet { + response, err = sys.httpClient.SendRequest(method, fmt.Sprintf("%v%v?%v", sys.serverURL, endpoint, body.Encode()), nil, header, nil) + } else { + response, err = sys.httpClient.SendRequest(method, fmt.Sprintf("%v%v", sys.serverURL, endpoint), strings.NewReader(body.Encode()), header, nil) + } + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() + if err != nil { + return nil, errors.Wrap(err, "Error reading the response data") + } + return data, nil +} + +// DownloadReportFile downloads a report file from Fortify backend +func (sys *SystemInstance) DownloadReportFile(endpoint string, projectVersionID int64) ([]byte, error) { + token, err := sys.getReportFileToken() + if err != nil { + return nil, errors.Wrap(err, "Error fetching report download token") + } + defer sys.invalidateFileTokens() + data, err := sys.downloadFile(endpoint, http.MethodGet, "application/octet-stream", token.Token, projectVersionID) + if err != nil { + return nil, errors.Wrap(err, "Error downloading report file") + } + return data, nil +} + +// DownloadResultFile downloads a result file from Fortify backend +func (sys *SystemInstance) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) { + token, err := sys.getFileDownloadToken() + if err != nil { + return nil, errors.Wrap(err, "Error fetching result file download token") + } + defer sys.invalidateFileTokens() + data, err := sys.downloadFile(endpoint, http.MethodGet, "application/zip", token.Token, projectVersionID) + if err != nil { + return nil, errors.Wrap(err, "Error downloading result file") + } + return data, nil +} diff --git a/pkg/fortify/fortify_test.go b/pkg/fortify/fortify_test.go new file mode 100644 index 000000000..aa8e4fe40 --- /dev/null +++ b/pkg/fortify/fortify_test.go @@ -0,0 +1,1330 @@ +package fortify + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + "time" + + "github.com/go-openapi/strfmt" + ff "github.com/piper-validation/fortify-client-go/fortify" + "github.com/piper-validation/fortify-client-go/models" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + + piperHttp "github.com/SAP/jenkins-library/pkg/http" +) + +func spinUpServer(f func(http.ResponseWriter, *http.Request)) (*SystemInstance, *httptest.Server) { + server := httptest.NewServer(http.HandlerFunc(f)) + + parts := strings.Split(server.URL, "://") + client := ff.NewHTTPClientWithConfig(strfmt.Default, &ff.TransportConfig{ + Host: parts[1], + Schemes: []string{parts[0]}, + BasePath: ""}, + ) + + httpClient := &piperHttp.Client{} + httpClientOptions := piperHttp.ClientOptions{Token: "test2456", TransportTimeout: 60 * time.Second} + httpClient.SetOptions(httpClientOptions) + + sys := NewSystemInstanceForClient(client, httpClient, server.URL, "test2456", 60*time.Second) + return sys, server +} + +func TestCreateTransportConfig(t *testing.T) { + t.Run("Valid URL", func(t *testing.T) { + config := createTransportConfig("http://some.fortify.host.com/ssc", "api/v2") + assert.Equal(t, []string{"http"}, config.Schemes) + assert.Equal(t, "some.fortify.host.com", config.Host) + assert.Equal(t, "ssc/api/v2", config.BasePath) + }) + t.Run("URL missing scheme results in no error", func(t *testing.T) { + config := createTransportConfig("some.fortify.host.com/ssc", "api/v1") + assert.Equal(t, []string{"https"}, config.Schemes) + assert.Equal(t, "some.fortify.host.com", config.Host) + assert.Equal(t, "ssc/api/v1", config.BasePath) + }) + t.Run("URL with more than one slash is accepted", func(t *testing.T) { + config := createTransportConfig("https://some.fortify.host.com/some/path/ssc", "api/v1") + assert.Equal(t, []string{"https"}, config.Schemes) + assert.Equal(t, "some.fortify.host.com", config.Host) + assert.Equal(t, "some/path/ssc/api/v1", config.BasePath) + }) +} + +func TestNewSystemInstance(t *testing.T) { + sys := NewSystemInstance("https://some.fortify.host.com/ssc", "api/v1", "akjhskjhks", 10*time.Second) + assert.IsType(t, ff.Fortify{}, *sys.client, "Expected to get a Fortify client instance") + assert.IsType(t, piperHttp.Client{}, *sys.httpClient, "Expected to get a HTTP client instance") + assert.IsType(t, logrus.Entry{}, *sys.logger, "Expected to get a logrus entry instance") + assert.Equal(t, 10*time.Second, sys.timeout, "Expected different timeout value") + assert.Equal(t, "akjhskjhks", sys.token, "Expected different token value") +} + +func TestGetProjectByName(t *testing.T) { + // Start a local HTTP server + autocreateCalled := false + commitCalled := false + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projects" && req.URL.RawQuery == "fulltextsearch=true&q=name%3Dpython-test" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [{"_href": "https://fortify/ssc/api/v1/projects/4711","createdBy": "someUser","name": "python-test", + "description": "","id": 4711,"creationDate": "2018-12-03T06:29:38.197+0000","issueTemplateId": "dasdasdasdsadasdasdasdasdas"}], + "count": 1,"responseCode": 200,"links": {"last": {"href": "https://fortify/ssc/api/v1/projects?q=name%A3python-test&start=0"}, + "first": {"href": "https://fortify/ssc/api/v1/projects?q=name%A3python-test&start=0"}}}`)) + return + } + if req.URL.Path == "/projects" && req.URL.RawQuery == "fulltextsearch=true&q=name%3Dpython-empty" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [],"count": 0,"responseCode": 404,"links": {}}`)) + return + } + if req.URL.Path == "/projects" && req.URL.RawQuery == "fulltextsearch=true&q=name%3Dpython-error" { + rw.WriteHeader(400) + return + } + if req.URL.Path == "/projectVersions" && req.Method == "POST" { + autocreateCalled = true + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte( + `{"data":{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + return + } + if req.URL.Path == "/projectVersions/10172" { + commitCalled = true + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": {"_href": "https://fortify/ssc/api/v1/projects/815", "committed": true,"createdBy": "someUser","name": "autocreate", + "description": "","id": 815,"creationDate": "2018-12-03T06:29:38.197+0000","issueTemplateId": "dasdasdasdsadasdasdasdasdas"}, + "count": 1,"responseCode": 200,"links": {"last": {"href": "https://fortify/ssc/api/v1/projects?q=name%A3python-test&start=0"}, + "first": {"href": ""}}}`)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetProjectByName("python-test", false, "") + assert.NoError(t, err, "GetProjectByName call not successful") + assert.Equal(t, "python-test", strings.ToLower(*result.Name), "Expected to get python-test") + }) + + t.Run("test empty", func(t *testing.T) { + _, err := sys.GetProjectByName("python-empty", false, "") + assert.Error(t, err, "Expected error but got success") + }) + + t.Run("test error", func(t *testing.T) { + _, err := sys.GetProjectByName("python-error", false, "") + assert.Error(t, err, "Expected error but got success") + }) + + t.Run("test auto create success", func(t *testing.T) { + result, err := sys.GetProjectByName("autocreate", true, "123456") + assert.NoError(t, err, "GetProjectByName call not successful") + assert.Equal(t, true, autocreateCalled, "Expected autocreation function to be called but wasn't") + assert.Equal(t, true, commitCalled, "Expected commit function to be called but wasn't") + assert.Equal(t, "autocreate", strings.ToLower(*result.Name), "Expected to get autocreate project") + }) +} + +func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projects/4711/versions" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + return + } + if req.URL.Path == "/projects/777/versions" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [],"count": 0,"responseCode": 404,"links": {}}`)) + return + } + if req.URL.Path == "/projects/999/versions" { + rw.WriteHeader(500) + return + } + if req.URL.Path == "/projectVersions" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte( + `{"data":{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + return + } + if req.URL.Path == "/projectVersions/0" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data":{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + return + } + }) + + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(4711, "0", false, "") + assert.NoError(t, err, "GetProjectVersionDetailsByNameAndProjectID call not successful") + assert.Equal(t, "0", *result.Name, "Expected to get project version with different name") + }) + + t.Run("test empty", func(t *testing.T) { + _, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(777, "python-empty", false, "") + assert.Error(t, err, "Expected error but got success") + }) + + t.Run("test HTTP error", func(t *testing.T) { + _, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(999, "python-http-error", false, "") + assert.Error(t, err, "Expected error but got success") + }) + + t.Run("test auto create success", func(t *testing.T) { + result, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(815, "0", true, "autocreate") + assert.NoError(t, err, "GetProjectVersionDetailsByNameAndProjectID call not successful") + assert.Equal(t, "0", *result.Name, "Expected to get project version with different name") + assert.Equal(t, "autocreate", *result.Project.Name, "Expected to get project with different name") + }) +} + +func TestGetProjectVersionAttributesByProjectVersionID(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/4711/attributes" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [{"_href": "https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, + "values": null,"guid": "gdgfdgfdgfdgfd","id": 4712,"value": "abcd"}],"count": 8,"responseCode": 200}`)) + return + } + if req.URL.Path == "/projectVersions/777/attributes" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [],"count": 0,"responseCode": 404,"links": {}}`)) + return + } + if req.URL.Path == "/projectVersions/999/attributes" { + rw.WriteHeader(500) + return + } + }) + + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetProjectVersionAttributesByProjectVersionID(4711) + assert.NoError(t, err, "GetProjectVersionAttributesByProjectVersionID call not successful") + assert.Equal(t, "abcd", *result[0].Value, "Expected to get attribute with different value") + assert.Equal(t, int64(4712), result[0].ID, "Expected to get attribute with different id") + }) + + t.Run("test empty", func(t *testing.T) { + result, err := sys.GetProjectVersionAttributesByProjectVersionID(777) + assert.NoError(t, err, "GetProjectVersionAttributesByID call not successful") + assert.Equal(t, 0, len(result), "Expected to not get any attributes") + }) + + t.Run("test HTTP error", func(t *testing.T) { + _, err := sys.GetProjectVersionAttributesByProjectVersionID(999) + assert.Error(t, err, "Expected error but got success") + }) +} + +func TestSetProjectVersionAttributesByProjectVersionID(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/4711/attributes" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyString := string(bodyBytes) + response := `{"data": ` + response += bodyString + response += `,"count": 1,"responseCode": 200}` + rw.WriteHeader(200) + rw.Write([]byte(response)) + return + } + }) + + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + value := "abcd" + defID := int64(18) + attributes := []*models.Attribute{&models.Attribute{ID: 4712, Value: &value, AttributeDefinitionID: &defID}} + result, err := sys.SetProjectVersionAttributesByProjectVersionID(4711, attributes) + assert.NoError(t, err, "SetProjectVersionAttributesByProjectVersionID call not successful") + assert.Equal(t, 1, len(result), "Expected to get slice with different amount of values") + assert.Equal(t, "abcd", *result[0].Value, "Expected to get attribute with different value") + assert.Equal(t, int64(4712), result[0].ID, "Expected to get attribute with different id") + }) +} + +func TestCreateProjectVersion(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent := string(bodyBytes) + responseContent := `{"data": ` + responseContent += bodyContent + responseContent += `,"count": 1,"responseCode": 201,"links": {}}` + fmt.Println(responseContent) + rw.WriteHeader(201) + rw.Write([]byte(responseContent)) + return + } + }) + + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + int64Value := int64(65) + int32Value := int32(876) + float32Value := float32(19.12) + now := models.NewIso8601MilliDateTime() + enabled := true + disabled := false + name := "Test new PV" + owner := "someUser" + masterGUID := "dsadaoudoiud" + project := models.Project{CreatedBy: &owner, CreationDate: now, Description: name, ID: int64Value, IssueTemplateID: &name, Name: &name} + projectVersionState := models.ProjectVersionState{AnalysisResultsExist: &disabled, AnalysisUploadEnabled: &disabled, + AttentionRequired: &disabled, AuditEnabled: &enabled, BatchBugSubmissionExists: &disabled, Committed: &enabled, + CriticalPriorityIssueCountDelta: &int32Value, DeltaPeriod: &int32Value, ExtraMessage: &name, HasCustomIssues: &disabled, + ID: &int64Value, IssueCountDelta: &int32Value, LastFprUploadDate: &now, MetricEvaluationDate: &now, PercentAuditedDelta: &float32Value, + PercentCriticalPriorityIssuesAuditedDelta: &float32Value} + version := models.ProjectVersion{AssignedIssuesCount: int64Value, Project: &project, Name: &name, Active: &enabled, + Committed: &enabled, AttachmentsOutOfDate: disabled, AutoPredict: disabled, BugTrackerEnabled: &disabled, + CustomTagValuesAutoApply: disabled, RefreshRequired: disabled, Owner: &owner, ServerVersion: &float32Value, + SnapshotOutOfDate: &disabled, StaleIssueTemplate: &disabled, MasterAttrGUID: &masterGUID, + LatestScanID: &int64Value, IssueTemplateName: &name, IssueTemplateModifiedTime: &int64Value, + IssueTemplateID: &name, Description: &name, CreatedBy: &owner, BugTrackerPluginID: &name, Mode: "NONE", + CurrentState: &projectVersionState, ID: int64Value, LoadProperties: "", CreationDate: &now, + MigrationVersion: float32Value, ObfuscatedID: "", PredictionPolicy: "", SecurityGroup: "", + SiteID: "", SourceBasePath: "", Status: "", TracesOutOfDate: false} + result, err := sys.CreateProjectVersion(&version) + assert.NoError(t, err, "CreateProjectVersion call not successful") + assert.Equal(t, name, *result.Name, "Expected to get PV with different value") + assert.Equal(t, int64(65), result.ID, "Expected to get PV with different id") + }) +} + +func TestProjectVersionCopyFromPartial(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/action/copyFromPartial" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.Write([]byte( + `{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + expected := `{"copyAnalysisProcessingRules":true,"copyBugTrackerConfiguration":true,"copyCurrentStateFpr":true,"copyCustomTags":true,"previousProjectVersionId":10172,"projectVersionId":10173} +` + err := sys.ProjectVersionCopyFromPartial(10172, 10173) + assert.NoError(t, err, "ProjectVersionCopyFromPartial call not successful") + assert.Equal(t, expected, bodyContent, "Different request content expected") + }) +} + +func TestProjectVersionCopyCurrentState(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/action/copyCurrentState" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.Write([]byte( + `{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + expected := `{"copyCurrentStateFpr":true,"previousProjectVersionId":10172,"projectVersionId":10173} +` + err := sys.ProjectVersionCopyCurrentState(10172, 10173) + assert.NoError(t, err, "ProjectVersionCopyCurrentState call not successful") + assert.Equal(t, expected, bodyContent, "Different request content expected") + }) +} + +func TestProjectVersionCopyPermissions(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + referenceContent := `[{"displayName":"some user","email":"some.one@test.com","entityName":"some_user","firstName":"some","id":589,"lastName":"user","type":"User"}] +` + response := `{"data": ` + response += referenceContent + response += `,"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/authEntities" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + if req.URL.Path == "/projectVersions/10173/authEntities" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + err := sys.ProjectVersionCopyPermissions(10172, 10173) + assert.NoError(t, err, "ProjectVersionCopyPermissions call not successful") + assert.Equal(t, referenceContent, bodyContent, "Different request content expected") + }) +} + +func TestCommitProjectVersion(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + referenceContent := `{"active":null,"bugTrackerEnabled":null,"bugTrackerPluginId":null,"committed":true,"createdBy":null,"creationDate":null,"description":null,"issueTemplateId":null,"issueTemplateModifiedTime":null,"issueTemplateName":null,"latestScanId":null,"masterAttrGuid":null,"name":null,"owner":null,"serverVersion":null,"snapshotOutOfDate":null,"staleIssueTemplate":null} +` + response := `{"data": ` + response += referenceContent + response += `,"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.CommitProjectVersion(10172) + assert.NoError(t, err, "CommitProjectVersion call not successful") + assert.Equal(t, true, *result.Committed, "Different result content expected") + assert.Equal(t, referenceContent, bodyContent, "Different request content expected") + }) +} + +func TestInactivateProjectVersion(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + referenceContent := `{"active":false,"bugTrackerEnabled":null,"bugTrackerPluginId":null,"committed":true,"createdBy":null,"creationDate":null,"description":null,"issueTemplateId":null,"issueTemplateModifiedTime":null,"issueTemplateName":null,"latestScanId":null,"masterAttrGuid":null,"name":null,"owner":null,"serverVersion":null,"snapshotOutOfDate":null,"staleIssueTemplate":null} +` + response := `{"data": ` + response += referenceContent + response += `,"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.inactivateProjectVersion(10172) + assert.NoError(t, err, "InactivateProjectVersion call not successful") + assert.Equal(t, true, *result.Committed, "Different result content expected") + assert.Equal(t, false, *result.Active, "Different result content expected") + assert.Equal(t, referenceContent, bodyContent, "Different request content expected") + }) +} + +func TestGetArtifactsOfProjectVersion(t *testing.T) { + // Start a local HTTP server + response := `{"data": [{"artifactType": "FPR","fileName": "df54e2ade34c4f6aaddf35679dd87a21.tmp","approvalDate": null,"messageCount": 0, + "scanErrorsCount": 0,"uploadIP": "10.238.8.48","allowApprove": false,"allowPurge": false,"lastScanDate": "2019-11-26T22:37:52.000+0000", + "fileURL": null,"id": 56,"purged": false,"webInspectStatus": "NONE","inModifyingStatus": false,"originalFileName": "result.fpr", + "allowDelete": true,"scaStatus": "PROCESSED","indexed": true,"runtimeStatus": "NONE","userName": "some_user","versionNumber": null, + "otherStatus": "NOT_EXIST","uploadDate": "2019-11-26T22:38:11.813+0000","approvalComment": null,"approvalUsername": null,"fileSize": 984703, + "messages": "","auditUpdated": false,"status": "PROCESS_COMPLETE"}],"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/artifacts" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetArtifactsOfProjectVersion(10172) + assert.NoError(t, err, "GetArtifactsOfProjectVersion call not successful") + assert.Equal(t, 1, len(result), "Different result content expected") + assert.Equal(t, int64(56), result[0].ID, "Different result content expected") + }) +} + +func TestGetFilterSetOfProjectVersionByTitle(t *testing.T) { + // Start a local HTTP server + response := `{"data":[{"defaultFilterSet":true,"folders":[ + {"id":1,"guid":"4711","name":"Corporate Security Requirements","color":"000000"}, + {"id":2,"guid":"4712","name":"Audit All","color":"ff0000"}, + {"id":3,"guid":"4713","name":"Spot Checks of Each Category","color":"ff8000"}, + {"id":4,"guid":"4714","name":"Optional","color":"808080"}],"description":"", + "guid":"666","title":"Special"}],"count":1,"responseCode":200}}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/filterSets" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetFilterSetOfProjectVersionByTitle(10172, "Special") + assert.NoError(t, err, "GetFilterSetOfProjectVersionByTitle call not successful") + assert.Equal(t, "Special", result.Title, "Different result content expected") + }) + + t.Run("test default", func(t *testing.T) { + result, err := sys.GetFilterSetOfProjectVersionByTitle(10172, "") + assert.NoError(t, err, "GetFilterSetOfProjectVersionByTitle call not successful") + assert.Equal(t, "Special", result.Title, "Different result content expected") + }) +} + +func TestGetIssueFilterSelectorOfProjectVersionByName(t *testing.T) { + // Start a local HTTP server + response := `{"data":{"groupBySet": [{"entityType": "CUSTOMTAG","guid": "adsffghjkl","displayName": "Analysis", + "value": "87f2364f-dcd4-49e6-861d-f8d3f351686b","description": ""},{"entityType": "ISSUE","guid": "lkjhgfd", + "displayName": "Category","value": "11111111-1111-1111-1111-111111111165","description": ""}],"filterBySet":[{ + "entityType": "CUSTOMTAG","filterSelectorType": "LIST","guid": "87f2364f-dcd4-49e6-861d-f8d3f351686b","displayName": "Analysis", + "value": "87f2364f-dcd4-49e6-861d-f8d3f351686b","description": "The analysis tag must be set.", + "selectorOptions": []},{"entityType": "FOLDER","filterSelectorType": "LIST","guid": "userAssignment","displayName": "Folder", + "value": "FOLDER","description": "","selectorOptions": []}]},"responseCode":200}}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/issueSelectorSet" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success one", func(t *testing.T) { + result, err := sys.GetIssueFilterSelectorOfProjectVersionByName(10172, []string{"Analysis"}, nil) + assert.NoError(t, err, "GetIssueFilterSelectorOfProjectVersionByName call not successful") + assert.NotNil(t, result, "Expected non nil value") + assert.Equal(t, 1, len(result.FilterBySet), "Different result expected") + assert.Equal(t, 1, len(result.GroupBySet), "Different result expected") + }) + + t.Run("test success several", func(t *testing.T) { + result, err := sys.GetIssueFilterSelectorOfProjectVersionByName(10172, []string{"Analysis", "Folder"}, nil) + assert.NoError(t, err, "GetIssueFilterSelectorOfProjectVersionByName call not successful") + assert.NotNil(t, result, "Expected non nil value") + assert.Equal(t, 2, len(result.FilterBySet), "Different result expected") + assert.Equal(t, 1, len(result.GroupBySet), "Different result expected") + }) + + t.Run("test empty", func(t *testing.T) { + result, err := sys.GetIssueFilterSelectorOfProjectVersionByName(10172, []string{"Some", "Other"}, nil) + assert.NoError(t, err, "GetIssueFilterSelectorOfProjectVersionByName call not successful") + assert.NotNil(t, result, "Expected non nil value") + assert.Equal(t, 0, len(result.FilterBySet), "Different result expected") + assert.Equal(t, 0, len(result.GroupBySet), "Different result expected") + }) +} + +func TestReduceIssueFilterSelectorSet(t *testing.T) { + sys, _ := spinUpServer(func(rw http.ResponseWriter, req *http.Request) {}) + name1 := "Special" + name2 := "Other" + guid := "FOLDER" + options := []*models.SelectorOption{&models.SelectorOption{GUID: "1234567", DisplayName: "Test"}, &models.SelectorOption{GUID: "1234568", DisplayName: "Test2"}} + filterSet := models.IssueFilterSelectorSet{FilterBySet: []*models.IssueFilterSelector{}, GroupBySet: []*models.IssueSelector{}} + filterSet.FilterBySet = append(filterSet.FilterBySet, &models.IssueFilterSelector{DisplayName: name1, SelectorOptions: options}) + filterSet.FilterBySet = append(filterSet.FilterBySet, &models.IssueFilterSelector{DisplayName: name2}) + filterSet.GroupBySet = append(filterSet.GroupBySet, &models.IssueSelector{DisplayName: &name2, GUID: &guid}) + reducedFilterSet := sys.ReduceIssueFilterSelectorSet(&filterSet, []string{"Special"}, []string{"Test"}) + assert.Equal(t, 1, len(reducedFilterSet.FilterBySet), "Different result expected") + assert.Equal(t, 1, len(reducedFilterSet.FilterBySet[0].SelectorOptions), "Different result expected") + assert.Equal(t, "Test", reducedFilterSet.FilterBySet[0].SelectorOptions[0].DisplayName, "Different result expected") + assert.Equal(t, 0, len(reducedFilterSet.GroupBySet), "Different result expected") +} + +func TestGetProjectIssuesByIDAndFilterSetGroupedBySelector(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/filterSets" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data":[{"defaultFilterSet":true,"folders":[ + {"id":1,"guid":"4711","name":"Corporate Security Requirements","color":"000000"}, + {"id":2,"guid":"4712","name":"Audit All","color":"ff0000"}, + {"id":3,"guid":"4713","name":"Spot Checks of Each Category","color":"ff8000"}, + {"id":4,"guid":"4714","name":"Optional","color":"808080"}],"description":"", + "guid":"666","title":"Special"}],"count":1,"responseCode":200}}`)) + return + } + if req.URL.Path == "/projectVersions/10172/issueGroups" { + assert.Equal(t, "filterset=666&groupingtype=FOLDER&showsuppressed=true", req.URL.RawQuery) + return + } + rw.WriteHeader(400) + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + name := "Special" + guid := "FOLDER" + filterSet := models.IssueFilterSelectorSet{FilterBySet: []*models.IssueFilterSelector{}, GroupBySet: []*models.IssueSelector{}} + filterSet.FilterBySet = append(filterSet.FilterBySet, &models.IssueFilterSelector{DisplayName: name}) + filterSet.GroupBySet = append(filterSet.GroupBySet, &models.IssueSelector{DisplayName: &name, GUID: &guid}) + _, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(10172, "", "666", &filterSet) + assert.NoError(t, err, "GetProjectIssuesByIDAndFilterSetGroupedByFolder call not successful") + }) +} + +func TestGetProjectIssuesByIDAndFilterSetGroupedByCategory(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/filterSets" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data":[{"defaultFilterSet":true,"folders":[ + {"id":1,"guid":"4711","name":"Corporate Security Requirements","color":"000000"}, + {"id":2,"guid":"4712","name":"Audit All","color":"ff0000"}, + {"id":3,"guid":"4713","name":"Spot Checks of Each Category","color":"ff8000"}, + {"id":4,"guid":"4714","name":"Optional","color":"808080"}],"description":"", + "guid":"666","title":"Special"}],"count":1,"responseCode":200}}`)) + return + } + if req.URL.Path == "/projectVersions/10172/issueGroups" { + assert.Equal(t, "filter=4713&filterset=666&groupingtype=11111111-1111-1111-1111-111111111165&showsuppressed=true", req.URL.RawQuery) + return + } + rw.WriteHeader(400) + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + name := "Special" + guid := "11111111-1111-1111-1111-111111111165" + filterSet := models.IssueFilterSelectorSet{FilterBySet: []*models.IssueFilterSelector{}, GroupBySet: []*models.IssueSelector{}} + filterSet.FilterBySet = append(filterSet.FilterBySet, &models.IssueFilterSelector{DisplayName: name}) + filterSet.GroupBySet = append(filterSet.GroupBySet, &models.IssueSelector{DisplayName: &name, GUID: &guid}) + _, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(10172, "4713", "666", &filterSet) + assert.NoError(t, err, "GetProjectIssuesByIDAndFilterSetGroupedByCategory call not successful") + }) +} + +func TestGetProjectIssuesByIDAndFilterSetGroupedByAnalysis(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/filterSets" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data":[{"defaultFilterSet":true,"folders":[ + {"id":1,"guid":"4711","name":"Corporate Security Requirements","color":"000000"}, + {"id":2,"guid":"4712","name":"Audit All","color":"ff0000"}, + {"id":3,"guid":"4713","name":"Spot Checks of Each Category","color":"ff8000"}, + {"id":4,"guid":"4714","name":"Optional","color":"808080"}],"description":"", + "guid":"666","title":"Special"}],"count":1,"responseCode":200}}`)) + return + } + if req.URL.Path == "/projectVersions/10172/issueGroups" { + assert.Equal(t, "filterset=666&groupingtype=87f2364f-dcd4-49e6-861d-f8d3f351686b&showsuppressed=true", req.URL.RawQuery) + return + } + rw.WriteHeader(400) + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + name := "Special" + guid := "87f2364f-dcd4-49e6-861d-f8d3f351686b" + filterSet := models.IssueFilterSelectorSet{FilterBySet: []*models.IssueFilterSelector{}, GroupBySet: []*models.IssueSelector{}} + filterSet.FilterBySet = append(filterSet.FilterBySet, &models.IssueFilterSelector{DisplayName: name}) + filterSet.GroupBySet = append(filterSet.GroupBySet, &models.IssueSelector{DisplayName: &name, GUID: &guid}) + _, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(10172, "", "666", &filterSet) + assert.NoError(t, err, "GetProjectIssuesByIDAndFilterSetGroupedByAnalysis call not successful") + }) +} + +func TestGetIssueStatisticsOfProjectVersion(t *testing.T) { + // Start a local HTTP server + response := `{"data": [{"filterSetId": 3887,"hiddenCount": 0,"suppressedDisplayableCount": 0,"suppressedCount": 11,"hiddenDisplayableCount": 0,"projectVersionId": 10172, + "removedDisplayableCount": 0,"removedCount": 747}],"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projectVersions/10172/issueStatistics" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetIssueStatisticsOfProjectVersion(10172) + assert.NoError(t, err, "GetArtifactsOfProjectVersion call not successful") + assert.Equal(t, 1, len(result), "Different result content expected") + assert.Equal(t, int64(10172), *result[0].ProjectVersionID, "Different result content expected") + assert.Equal(t, int32(11), *result[0].SuppressedCount, "Different result content expected") + }) +} + +func TestGenerateQGateReport(t *testing.T) { + // Start a local HTTP server + data := "" + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/reports" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + data = string(bodyBytes) + response := `{"data": ` + response += data + response += `,"responseCode": 201}` + rw.WriteHeader(201) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GenerateQGateReport(2837, 17540, 18, "Fortify", "develop", "PDF") + assert.NoError(t, err, "GetArtifactsOfProjectVersion call not successful") + assert.Equal(t, int64(2837), result.Projects[0].ID, "Different result content expected") + assert.Equal(t, int64(17540), result.Projects[0].Versions[0].ID, "Different result content expected") + assert.Equal(t, int64(18), *result.ReportDefinitionID, "Different result content expected") + }) +} + +func TestGetReportDetails(t *testing.T) { + // Start a local HTTP server + response := `{"data": {"id":999,"name":"FortifyReport","note":"","type":"PORTFOLIO","reportDefinitionId":18,"format":"PDF", + "projects":[{"id":2837,"name":"Fortify","versions":[{"id":17540,"name":"develop"}]}],"projectVersionDisplayName":"develop", + "inputReportParameters":[{"name":"Q-gate-report","identifier":"projectVersionId","paramValue":17540,"type":"SINGLE_PROJECT"}]},"count": 1,"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/reports/999" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.GetReportDetails(999) + assert.NoError(t, err, "GetReportDetails call not successful") + assert.Equal(t, int64(999), result.ID, "Different result content expected") + }) +} + +func TestGetFileUploadToken(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + reference := `{"fileTokenType":"UPLOAD"} +` + response := `{"data": {"fileTokenType": "UPLOAD","token": "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj"},"responseCode": 201}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.WriteHeader(201) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.getFileUploadToken() + assert.NoError(t, err, "getFileUploadToken call not successful") + assert.Equal(t, "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj", result.Token, "Different result content expected") + assert.Equal(t, reference, bodyContent, "Different request content expected") + }) +} + +func TestGetFileDownloadToken(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + reference := `{"fileTokenType":"DOWNLOAD"} +` + response := `{"data": {"fileTokenType": "DOWNLOAD","token": "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj"},"responseCode": 201}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.WriteHeader(201) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.getFileDownloadToken() + assert.NoError(t, err, "getFileDownloadToken call not successful") + assert.Equal(t, "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj", result.Token, "Different result content expected") + assert.Equal(t, reference, bodyContent, "Different request content expected") + }) +} + +func TestGetReportFileToken(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + reference := `{"fileTokenType":"REPORT_FILE"} +` + response := `{"data": {"fileTokenType": "REPORT_FILE","token": "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj"},"responseCode": 201}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.WriteHeader(201) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + result, err := sys.getReportFileToken() + assert.NoError(t, err, "getReportFileToken call not successful") + assert.Equal(t, "ZjE1OTdjZjEtMjAzNS00NTFmLThiOWItNzBkYzI0MWEzZGNj", result.Token, "Different result content expected") + assert.Equal(t, reference, bodyContent, "Different request content expected") + }) +} + +func TestInvalidateFileToken(t *testing.T) { + // Start a local HTTP server + response := `{"responseCode": 200}` + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" && req.Method == "DELETE" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(response)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + err := sys.invalidateFileTokens() + assert.NoError(t, err, "invalidateFileTokens call not successful") + }) +} + +func TestUploadResultFile(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + getTokenCalled := false + invalidateTokenCalled := false + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" && req.Method == "DELETE" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(`{"responseCode": 200}`)) + invalidateTokenCalled = true + return + } + if req.URL.Path == "/fileTokens" && req.Method == "POST" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte(`{"data": {"token": "89ee873"}, "responseCode": 201}`)) + getTokenCalled = true + return + } + if req.URL.Path == "/upload/resultFileUpload.html" && req.URL.RawQuery == "mat=89ee873" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent = string(bodyBytes) + rw.WriteHeader(200) + rw.Write([]byte("OK")) + return + } + }) + // Close the server when test finishes + defer server.Close() + + testFile, err := ioutil.TempFile("", "result.fpr") + if err != nil { + t.FailNow() + } + defer os.RemoveAll(testFile.Name()) // clean up + + t.Run("test success", func(t *testing.T) { + err := sys.UploadResultFile("/upload/resultFileUpload.html", testFile.Name(), 10770) + assert.NoError(t, err, "UploadFile call not successful") + assert.Contains(t, bodyContent, `Content-Disposition: form-data; name="file"; filename=`, "Expected different content in request body") + assert.Contains(t, bodyContent, `Content-Disposition: form-data; name="entityId"`, "Expected different content in request body") + assert.Contains(t, bodyContent, `10770`, "Expected different content in request body") + assert.Equal(t, true, getTokenCalled, "Expected GetUploadToken to be called") + assert.Equal(t, true, invalidateTokenCalled, "Expected InvalidateFileTokens to be called") + }) +} + +func TestDownloadResultFile(t *testing.T) { + // Start a local HTTP server + bodyContent := "" + getTokenCalled := false + invalidateTokenCalled := false + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" && req.Method == "DELETE" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(`{"responseCode": 200}`)) + invalidateTokenCalled = true + return + } + if req.URL.Path == "/fileTokens" && req.Method == "POST" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte(`{"data": {"token": "89ee873"}, "responseCode": 201}`)) + getTokenCalled = true + return + } + if req.URL.Path == "/download/currentStateFprDownload.html" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyContent = req.URL.RawQuery + rw.WriteHeader(200) + rw.Write([]byte("OK")) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + data, err := sys.DownloadResultFile("/download/currentStateFprDownload.html", 10775) + assert.NoError(t, err, "DownloadResultFile call not successful") + assert.Equal(t, "id=10775&mat=89ee873", bodyContent, "Expected different request body") + assert.Equal(t, []byte("OK"), data, "Expected different result") + assert.Equal(t, true, getTokenCalled, "Expected GetUploadToken to be called") + assert.Equal(t, true, invalidateTokenCalled, "Expected InvalidateFileTokens to be called") + }) +} + +func TestDownloadReportFile(t *testing.T) { + // Start a local HTTP server + getTokenCalled := false + invalidateTokenCalled := false + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/fileTokens" && req.Method == "DELETE" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(`{"responseCode": 200}`)) + invalidateTokenCalled = true + return + } + if req.URL.Path == "/fileTokens" && req.Method == "POST" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte(`{"data": {"token": "89ee873"}, "responseCode": 201}`)) + getTokenCalled = true + return + } + if req.URL.Path == "/transfer/reportDownload.html" && req.URL.RawQuery == "id=10775&mat=89ee873" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte("OK")) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + data, err := sys.DownloadReportFile("/transfer/reportDownload.html", 10775) + assert.NoError(t, err, "DownloadReportFile call not successful") + assert.Equal(t, []byte("OK"), data, "Expected different result") + assert.Equal(t, true, getTokenCalled, "Expected GetUploadToken to be called") + assert.Equal(t, true, invalidateTokenCalled, "Expected InvalidateFileTokens to be called") + }) +} + +func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { + // Start a local HTTP server + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projects/4711/versions" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(`{"data": [], "count": 0, "responseCode": 200}`)) + return + } + if req.URL.Path == "/projectVersions" && req.Method == "POST" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyContent := string(bodyBytes) + responseContent := `{"data": ` + responseContent += bodyContent + responseContent += `,"count": 1,"responseCode": 201,"links": {}}` + fmt.Println(responseContent) + rw.WriteHeader(201) + rw.Write([]byte(responseContent)) + return + } + if req.URL.Path == "/projectVersions/4711/attributes" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data": [{"_href": "https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, + "values": null,"guid": "gdgfdgfdgfdgfd","id": 4712,"value": "abcd"}],"count": 8,"responseCode": 200}`)) + return + } + if req.URL.Path == "/projectVersions/4712/attributes" { + header := rw.Header() + header.Add("Content-type", "application/json") + bodyBytes, _ := ioutil.ReadAll(req.Body) + bodyString := string(bodyBytes) + response := `{"data": ` + response += bodyString + response += `,"count": 1,"responseCode": 200}` + rw.WriteHeader(200) + rw.Write([]byte(response)) + return + } + if req.URL.Path == "/projectVersions/action/copyFromPartial" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + return + } + if req.URL.Path == "/projectVersions/10172" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data": {"active":null,"bugTrackerEnabled":null,"bugTrackerPluginId":null,"committed":true,"createdBy":null,"creationDate":null,"description":null,"issueTemplateId":null,"issueTemplateModifiedTime":null,"issueTemplateName":null,"latestScanId":null,"masterAttrGuid":null,"name":null,"owner":null,"serverVersion":null,"snapshotOutOfDate":null,"staleIssueTemplate":null}, "responseCode": 200}`)) + return + } + if req.URL.Path == "/projectVersions/action/copyCurrentState" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte( + `{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + return + } + if req.URL.Path == "/projectVersions/10172/authEntities" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data": [{"displayName":"some user","email":"some.one@test.com","entityName":"some_user","firstName":"some","id":589,"lastName":"user","type":"User"}],"count": 1,"responseCode": 200}`)) + return + } + if req.URL.Path == "/projectVersions/10173/authEntities" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data": [{"displayName":"some user","email":"some.one@test.com","entityName":"some_user","firstName":"some","id":589,"lastName":"user","type":"User"}],"count": 1,"responseCode": 200}`)) + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + int64Value := int64(65) + int32Value := int32(876) + float32Value := float32(19.12) + now := models.NewIso8601MilliDateTime() + enabled := true + disabled := false + name := "Test new PV" + owner := "someUser" + masterGUID := "dsadaoudoiud" + project := models.Project{CreatedBy: &owner, CreationDate: now, Description: name, ID: int64Value, IssueTemplateID: &name, Name: &name} + projectVersionState := models.ProjectVersionState{AnalysisResultsExist: &disabled, AnalysisUploadEnabled: &disabled, + AttentionRequired: &disabled, AuditEnabled: &enabled, BatchBugSubmissionExists: &disabled, Committed: &enabled, + CriticalPriorityIssueCountDelta: &int32Value, DeltaPeriod: &int32Value, ExtraMessage: &name, HasCustomIssues: &disabled, + ID: &int64Value, IssueCountDelta: &int32Value, LastFprUploadDate: &now, MetricEvaluationDate: &now, PercentAuditedDelta: &float32Value, + PercentCriticalPriorityIssuesAuditedDelta: &float32Value} + masterProjectVersion := models.ProjectVersion{AssignedIssuesCount: int64Value, Project: &project, Name: &name, Active: &enabled, + Committed: &enabled, AttachmentsOutOfDate: disabled, AutoPredict: disabled, BugTrackerEnabled: &disabled, + CustomTagValuesAutoApply: disabled, RefreshRequired: disabled, Owner: &owner, ServerVersion: &float32Value, + SnapshotOutOfDate: &disabled, StaleIssueTemplate: &disabled, MasterAttrGUID: &masterGUID, + LatestScanID: &int64Value, IssueTemplateName: &name, IssueTemplateModifiedTime: &int64Value, + IssueTemplateID: &name, Description: &name, CreatedBy: &owner, BugTrackerPluginID: &name, Mode: "NONE", + CurrentState: &projectVersionState, ID: int64Value, LoadProperties: "", CreationDate: &now, + MigrationVersion: float32Value, ObfuscatedID: "", PredictionPolicy: "", SecurityGroup: "", + SiteID: "", SourceBasePath: "", Status: "", TracesOutOfDate: false} + prProjectVersion, err := sys.LookupOrCreateProjectVersionDetailsForPullRequest(4711, &masterProjectVersion, "PR-815") + assert.NoError(t, err, "LookupOrCreateProjectVersionDetailsForPullRequest call not successful") + assert.Equal(t, "PR-815", *prProjectVersion.Name, "Expected different result") + assert.Equal(t, masterProjectVersion.Description, prProjectVersion.Description, "Expected different result") + assert.Equal(t, masterProjectVersion.Active, prProjectVersion.Active, "Expected different result") + assert.Equal(t, masterProjectVersion.Committed, prProjectVersion.Committed, "Expected different result") + assert.Equal(t, masterProjectVersion.Project.Name, prProjectVersion.Project.Name, "Expected different result") + assert.Equal(t, masterProjectVersion.Project.Description, prProjectVersion.Project.Description, "Expected different result") + assert.Equal(t, masterProjectVersion.Project.ID, prProjectVersion.Project.ID, "Expected different result") + assert.Equal(t, masterProjectVersion.IssueTemplateID, prProjectVersion.IssueTemplateID, "Expected different result") + }) +} + +func TestMergeProjectVersionStateOfPRIntoMaster(t *testing.T) { + // Start a local HTTP server + getPRProjectVersionCalled := false + invalidateTokenCalled := false + getTokenCalled := false + downloadCalled := false + uploadCalled := false + inactivateCalled := false + sys, server := spinUpServer(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/projects/4711/versions" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data":[{"latestScanId":null,"serverVersion":17.2,"tracesOutOfDate":false,"attachmentsOutOfDate":false,"description":"", + "project":{"id":4711,"name":"product.some.com","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", + "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, + "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, + "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", + "migrationVersion":null,"createdBy":"admin","name":"PR-815","siteId":null,"staleIssueTemplate":false,"autoPredict":null, + "currentState":{"id":10172,"committed":true,"attentionRequired":false,"analysisResultsExist":true,"auditEnabled":true, + "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, + "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, + "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + getPRProjectVersionCalled = true + return + } + if req.URL.Path == "/fileTokens" && req.Method == "DELETE" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte(`{"responseCode": 200}`)) + invalidateTokenCalled = true + return + } + if req.URL.Path == "/fileTokens" && req.Method == "POST" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(201) + rw.Write([]byte(`{"data": {"token": "89ee873"}, "responseCode": 201}`)) + getTokenCalled = true + return + } + if req.URL.Path == "/download/currentStateFprDownload.html" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte("OK")) + downloadCalled = true + return + } + if req.URL.Path == "/upload/resultFileUpload.html" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.WriteHeader(200) + rw.Write([]byte("OK")) + uploadCalled = true + return + } + if req.URL.Path == "/projectVersions/10172" { + header := rw.Header() + header.Add("Content-type", "application/json") + rw.Write([]byte(`{"data": {"active":false,"bugTrackerEnabled":null,"bugTrackerPluginId":null,"committed":true,"createdBy":null,"creationDate":null,"description":null,"issueTemplateId":null,"issueTemplateModifiedTime":null,"issueTemplateName":null,"latestScanId":null,"masterAttrGuid":null,"name":null,"owner":null,"serverVersion":null,"snapshotOutOfDate":null,"staleIssueTemplate":null}, "responseCode": 200}`)) + inactivateCalled = true + return + } + }) + // Close the server when test finishes + defer server.Close() + + t.Run("test success", func(t *testing.T) { + err := sys.MergeProjectVersionStateOfPRIntoMaster("/download/currentStateFprDownload.html", "/upload/resultFileUpload.html", 4711, 10171, "PR-815") + assert.NoError(t, err, "MergeProjectVersionStateOfPRIntoMaster call not successful") + assert.Equal(t, true, getPRProjectVersionCalled, "Expected different value") + assert.Equal(t, true, invalidateTokenCalled, "Expected different value") + assert.Equal(t, true, getTokenCalled, "Expected different value") + assert.Equal(t, true, downloadCalled, "Expected different value") + assert.Equal(t, true, uploadCalled, "Expected different value") + assert.Equal(t, true, inactivateCalled, "Expected different value") + }) +} diff --git a/pkg/generator/helper/helper.go b/pkg/generator/helper/helper.go index 5668345f5..cc4b79239 100644 --- a/pkg/generator/helper/helper.go +++ b/pkg/generator/helper/helper.go @@ -7,9 +7,12 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "strings" "text/template" + "github.com/Masterminds/sprig" + "github.com/SAP/jenkins-library/pkg/config" "github.com/SAP/jenkins-library/pkg/piperutils" ) @@ -56,8 +59,13 @@ import ( ) type {{ .StepName }}Options struct { - {{- range $key, $value := .StepParameters }} - {{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + `{{end}} + {{- $names := list ""}} + {{- range $key, $value := uniqueName .StepParameters }} + {{ if ne (has $value.Name $names) true -}} + {{ $names | last }}{{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + ` + {{- else -}} + {{- $names = append $names $value.Name }} {{ end -}} + {{ end }} } {{ range $notused, $oRes := .OutputResources }} @@ -124,7 +132,7 @@ func {{.CobraCmdFuncName}}() *cobra.Command { } func {{.FlagsFunc}}(cmd *cobra.Command, stepConfig *{{.StepName}}Options) { - {{- range $key, $value := .StepParameters }} + {{- range $key, $value := uniqueName .StepParameters }} cmd.Flags().{{ $value.Type | flagType }}(&stepConfig.{{ $value.Name | golangName }}, "{{ $value.Name }}", {{ $value.Default }}, "{{ $value.Description }}"){{ end }} {{- printf "\n" }} {{- range $key, $value := .StepParameters }}{{ if $value.Mandatory }} @@ -300,9 +308,9 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) { case "int": param.Default = fmt.Sprintf("%v", param.Default) case "string": - param.Default = fmt.Sprintf("\"%v\"", param.Default) + param.Default = fmt.Sprintf("`%v`", param.Default) case "[]string": - param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(getStringSliceFromInterface(param.Default), "\", \"")) + param.Default = fmt.Sprintf("[]string{`%v`}", strings.Join(getStringSliceFromInterface(param.Default), "`, `")) default: return false, fmt.Errorf("Meta data type not set or not known: '%v'", param.Type) } @@ -432,12 +440,12 @@ func MetadataFiles(sourceDirectory string) ([]string, error) { func stepTemplate(myStepInfo stepInfo) []byte { - funcMap := template.FuncMap{ - "flagType": flagType, - "golangName": golangNameTitle, - "title": strings.Title, - "longName": longName, - } + funcMap := sprig.HermeticTxtFuncMap() + funcMap["flagType"] = flagType + funcMap["golangName"] = golangNameTitle + funcMap["title"] = strings.Title + funcMap["longName"] = longName + funcMap["uniqueName"] = mustUniqName tmpl, err := template.New("step").Funcs(funcMap).Parse(stepGoTemplate) checkError(err) @@ -451,11 +459,11 @@ func stepTemplate(myStepInfo stepInfo) []byte { func stepTestTemplate(myStepInfo stepInfo) []byte { - funcMap := template.FuncMap{ - "flagType": flagType, - "golangName": golangNameTitle, - "title": strings.Title, - } + funcMap := sprig.HermeticTxtFuncMap() + funcMap["flagType"] = flagType + funcMap["golangName"] = golangNameTitle + funcMap["title"] = strings.Title + funcMap["uniqueName"] = mustUniqName tmpl, err := template.New("stepTest").Funcs(funcMap).Parse(stepTestGoTemplate) checkError(err) @@ -469,9 +477,9 @@ func stepTestTemplate(myStepInfo stepInfo) []byte { func stepImplementation(myStepInfo stepInfo) []byte { - funcMap := template.FuncMap{ - "title": strings.Title, - } + funcMap := sprig.HermeticTxtFuncMap() + funcMap["title"] = strings.Title + funcMap["uniqueName"] = mustUniqName tmpl, err := template.New("impl").Funcs(funcMap).Parse(stepGoImplementationTemplate) checkError(err) @@ -536,3 +544,27 @@ func getStringSliceFromInterface(iSlice interface{}) []string { return s } + +func mustUniqName(list []config.StepParameters) ([]config.StepParameters, error) { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + names := []string{} + dest := []config.StepParameters{} + var item config.StepParameters + for i := 0; i < l; i++ { + item = l2.Index(i).Interface().(config.StepParameters) + if !piperutils.ContainsString(names, item.Name) { + names = append(names, item.Name) + dest = append(dest, item) + } + } + + return dest, nil + default: + return nil, fmt.Errorf("Cannot find uniq on type %s", tp) + } +} diff --git a/pkg/generator/helper/helper_test.go b/pkg/generator/helper/helper_test.go index 675c5f18a..97cfe2b93 100644 --- a/pkg/generator/helper/helper_test.go +++ b/pkg/generator/helper/helper_test.go @@ -145,11 +145,11 @@ func TestSetDefaultParameters(t *testing.T) { } expected := []string{ - "\"val0\"", + "`val0`", "os.Getenv(\"PIPER_param1\")", "true", "false", - "[]string{\"val4_1\", \"val4_2\"}", + "[]string{`val4_1`, `val4_2`}", "[]string{}", "0", "1", diff --git a/pkg/generator/helper/testdata/TestProcessMetaFiles/custom_step_code_generated.golden b/pkg/generator/helper/testdata/TestProcessMetaFiles/custom_step_code_generated.golden index ec6b3da85..f4f061ca6 100644 --- a/pkg/generator/helper/testdata/TestProcessMetaFiles/custom_step_code_generated.golden +++ b/pkg/generator/helper/testdata/TestProcessMetaFiles/custom_step_code_generated.golden @@ -149,7 +149,7 @@ func TestStepCommand() *cobra.Command { } func addTestStepFlags(cmd *cobra.Command, stepConfig *testStepOptions) { - cmd.Flags().StringVar(&stepConfig.Param0, "param0", "val0", "param0 description") + cmd.Flags().StringVar(&stepConfig.Param0, "param0", `val0`, "param0 description") cmd.Flags().StringVar(&stepConfig.Param1, "param1", os.Getenv("PIPER_param1"), "param1 description") cmd.Flags().StringVar(&stepConfig.Param2, "param2", os.Getenv("PIPER_param2"), "param1 description") diff --git a/pkg/generator/helper/testdata/TestProcessMetaFiles/step_code_generated.golden b/pkg/generator/helper/testdata/TestProcessMetaFiles/step_code_generated.golden index fb6b1772c..2ca3c0114 100644 --- a/pkg/generator/helper/testdata/TestProcessMetaFiles/step_code_generated.golden +++ b/pkg/generator/helper/testdata/TestProcessMetaFiles/step_code_generated.golden @@ -148,7 +148,7 @@ func TestStepCommand() *cobra.Command { } func addTestStepFlags(cmd *cobra.Command, stepConfig *testStepOptions) { - cmd.Flags().StringVar(&stepConfig.Param0, "param0", "val0", "param0 description") + cmd.Flags().StringVar(&stepConfig.Param0, "param0", `val0`, "param0 description") cmd.Flags().StringVar(&stepConfig.Param1, "param1", os.Getenv("PIPER_param1"), "param1 description") cmd.Flags().StringVar(&stepConfig.Param2, "param2", os.Getenv("PIPER_param2"), "param1 description") diff --git a/pkg/http/http.go b/pkg/http/http.go index 789114232..84105ea06 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -2,6 +2,7 @@ package http import ( "bytes" + "context" "fmt" "io" "mime/multipart" @@ -12,19 +13,22 @@ import ( "time" "github.com/SAP/jenkins-library/pkg/log" + "github.com/motemen/go-nuts/roundtime" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // Client defines an http client object type Client struct { - maxRequestDuration time.Duration - transportTimeout time.Duration - username string - password string - token string - logger *logrus.Entry - cookieJar http.CookieJar + maxRequestDuration time.Duration + transportTimeout time.Duration + username string + password string + token string + logger *logrus.Entry + cookieJar http.CookieJar + doLogRequestBodyOnDebug bool + doLogResponseBodyOnDebug bool } // ClientOptions defines the options to be set on the client @@ -36,12 +40,39 @@ type ClientOptions struct { MaxRequestDuration time.Duration // TransportTimeout defaults to 10 seconds, if not specified. It is // used for the transport layer and duration of handshakes and such. - TransportTimeout time.Duration - Username string - Password string - Token string - Logger *logrus.Entry - CookieJar http.CookieJar + TransportTimeout time.Duration + Username string + Password string + Token string + Logger *logrus.Entry + CookieJar http.CookieJar + DoLogRequestBodyOnDebug bool + DoLogResponseBodyOnDebug bool +} + +// TransportWrapper is a wrapper for central logging capabilities +type TransportWrapper struct { + Transport http.RoundTripper + doLogRequestBodyOnDebug bool + doLogResponseBodyOnDebug bool +} + +// UploadRequestData encapsulates the parameters for calling uploader.Upload() +type UploadRequestData struct { + // Method is the HTTP method used for the request. Must be one of http.MethodPost or http.MethodPut. + Method string + // URL for the request + URL string + // File path to be stored in the created form field. + File string + // Form field name under which the file name will be stored. + FileFieldName string + // Additional form fields which will be added to the request if not nil. + FormFields map[string]string + // Reader from which the file contents will be read. + FileContent io.Reader + Header http.Header + Cookies []*http.Cookie } // Sender provides an interface to the piper http client for uid/pwd and token authenticated requests @@ -55,18 +86,36 @@ type Uploader interface { Sender UploadRequest(method, url, file, fieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) UploadFile(url, file, fieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) + Upload(data UploadRequestData) (*http.Response, error) } // UploadFile uploads a file's content as multipart-form POST request to the specified URL -func (c *Client) UploadFile(url, file, fieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) { - return c.UploadRequest(http.MethodPost, url, file, fieldName, header, cookies) +func (c *Client) UploadFile(url, file, fileFieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) { + return c.UploadRequest(http.MethodPost, url, file, fileFieldName, header, cookies) } // UploadRequest uploads a file's content as multipart-form with given http method request to the specified URL -func (c *Client) UploadRequest(method, url, file, fieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) { +func (c *Client) UploadRequest(method, url, file, fileFieldName string, header http.Header, cookies []*http.Cookie) (*http.Response, error) { + fileHandle, err := os.Open(file) + if err != nil { + return &http.Response{}, errors.Wrapf(err, "unable to locate file %v", file) + } + defer fileHandle.Close() + return c.Upload(UploadRequestData{ + Method: method, + URL: url, + File: file, + FileFieldName: fileFieldName, + FileContent: fileHandle, + Header: header, + Cookies: cookies, + }) +} - if method != http.MethodPost && method != http.MethodPut { - return nil, errors.New(fmt.Sprintf("Http method %v is not allowed. Possible values are %v or %v", method, http.MethodPost, http.MethodPut)) +// Upload uploads a file's content as multipart-form with given http method request to the specified URL +func (c *Client) Upload(data UploadRequestData) (*http.Response, error) { + if data.Method != http.MethodPost && data.Method != http.MethodPut { + return nil, errors.New(fmt.Sprintf("Http method %v is not allowed. Possible values are %v or %v", data.Method, http.MethodPost, http.MethodPut)) } httpClient := c.initialize() @@ -74,27 +123,30 @@ func (c *Client) UploadRequest(method, url, file, fieldName string, header http. bodyBuffer := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuffer) - fileWriter, err := bodyWriter.CreateFormFile(fieldName, file) - if err != nil { - return &http.Response{}, errors.Wrapf(err, "error creating form file %v for field %v", file, fieldName) + if data.FormFields != nil { + for fieldName, fieldValue := range data.FormFields { + err := bodyWriter.WriteField(fieldName, fieldValue) + if err != nil { + return &http.Response{}, errors.Wrapf(err, "error writing form field %v with value %v", fieldName, fieldValue) + } + } } - fileHandle, err := os.Open(file) + fileWriter, err := bodyWriter.CreateFormFile(data.FileFieldName, data.File) if err != nil { - return &http.Response{}, errors.Wrapf(err, "unable to locate file %v", file) + return &http.Response{}, errors.Wrapf(err, "error creating form file %v for field %v", data.File, data.FileFieldName) } - defer fileHandle.Close() - _, err = io.Copy(fileWriter, fileHandle) + _, err = io.Copy(fileWriter, data.FileContent) if err != nil { - return &http.Response{}, errors.Wrapf(err, "unable to copy file content of %v into request body", file) + return &http.Response{}, errors.Wrapf(err, "unable to copy file content of %v into request body", data.File) } err = bodyWriter.Close() - request, err := c.createRequest(method, url, bodyBuffer, &header, cookies) + request, err := c.createRequest(data.Method, data.URL, bodyBuffer, &data.Header, data.Cookies) if err != nil { - c.logger.Debugf("New %v request to %v", method, url) - return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v", method, url) + c.logger.Debugf("New %v request to %v", data.Method, data.URL) + return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v", data.Method, data.URL) } startBoundary := strings.Index(bodyWriter.FormDataContentType(), "=") + 1 @@ -105,7 +157,7 @@ func (c *Client) UploadRequest(method, url, file, fieldName string, header http. response, err := httpClient.Do(request) if err != nil { - return response, errors.Wrapf(err, "HTTP %v request to %v failed with error", method, url) + return response, errors.Wrapf(err, "HTTP %v request to %v failed with error", data.Method, data.URL) } return c.handleResponse(response) @@ -117,13 +169,12 @@ func (c *Client) SendRequest(method, url string, body io.Reader, header http.Hea request, err := c.createRequest(method, url, body, &header, cookies) if err != nil { - c.logger.Debugf("New %v request to %v", method, url) return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v", method, url) } response, err := httpClient.Do(request) if err != nil { - return response, errors.Wrapf(err, "error opening %v", url) + return response, errors.Wrapf(err, "error calling %v", url) } return c.handleResponse(response) @@ -131,6 +182,8 @@ func (c *Client) SendRequest(method, url string, body io.Reader, header http.Hea // SetOptions sets options used for the http client func (c *Client) SetOptions(options ClientOptions) { + c.doLogRequestBodyOnDebug = options.DoLogRequestBodyOnDebug + c.doLogResponseBodyOnDebug = options.DoLogResponseBodyOnDebug c.transportTimeout = options.TransportTimeout c.maxRequestDuration = options.MaxRequestDuration c.username = options.Username @@ -149,15 +202,18 @@ func (c *Client) initialize() *http.Client { c.applyDefaults() c.logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/http") - var transport = &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: c.transportTimeout, - }).DialContext, - ResponseHeaderTimeout: c.transportTimeout, - ExpectContinueTimeout: c.transportTimeout, - TLSHandshakeTimeout: c.transportTimeout, + var transport = &TransportWrapper{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: c.transportTimeout, + }).DialContext, + ResponseHeaderTimeout: c.transportTimeout, + ExpectContinueTimeout: c.transportTimeout, + TLSHandshakeTimeout: c.transportTimeout, + }, + doLogRequestBodyOnDebug: c.doLogRequestBodyOnDebug, + doLogResponseBodyOnDebug: c.doLogResponseBodyOnDebug, } - var httpClient = &http.Client{ Timeout: c.maxRequestDuration, Transport: transport, @@ -168,8 +224,71 @@ func (c *Client) initialize() *http.Client { return httpClient } +type contextKey struct { + name string +} + +var contextKeyRequestStart = &contextKey{"RequestStart"} + +// RoundTrip is the core part of this module and implements http.RoundTripper. +// Executes HTTP request with request/response logging. +func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) { + ctx := context.WithValue(req.Context(), contextKeyRequestStart, time.Now()) + req = req.WithContext(ctx) + + t.logRequest(req) + resp, err := t.Transport.RoundTrip(req) + t.logResponse(resp) + + return resp, err +} + +func (t *TransportWrapper) logRequest(req *http.Request) { + log.Entry().Debug("--------------------------------") + log.Entry().Debugf("--> %v request to %v", req.Method, req.URL) + log.Entry().Debugf("headers: %v", req.Header) + log.Entry().Debugf("cookies: %v", transformCookies(req.Cookies())) + if t.doLogRequestBodyOnDebug { + log.Entry().Debugf("body: %v", transformBody(req.Body)) + } + log.Entry().Debug("--------------------------------") +} + +func (t *TransportWrapper) logResponse(resp *http.Response) { + if resp != nil { + ctx := resp.Request.Context() + if start, ok := ctx.Value(contextKeyRequestStart).(time.Time); ok { + log.Entry().Debugf("<-- response %v %v (%v)", resp.StatusCode, resp.Request.URL, roundtime.Duration(time.Now().Sub(start), 2)) + } else { + log.Entry().Debugf("<-- response %v %v", resp.StatusCode, resp.Request.URL) + } + if t.doLogResponseBodyOnDebug { + log.Entry().Debugf("body: %v", transformBody(resp.Body)) + } + } else { + log.Entry().Debug("response ") + } + log.Entry().Debug("--------------------------------") +} + +func transformCookies(cookies []*http.Cookie) string { + result := "" + for _, c := range cookies { + result = fmt.Sprintf("%v %v", result, c.String()) + } + return result +} + +func transformBody(body io.ReadCloser) string { + if body == nil { + return "" + } + buf := new(bytes.Buffer) + buf.ReadFrom(body) + return buf.String() +} + func (c *Client) createRequest(method, url string, body io.Reader, header *http.Header, cookies []*http.Cookie) (*http.Request, error) { - c.logger.Debugf("New %v request to %v", method, url) request, err := http.NewRequest(method, url, body) if err != nil { return &http.Request{}, err @@ -215,7 +334,7 @@ func (c *Client) handleResponse(response *http.Response) (*http.Response, error) case http.StatusNotFound: c.logger.WithField("HTTP Error", "404 (Not Found)").Error("Requested resource could not be found") case http.StatusInternalServerError: - c.logger.WithField("HTTP Error", "500 (Internal Server Error)").Error("Unknown error occured.") + c.logger.WithField("HTTP Error", "500 (Internal Server Error)").Error("Unknown error occurred.") } return response, fmt.Errorf("Request to %v returned with response %v", response.Request.URL, response.Status) diff --git a/pkg/piperutils/slices.go b/pkg/piperutils/slices.go index 9f885ea96..fbe23a125 100644 --- a/pkg/piperutils/slices.go +++ b/pkg/piperutils/slices.go @@ -24,6 +24,16 @@ func ContainsString(s []string, e string) bool { return false } +//ContainsStringPart check wether the element is contained as part of one of the elements of the slice +func ContainsStringPart(s []string, part string) bool { + for _, a := range s { + if strings.Contains(a, part) { + return true + } + } + return false +} + //Prefix adds a prefix to each element of the slice func Prefix(in []string, prefix string) []string { return _prefix(in, prefix, true) diff --git a/pkg/piperutils/templateUtils.go b/pkg/piperutils/templateUtils.go new file mode 100644 index 000000000..49d52d8dd --- /dev/null +++ b/pkg/piperutils/templateUtils.go @@ -0,0 +1,30 @@ +package piperutils + +import ( + "bytes" + "fmt" + "text/template" +) + +// ExecuteTemplate parses the provided template, substitutes values and returns the output +func ExecuteTemplate(txtTemplate string, context interface{}) (string, error) { + return ExecuteTemplateFunctions(txtTemplate, nil, context) +} + +// ExecuteTemplateFunctions parses the provided template, applies the transformation functions, substitutes values and returns the output +func ExecuteTemplateFunctions(txtTemplate string, functionMap template.FuncMap, context interface{}) (string, error) { + template := template.New("tmp") + if functionMap != nil { + template = template.Funcs(functionMap) + } + template, err := template.Parse(txtTemplate) + if err != nil { + return "", fmt.Errorf("Failed to parse template defintion %v: %w", txtTemplate, err) + } + var output bytes.Buffer + err = template.Execute(&output, context) + if err != nil { + return "", fmt.Errorf("Failed to transform template defintion %v: %w", txtTemplate, err) + } + return output.String(), nil +} diff --git a/pkg/piperutils/templateUtils_test.go b/pkg/piperutils/templateUtils_test.go new file mode 100644 index 000000000..ee5c664bf --- /dev/null +++ b/pkg/piperutils/templateUtils_test.go @@ -0,0 +1,46 @@ +package piperutils + +import ( + "github.com/stretchr/testify/assert" + "testing" + "text/template" +) + +type SomeDescriptor struct { + GroupID string + ArtifactID string + Version string +} + +func TestExecuteTemplate(t *testing.T) { + t.Run("test success", func(t *testing.T) { + context := SomeDescriptor{GroupID: "com.sap.cp.jenkins", ArtifactID: "piper", Version: "1.2.3"} + result, err := ExecuteTemplate("{{ .GroupID }}-{{ .ArtifactID }}:{{ .Version}}", context) + assert.NoError(t, err, "Didn't expect error but got one") + assert.Equal(t, "com.sap.cp.jenkins-piper:1.2.3", result, "Expected different result") + }) + + t.Run("test template error", func(t *testing.T) { + context := SomeDescriptor{GroupID: "com.sap.cp.jenkins", ArtifactID: "piper", Version: "1.2.3"} + _, err := ExecuteTemplate("{{ $+++.+++GroupID }}-{{ .ArtifactID }}:{{ .Version}}", context) + assert.Error(t, err, "Expected error but got none") + }) + + t.Run("test functions", func(t *testing.T) { + functions := template.FuncMap{ + "testFunc": reverse, + } + context := SomeDescriptor{GroupID: "com.sap.cp.jenkins", ArtifactID: "piper", Version: "1.2.3"} + result, err := ExecuteTemplateFunctions("{{ testFunc .GroupID }}-{{ .ArtifactID }}:{{ .Version}}", functions, context) + assert.NoError(t, err, "Didn't expect error but got one") + assert.Equal(t, "sniknej.pc.pas.moc-piper:1.2.3", result, "Expected different result") + }) +} + +func reverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} diff --git a/pkg/piperutils/testdata/2_setup.py b/pkg/piperutils/testdata/2_setup.py new file mode 100644 index 000000000..c38672ec2 --- /dev/null +++ b/pkg/piperutils/testdata/2_setup.py @@ -0,0 +1,22 @@ +from setuptools import setup, find_packages +from codecs import open +from os import path + +def get_version(): + with open('version.txt') as ver_file: + version_str = ver_file.readline().rstrip() + return version_str + + +def get_install_requires(): + with open('requirements.txt') as reqs_file: + reqs = [line.rstrip() for line in reqs_file.readlines()] + return reqs + +setup(name="some-test", + version="1.0.0", + python_requires='>=3', + packages=find_packages(exclude=['contrib', 'docs', 'tests*', 'coverage', 'bin']), + description="test", + install_requires=get_install_requires(), + ) \ No newline at end of file diff --git a/pkg/piperutils/testdata/VERSION.TXT b/pkg/piperutils/testdata/VERSION.TXT new file mode 100644 index 000000000..5deada829 --- /dev/null +++ b/pkg/piperutils/testdata/VERSION.TXT @@ -0,0 +1 @@ +1.0.0-SNAPSHOT \ No newline at end of file diff --git a/pkg/piperutils/testdata/setup.py b/pkg/piperutils/testdata/setup.py new file mode 100644 index 000000000..e55c2fe0f --- /dev/null +++ b/pkg/piperutils/testdata/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup, find_packages +from codecs import open +from os import path + +def get_version(): + with open('version.txt') as ver_file: + version_str = ver_file.readline().rstrip() + return version_str + + +def get_install_requires(): + with open('requirements.txt') as reqs_file: + reqs = [line.rstrip() for line in reqs_file.readlines()] + return reqs + +setup(name="some-test", + version=get_version(), + python_requires='>=3', + packages=find_packages(exclude=['contrib', 'docs', 'tests*', 'coverage', 'bin']), + description="test", + install_requires=get_install_requires(), + ) \ No newline at end of file diff --git a/pkg/piperutils/testdata/test2_pom.xml b/pkg/piperutils/testdata/test2_pom.xml new file mode 100644 index 000000000..0f611c99b --- /dev/null +++ b/pkg/piperutils/testdata/test2_pom.xml @@ -0,0 +1,15 @@ + + + + 4.0.0 + parent-inherit-test + 1.0.0 + jar + + + com.sap.ldi + ldi-parent-root + 7.4.0 + + \ No newline at end of file diff --git a/pkg/piperutils/testdata/test_pom.xml b/pkg/piperutils/testdata/test_pom.xml new file mode 100644 index 000000000..61629ebaa --- /dev/null +++ b/pkg/piperutils/testdata/test_pom.xml @@ -0,0 +1,9 @@ + + + + 4.0.0 + test.groupID + test-articatID + 1.0.0 + \ No newline at end of file diff --git a/pkg/versioning/descriptorUtils.go b/pkg/versioning/descriptorUtils.go new file mode 100644 index 000000000..58c0bea84 --- /dev/null +++ b/pkg/versioning/descriptorUtils.go @@ -0,0 +1,47 @@ +package versioning + +import ( + "github.com/Masterminds/sprig" + + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperutils" +) + +const ( + // SchemeMajorVersion is the versioning scheme based on the major version only + SchemeMajorVersion = `{{(split "." (split "-" .Version)._0)._0}}` + // SchemeMajorMinorVersion is the versioning scheme based on the major version only + SchemeMajorMinorVersion = `{{(split "." (split "-" .Version)._0)._0}}.{{(split "." (split "-" .Version)._0)._1}}` + // SchemeSemanticVersion is the versioning scheme based on the major.minor.micro version + SchemeSemanticVersion = `{{(split "-" .Version)._0}}` + // SchemeFullVersion is the versioning scheme based on the full version + SchemeFullVersion = "{{.Version}}" +) + +// DetermineProjectCoordinates resolve the coordinates of the project for use in 3rd party scan tools +func DetermineProjectCoordinates(nameTemplate, versionScheme string, gav Coordinates) (string, string) { + projectName, err := piperutils.ExecuteTemplateFunctions(nameTemplate, sprig.HermeticTxtFuncMap(), gav) + if err != nil { + log.Entry().Warnf("Unable to resolve fortify project name: %v", err) + } + + var versionTemplate string + if versionScheme == "full" { + versionTemplate = SchemeFullVersion + } + if versionScheme == "major" { + versionTemplate = SchemeMajorVersion + } + if versionScheme == "major-minor" { + versionTemplate = SchemeMajorMinorVersion + } + if versionScheme == "semantic" { + versionTemplate = SchemeSemanticVersion + } + + projectVersion, err := piperutils.ExecuteTemplateFunctions(versionTemplate, sprig.HermeticTxtFuncMap(), gav) + if err != nil { + log.Entry().Warnf("Unable to resolve fortify project version: %v", err) + } + return projectName, projectVersion +} diff --git a/pkg/versioning/descriptorUtils_test.go b/pkg/versioning/descriptorUtils_test.go new file mode 100644 index 000000000..4f4d5f264 --- /dev/null +++ b/pkg/versioning/descriptorUtils_test.go @@ -0,0 +1,92 @@ +package versioning + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type mavenMock struct { + groupID string + artifactID string + version string + packaging string +} + +func (m *mavenMock) VersioningScheme() string { + return "maven" +} +func (m *mavenMock) GetVersion() (string, error) { + return m.version, nil +} +func (m *mavenMock) SetVersion(v string) error { + m.version = v + return nil +} +func (m *mavenMock) GetCoordinates() (Coordinates, error) { + return &MavenDescriptor{GroupID: m.groupID, ArtifactID: m.artifactID, Version: m.version, Packaging: m.packaging}, nil +} + +type pipMock struct { + artifactID string + version string +} + +func (p *pipMock) VersioningScheme() string { + return "pep440" +} +func (p *pipMock) GetVersion() (string, error) { + return p.version, nil +} +func (p *pipMock) SetVersion(v string) error { + p.version = v + return nil +} +func (p *pipMock) GetCoordinates() (Coordinates, error) { + return &PipDescriptor{ArtifactID: p.artifactID, Version: p.version}, nil +} + +func TestDetermineProjectCoordinates(t *testing.T) { + nameTemplate := `{{list .GroupID .ArtifactID | join "-" | trimAll "-"}}` + + t.Run("maven", func(t *testing.T) { + gav, _ := (&mavenMock{groupID: "com.test.pkg", artifactID: "analyzer", version: "1.2.3"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "major", gav) + assert.Equal(t, "com.test.pkg-analyzer", name, "Expected different project name") + assert.Equal(t, "1", version, "Expected different project version") + }) + + t.Run("maven major-minor", func(t *testing.T) { + gav, _ := (&mavenMock{groupID: "com.test.pkg", artifactID: "analyzer", version: "1.2.3"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "major-minor", gav) + assert.Equal(t, "com.test.pkg-analyzer", name, "Expected different project name") + assert.Equal(t, "1.2", version, "Expected different project version") + }) + + t.Run("maven full", func(t *testing.T) { + gav, _ := (&mavenMock{groupID: "com.test.pkg", artifactID: "analyzer", version: "1.2.3-7864387648746"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "full", gav) + assert.Equal(t, "com.test.pkg-analyzer", name, "Expected different project name") + assert.Equal(t, "1.2.3-7864387648746", version, "Expected different project version") + }) + + t.Run("maven semantic", func(t *testing.T) { + gav, _ := (&mavenMock{groupID: "com.test.pkg", artifactID: "analyzer", version: "1.2.3-7864387648746"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "semantic", gav) + assert.Equal(t, "com.test.pkg-analyzer", name, "Expected different project name") + assert.Equal(t, "1.2.3", version, "Expected different project version") + }) + + t.Run("maven empty", func(t *testing.T) { + gav, _ := (&mavenMock{groupID: "com.test.pkg", artifactID: "analyzer", version: "0-SNAPSHOT"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "snapshot", gav) + assert.Equal(t, "com.test.pkg-analyzer", name, "Expected different project name") + assert.Equal(t, "", version, "Expected different project version") + }) + + t.Run("python", func(t *testing.T) { + gav, _ := (&pipMock{artifactID: "python-test", version: "2.2.3"}).GetCoordinates() + name, version := DetermineProjectCoordinates(nameTemplate, "major", gav) + assert.Equal(t, "python-test", name, "Expected different project name") + assert.Equal(t, "2", version, "Expected different project version") + }) +} diff --git a/pkg/versioning/docker.go b/pkg/versioning/docker.go index 4e99190e4..43341e4b2 100644 --- a/pkg/versioning/docker.go +++ b/pkg/versioning/docker.go @@ -141,3 +141,8 @@ func (d *Docker) versionFromBaseImageTag() string { } return "" } + +// GetCoordinates returns the coordinates +func (d *Docker) GetCoordinates() (Coordinates, error) { + return nil, nil +} diff --git a/pkg/versioning/inifile.go b/pkg/versioning/inifile.go index c24a8268b..5110d15a3 100644 --- a/pkg/versioning/inifile.go +++ b/pkg/versioning/inifile.go @@ -85,3 +85,8 @@ func (i *INIfile) SetVersion(version string) error { } return nil } + +// GetCoordinates returns the coordinates +func (i *INIfile) GetCoordinates() (Coordinates, error) { + return nil, nil +} diff --git a/pkg/versioning/jsonfile.go b/pkg/versioning/jsonfile.go index bff1e0fb1..602c39a54 100644 --- a/pkg/versioning/jsonfile.go +++ b/pkg/versioning/jsonfile.go @@ -76,3 +76,8 @@ func (j *JSONfile) SetVersion(version string) error { return nil } + +// GetCoordinates returns the coordinates +func (j *JSONfile) GetCoordinates() (Coordinates, error) { + return nil, nil +} diff --git a/pkg/versioning/maven.go b/pkg/versioning/maven.go index 7c5c7a7b2..179df851a 100644 --- a/pkg/versioning/maven.go +++ b/pkg/versioning/maven.go @@ -21,6 +21,14 @@ type mavenRunner interface { Evaluate(string, string, mavenExecRunner) (string, error) } +// MavenDescriptor holds the unique identifier combination for Maven built Java artifacts +type MavenDescriptor struct { + GroupID string + ArtifactID string + Version string + Packaging string +} + // Maven defines a maven artifact used for versioning type Maven struct { pomPath string @@ -46,6 +54,62 @@ func (m *Maven) VersioningScheme() string { return "maven" } +// GetCoordinates reads the coordinates from the maven pom.xml descriptor file +func (m *Maven) GetCoordinates() (Coordinates, error) { + result := &MavenDescriptor{} + var err error + result.GroupID, err = m.GetGroupID() + if err != nil { + return nil, err + } + result.ArtifactID, err = m.GetArtifactID() + if err != nil { + return nil, err + } + result.Version, err = m.GetVersion() + if err != nil { + return nil, err + } + result.Packaging, err = m.GetPackaging() + if err != nil { + return nil, err + } + return result, nil +} + +// GetPackaging returns the current ID of the Group +func (m *Maven) GetPackaging() (string, error) { + m.init() + + packaging, err := m.runner.Evaluate(m.pomPath, "project.packaging", m.execRunner) + if err != nil { + return "", errors.Wrap(err, "Maven - getting packaging failed") + } + return packaging, nil +} + +// GetGroupID returns the current ID of the Group +func (m *Maven) GetGroupID() (string, error) { + m.init() + + groupID, err := m.runner.Evaluate(m.pomPath, "project.groupId", m.execRunner) + if err != nil { + return "", errors.Wrap(err, "Maven - getting groupId failed") + } + return groupID, nil +} + +// GetArtifactID returns the current ID of the artifact +func (m *Maven) GetArtifactID() (string, error) { + m.init() + + artifactID, err := m.runner.Evaluate(m.pomPath, "project.artifactId", m.execRunner) + if err != nil { + return "", errors.Wrap(err, "Maven - getting artifactId failed") + } + return artifactID, nil +} + // GetVersion returns the current version of the artifact func (m *Maven) GetVersion() (string, error) { m.init() diff --git a/pkg/versioning/pip.go b/pkg/versioning/pip.go new file mode 100644 index 000000000..d94a0967c --- /dev/null +++ b/pkg/versioning/pip.go @@ -0,0 +1,146 @@ +package versioning + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" + + "github.com/pkg/errors" +) + +const ( + // NameRegex is used to match the pip descriptor artifact name + NameRegex = "(?s)(.*)name=['\"](.*?)['\"](.*)" + // VersionRegex is used to match the pip descriptor artifact version + VersionRegex = "(?s)(.*)version=['\"](.*?)['\"](.*)" +) + +// PipDescriptor holds the unique identifier combination for pip built Python artifacts +type PipDescriptor struct { + GroupID string + ArtifactID string + Version string + Packaging string +} + +// Pip utility to interact with Python specific versioning +type Pip struct { + path string + readFile func(string) ([]byte, error) + writeFile func(string, []byte, os.FileMode) error + fileExists func(string) (bool, error) + buildDescriptorContent string +} + +func (p *Pip) init() error { + if p.readFile == nil { + p.readFile = ioutil.ReadFile + } + + if p.writeFile == nil { + p.writeFile = ioutil.WriteFile + } + + if len(p.buildDescriptorContent) > 0 { + content, err := p.readFile(p.path) + if err != nil { + return errors.Wrapf(err, "failed to read file '%v'", p.path) + } + p.buildDescriptorContent = string(content) + } + return nil +} + +// GetVersion returns the Pip descriptor version property +func (p *Pip) GetVersion() (string, error) { + buildDescriptorFilePath := p.path + var err error + if strings.Contains(p.path, "setup.py") { + buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION"}, p.fileExists) + if err != nil { + err = p.init() + if err != nil { + return "", errors.Wrapf(err, "failed to read file '%v'", p.path) + } + if evaluateResult(p.buildDescriptorContent, VersionRegex) { + compile := regexp.MustCompile(VersionRegex) + values := compile.FindStringSubmatch(p.buildDescriptorContent) + return values[2], nil + } + return "", errors.Wrap(err, "failed to retrieve version") + } + } + artifact := &Versionfile{ + path: buildDescriptorFilePath, + versioningScheme: p.VersioningScheme(), + } + return artifact.GetVersion() +} + +// SetVersion sets the Pip descriptor version property +func (p *Pip) SetVersion(v string) error { + buildDescriptorFilePath := p.path + var err error + if strings.Contains(p.path, "setup.py") { + buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION"}, p.fileExists) + if err != nil { + err = p.init() + if err != nil { + return errors.Wrapf(err, "failed to read file '%v'", p.path) + } + if evaluateResult(p.buildDescriptorContent, VersionRegex) { + compile := regexp.MustCompile(VersionRegex) + values := compile.FindStringSubmatch(p.buildDescriptorContent) + p.buildDescriptorContent = strings.ReplaceAll(p.buildDescriptorContent, fmt.Sprintf("version='%v'", values[2]), fmt.Sprintf("version='%v'", v)) + p.buildDescriptorContent = strings.ReplaceAll(p.buildDescriptorContent, fmt.Sprintf("version=\"%v\"", values[2]), fmt.Sprintf("version=\"%v\"", v)) + p.writeFile(p.path, []byte(p.buildDescriptorContent), 0600) + } else { + return errors.Wrap(err, "failed to retrieve version") + } + } + } + artifact := &Versionfile{ + path: buildDescriptorFilePath, + versioningScheme: p.VersioningScheme(), + } + return artifact.SetVersion(v) +} + +// VersioningScheme returns the relevant versioning scheme +func (p *Pip) VersioningScheme() string { + return "pep440" +} + +// GetCoordinates returns the pip build descriptor coordinates +func (p *Pip) GetCoordinates() (Coordinates, error) { + err := p.init() + if err != nil { + return nil, err + } + + descriptor := &PipDescriptor{} + if evaluateResult(p.buildDescriptorContent, NameRegex) { + compile := regexp.MustCompile(NameRegex) + values := compile.FindStringSubmatch(p.buildDescriptorContent) + descriptor.ArtifactID = values[2] + } else { + descriptor.ArtifactID = "" + } + + descriptor.Version, err = p.GetVersion() + if err != nil { + return nil, errors.Wrap(err, "failed to retrieve coordinates") + } + + return descriptor, nil +} + +func evaluateResult(value, regex string) bool { + if len(value) > 0 { + match, _ := regexp.MatchString(regex, value) + return match + } + return true +} diff --git a/pkg/versioning/pip_test.go b/pkg/versioning/pip_test.go new file mode 100644 index 000000000..74b9ce700 --- /dev/null +++ b/pkg/versioning/pip_test.go @@ -0,0 +1,3 @@ +package versioning + +import () diff --git a/pkg/versioning/versionfile.go b/pkg/versioning/versionfile.go index cd06dd571..d155f6eaf 100644 --- a/pkg/versioning/versionfile.go +++ b/pkg/versioning/versionfile.go @@ -60,3 +60,8 @@ func (v *Versionfile) SetVersion(version string) error { return nil } + +// GetCoordinates returns the coordinates +func (v *Versionfile) GetCoordinates() (Coordinates, error) { + return nil, nil +} diff --git a/pkg/versioning/versioning.go b/pkg/versioning/versioning.go index 9570c2fdc..8735f760a 100644 --- a/pkg/versioning/versioning.go +++ b/pkg/versioning/versioning.go @@ -9,11 +9,15 @@ import ( "github.com/SAP/jenkins-library/pkg/maven" ) +// Coordinates to address the artifact +type Coordinates interface{} + // Artifact defines the versioning operations for various build tools type Artifact interface { VersioningScheme() string GetVersion() (string, error) SetVersion(string) error + GetCoordinates() (Coordinates, error) } // Options define build tool specific settings in order to properly retrieve e.g. the version of an artifact @@ -109,14 +113,14 @@ func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, execR case "pip": if len(buildDescriptorFilePath) == 0 { var err error - buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION"}, fileExists) + buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION", "setup.py"}, fileExists) if err != nil { return artifact, err } } - artifact = &Versionfile{ - path: buildDescriptorFilePath, - versioningScheme: "pep440", + artifact = &Pip{ + path: buildDescriptorFilePath, + fileExists: fileExists, } case "sbt": if len(buildDescriptorFilePath) == 0 { diff --git a/pkg/versioning/versioning_test.go b/pkg/versioning/versioning_test.go index ee0cc92f0..d96becf21 100644 --- a/pkg/versioning/versioning_test.go +++ b/pkg/versioning/versioning_test.go @@ -112,7 +112,7 @@ func TestGetArtifact(t *testing.T) { assert.NoError(t, err) - theType, ok := pip.(*Versionfile) + theType, ok := pip.(*Pip) assert.True(t, ok) assert.Equal(t, "version.txt", theType.path) assert.Equal(t, "pep440", pip.VersioningScheme()) @@ -122,7 +122,7 @@ func TestGetArtifact(t *testing.T) { fileExists = func(string) (bool, error) { return false, nil } _, err := GetArtifact("pip", "", &Options{}, nil) - assert.EqualError(t, err, "no build descriptor available, supported: [version.txt VERSION]") + assert.EqualError(t, err, "no build descriptor available, supported: [version.txt VERSION setup.py]") }) t.Run("sbt", func(t *testing.T) { diff --git a/pkg/versioning/yamlfile.go b/pkg/versioning/yamlfile.go index fd5360e7b..7c174b965 100644 --- a/pkg/versioning/yamlfile.go +++ b/pkg/versioning/yamlfile.go @@ -77,3 +77,8 @@ func (y *YAMLfile) SetVersion(version string) error { return nil } + +// GetCoordinates returns the coordinates +func (y *YAMLfile) GetCoordinates() (Coordinates, error) { + return nil, nil +} diff --git a/resources/metadata/fortify.yaml b/resources/metadata/fortify.yaml new file mode 100644 index 000000000..c6460e241 --- /dev/null +++ b/resources/metadata/fortify.yaml @@ -0,0 +1,472 @@ +metadata: + name: fortifyExecuteScan + description: This BETA step executes a Fortify scan on the specified project to perform static code analysis and check the source code for security flaws. + longDescription: |- + This step executes a Fortify scan on the specified project to perform static code analysis and check the source code for security flaws. + + The Fortify step triggers a scan locally on your Jenkins within a docker container so finally you have to supply a docker image with a Fortify SCA + and Java plus Maven or alternatively Python installed into it for being able to perform any scans. + + DISCLAIMER: The step has not yet been tested on a wide variaty of projects, and is therefore considered of BETA quality. +spec: + inputs: + secrets: + - name: fortifyCredentialsId + description: Jenkins 'Secret text' credentials ID containing token to authenticate to Fortify SSC. + type: jenkins + - name: githubTokenCredentialsId + description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub. + type: jenkins + resources: + - name: commonPipelineEnvironment + resourceSpec: + type: piperEnvironment + - name: buildDescriptor + type: stash + - name: deployDescriptor + type: stash + - name: tests + type: stash + - name: opensourceConfiguration + type: stash + params: + - name: authToken + type: string + description: The FortifyToken to use for authentication + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + secret: true + - name: githubToken + description: GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + type: string + secret: true + - name: autoCreate + type: bool + description: Whether Fortify project and project version shall be implicitly auto created in case they cannot be found in the backend + scope: + - PARAMETERS + - STAGES + - STEPS + - name: mvnCustomArgs + type: string + description: Allows providing additional Maven command line parameters + scope: + - PARAMETERS + - STAGES + - STEPS + default: '' + - name: modulePath + type: string + description: Allows providing the path for the module to scan + scope: + - PARAMETERS + - STAGES + - STEPS + default: './' + - name: pythonRequirementsFile + type: string + description: 'The requirements file used in `buildTool: ''pip''` to populate + the build environment with the necessary dependencies' + scope: + - PARAMETERS + - STAGES + - STEPS + - name: autodetectClasspath + type: bool + description: Whether the classpath is automatically determined via build tool i.e. maven or pip or not at all + scope: + - PARAMETERS + - STAGES + - STEPS + default: true + - name: mustAuditIssueGroups + type: string + description: Comma separated list of issue groups that must be audited completely + scope: + - PARAMETERS + - STAGES + - STEPS + default: 'Corporate Security Requirements, Audit All' + - name: spotAuditIssueGroups + type: string + description: 'Comma separated list of issue groups that are spot checked and for which `spotCheckMinimum` audited issues are enforced' + scope: + - PARAMETERS + - STAGES + - STEPS + default: 'Spot Checks of Each Category' + - name: pythonRequirementsInstallSuffix + type: string + description: 'The suffix for the command used to install the requirements file in `buildTool: ''pip''` to populate + the build environment with the necessary dependencies' + scope: + - PARAMETERS + - STAGES + - STEPS + - name: pythonVersion + type: string + description: 'Python version to be used in `buildTool: ''pip''`' + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: python3 + - name: uploadResults + type: bool + description: Whether results shall be uploaded or not + scope: + - PARAMETERS + - STAGES + - STEPS + default: true + - name: buildDescriptorFile + type: string + description: 'Path to the build descriptor file addressing the module/folder + to be scanned. Defaults are for buildTool=`maven`: `./pom.xml`, buildTool=`pip`: + `./setup.py`.' + scope: + - PARAMETERS + - STAGES + - STEPS + - name: commitId + description: 'Set the Git commit ID for identifying artifacts throughout the scan.' + resourceRef: + - name: commonPipelineEnvironment + param: git/commitId + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + - name: commitMessage + description: 'Set the Git commit message for identifying pull request merges throughout the scan.' + resourceRef: + - name: commonPipelineEnvironment + param: git/commitMessage + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + - name: githubApiUrl + description: Set the GitHub API url. + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + type: string + default: https://api.github.com + - name: owner + aliases: + - name: githubOrg + description: 'Set the GitHub organization.' + resourceRef: + - name: commonPipelineEnvironment + param: github/owner + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + - name: repository + aliases: + - name: githubRepo + description: 'Set the GitHub repository.' + resourceRef: + - name: commonPipelineEnvironment + param: github/repository + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + - name: memory + type: string + description: The amount of memory granted to the translate/scan executions + scope: + - PARAMETERS + - STAGES + - STEPS + default: -Xmx4G -Xms512M + - name: updateRulePack + type: bool + description: Whether the rule pack shall be updated and pulled from Fortify SSC before scanning or not + scope: + - PARAMETERS + - STAGES + - STEPS + default: true + - name: pythonExcludes + type: string + description: 'The excludes pattern used in `buildTool: ''pip''` for excluding + specific .py files i.e. tests' + scope: + - PARAMETERS + - STAGES + - STEPS + default: -exclude ./**/tests/**/*;./**/setup.py + deprecated: true + - name: reportDownloadEndpoint + aliases: + - name: fortifyReportDownloadEndpoint + type: string + description: Fortify SSC endpoint for Report downloads + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: /transfer/reportDownload.html + - name: pollingMinutes + type: int + description: The number of minutes for which an uploaded FPR artifact's status is being polled to finish queuing/processing, if exceeded polling will be stopped and an error will be thrown + scope: + - PARAMETERS + - STAGES + - STEPS + default: 30 + - name: quickScan + type: bool + description: Whether a quick scan should be performed, please consult the related Fortify documentation on JAM on the impact of this setting + scope: + - PARAMETERS + - STAGES + - STEPS + default: false + - name: translate + type: string + description: JSON string of list of maps with required key `'src'`, and optional keys `'exclude'`, `'libDirs'`, `'aspnetcore'`, and `'dotNetCoreVersion'` + scope: + - PARAMETERS + - STAGES + - STEPS + - name: apiEndpoint + aliases: + - name: fortifyApiEndpoint + type: string + description: Fortify SSC endpoint used for uploading the scan results and checking the audit state + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: /api/v1 + - name: reportType + type: string + description: The type of report to be generated + scope: + - PARAMETERS + - STAGES + - STEPS + default: PDF + - name: pythonAdditionalPath + type: string + description: 'The addional path which can be used in `buildTool: ''pip''` for + customization purposes' + scope: + - PARAMETERS + - STAGES + - STEPS + default: ./lib;. + deprecated: true + - name: artifactUrl + type: string + description: 'Path/Url pointing to an additional artifact repository for resolution of additional artifacts during the build' + scope: + - PARAMETERS + - STAGES + - STEPS + - name: considerSuspicious + type: bool + description: Whether suspicious issues should trigger the check to fail or not + scope: + - PARAMETERS + - STAGES + - STEPS + default: true + - name: fprUploadEndpoint + aliases: + - name: fortifyFprUploadEndpoint + type: string + description: Fortify SSC endpoint for FPR uploads + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: /upload/resultFileUpload.html + - name: projectName + aliases: + - name: fortifyProjectName + type: string + description: The project used for reporting results in SSC + scope: + - PARAMETERS + - STAGES + - STEPS + default: '{{list .GroupID .ArtifactID | join "-" | trimAll "-"}}' + - name: pythonIncludes + type: string + description: 'The includes pattern used in `buildTool: ''pip''` for including + .py files' + scope: + - PARAMETERS + - STAGES + - STEPS + default: ./**/* + deprecated: true + - name: reporting + type: bool + description: Influences whether a report is generated or not + scope: + - PARAMETERS + - STAGES + - STEPS + default: false + - name: serverUrl + aliases: + - name: fortifyServerUrl + - name: sscUrl + deprecated: true + type: string + description: Fortify SSC Url to be used for accessing the APIs + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + - name: buildDescriptorExcludeList + type: string + description: Build descriptor files to exclude modules from being scanned + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: [] + - name: pullRequestMessageRegexGroup + type: int + description: The group number for extracting the pull request id in `pullRequestMessageRegex` + scope: + - PARAMETERS + - STAGES + - STEPS + default: 1 + - name: deltaMinutes + type: int + description: The number of minutes for which an uploaded FPR artifact is considered to be recent and healthy, if exceeded an error will be thrown + scope: + - PARAMETERS + - STAGES + - STEPS + default: 5 + - name: spotCheckMinimum + type: int + description: The minimum number of issues that must be audited per category in the `Spot Checks of each Category` folder to avoid an error being thrown + scope: + - PARAMETERS + - STAGES + - STEPS + default: 1 + - name: fprDownloadEndpoint + aliases: + - name: fortifyFprDownloadEndpoint + type: string + description: Fortify SSC endpoint for FPR downloads + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: /download/currentStateFprDownload.html + - name: defaultVersioningModel + type: string + description: The default project versioning model used in case `projectVersion` parameter is empty for creating the version based on the build descriptor version to report results in SSC, can be one of `'major'`, `'major-minor'`, `'semantic'`, `'full'` + scope: + - PARAMETERS + - STAGES + - STEPS + default: 'major' + - name: pythonInstallCommand + type: string + description: 'Additional install command that can be run when `buildTool: ''pip''` + is used which allows further customizing the execution environment of the + scan' + scope: + - PARAMETERS + - STAGES + - STEPS + default: "{{.Pip}} install --user ." + - name: reportTemplateId + type: int + description: Report template ID to be used for generating the Fortify report + scope: + - PARAMETERS + - STAGES + - STEPS + default: 18 + - name: filterSetTitle + type: string + description: Title of the filter set to use for analysing the results + scope: + - PARAMETERS + - STAGES + - STEPS + default: "SAP" + - name: pullRequestName + type: string + description: The name of the pull request branch which will trigger creation of a new version in Fortify SSC based on the master branch version + scope: + - PARAMETERS + - STAGES + - STEPS + - name: pullRequestMessageRegex + type: string + description: Regex used to identify the PR-XXX reference within the merge commit message + scope: + - PARAMETERS + - STAGES + - STEPS + default: '.*Merge pull request #(\\d+) from.*' + - name: buildTool + type: string + description: Scan type used for the step which can be `'maven'`, `'pip'` + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + default: maven + containers: + - image: ppiper/fortify + workingDir: /home/piper + outputs: + resources: + - name: influx + type: influx + params: + - name: fortify_data + fields: + - name: projectName + - name: projectVersion + - name: violations + - name: corporateTotal + - name: corporateAudited + - name: auditAllTotal + - name: auditAllAudited + - name: spotChecksTotal + - name: spotChecksAudited + - name: spotChecksGap + - name: suspicious + - name: exploitable + - name: suppressed diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 4555acb41..690fe7c11 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -136,6 +136,7 @@ public class CommonStepsTest extends BasePiperTest{ 'abapEnvironmentRunATCCheck', //implementing new golang pattern without fields 'sonarExecuteScan', //implementing new golang pattern without fields 'gctsCreateRepository', //implementing new golang pattern without fields + 'fortifyExecuteScan', //implementing new golang pattern without fields 'gctsDeploy', //implementing new golang pattern without fields ] diff --git a/vars/fortifyExecuteScan.groovy b/vars/fortifyExecuteScan.groovy new file mode 100644 index 000000000..94465d422 --- /dev/null +++ b/vars/fortifyExecuteScan.groovy @@ -0,0 +1,17 @@ +import com.sap.piper.DownloadCacheUtils +import groovy.transform.Field + +import static com.sap.piper.Prerequisites.checkScript + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/fortify.yaml' + +//Metadata maintained in file project://resources/metadata/fortify.yaml + +void call(Map parameters = [:]) { + final script = checkScript(this, parameters) ?: this + parameters = DownloadCacheUtils.injectDownloadCacheInMavenParameters(script, parameters) + + List credentials = [[type: 'token', id: 'fortifyCredentialsId', env: ['PIPER_authToken']]] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}