mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
feat(detectExecuteScan) execution of rapid scans (#4211)
Co-authored-by: akram8008 <900658008.akram@email.com> Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> Co-authored-by: Andrei Kireev <andrei.kireev@sap.com> Co-authored-by: ffeldmann <f.feldmann@sap.com> Co-authored-by: sumeet patil <sumeet.patil@sap.com>
This commit is contained in:
parent
cc3bc76943
commit
f4fbf0f1ed
@ -14,20 +14,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
bd "github.com/SAP/jenkins-library/pkg/blackduck"
|
bd "github.com/SAP/jenkins-library/pkg/blackduck"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/command"
|
||||||
piperGithub "github.com/SAP/jenkins-library/pkg/github"
|
piperGithub "github.com/SAP/jenkins-library/pkg/github"
|
||||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||||
"github.com/SAP/jenkins-library/pkg/maven"
|
|
||||||
"github.com/SAP/jenkins-library/pkg/reporting"
|
|
||||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/command"
|
|
||||||
"github.com/SAP/jenkins-library/pkg/log"
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/maven"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
||||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/reporting"
|
||||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||||
"github.com/SAP/jenkins-library/pkg/toolrecord"
|
"github.com/SAP/jenkins-library/pkg/toolrecord"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||||
|
|
||||||
"github.com/google/go-github/v45/github"
|
"github.com/google/go-github/v45/github"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type detectUtils interface {
|
type detectUtils interface {
|
||||||
@ -46,14 +46,16 @@ type detectUtils interface {
|
|||||||
|
|
||||||
GetIssueService() *github.IssuesService
|
GetIssueService() *github.IssuesService
|
||||||
GetSearchService() *github.SearchService
|
GetSearchService() *github.SearchService
|
||||||
|
GetProvider() orchestrator.OrchestratorSpecificConfigProviding
|
||||||
}
|
}
|
||||||
|
|
||||||
type detectUtilsBundle struct {
|
type detectUtilsBundle struct {
|
||||||
*command.Command
|
*command.Command
|
||||||
*piperutils.Files
|
*piperutils.Files
|
||||||
*piperhttp.Client
|
*piperhttp.Client
|
||||||
issues *github.IssuesService
|
issues *github.IssuesService
|
||||||
search *github.SearchService
|
search *github.SearchService
|
||||||
|
provider orchestrator.OrchestratorSpecificConfigProviding
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *detectUtilsBundle) GetIssueService() *github.IssuesService {
|
func (d *detectUtilsBundle) GetIssueService() *github.IssuesService {
|
||||||
@ -64,6 +66,10 @@ func (d *detectUtilsBundle) GetSearchService() *github.SearchService {
|
|||||||
return d.search
|
return d.search
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *detectUtilsBundle) GetProvider() orchestrator.OrchestratorSpecificConfigProviding {
|
||||||
|
return d.provider
|
||||||
|
}
|
||||||
|
|
||||||
type blackduckSystem struct {
|
type blackduckSystem struct {
|
||||||
Client bd.Client
|
Client bd.Client
|
||||||
}
|
}
|
||||||
@ -104,6 +110,15 @@ func newDetectUtils(client *github.Client) detectUtils {
|
|||||||
}
|
}
|
||||||
utils.Stdout(log.Writer())
|
utils.Stdout(log.Writer())
|
||||||
utils.Stderr(log.Writer())
|
utils.Stderr(log.Writer())
|
||||||
|
|
||||||
|
provider, err := orchestrator.NewOrchestratorSpecificConfigProvider()
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Warning(err)
|
||||||
|
provider = &orchestrator.UnknownOrchestratorConfigProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.provider = provider
|
||||||
|
|
||||||
return &utils
|
return &utils
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +174,10 @@ func runDetect(ctx context.Context, config detectExecuteScanOptions, utils detec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blackduckSystem := newBlackduckSystem(config)
|
||||||
|
|
||||||
args := []string{"./detect.sh"}
|
args := []string{"./detect.sh"}
|
||||||
args, err = addDetectArgs(args, config, utils)
|
args, err = addDetectArgs(args, config, utils, blackduckSystem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -173,7 +190,6 @@ func runDetect(ctx context.Context, config detectExecuteScanOptions, utils detec
|
|||||||
utils.SetEnv(envs)
|
utils.SetEnv(envs)
|
||||||
|
|
||||||
err = utils.RunShell("/bin/bash", script)
|
err = utils.RunShell("/bin/bash", script)
|
||||||
blackduckSystem := newBlackduckSystem(config)
|
|
||||||
reportingErr := postScanChecksAndReporting(ctx, config, influx, utils, blackduckSystem)
|
reportingErr := postScanChecksAndReporting(ctx, config, influx, utils, blackduckSystem)
|
||||||
if reportingErr != nil {
|
if reportingErr != nil {
|
||||||
if strings.Contains(reportingErr.Error(), "License Policy Violations found") {
|
if strings.Contains(reportingErr.Error(), "License Policy Violations found") {
|
||||||
@ -295,7 +311,7 @@ func getDetectScript(config detectExecuteScanOptions, utils detectUtils) error {
|
|||||||
return utils.DownloadFile("https://detect.synopsys.com/detect7.sh", "detect.sh", nil, nil)
|
return utils.DownloadFile("https://detect.synopsys.com/detect7.sh", "detect.sh", nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectUtils) ([]string, error) {
|
func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectUtils, sys *blackduckSystem) ([]string, error) {
|
||||||
detectVersionName := getVersionName(config)
|
detectVersionName := getVersionName(config)
|
||||||
// Split on spaces, the scanPropeties, so that each property is available as a single string
|
// Split on spaces, the scanPropeties, so that each property is available as a single string
|
||||||
// instead of all properties being part of a single string
|
// instead of all properties being part of a single string
|
||||||
@ -390,6 +406,18 @@ func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectU
|
|||||||
args = append(args, fmt.Sprintf("\"--detect.maven.build.command='%v'\"", strings.Join(mavenArgs, " ")))
|
args = append(args, fmt.Sprintf("\"--detect.maven.build.command='%v'\"", strings.Join(mavenArgs, " ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rapid scan on pull request
|
||||||
|
if utils.GetProvider().IsPullRequest() {
|
||||||
|
log.Entry().Debug("pull request detected")
|
||||||
|
args = append(args, "--detect.blackduck.scan.mode='RAPID'")
|
||||||
|
_, err := sys.Client.GetProjectVersion(config.ProjectName, config.Version)
|
||||||
|
if err == nil {
|
||||||
|
args = append(args, "--detect.blackduck.rapid.compare.mode='BOM_COMPARE_STRICT'")
|
||||||
|
}
|
||||||
|
args = append(args, "--detect.cleanup=false")
|
||||||
|
args = append(args, "--detect.output.path='report'")
|
||||||
|
}
|
||||||
|
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,6 +526,33 @@ func isMajorVulnerability(v bd.Vulnerability) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error {
|
func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error {
|
||||||
|
|
||||||
|
if utils.GetProvider().IsPullRequest() {
|
||||||
|
issueNumber, err := strconv.Atoi(utils.GetProvider().GetPullRequestConfig().Key)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().Warning("Can not get issue number ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
commentBody, err := reporting.RapidScanResult("./report")
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().Warning("Couldn't read file of report of rapid scan, error: ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, _, err = utils.GetIssueService().CreateComment(ctx,
|
||||||
|
config.Owner,
|
||||||
|
config.Repository,
|
||||||
|
issueNumber,
|
||||||
|
&github.IssueComment{
|
||||||
|
Body: &commentBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().Warning("Can send request to github ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
errorsOccured := []string{}
|
errorsOccured := []string{}
|
||||||
vulns, err := getVulnerabilitiesWithComponents(config, influx, sys)
|
vulns, err := getVulnerabilitiesWithComponents(config, influx, sys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
piperGithub "github.com/SAP/jenkins-library/pkg/github"
|
piperGithub "github.com/SAP/jenkins-library/pkg/github"
|
||||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||||
"github.com/SAP/jenkins-library/pkg/mock"
|
"github.com/SAP/jenkins-library/pkg/mock"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
||||||
|
|
||||||
"github.com/google/go-github/v45/github"
|
"github.com/google/go-github/v45/github"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -25,7 +27,12 @@ type detectTestUtilsBundle struct {
|
|||||||
downloadedFiles map[string]string // src, dest
|
downloadedFiles map[string]string // src, dest
|
||||||
*mock.ShellMockRunner
|
*mock.ShellMockRunner
|
||||||
*mock.FilesMock
|
*mock.FilesMock
|
||||||
customEnv []string
|
customEnv []string
|
||||||
|
orchestrator *orchestratorConfigProviderMock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *detectTestUtilsBundle) GetProvider() orchestrator.OrchestratorSpecificConfigProviding {
|
||||||
|
return d.orchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *detectTestUtilsBundle) GetIssueService() *github.IssuesService {
|
func (d *detectTestUtilsBundle) GetIssueService() *github.IssuesService {
|
||||||
@ -36,6 +43,15 @@ func (d *detectTestUtilsBundle) GetSearchService() *github.SearchService {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type orchestratorConfigProviderMock struct {
|
||||||
|
orchestrator.UnknownOrchestratorConfigProvider
|
||||||
|
isPullRequest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orchestratorConfigProviderMock) IsPullRequest() bool {
|
||||||
|
return o.isPullRequest
|
||||||
|
}
|
||||||
|
|
||||||
type httpMockClient struct {
|
type httpMockClient struct {
|
||||||
responseBodyForURL map[string]string
|
responseBodyForURL map[string]string
|
||||||
errorMessageForURL map[string]string
|
errorMessageForURL map[string]string
|
||||||
@ -73,6 +89,8 @@ func newBlackduckMockSystem(config detectExecuteScanOptions) blackduckSystem {
|
|||||||
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/vunlerable-bom-components?limit=999&offset=0": vulnerabilitiesContent,
|
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/vunlerable-bom-components?limit=999&offset=0": vulnerabilitiesContent,
|
||||||
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/components?filter=policyCategory%3Alicense&limit=999&offset=0": componentsContent,
|
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/components?filter=policyCategory%3Alicense&limit=999&offset=0": componentsContent,
|
||||||
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/policy-status": policyStatusContent,
|
"https://my.blackduck.system/api/projects/5ca86e11/versions/a6c94786/policy-status": policyStatusContent,
|
||||||
|
"https://my.blackduck.system/api/projects?q=name%3ARapid_scan_on_PRs": projectContentRapidScan,
|
||||||
|
"https://my.blackduck.system/api/projects/654ggfdgf-1983-4e7b-97d4-eb1a0aeffbbf/versions?limit=100&offset=0": projectVersionContentRapid,
|
||||||
},
|
},
|
||||||
header: map[string]http.Header{},
|
header: map[string]http.Header{},
|
||||||
}
|
}
|
||||||
@ -193,6 +211,48 @@ const (
|
|||||||
"severityLevels": [{"name":"BLOCKER", "value": 1}, {"name": "CRITICAL", "value": 1}]
|
"severityLevels": [{"name":"BLOCKER", "value": 1}, {"name": "CRITICAL", "value": 1}]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
projectContentRapidScan = `{
|
||||||
|
"totalCount": 1,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Rapid_scan_on_PRs",
|
||||||
|
"_meta": {
|
||||||
|
"href": "https://my.blackduck.system/api/projects/654ggfdgf-1983-4e7b-97d4-eb1a0aeffbbf",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "versions",
|
||||||
|
"href": "https://my.blackduck.system/api/projects/654ggfdgf-1983-4e7b-97d4-eb1a0aeffbbf/versions"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
projectVersionContentRapid = `{
|
||||||
|
"totalCount": 1,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"versionName": "1.0",
|
||||||
|
"_meta": {
|
||||||
|
"href": "https://my.blackduck.system/api/projects/654ggfdgf-1983-4e7b-97d4-eb1a0aeffbbf/versions/54357fds-0ee6-414f-9054-90d549c69c36",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "components",
|
||||||
|
"href": "https://my.blackduck.system/api/projects/5ca86e11/versions/654784382/components"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "vulnerable-components",
|
||||||
|
"href": "https://my.blackduck.system/api/projects/5ca86e11/versions/654784382/vunlerable-bom-components"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "policy-status",
|
||||||
|
"href": "https://my.blackduck.system/api/projects/5ca86e11/versions/654784382/policy-status"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *detectTestUtilsBundle) RunExecutable(string, ...string) error {
|
func (c *detectTestUtilsBundle) RunExecutable(string, ...string) error {
|
||||||
@ -222,10 +282,11 @@ func (w *detectTestUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.Cr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDetectTestUtilsBundle() *detectTestUtilsBundle {
|
func newDetectTestUtilsBundle(isPullRequest bool) *detectTestUtilsBundle {
|
||||||
utilsBundle := detectTestUtilsBundle{
|
utilsBundle := detectTestUtilsBundle{
|
||||||
ShellMockRunner: &mock.ShellMockRunner{},
|
ShellMockRunner: &mock.ShellMockRunner{},
|
||||||
FilesMock: &mock.FilesMock{},
|
FilesMock: &mock.FilesMock{},
|
||||||
|
orchestrator: &orchestratorConfigProviderMock{isPullRequest: isPullRequest},
|
||||||
}
|
}
|
||||||
return &utilsBundle
|
return &utilsBundle
|
||||||
}
|
}
|
||||||
@ -235,7 +296,7 @@ func TestRunDetect(t *testing.T) {
|
|||||||
t.Run("success case", func(t *testing.T) {
|
t.Run("success case", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utilsMock := newDetectTestUtilsBundle()
|
utilsMock := newDetectTestUtilsBundle(false)
|
||||||
utilsMock.AddFile("detect.sh", []byte(""))
|
utilsMock.AddFile("detect.sh", []byte(""))
|
||||||
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
|
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
|
||||||
|
|
||||||
@ -251,7 +312,7 @@ func TestRunDetect(t *testing.T) {
|
|||||||
t.Run("success case detect 6", func(t *testing.T) {
|
t.Run("success case detect 6", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utilsMock := newDetectTestUtilsBundle()
|
utilsMock := newDetectTestUtilsBundle(false)
|
||||||
utilsMock.AddFile("detect.sh", []byte(""))
|
utilsMock.AddFile("detect.sh", []byte(""))
|
||||||
options := detectExecuteScanOptions{
|
options := detectExecuteScanOptions{
|
||||||
CustomEnvironmentVariables: []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"},
|
CustomEnvironmentVariables: []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"},
|
||||||
@ -270,7 +331,7 @@ func TestRunDetect(t *testing.T) {
|
|||||||
t.Run("success case detect 6 from OS env", func(t *testing.T) {
|
t.Run("success case detect 6 from OS env", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utilsMock := newDetectTestUtilsBundle()
|
utilsMock := newDetectTestUtilsBundle(false)
|
||||||
utilsMock.AddFile("detect.sh", []byte(""))
|
utilsMock.AddFile("detect.sh", []byte(""))
|
||||||
utilsMock.customEnv = []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"}
|
utilsMock.customEnv = []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"}
|
||||||
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
|
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
|
||||||
@ -287,7 +348,7 @@ func TestRunDetect(t *testing.T) {
|
|||||||
t.Run("failure case", func(t *testing.T) {
|
t.Run("failure case", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utilsMock := newDetectTestUtilsBundle()
|
utilsMock := newDetectTestUtilsBundle(false)
|
||||||
utilsMock.ShouldFailOnCommand = map[string]error{"./detect.sh --blackduck.url= --blackduck.api.token= \"--detect.project.name=''\" \"--detect.project.version.name=''\" \"--detect.code.location.name=''\" --detect.source.path='.'": fmt.Errorf("")}
|
utilsMock.ShouldFailOnCommand = map[string]error{"./detect.sh --blackduck.url= --blackduck.api.token= \"--detect.project.name=''\" \"--detect.project.version.name=''\" \"--detect.code.location.name=''\" --detect.source.path='.'": fmt.Errorf("")}
|
||||||
utilsMock.ExitCode = 3
|
utilsMock.ExitCode = 3
|
||||||
utilsMock.AddFile("detect.sh", []byte(""))
|
utilsMock.AddFile("detect.sh", []byte(""))
|
||||||
@ -300,7 +361,7 @@ func TestRunDetect(t *testing.T) {
|
|||||||
t.Run("maven parameters", func(t *testing.T) {
|
t.Run("maven parameters", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utilsMock := newDetectTestUtilsBundle()
|
utilsMock := newDetectTestUtilsBundle(false)
|
||||||
utilsMock.CurrentDir = "root_folder"
|
utilsMock.CurrentDir = "root_folder"
|
||||||
utilsMock.AddFile("detect.sh", []byte(""))
|
utilsMock.AddFile("detect.sh", []byte(""))
|
||||||
err := runDetect(ctx, detectExecuteScanOptions{
|
err := runDetect(ctx, detectExecuteScanOptions{
|
||||||
@ -322,9 +383,10 @@ func TestRunDetect(t *testing.T) {
|
|||||||
func TestAddDetectArgs(t *testing.T) {
|
func TestAddDetectArgs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
args []string
|
args []string
|
||||||
options detectExecuteScanOptions
|
options detectExecuteScanOptions
|
||||||
expected []string
|
isPullRequest bool
|
||||||
|
expected []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
args: []string{"--testProp1=1"},
|
args: []string{"--testProp1=1"},
|
||||||
@ -645,13 +707,76 @@ func TestAddDetectArgs(t *testing.T) {
|
|||||||
"--detect.source.path='.'",
|
"--detect.source.path='.'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--testProp1=1"},
|
||||||
|
options: detectExecuteScanOptions{
|
||||||
|
ServerURL: "https://server.url",
|
||||||
|
Token: "apiToken",
|
||||||
|
ProjectName: "Rapid_scan_on_PRs",
|
||||||
|
Version: "1.0",
|
||||||
|
VersioningModel: "major-minor",
|
||||||
|
CodeLocation: "",
|
||||||
|
ScanPaths: []string{"path1", "path2"},
|
||||||
|
MinScanInterval: 4,
|
||||||
|
CustomScanVersion: "1.0",
|
||||||
|
},
|
||||||
|
isPullRequest: true,
|
||||||
|
expected: []string{
|
||||||
|
"--testProp1=1",
|
||||||
|
"--detect.blackduck.signature.scanner.arguments='--min-scan-interval=4'",
|
||||||
|
"--blackduck.url=https://server.url",
|
||||||
|
"--blackduck.api.token=apiToken",
|
||||||
|
"\"--detect.project.name='Rapid_scan_on_PRs'\"",
|
||||||
|
"\"--detect.project.version.name='1.0'\"",
|
||||||
|
"\"--detect.code.location.name='Rapid_scan_on_PRs/1.0'\"",
|
||||||
|
"--detect.blackduck.signature.scanner.paths=path1,path2",
|
||||||
|
"--detect.source.path='.'",
|
||||||
|
"--detect.blackduck.scan.mode='RAPID'",
|
||||||
|
"--detect.blackduck.rapid.compare.mode='BOM_COMPARE_STRICT'",
|
||||||
|
"--detect.cleanup=false",
|
||||||
|
"--detect.output.path='report'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--testProp1=1"},
|
||||||
|
options: detectExecuteScanOptions{
|
||||||
|
ServerURL: "https://server.url",
|
||||||
|
Token: "apiToken",
|
||||||
|
ProjectName: "Rapid_scan_on_PRs",
|
||||||
|
Version: "2.0",
|
||||||
|
VersioningModel: "major-minor",
|
||||||
|
CodeLocation: "",
|
||||||
|
ScanPaths: []string{"path1", "path2"},
|
||||||
|
MinScanInterval: 4,
|
||||||
|
CustomScanVersion: "2.0",
|
||||||
|
},
|
||||||
|
isPullRequest: true,
|
||||||
|
expected: []string{
|
||||||
|
"--testProp1=1",
|
||||||
|
"--detect.blackduck.signature.scanner.arguments='--min-scan-interval=4'",
|
||||||
|
"--blackduck.url=https://server.url",
|
||||||
|
"--blackduck.api.token=apiToken",
|
||||||
|
"\"--detect.project.name='Rapid_scan_on_PRs'\"",
|
||||||
|
"\"--detect.project.version.name='2.0'\"",
|
||||||
|
"\"--detect.code.location.name='Rapid_scan_on_PRs/2.0'\"",
|
||||||
|
"--detect.blackduck.signature.scanner.paths=path1,path2",
|
||||||
|
"--detect.source.path='.'",
|
||||||
|
"--detect.blackduck.scan.mode='RAPID'",
|
||||||
|
"--detect.cleanup=false",
|
||||||
|
"--detect.output.path='report'",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range testData {
|
for k, v := range testData {
|
||||||
v := v
|
v := v
|
||||||
t.Run(fmt.Sprintf("run %v", k), func(t *testing.T) {
|
t.Run(fmt.Sprintf("run %v", k), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
got, err := addDetectArgs(v.args, v.options, newDetectTestUtilsBundle())
|
|
||||||
|
config := detectExecuteScanOptions{Token: "token", ServerURL: "https://my.blackduck.system", ProjectName: v.options.ProjectName, Version: v.options.Version, CustomScanVersion: v.options.CustomScanVersion}
|
||||||
|
sys := newBlackduckMockSystem(config)
|
||||||
|
|
||||||
|
got, err := addDetectArgs(v.args, v.options, newDetectTestUtilsBundle(v.isPullRequest), &sys)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, v.expected, got)
|
assert.Equal(t, v.expected, got)
|
||||||
})
|
})
|
||||||
@ -681,7 +806,7 @@ func TestPostScanChecksAndReporting(t *testing.T) {
|
|||||||
t.Run("Reporting after scan", func(t *testing.T) {
|
t.Run("Reporting after scan", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
config := detectExecuteScanOptions{Token: "token", ServerURL: "https://my.blackduck.system", ProjectName: "SHC-PiperTest", Version: "", CustomScanVersion: "1.0"}
|
config := detectExecuteScanOptions{Token: "token", ServerURL: "https://my.blackduck.system", ProjectName: "SHC-PiperTest", Version: "", CustomScanVersion: "1.0"}
|
||||||
utils := newDetectTestUtilsBundle()
|
utils := newDetectTestUtilsBundle(false)
|
||||||
sys := newBlackduckMockSystem(config)
|
sys := newBlackduckMockSystem(config)
|
||||||
err := postScanChecksAndReporting(ctx, config, &detectExecuteScanInflux{}, utils, &sys)
|
err := postScanChecksAndReporting(ctx, config, &detectExecuteScanInflux{}, utils, &sys)
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ If you have configured your orchestrator to detect pull requests, then the `dete
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. Enable detecExecuationScan in the orchestrator.
|
2. Enable detecExecuationScan in the orchestrator.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
@Library('piper-lib') _
|
@Library('piper-lib') _
|
||||||
|
420
pkg/reporting/pullRequestReport.go
Normal file
420
pkg/reporting/pullRequestReport.go
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
package reporting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Components - for parsing from file
|
||||||
|
type Components []Component
|
||||||
|
|
||||||
|
type Component struct {
|
||||||
|
ComponentName string `json:"componentName"`
|
||||||
|
ComponentVersion string `json:"versionName"`
|
||||||
|
ComponentIdentifier string `json:"componentIdentifier"`
|
||||||
|
ViolatingPolicyNames []string `json:"violatingPolicyNames"`
|
||||||
|
PolicyViolationVulnerabilities []PolicyViolationVulnerability `json:"policyViolationVulnerabilities"`
|
||||||
|
PolicyViolationLicenses []PolicyViolationLicense `json:"policyViolationLicenses"`
|
||||||
|
WarningMessage string `json:"warningMessage"`
|
||||||
|
ErrorMessage string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyViolationVulnerability struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ViolatingPolicyNames []string `json:"ViolatingPolicyNames"`
|
||||||
|
WarningMessage string `json:"warningMessage"`
|
||||||
|
ErrorMessage string `json:"errorMessage"`
|
||||||
|
Meta Meta `json:"_meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyViolationLicense struct {
|
||||||
|
LicenseName string `json:"licenseName"`
|
||||||
|
ViolatingPolicyNames []string `json:"violatingPolicyNames"`
|
||||||
|
Meta Meta `json:"_meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RapidScanReport - for commenting to pull requests
|
||||||
|
type RapidScanReport struct {
|
||||||
|
Success bool
|
||||||
|
|
||||||
|
ExecutedTime string
|
||||||
|
|
||||||
|
MainTableHeaders []string
|
||||||
|
MainTableValues [][]string
|
||||||
|
|
||||||
|
VulnerabilitiesTable []Vulnerabilities
|
||||||
|
LicensesTable []Licenses
|
||||||
|
OtherViolationsTable []OtherViolations
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vulnerabilities struct {
|
||||||
|
PolicyViolationName string
|
||||||
|
Values []Vulnerability
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vulnerability struct {
|
||||||
|
VulnerabilityID string
|
||||||
|
VulnerabilityScore string
|
||||||
|
ComponentName string
|
||||||
|
VulnerabilityHref string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Licenses struct {
|
||||||
|
PolicyViolationName string
|
||||||
|
Values []License
|
||||||
|
}
|
||||||
|
|
||||||
|
type License struct {
|
||||||
|
LicenseName string
|
||||||
|
ComponentName string
|
||||||
|
LicenseHref string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OtherViolations struct {
|
||||||
|
PolicyViolationName string
|
||||||
|
Values []OtherViolation
|
||||||
|
}
|
||||||
|
|
||||||
|
type OtherViolation struct {
|
||||||
|
ComponentName string
|
||||||
|
}
|
||||||
|
|
||||||
|
const rapidReportMdTemplate = `
|
||||||
|
## {{if .Success}}:heavy_check_mark: OSS related checks passed successfully
|
||||||
|
### :clipboard: OSS related checks executed by Black Duck - rapid scan passed successfully.
|
||||||
|
<a href="https://community.synopsys.com/s/document-item?bundleId=integrations-detect&topicId=downloadingandrunning%2Frapidscan.html&_LANG=enus"><h3>RAPID SCAN</h3> </a>
|
||||||
|
|
||||||
|
{{else}} :x: OSS related checks failed
|
||||||
|
### :clipboard: Policies violated by added OSS components
|
||||||
|
<table>
|
||||||
|
<tr>{{range $s := .MainTableHeaders -}}<td><b>{{$s}}</b></td>{{- end}}</tr>
|
||||||
|
{{range $s := .MainTableValues -}}<tr>{{range $s1 := $s }}<td>{{$s1}}</td>{{- end}}</tr>
|
||||||
|
{{- end}}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{range $index := .VulnerabilitiesTable -}}
|
||||||
|
<details><summary>
|
||||||
|
{{$len := len $index.Values}}
|
||||||
|
{{if le $len 1}} <h3> {{$len}} Policy Violation of {{$index.PolicyViolationName}}</h3>
|
||||||
|
{{else}}<h3> {{$len}} Policy Violations of {{$index.PolicyViolationName}} </h3> {{end}}
|
||||||
|
</summary>
|
||||||
|
<table>
|
||||||
|
<tr><td><b>Vulnerability ID</b></td><td><b>Vulnerability Score</b></td><td><b>Component Name</b></td></tr>
|
||||||
|
{{range $value := $index.Values -}}
|
||||||
|
<tr>
|
||||||
|
<td> <a href="{{$value.VulnerabilityHref}}"> {{$value.VulnerabilityID}} </a> </td><td>{{$value.VulnerabilityScore}}</td><td>{{$value.ComponentName}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end -}}
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{{end -}}
|
||||||
|
{{range $index := .LicensesTable -}}
|
||||||
|
<details><summary>
|
||||||
|
{{$len := len $index.Values}}
|
||||||
|
{{if le $len 1}} <h3> {{$len}} Policy Violation of {{$index.PolicyViolationName}}</h3>
|
||||||
|
{{else}}<h3> {{$len}} Policy Violations of {{$index.PolicyViolationName}} </h3> {{end}}
|
||||||
|
</summary>
|
||||||
|
<table>
|
||||||
|
<tr><td><b>License Name</b></td><td><b>Component Name</b></td></tr>
|
||||||
|
{{range $value := $index.Values -}}
|
||||||
|
<tr><td> <a href="{{$value.LicenseHref}}"> {{$value.LicenseName}} </a> </td><td>{{$value.ComponentName}}</td></tr>
|
||||||
|
{{end -}}
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{{end -}}
|
||||||
|
{{range $index := .OtherViolationsTable -}}
|
||||||
|
<details><summary>
|
||||||
|
{{$len := len $index.Values}}
|
||||||
|
{{if le $len 1}} <h3> {{$len}} Policy Violation of {{$index.PolicyViolationName}}</h3>
|
||||||
|
{{else}}<h3> {{$len}} Policy Violations of {{$index.PolicyViolationName}} </h3> {{end}}
|
||||||
|
</summary>
|
||||||
|
<table>
|
||||||
|
<tr><td><b>Component Name</b></td></tr>
|
||||||
|
{{range $value := $index.Values -}}
|
||||||
|
<tr><td>{{$value.ComponentName}}</td></tr>
|
||||||
|
{{end -}}
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{{end -}}
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// RapidScanResult reads result of Rapid scan from generated file
|
||||||
|
func RapidScanResult(dir string) (string, error) {
|
||||||
|
components, removeDir, err := findAndReadJsonFile(dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if components == nil {
|
||||||
|
return "", errors.New("couldn't parse info from file")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := createMarkdownReport(components)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(removeDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().Error("Couldn't remove report file", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files []os.DirEntry
|
||||||
|
|
||||||
|
// findLastCreatedDir finds last created directory
|
||||||
|
func findLastCreatedDir(directories []os.DirEntry) os.DirEntry {
|
||||||
|
lastCreatedDir := directories[0]
|
||||||
|
for _, dir := range directories {
|
||||||
|
if dir.Name() > lastCreatedDir.Name() {
|
||||||
|
lastCreatedDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastCreatedDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAndReadJsonFile find file BlackDuck_DeveloperMode_Result.json generated by detectExecuteStep and read it
|
||||||
|
func findAndReadJsonFile(dir string) (*Components, string, error) {
|
||||||
|
var err error
|
||||||
|
filePath := dir + "/runs"
|
||||||
|
allFiles, err := os.ReadDir(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if allFiles == nil {
|
||||||
|
return nil, "", errors.New("no report files")
|
||||||
|
}
|
||||||
|
lastDir := findLastCreatedDir(allFiles)
|
||||||
|
removeDir := filePath + "/" + lastDir.Name()
|
||||||
|
filePath = filePath + "/" + lastDir.Name() + "/scan"
|
||||||
|
files, err := os.ReadDir(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if files == nil {
|
||||||
|
return nil, "", errors.New("no report files")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.IsDir() && strings.HasSuffix(file.Name(), "BlackDuck_DeveloperMode_Result.json") {
|
||||||
|
var result Components
|
||||||
|
jsonFile, err := os.Open(filePath + "/" + file.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
fileBody, err := io.ReadAll(jsonFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(fileBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
err = jsonFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().Error(fmt.Sprintf("Couldn't close %s", jsonFile.Name()), err)
|
||||||
|
}
|
||||||
|
return &result, removeDir, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createMarkdownReport creates markdown report to upload it as GitHub PR comment
|
||||||
|
func createMarkdownReport(components *Components) (*bytes.Buffer, error) {
|
||||||
|
// preparing report
|
||||||
|
var scanReport RapidScanReport
|
||||||
|
scanReport.Success = true
|
||||||
|
|
||||||
|
// getting reports to maps
|
||||||
|
allPolicyViolationsMapUsed := make(map[string]bool)
|
||||||
|
countPolicyViolationComponent := make(map[string]map[string]int)
|
||||||
|
vulnerabilities := make(map[string][]Vulnerability)
|
||||||
|
licenses := make(map[string][]License)
|
||||||
|
otherViolations := make(map[string][]OtherViolation)
|
||||||
|
componentNames := make([]string, len(*components))
|
||||||
|
|
||||||
|
for idx, component := range *components {
|
||||||
|
componentName := component.ComponentName + " " + component.ComponentVersion + " (" + component.ComponentIdentifier + ")"
|
||||||
|
componentNames[idx] = componentName
|
||||||
|
|
||||||
|
// for others
|
||||||
|
for _, policyViolationName := range component.ViolatingPolicyNames {
|
||||||
|
if !allPolicyViolationsMapUsed[policyViolationName] {
|
||||||
|
allPolicyViolationsMapUsed[policyViolationName] = true
|
||||||
|
scanReport.MainTableHeaders = append(scanReport.MainTableHeaders, policyViolationName)
|
||||||
|
}
|
||||||
|
if countPolicyViolationComponent[policyViolationName] == nil {
|
||||||
|
countPolicyViolationComponent[policyViolationName] = make(map[string]int)
|
||||||
|
}
|
||||||
|
msg := component.ErrorMessage + " " + component.WarningMessage
|
||||||
|
if strings.Contains(msg, policyViolationName) {
|
||||||
|
countPolicyViolationComponent[policyViolationName][componentName]++
|
||||||
|
otherViolations[policyViolationName] = append(otherViolations[policyViolationName], OtherViolation{ComponentName: componentName})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for Vulnerabilities
|
||||||
|
for _, policyVulnerability := range component.PolicyViolationVulnerabilities {
|
||||||
|
for _, policyViolationName := range policyVulnerability.ViolatingPolicyNames {
|
||||||
|
if countPolicyViolationComponent[policyViolationName] == nil {
|
||||||
|
countPolicyViolationComponent[policyViolationName] = make(map[string]int)
|
||||||
|
}
|
||||||
|
countPolicyViolationComponent[policyViolationName][componentName]++
|
||||||
|
vulnerabilities[policyViolationName] = append(vulnerabilities[policyViolationName],
|
||||||
|
Vulnerability{
|
||||||
|
VulnerabilityID: policyVulnerability.Name,
|
||||||
|
VulnerabilityHref: policyVulnerability.Meta.Href,
|
||||||
|
VulnerabilityScore: getScore(policyVulnerability.ErrorMessage, "score") + " " + getScore(policyVulnerability.ErrorMessage, "severity"),
|
||||||
|
ComponentName: componentName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for Licenses
|
||||||
|
for _, policyViolationLicense := range component.PolicyViolationLicenses {
|
||||||
|
for _, policyViolationName := range policyViolationLicense.ViolatingPolicyNames {
|
||||||
|
if countPolicyViolationComponent[policyViolationName] == nil {
|
||||||
|
countPolicyViolationComponent[policyViolationName] = make(map[string]int)
|
||||||
|
}
|
||||||
|
countPolicyViolationComponent[policyViolationName][componentName]++
|
||||||
|
licenses[policyViolationName] = append(licenses[policyViolationName],
|
||||||
|
License{
|
||||||
|
LicenseName: policyViolationLicense.LicenseName,
|
||||||
|
LicenseHref: policyViolationLicense.Meta.Href + "/license-terms",
|
||||||
|
ComponentName: componentName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanReport.MainTableHeaders != nil && componentNames != nil {
|
||||||
|
scanReport.Success = false
|
||||||
|
|
||||||
|
// MainTable sort & copy
|
||||||
|
sort.Strings(scanReport.MainTableHeaders)
|
||||||
|
sort.Strings(componentNames)
|
||||||
|
scanReport.MainTableHeaders = append([]string{"Component name"}, scanReport.MainTableHeaders...)
|
||||||
|
for i := range componentNames {
|
||||||
|
scanReport.MainTableValues = append(scanReport.MainTableValues, []string{})
|
||||||
|
scanReport.MainTableValues[i] = append(scanReport.MainTableValues[i], componentNames[i])
|
||||||
|
for j := 1; j < len(scanReport.MainTableHeaders); j++ {
|
||||||
|
policyV := scanReport.MainTableHeaders[j]
|
||||||
|
comp := componentNames[i]
|
||||||
|
count := strconv.Itoa(countPolicyViolationComponent[policyV][comp])
|
||||||
|
scanReport.MainTableValues[i] = append(scanReport.MainTableValues[i], count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VulnerabilitiesTable sort & copy
|
||||||
|
for key := range vulnerabilities {
|
||||||
|
item := vulnerabilities[key]
|
||||||
|
sort.Slice(item, func(i, j int) bool {
|
||||||
|
return scoreLogicSort(item[i].VulnerabilityScore, item[j].VulnerabilityScore)
|
||||||
|
})
|
||||||
|
scanReport.VulnerabilitiesTable = append(scanReport.VulnerabilitiesTable, Vulnerabilities{
|
||||||
|
PolicyViolationName: key,
|
||||||
|
Values: item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(scanReport.VulnerabilitiesTable, func(i, j int) bool {
|
||||||
|
return scanReport.VulnerabilitiesTable[i].PolicyViolationName < scanReport.VulnerabilitiesTable[j].PolicyViolationName
|
||||||
|
})
|
||||||
|
|
||||||
|
// LicensesTable sort & copy
|
||||||
|
for key := range licenses {
|
||||||
|
item := licenses[key]
|
||||||
|
sort.Slice(item, func(i, j int) bool {
|
||||||
|
if item[i].LicenseName < item[j].LicenseName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if item[i].LicenseName > item[j].LicenseName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return item[i].ComponentName < item[j].ComponentName
|
||||||
|
})
|
||||||
|
scanReport.LicensesTable = append(scanReport.LicensesTable, Licenses{
|
||||||
|
PolicyViolationName: key,
|
||||||
|
Values: item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(scanReport.LicensesTable, func(i, j int) bool {
|
||||||
|
return scanReport.LicensesTable[i].PolicyViolationName < scanReport.LicensesTable[j].PolicyViolationName
|
||||||
|
})
|
||||||
|
|
||||||
|
// OtherViolationsTable sort & copy
|
||||||
|
for key := range otherViolations {
|
||||||
|
item := otherViolations[key]
|
||||||
|
sort.Slice(item, func(i, j int) bool {
|
||||||
|
return item[i].ComponentName < item[j].ComponentName
|
||||||
|
})
|
||||||
|
scanReport.OtherViolationsTable = append(scanReport.OtherViolationsTable, OtherViolations{
|
||||||
|
PolicyViolationName: key,
|
||||||
|
Values: item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(scanReport.OtherViolationsTable, func(i, j int) bool {
|
||||||
|
return scanReport.OtherViolationsTable[i].PolicyViolationName < scanReport.OtherViolationsTable[j].PolicyViolationName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("report").Parse(rapidReportMdTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to create Markdown report template err:" + err.Error())
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = tmpl.Execute(buf, scanReport)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to create Markdown report template err:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScore extracts score or severity from error message
|
||||||
|
func getScore(message, key string) string {
|
||||||
|
indx := strings.Index(message, key)
|
||||||
|
if indx == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var result string
|
||||||
|
var notFirstSpace bool
|
||||||
|
for _, s := range message[indx+len(key):] {
|
||||||
|
if s == ' ' && notFirstSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
notFirstSpace = true
|
||||||
|
result = result + string(s)
|
||||||
|
}
|
||||||
|
return strings.Trim(result, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// scoreLogicSort sorts two scores
|
||||||
|
func scoreLogicSort(iStr, jStr string) bool {
|
||||||
|
if strings.Contains(iStr, "10.0") {
|
||||||
|
return true
|
||||||
|
} else if strings.Contains(jStr, "10.0") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if iStr >= jStr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
249
pkg/reporting/pullRequestReport_test.go
Normal file
249
pkg/reporting/pullRequestReport_test.go
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package reporting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Testing createMarkdownReport function
|
||||||
|
func TestCreateMarkdownReport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
testName string
|
||||||
|
components *Components
|
||||||
|
expectedErr error
|
||||||
|
expectedReport string
|
||||||
|
}{
|
||||||
|
|
||||||
|
{
|
||||||
|
testName: "Vulnerabilities were found",
|
||||||
|
components: &Components{
|
||||||
|
{
|
||||||
|
ComponentName: "qs - QS Querystring",
|
||||||
|
ComponentVersion: "5.2.1",
|
||||||
|
ComponentIdentifier: "npmjs:qs/5.2.1",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"High Vulnerability Security Issue",
|
||||||
|
},
|
||||||
|
PolicyViolationVulnerabilities: []PolicyViolationVulnerability{
|
||||||
|
{
|
||||||
|
Name: "CVE-2017-1000048",
|
||||||
|
ViolatingPolicyNames: []string{"High Vulnerability Security Issue"},
|
||||||
|
WarningMessage: "",
|
||||||
|
ErrorMessage: "Component qs - QS Querystring version 5.2.1 with ID npmjs:qs/5.2.1 violates policy" +
|
||||||
|
" High Vulnerability Security Issue: found vulnerability CVE-2017-1000048 with severity HIGH and CVSS score 7.5",
|
||||||
|
Meta: Meta{
|
||||||
|
Href: "https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2017-1000048",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PolicyViolationLicenses: nil,
|
||||||
|
WarningMessage: "",
|
||||||
|
ErrorMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ComponentName: "Lodash",
|
||||||
|
ComponentVersion: "4.17.10",
|
||||||
|
ComponentIdentifier: "npmjs:lodash/4.17.10",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"High Vulnerability Security Issue",
|
||||||
|
"Test High Severity Vuln Filter",
|
||||||
|
"OutdatedFOSSLibraries",
|
||||||
|
},
|
||||||
|
PolicyViolationVulnerabilities: []PolicyViolationVulnerability{
|
||||||
|
{
|
||||||
|
Name: "CVE-2019-10744",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"High Vulnerability Security Issue",
|
||||||
|
"Test High Severity Vuln Filter",
|
||||||
|
},
|
||||||
|
WarningMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy Test High Severity Vuln " +
|
||||||
|
"Filter: found vulnerability CVE-2019-10744 with severity CRITICAL and CVSS score 9.1",
|
||||||
|
ErrorMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy High Vulnerability " +
|
||||||
|
"Security Issue: found vulnerability CVE-2019-10744 with severity CRITICAL and CVSS score 9.1",
|
||||||
|
Meta: Meta{
|
||||||
|
Href: "https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2019-10744"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CVE-2020-8203",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"High Vulnerability Security Issue",
|
||||||
|
"Test High Severity Vuln Filter",
|
||||||
|
},
|
||||||
|
WarningMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy Test " +
|
||||||
|
"High Severity Vuln Filter: found vulnerability CVE-2020-8203 with severity HIGH and CVSS score 7.4",
|
||||||
|
ErrorMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy Test High Severity Vuln Filter: " +
|
||||||
|
"found vulnerability CVE-2020-8203 with severity HIGH and CVSS score 7.4",
|
||||||
|
Meta: Meta{
|
||||||
|
Href: "https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2020-8203",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "BDSA-2019-3842",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"High Vulnerability Security Issue",
|
||||||
|
"Test High Severity Vuln Filter",
|
||||||
|
},
|
||||||
|
WarningMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy Test High Severity Vuln Filter: found vulnerability BDSA-2019-3842 with severity HIGH and CVSS score 7.1",
|
||||||
|
ErrorMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy High Vulnerability Security Issue: found vulnerability BDSA-2019-3842 with severity HIGH and CVSS score 7.1",
|
||||||
|
Meta: Meta{
|
||||||
|
Href: "https://sap-staging.app.blackduck.com/api/vulnerabilities/BDSA-2019-3842",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PolicyViolationLicenses: nil,
|
||||||
|
WarningMessage: "Component Lodash version 4.17.10 with ID npmjs:lodash/4.17.10 violates policy OutdatedFOSSLibraries",
|
||||||
|
ErrorMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ComponentName: "Chalk",
|
||||||
|
ComponentVersion: "1.1.3",
|
||||||
|
ComponentIdentifier: "npmjs:chalk/1.1.3",
|
||||||
|
ViolatingPolicyNames: []string{
|
||||||
|
"OutdatedFOSSLibraries",
|
||||||
|
},
|
||||||
|
PolicyViolationVulnerabilities: nil,
|
||||||
|
PolicyViolationLicenses: nil,
|
||||||
|
WarningMessage: "Component Chalk version 1.1.3 with ID npmjs:chalk/1.1.3 violates policy OutdatedFOSSLibraries",
|
||||||
|
ErrorMessage: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedReport: "\n## :x: OSS related checks failed\n ### :clipboard: Policies violated by added OSS components\n " +
|
||||||
|
"<table>\n <tr><td><b>Component name</b></td><td><b>High Vulnerability Security Issue</b></td><td><b>OutdatedFOSSLibraries</b></td><td><b>" +
|
||||||
|
"Test High Severity Vuln Filter</b></td></tr>\n <tr><td>Chalk 1.1.3 (npmjs:chalk/1.1.3)</td><td>0</td><td>1</td><td>0</td></tr><tr><td>Lodash " +
|
||||||
|
"4.17.10 (npmjs:lodash/4.17.10)</td><td>3</td><td>1</td><td>3</td></tr><tr><td>qs - QS Querystring 5.2.1 " +
|
||||||
|
"(npmjs:qs/5.2.1)</td><td>1</td><td>0</td><td>0</td></tr>\n </table>\n\n<details><summary>\n\n<h3> 4 Policy " +
|
||||||
|
"Violations of High Vulnerability Security Issue </h3> \n</summary>\n\t<table>\n\t\t<tr><td><b>Vulnerability ID</b></td><td><b>Vulnerability" +
|
||||||
|
" Score</b></td><td><b>Component Name</b></td></tr>\n\t\t<tr>\n\t\t\t<td> <a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2019-10744\"> CVE-2019-10744 </a> </td><td>9.1 CRITICAL</td><td>Lodash 4.17.10 " +
|
||||||
|
"(npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t<tr>\n\t\t\t<td> <a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2017-1000048\"> " +
|
||||||
|
"CVE-2017-1000048 </a> </td><td>7.5 HIGH</td><td>qs - QS Querystring 5.2.1 (npmjs:qs/5.2.1)</td>\n\t\t\t</tr>\n\t\t<tr>\n\t\t\t<td> " +
|
||||||
|
"<a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2020-8203\"> CVE-2020-8203 </a> </td><td>7.4 HIGH</td><td>Lodash " +
|
||||||
|
"4.17.10 (npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t<tr>\n\t\t\t<td> <a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/BDSA-2019-3842\"> " +
|
||||||
|
"BDSA-2019-3842 </a> </td><td>7.1 HIGH</td><td>Lodash 4.17.10 (npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t</table>\n</details>\n<details><summary>\n\n<h3> " +
|
||||||
|
"3 Policy Violations of Test High Severity Vuln Filter </h3> \n</summary>\n\t<table>\n\t\t<tr><td><b>Vulnerability ID</b></td><td><b>Vulnerability " +
|
||||||
|
"Score</b></td><td><b>Component Name</b></td></tr>\n\t\t<tr>\n\t\t\t<td> <a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2019-10744\"> " +
|
||||||
|
"CVE-2019-10744 </a> </td><td>9.1 CRITICAL</td><td>Lodash 4.17.10 (npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t<tr>\n\t\t\t<td> " +
|
||||||
|
"<a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/CVE-2020-8203\"> CVE-2020-8203 </a> </td><td>7.4 " +
|
||||||
|
"HIGH</td><td>Lodash 4.17.10 (npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t<tr>\n\t\t\t<td> <a href=\"https://sap-staging.app.blackduck.com/api/vulnerabilities/BDSA-2019-3842\"> " +
|
||||||
|
"BDSA-2019-3842 </a> </td><td>7.1 HIGH</td><td>Lodash 4.17.10 (npmjs:lodash/4.17.10)</td>\n\t\t\t</tr>\n\t\t</table>\n</details>\n<details><summary>\n\n<h3> " +
|
||||||
|
"2 Policy Violations of OutdatedFOSSLibraries </h3> \n</summary>\n\t<table>\n\t\t<tr><td><b>Component Name</b></td></tr>\n\t\t<tr><td>Chalk 1.1.3 " +
|
||||||
|
"(npmjs:chalk/1.1.3)</td></tr>\n\t\t<tr><td>Lodash 4.17.10 (npmjs:lodash/4.17.10)</td></tr>\n\t\t</table>\n</details>\n\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "No vulnerabilities && successful build",
|
||||||
|
components: &Components{},
|
||||||
|
expectedReport: "\n## :heavy_check_mark: OSS related checks passed successfully\n ### :clipboard: OSS related checks executed by Black Duck " +
|
||||||
|
"- rapid scan passed successfully.\n" +
|
||||||
|
" <a href=\"https://community.synopsys.com/s/document-item?bundleId=integrations-detect&topicId=downloadingandrunning%2Frapidscan.html&_LANG=enus\">" +
|
||||||
|
"<h3>RAPID SCAN</h3> </a>\n\n\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
t.Run(c.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf, err := createMarkdownReport(c.components)
|
||||||
|
|
||||||
|
assert.Equal(t, c.expectedErr, err)
|
||||||
|
assert.Equal(t, c.expectedReport, buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing getScore function
|
||||||
|
func TestGetScore(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
testName string
|
||||||
|
message string
|
||||||
|
key string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "Score 7.5",
|
||||||
|
message: "Component qs - QS Querystring version 5.2.1 with ID npmjs:qs/5.2.1 violates policy High " +
|
||||||
|
"Vulnerability Security Issue: found vulnerability CVE-2017-1000048 with severity HIGH and CVSS score 7.5",
|
||||||
|
key: "score",
|
||||||
|
expected: "7.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "CRITICAL severity",
|
||||||
|
message: "Component minimist version 0.0.8 with ID npmjs:minimist/0.0.8 violates policy High " +
|
||||||
|
"Vulnerability Security Issue: found vulnerability CVE-2021-44906 with severity CRITICAL and CVSS score 9.8",
|
||||||
|
key: "severity",
|
||||||
|
expected: "CRITICAL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "No severity",
|
||||||
|
message: "Component minimist version 0.0.8 with ID npmjs:minimist/0.0.8 violates policy High " +
|
||||||
|
"Vulnerability Security Issue: found vulnerability CVE-2021-44906 with CVSS score 9.8",
|
||||||
|
key: "severity",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
t.Run(c.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got := getScore(c.message, c.key)
|
||||||
|
assert.Equal(t, c.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing scoreLogicSort function
|
||||||
|
func TestScoreLogicSort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
testName string
|
||||||
|
leftScore string
|
||||||
|
rightScore string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "left score is higher",
|
||||||
|
leftScore: "8.8 HIGH",
|
||||||
|
rightScore: "8.1 HIGH",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "right score is higher",
|
||||||
|
leftScore: "7.9 HIGH",
|
||||||
|
rightScore: "9.3 CRITICAL",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "left score equals 10.0",
|
||||||
|
leftScore: "10.0 CRITICAL",
|
||||||
|
rightScore: "8.1 HIGH",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "right score equals 10.0",
|
||||||
|
leftScore: "7.9 HIGH",
|
||||||
|
rightScore: "10.0 CRITICAL",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "both scores equal 10.0",
|
||||||
|
leftScore: "10.0 CRITICAL",
|
||||||
|
rightScore: "10.0 CRITICAL",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
t.Run(c.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got := scoreLogicSort(c.leftScore, c.rightScore)
|
||||||
|
assert.Equal(t, c.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user