You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(sonar): add hotspot security report (#5365)
Co-authored-by: Yuriy.Tereshchuk <astro.lutsk.aa@gmail.com>
This commit is contained in:
committed by
GitHub
parent
15492ee833
commit
fcd167b2e6
@@ -250,9 +250,25 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = getStaticCodeCheckResults(config, &taskReport, serverUrl, influx, apiClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = getHotSpotSecurityCheckResults(config, &taskReport, serverUrl, apiClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStaticCodeCheckResults(config sonarExecuteScanOptions, taskReport *SonarUtils.TaskReportData, serverUrl string, influx *sonarExecuteScanInflux, apiClient SonarUtils.Sender) error {
|
||||||
// fetch number of issues by severity
|
// fetch number of issues by severity
|
||||||
issueService := SonarUtils.NewIssuesService(serverUrl, config.Token, taskReport.ProjectKey, config.Organization, config.BranchName, config.ChangeID, apiClient)
|
issueService := SonarUtils.NewIssuesService(serverUrl, config.Token, taskReport.ProjectKey, config.Organization, config.BranchName, config.ChangeID, apiClient)
|
||||||
var categories []SonarUtils.Severity
|
var categories []SonarUtils.Severity
|
||||||
|
var err error
|
||||||
influx.sonarqube_data.fields.blocker_issues, err = issueService.GetNumberOfBlockerIssues(&categories)
|
influx.sonarqube_data.fields.blocker_issues, err = issueService.GetNumberOfBlockerIssues(&categories)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -274,7 +290,7 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reportData := SonarUtils.ReportData{
|
reportData := SonarUtils.ReportCodeCheckData{
|
||||||
ServerURL: taskReport.ServerURL,
|
ServerURL: taskReport.ServerURL,
|
||||||
ProjectKey: taskReport.ProjectKey,
|
ProjectKey: taskReport.ProjectKey,
|
||||||
TaskID: taskReport.TaskID,
|
TaskID: taskReport.TaskID,
|
||||||
@@ -282,7 +298,7 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
|||||||
BranchName: config.BranchName,
|
BranchName: config.BranchName,
|
||||||
Organization: config.Organization,
|
Organization: config.Organization,
|
||||||
Errors: categories[:],
|
Errors: categories[:],
|
||||||
NumberOfIssues: SonarUtils.Issues{
|
NumberOfIssues: &SonarUtils.Issues{
|
||||||
Blocker: influx.sonarqube_data.fields.blocker_issues,
|
Blocker: influx.sonarqube_data.fields.blocker_issues,
|
||||||
Critical: influx.sonarqube_data.fields.critical_issues,
|
Critical: influx.sonarqube_data.fields.critical_issues,
|
||||||
Major: influx.sonarqube_data.fields.major_issues,
|
Major: influx.sonarqube_data.fields.major_issues,
|
||||||
@@ -307,12 +323,29 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
|||||||
|
|
||||||
log.Entry().Debugf("Influx values: %v", influx.sonarqube_data.fields)
|
log.Entry().Debugf("Influx values: %v", influx.sonarqube_data.fields)
|
||||||
|
|
||||||
err = SonarUtils.WriteReport(reportData, sonar.workingDir, os.WriteFile)
|
return SonarUtils.WriteCodeCheckReport(reportData, sonar.workingDir, os.WriteFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHotSpotSecurityCheckResults(config sonarExecuteScanOptions, taskReport *SonarUtils.TaskReportData, serverUrl string, apiClient SonarUtils.Sender) error {
|
||||||
|
// fetch number of issues by severity
|
||||||
|
issueService := SonarUtils.NewIssuesService(serverUrl, config.Token, taskReport.ProjectKey, config.Organization, config.BranchName, config.ChangeID, apiClient)
|
||||||
|
var hotspotissues []SonarUtils.SecurityHotspot
|
||||||
|
err := issueService.GetHotSpotSecurityIssues(&hotspotissues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
reportData := SonarUtils.ReportHotSpotData{
|
||||||
|
ServerURL: taskReport.ServerURL,
|
||||||
|
ProjectKey: taskReport.ProjectKey,
|
||||||
|
TaskID: taskReport.TaskID,
|
||||||
|
ChangeID: config.ChangeID,
|
||||||
|
BranchName: config.BranchName,
|
||||||
|
Organization: config.Organization,
|
||||||
|
SecurityHotspots: hotspotissues[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
return SonarUtils.WriteHotSpotReport(reportData, sonar.workingDir, os.WriteFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInOptions returns true, if the given property is already provided in config.Options.
|
// isInOptions returns true, if the given property is already provided in config.Options.
|
||||||
|
@@ -153,6 +153,7 @@ func TestRunSonar(t *testing.T) {
|
|||||||
// add response handler
|
// add response handler
|
||||||
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointCeTask+"", httpmock.NewStringResponder(http.StatusOK, `{ "task": { "componentId": "AXERR2JBbm9IiM5TEST", "status": "SUCCESS" }}`))
|
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointCeTask+"", httpmock.NewStringResponder(http.StatusOK, `{ "task": { "componentId": "AXERR2JBbm9IiM5TEST", "status": "SUCCESS" }}`))
|
||||||
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, `{ "total": 0 }`))
|
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, `{ "total": 0 }`))
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointHotSpotsSearch+"", httpmock.NewStringResponder(http.StatusOK, `{ "total": 0 }`))
|
||||||
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointMeasuresComponent+"", httpmock.NewStringResponder(http.StatusOK, measuresComponentResponse))
|
httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointMeasuresComponent+"", httpmock.NewStringResponder(http.StatusOK, measuresComponentResponse))
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
t.Run("default", func(t *testing.T) {
|
||||||
|
@@ -12,6 +12,9 @@ import (
|
|||||||
// EndpointIssuesSearch API endpoint for https://sonarcloud.io/web_api/api/issues/search
|
// EndpointIssuesSearch API endpoint for https://sonarcloud.io/web_api/api/issues/search
|
||||||
const EndpointIssuesSearch = "issues/search"
|
const EndpointIssuesSearch = "issues/search"
|
||||||
|
|
||||||
|
// EndpointHotSpotSearch API endpoint for https://sonarcloud.io/web_api/api/hotspots/search
|
||||||
|
const EndpointHotSpotsSearch = "hotspots/search"
|
||||||
|
|
||||||
// IssueService ...
|
// IssueService ...
|
||||||
type IssueService struct {
|
type IssueService struct {
|
||||||
Organization string
|
Organization string
|
||||||
@@ -50,6 +53,35 @@ func (service *IssueService) SearchIssues(options *IssuesSearchOption) (*sonargo
|
|||||||
return result, response, nil
|
return result, response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchIssues ...
|
||||||
|
func (service *IssueService) SearchHotSpots(options *HotSpotSearchOption) (*HotSpotSearchObject, *http.Response, error) {
|
||||||
|
request, err := service.apiClient.create("GET", EndpointHotSpotsSearch, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// use custom HTTP client to send request
|
||||||
|
response, err := service.apiClient.send(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// reuse response verrification from sonargo
|
||||||
|
err = sonargo.CheckResponse(response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// log response
|
||||||
|
log.Entry().Debugf("HTTP Response: %v", func() string { rsp, _ := httputil.DumpResponse(response, true); return string(rsp) }())
|
||||||
|
|
||||||
|
// decode JSON response
|
||||||
|
result := new(HotSpotSearchObject)
|
||||||
|
err = service.apiClient.decode(response, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response, err
|
||||||
|
}
|
||||||
|
return result, response, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (service *IssueService) getIssueCount(severity issueSeverity, categories *[]Severity) (int, error) {
|
func (service *IssueService) getIssueCount(severity issueSeverity, categories *[]Severity) (int, error) {
|
||||||
options := &IssuesSearchOption{
|
options := &IssuesSearchOption{
|
||||||
ComponentKeys: service.Project,
|
ComponentKeys: service.Project,
|
||||||
@@ -114,6 +146,34 @@ func (service *IssueService) GetNumberOfInfoIssues(categories *[]Severity) (int,
|
|||||||
return service.getIssueCount(info, categories)
|
return service.getIssueCount(info, categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *IssueService) GetHotSpotSecurityIssues(securityHotspots *[]SecurityHotspot) error {
|
||||||
|
options := &HotSpotSearchOption{
|
||||||
|
Project: service.Project,
|
||||||
|
Status: to_review,
|
||||||
|
}
|
||||||
|
result, _, err := service.SearchHotSpots(options)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to fetch the numer of hotspots.")
|
||||||
|
}
|
||||||
|
|
||||||
|
table := map[string]int{}
|
||||||
|
service.updateHotSpotTypesTable(&result.HotSpots, table)
|
||||||
|
for priority, hotspots := range table {
|
||||||
|
var hotspot SecurityHotspot
|
||||||
|
hotspot.Priority = priority
|
||||||
|
hotspot.Hotspots = hotspots
|
||||||
|
*securityHotspots = append(*securityHotspots, hotspot)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IssueService) updateHotSpotTypesTable(issues *[]HotSpot, table map[string]int) {
|
||||||
|
for _, issue := range *issues {
|
||||||
|
table[issue.VulnerabilityProbability]++
|
||||||
|
}
|
||||||
|
delete(table, "") // remove undefined key if any exists in response
|
||||||
|
}
|
||||||
|
|
||||||
// NewIssuesService returns a new instance of a service for the issues API endpoint.
|
// NewIssuesService returns a new instance of a service for the issues API endpoint.
|
||||||
func NewIssuesService(host, token, project, organization, branch, pullRequest string, client Sender) *IssueService {
|
func NewIssuesService(host, token, project, organization, branch, pullRequest string, client Sender) *IssueService {
|
||||||
return &IssueService{
|
return &IssueService{
|
||||||
|
@@ -332,3 +332,213 @@ const responseIssueSearchBug = `{
|
|||||||
],
|
],
|
||||||
"facets": []
|
"facets": []
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
func TestHotSpotService(t *testing.T) {
|
||||||
|
testURL := "https://example.org"
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
sender := &piperhttp.Client{}
|
||||||
|
sender.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||||
|
// add response handler
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointHotSpotsSearch+"", httpmock.NewStringResponder(http.StatusOK, responseHotSpotSearchMedium))
|
||||||
|
// create service instance
|
||||||
|
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||||
|
// Severities
|
||||||
|
var hotspots []SecurityHotspot
|
||||||
|
// test
|
||||||
|
err := serviceUnderTest.GetHotSpotSecurityIssues(&hotspots)
|
||||||
|
// assert
|
||||||
|
assert.Equal(t, []SecurityHotspot{{Priority: "MEDIUM", Hotspots: 1}}, hotspots)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||||
|
})
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
sender := &piperhttp.Client{}
|
||||||
|
sender.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||||
|
// add response handler
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointHotSpotsSearch+"", httpmock.NewStringResponder(http.StatusNotFound, responseHotSpotSearchError))
|
||||||
|
// create service instance
|
||||||
|
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||||
|
// Severities
|
||||||
|
var hotspots []SecurityHotspot
|
||||||
|
// test
|
||||||
|
err := serviceUnderTest.GetHotSpotSecurityIssues(&hotspots)
|
||||||
|
// assert
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||||
|
})
|
||||||
|
t.Run("multiple severities", func(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
sender := &piperhttp.Client{}
|
||||||
|
sender.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||||
|
// add response handler
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointHotSpotsSearch+"", httpmock.NewStringResponder(http.StatusOK, responseHotSpotSearchMultiple))
|
||||||
|
// create service instance
|
||||||
|
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||||
|
// Severities
|
||||||
|
// Severities
|
||||||
|
var hotspots []SecurityHotspot
|
||||||
|
// test
|
||||||
|
err := serviceUnderTest.GetHotSpotSecurityIssues(&hotspots)
|
||||||
|
// assert
|
||||||
|
assert.Equal(t, []SecurityHotspot{
|
||||||
|
{Priority: "MEDIUM", Hotspots: 2},
|
||||||
|
{Priority: "LOW", Hotspots: 1},
|
||||||
|
}, hotspots)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseHotSpotSearchMedium = `{
|
||||||
|
"paging": {
|
||||||
|
"pageIndex": 1,
|
||||||
|
"pageSize": 100,
|
||||||
|
"total": 1
|
||||||
|
},
|
||||||
|
"hotspots": [
|
||||||
|
{
|
||||||
|
"key": "d1502ebc-941a-4262-8081-04914834d75a",
|
||||||
|
"component": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"project": "java-camera-viewer",
|
||||||
|
"securityCategory": "weak-cryptography",
|
||||||
|
"vulnerabilityProbability": "MEDIUM",
|
||||||
|
"status": "TO_REVIEW",
|
||||||
|
"line": 658,
|
||||||
|
"message": "Make sure that using this pseudorandom number generator is safe here.",
|
||||||
|
"author": "",
|
||||||
|
"creationDate": "2025-03-31T18:16:09+0000",
|
||||||
|
"updateDate": "2025-04-23T11:02:47+0000",
|
||||||
|
"textRange": {
|
||||||
|
"startLine": 658,
|
||||||
|
"endLine": 658,
|
||||||
|
"startOffset": 16799,
|
||||||
|
"endOffset": 16812
|
||||||
|
},
|
||||||
|
"flows": [],
|
||||||
|
"ruleKey": "javascript:S2245",
|
||||||
|
"messageFormattings": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"key": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"qualifier": "FIL",
|
||||||
|
"name": "configuration-cache-report.html",
|
||||||
|
"longName": "build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"path": "build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "java-camera-viewer",
|
||||||
|
"qualifier": "TRK",
|
||||||
|
"name": "java-camera-viewer",
|
||||||
|
"longName": "java-camera-viewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
const responseHotSpotSearchError = `{
|
||||||
|
"errors":[
|
||||||
|
{
|
||||||
|
"msg":"Project java-camera-viewer1 not found"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
const responseHotSpotSearchMultiple = `{
|
||||||
|
"paging": {
|
||||||
|
"pageIndex": 1,
|
||||||
|
"pageSize": 100,
|
||||||
|
"total": 1
|
||||||
|
},
|
||||||
|
"hotspots": [
|
||||||
|
{
|
||||||
|
"key": "d1502ebc-941a-4262-8081-04914834d75a",
|
||||||
|
"component": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"project": "java-camera-viewer",
|
||||||
|
"securityCategory": "weak-cryptography",
|
||||||
|
"vulnerabilityProbability": "MEDIUM",
|
||||||
|
"status": "TO_REVIEW",
|
||||||
|
"line": 658,
|
||||||
|
"message": "Make sure that using this pseudorandom number generator is safe here.",
|
||||||
|
"author": "",
|
||||||
|
"creationDate": "2025-03-31T18:16:09+0000",
|
||||||
|
"updateDate": "2025-04-23T11:02:47+0000",
|
||||||
|
"textRange": {
|
||||||
|
"startLine": 658,
|
||||||
|
"endLine": 658,
|
||||||
|
"startOffset": 16799,
|
||||||
|
"endOffset": 16812
|
||||||
|
},
|
||||||
|
"flows": [],
|
||||||
|
"ruleKey": "javascript:S2245",
|
||||||
|
"messageFormattings": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "d1502ebc-941a-4262-8081-04914834d75b",
|
||||||
|
"component": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"project": "java-camera-viewer",
|
||||||
|
"securityCategory": "weak-cryptography",
|
||||||
|
"vulnerabilityProbability": "MEDIUM",
|
||||||
|
"status": "TO_REVIEW",
|
||||||
|
"line": 658,
|
||||||
|
"message": "Make sure that using this pseudorandom number generator is safe here.",
|
||||||
|
"author": "",
|
||||||
|
"creationDate": "2025-03-31T18:16:09+0000",
|
||||||
|
"updateDate": "2025-04-23T11:02:47+0000",
|
||||||
|
"textRange": {
|
||||||
|
"startLine": 658,
|
||||||
|
"endLine": 658,
|
||||||
|
"startOffset": 16799,
|
||||||
|
"endOffset": 16812
|
||||||
|
},
|
||||||
|
"flows": [],
|
||||||
|
"ruleKey": "javascript:S2245",
|
||||||
|
"messageFormattings": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "d1502ebc-941a-4262-8081-04914834d75c",
|
||||||
|
"component": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"project": "java-camera-viewer",
|
||||||
|
"securityCategory": "weak-cryptography",
|
||||||
|
"vulnerabilityProbability": "LOW",
|
||||||
|
"status": "TO_REVIEW",
|
||||||
|
"line": 658,
|
||||||
|
"message": "Make sure that using this pseudorandom number generator is safe here.",
|
||||||
|
"author": "",
|
||||||
|
"creationDate": "2025-03-31T18:16:09+0000",
|
||||||
|
"updateDate": "2025-04-23T11:02:47+0000",
|
||||||
|
"textRange": {
|
||||||
|
"startLine": 658,
|
||||||
|
"endLine": 658,
|
||||||
|
"startOffset": 16799,
|
||||||
|
"endOffset": 16812
|
||||||
|
},
|
||||||
|
"flows": [],
|
||||||
|
"ruleKey": "javascript:S2245",
|
||||||
|
"messageFormattings": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"key": "java-camera-viewer:build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"qualifier": "FIL",
|
||||||
|
"name": "configuration-cache-report.html",
|
||||||
|
"longName": "build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html",
|
||||||
|
"path": "build/reports/configuration-cache/cbcm7qev21o4weakgfsv12gen/9cfjq3uac12x26p527bjxudhn/configuration-cache-report.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "java-camera-viewer",
|
||||||
|
"qualifier": "TRK",
|
||||||
|
"name": "java-camera-viewer",
|
||||||
|
"longName": "java-camera-viewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
@@ -6,22 +6,40 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
const reportFileName = "sonarscan.json"
|
const reportCodeCheckFileName = "sonarscan.json"
|
||||||
|
const reportHotSpotFileName = "hotspot.json"
|
||||||
|
|
||||||
// ReportData is representing the data of the step report JSON
|
// ReportCodeCheckData is representing the data of the step report JSON
|
||||||
type ReportData struct {
|
type ReportCodeCheckData struct {
|
||||||
ServerURL string `json:"serverUrl"`
|
ServerURL string `json:"serverUrl"`
|
||||||
ProjectKey string `json:"projectKey"`
|
ProjectKey string `json:"projectKey"`
|
||||||
TaskID string `json:"taskId"`
|
TaskID string `json:"taskId"`
|
||||||
ChangeID string `json:"changeID,omitempty"`
|
ChangeID string `json:"changeID,omitempty"`
|
||||||
BranchName string `json:"branchName,omitempty"`
|
BranchName string `json:"branchName,omitempty"`
|
||||||
Organization string `json:"organization,omitempty"`
|
Organization string `json:"organization,omitempty"`
|
||||||
NumberOfIssues Issues `json:"numberOfIssues"`
|
NumberOfIssues *Issues `json:"numberOfIssues"`
|
||||||
Errors []Severity `json:"errors"`
|
Errors []Severity `json:"errors"`
|
||||||
Coverage *SonarCoverage `json:"coverage,omitempty"`
|
Coverage *SonarCoverage `json:"coverage,omitempty"`
|
||||||
LinesOfCode *SonarLinesOfCode `json:"linesOfCode,omitempty"`
|
LinesOfCode *SonarLinesOfCode `json:"linesOfCode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReportCodeCheckData is representing the data of the step report JSON
|
||||||
|
type ReportHotSpotData struct {
|
||||||
|
ServerURL string `json:"serverUrl"`
|
||||||
|
ProjectKey string `json:"projectKey"`
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
ChangeID string `json:"changeID,omitempty"`
|
||||||
|
BranchName string `json:"branchName,omitempty"`
|
||||||
|
Organization string `json:"organization,omitempty"`
|
||||||
|
SecurityHotspots []SecurityHotspot `json:"securityHotspots"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HotSpot Security Issues
|
||||||
|
type SecurityHotspot struct {
|
||||||
|
Priority string `json:"priority"`
|
||||||
|
Hotspots int `json:"hotspots"`
|
||||||
|
}
|
||||||
|
|
||||||
// Issues ...
|
// Issues ...
|
||||||
type Issues struct {
|
type Issues struct {
|
||||||
Blocker int `json:"blocker"`
|
Blocker int `json:"blocker"`
|
||||||
@@ -38,10 +56,18 @@ type Severity struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteReport ...
|
// WriteReport ...
|
||||||
func WriteReport(data ReportData, reportPath string, writeToFile func(f string, d []byte, p os.FileMode) error) error {
|
func WriteCodeCheckReport(data ReportCodeCheckData, reportPath string, writeToFile func(f string, d []byte, p os.FileMode) error) error {
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeToFile(filepath.Join(reportPath, reportFileName), jsonData, 0644)
|
return writeToFile(filepath.Join(reportPath, reportCodeCheckFileName), jsonData, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteHotSpotReport(data ReportHotSpotData, reportPath string, writeToFile func(f string, d []byte, p os.FileMode) error) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeToFile(filepath.Join(reportPath, reportHotSpotFileName), jsonData, 0644)
|
||||||
}
|
}
|
||||||
|
@@ -20,10 +20,10 @@ func writeToFileMock(f string, d []byte, p os.FileMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteReport(t *testing.T) {
|
func TestWriteCodeCheckReport(t *testing.T) {
|
||||||
// init
|
// init
|
||||||
const expected = `{"serverUrl":"https://sonarcloud.io","projectKey":"Piper-Validation/Golang","taskId":"mock.Anything","numberOfIssues":{"blocker":0,"critical":1,"major":2,"minor":3,"info":4},"errors":[{"severity":"CRITICAL","error_type":"CODE_SMELL","issues":10}],"coverage":{"coverage":13.7,"lineCoverage":37.1,"linesToCover":123,"uncoveredLines":23,"branchCoverage":42,"branchesToCover":30,"uncoveredBranches":3},"linesOfCode":{"total":327,"languageDistribution":[{"languageKey":"java","linesOfCode":327}]}}`
|
const expected = `{"serverUrl":"https://sonarcloud.io","projectKey":"Piper-Validation/Golang","taskId":"mock.Anything","numberOfIssues":{"blocker":0,"critical":1,"major":2,"minor":3,"info":4},"errors":[{"severity":"CRITICAL","error_type":"CODE_SMELL","issues":10}],"coverage":{"coverage":13.7,"lineCoverage":37.1,"linesToCover":123,"uncoveredLines":23,"branchCoverage":42,"branchesToCover":30,"uncoveredBranches":3},"linesOfCode":{"total":327,"languageDistribution":[{"languageKey":"java","linesOfCode":327}]}}`
|
||||||
testData := ReportData{
|
testData := ReportCodeCheckData{
|
||||||
ServerURL: "https://sonarcloud.io",
|
ServerURL: "https://sonarcloud.io",
|
||||||
ProjectKey: "Piper-Validation/Golang",
|
ProjectKey: "Piper-Validation/Golang",
|
||||||
TaskID: mock.Anything,
|
TaskID: mock.Anything,
|
||||||
@@ -34,7 +34,7 @@ func TestWriteReport(t *testing.T) {
|
|||||||
IssueCount: 10,
|
IssueCount: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NumberOfIssues: Issues{
|
NumberOfIssues: &Issues{
|
||||||
Critical: 1,
|
Critical: 1,
|
||||||
Major: 2,
|
Major: 2,
|
||||||
Minor: 3,
|
Minor: 3,
|
||||||
@@ -55,9 +55,29 @@ func TestWriteReport(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// test
|
// test
|
||||||
err := WriteReport(testData, "", writeToFileMock)
|
err := WriteCodeCheckReport(testData, "", writeToFileMock)
|
||||||
// assert
|
// assert
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, fileContent)
|
assert.Equal(t, expected, fileContent)
|
||||||
assert.Equal(t, reportFileName, fileName)
|
assert.Equal(t, reportCodeCheckFileName, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteHotSpotReport(t *testing.T) {
|
||||||
|
// init
|
||||||
|
const expected = `{"serverUrl":"https://sonarcloud.io","projectKey":"Piper-Validation/Golang","taskId":"mock.Anything","securityHotspots":[{"priority":"HIGH","hotspots":1},{"priority":"LOW","hotspots":4}]}`
|
||||||
|
testData := ReportHotSpotData{
|
||||||
|
ServerURL: "https://sonarcloud.io",
|
||||||
|
ProjectKey: "Piper-Validation/Golang",
|
||||||
|
TaskID: mock.Anything,
|
||||||
|
SecurityHotspots: []SecurityHotspot{
|
||||||
|
{Priority: "HIGH", Hotspots: 1},
|
||||||
|
{Priority: "LOW", Hotspots: 4},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// test
|
||||||
|
err := WriteHotSpotReport(testData, "", writeToFileMock)
|
||||||
|
// assert
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, fileContent)
|
||||||
|
assert.Equal(t, reportHotSpotFileName, fileName)
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,35 @@ type IssuesSearchOption struct {
|
|||||||
Types string `url:"types,omitempty"` // Description:"Comma-separated list of types.",ExampleValue:"CODE_SMELL,BUG"
|
Types string `url:"types,omitempty"` // Description:"Comma-separated list of types.",ExampleValue:"CODE_SMELL,BUG"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HotSpotSearchOption struct {
|
||||||
|
Project string `url:"project"` // Description:"Project name"
|
||||||
|
Status hotSpotStatus `url:"status"` // Security issue review status (TO_REVIEW | REVIEWED)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HotSpotSearchObject struct {
|
||||||
|
Paging interface{} `json:"paging"`
|
||||||
|
HotSpots []HotSpot `json:"hotspots"`
|
||||||
|
Components []interface{} `json:"components"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HotSpot struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Component string `json:"component"`
|
||||||
|
Project string `json:"project"`
|
||||||
|
SecurityCategory string `json:"securityCategory"`
|
||||||
|
VulnerabilityProbability string `json:"vulnerabilityProbability"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
CreationDate string `json:"creationDate"`
|
||||||
|
UpdateDate string `json:"updateDate"`
|
||||||
|
TextRange interface{} `json:"textRange"`
|
||||||
|
Flows []interface{} `json:"flows"`
|
||||||
|
RuleKey string `json:"ruleKey"`
|
||||||
|
MessageFormattings []interface{} `json:"messageFormattings"`
|
||||||
|
}
|
||||||
|
|
||||||
// MeasuresComponentOption is a copy from magicsong/sonargo plus the "internal" field branch.
|
// MeasuresComponentOption is a copy from magicsong/sonargo plus the "internal" field branch.
|
||||||
type MeasuresComponentOption struct {
|
type MeasuresComponentOption struct {
|
||||||
Branch string `url:"branch,omitempty"` // Description:"Branch key"
|
Branch string `url:"branch,omitempty"` // Description:"Branch key"
|
||||||
@@ -59,3 +88,14 @@ const (
|
|||||||
minor issueSeverity = "MINOR"
|
minor issueSeverity = "MINOR"
|
||||||
info issueSeverity = "INFO"
|
info issueSeverity = "INFO"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type hotSpotStatus string
|
||||||
|
|
||||||
|
func (s hotSpotStatus) ToString() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
reviwed hotSpotStatus = "REVIEWED"
|
||||||
|
to_review hotSpotStatus = "TO_REVIEW"
|
||||||
|
)
|
||||||
|
Reference in New Issue
Block a user