feat: return scan report and summary by header (#13898)

Add X-Accept-Vulnerabilities header to the list/get artifact and get
artifact vulnerability addition APIs, and these APIs will traverse the
mime types in this header and return the first report and summary found
from the mime type.

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2021-01-06 17:54:36 +08:00 committed by GitHub
parent 511bd86930
commit ed31cf9417
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 321 additions and 88 deletions

View File

@ -367,6 +367,7 @@ paths:
- $ref: '#/parameters/query' - $ref: '#/parameters/query'
- $ref: '#/parameters/page' - $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize' - $ref: '#/parameters/pageSize'
- $ref: '#/parameters/acceptVulnerabilities'
- name: with_tag - name: with_tag
in: query in: query
description: Specify whether the tags are included inside the returning artifacts description: Specify whether the tags are included inside the returning artifacts
@ -465,6 +466,7 @@ paths:
- $ref: '#/parameters/reference' - $ref: '#/parameters/reference'
- $ref: '#/parameters/page' - $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize' - $ref: '#/parameters/pageSize'
- $ref: '#/parameters/acceptVulnerabilities'
- name: with_tag - name: with_tag
in: query in: query
description: Specify whether the tags are inclued inside the returning artifacts description: Specify whether the tags are inclued inside the returning artifacts
@ -699,6 +701,38 @@ paths:
$ref: '#/responses/404' $ref: '#/responses/404'
'500': '500':
$ref: '#/responses/500' $ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/vulnerabilities:
get:
summary: Get the vulnerabilities addition of the specific artifact
description: Get the vulnerabilities addition of the artifact specified by the reference under the project and repository.
tags:
- artifact
operationId: getVulnerabilitiesAddition
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
- $ref: '#/parameters/reference'
- $ref: '#/parameters/acceptVulnerabilities'
responses:
'200':
description: Success
headers:
Content-Type:
description: The content type of the vulnerabilities addition
type: string
schema:
type: string
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/{addition}: /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/{addition}:
get: get:
summary: Get the addition of the specific artifact summary: Get the addition of the specific artifact
@ -715,7 +749,7 @@ paths:
in: path in: path
description: The type of addition. description: The type of addition.
type: string type: string
enum: [build_history, values.yaml, readme.md, dependencies, vulnerabilities] enum: [build_history, values.yaml, readme.md, dependencies]
required: true required: true
responses: responses:
'200': '200':
@ -2337,6 +2371,14 @@ parameters:
type: boolean type: boolean
required: false required: false
default: false default: false
acceptVulnerabilities:
name: X-Accept-Vulnerabilities
in: header
type: string
default: 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'
description: |-
A comma-separated lists of MIME types for the scan report or scan summary. The first mime type will be used when the report found for it.
Currently the mime type supports 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0' and 'application/vnd.security.vulnerability.report; version=1.1'
projectName: projectName:
name: project_name name: project_name
in: path in: path

View File

@ -16,6 +16,7 @@ package handler
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -27,14 +28,15 @@ import (
"github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/artifact/processor"
"github.com/goharbor/harbor/src/controller/event/metadata" "github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/controller/repository" "github.com/goharbor/harbor/src/controller/repository"
"github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/controller/tag" "github.com/goharbor/harbor/src/controller/tag"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/pkg/notification" "github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/scan/report"
"github.com/goharbor/harbor/src/server/v2.0/handler/assembler" "github.com/goharbor/harbor/src/server/v2.0/handler/assembler"
"github.com/goharbor/harbor/src/server/v2.0/handler/model" "github.com/goharbor/harbor/src/server/v2.0/handler/model"
"github.com/goharbor/harbor/src/server/v2.0/models" "github.com/goharbor/harbor/src/server/v2.0/models"
@ -42,10 +44,6 @@ import (
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) )
const (
vulnerabilitiesAddition = "vulnerabilities"
)
func newArtifactAPI() *artifactAPI { func newArtifactAPI() *artifactAPI {
return &artifactAPI{ return &artifactAPI{
artCtl: artifact.Ctl, artCtl: artifact.Ctl,
@ -100,7 +98,7 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
return a.SendError(ctx, err) return a.SendError(ctx, err)
} }
assembler := assembler.NewVulAssembler(boolValue(params.WithScanOverview)) assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities))
var artifacts []*models.Artifact var artifacts []*models.Artifact
for _, art := range arts { for _, art := range arts {
artifact := &model.Artifact{} artifact := &model.Artifact{}
@ -131,7 +129,7 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif
art := &model.Artifact{} art := &model.Artifact{}
art.Artifact = *artifact art.Artifact = *artifact
assembler.NewVulAssembler(boolValue(params.WithScanOverview)).WithArtifacts(art).Assemble(ctx) assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
return operation.NewGetArtifactOK().WithPayload(art.ToSwagger()) return operation.NewGetArtifactOK().WithPayload(art.ToSwagger())
} }
@ -335,6 +333,59 @@ func (a *artifactAPI) ListTags(ctx context.Context, params operation.ListTagsPar
WithPayload(ts) WithPayload(ts)
} }
func (a *artifactAPI) GetVulnerabilitiesAddition(ctx context.Context, params operation.GetVulnerabilitiesAdditionParams) middleware.Responder {
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceArtifactAddition); err != nil {
return a.SendError(ctx, err)
}
artifact, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil)
if err != nil {
return a.SendError(ctx, err)
}
vulnerabilities := make(map[string]interface{})
for _, mimeType := range parseScanReportMimeTypes(params.XAcceptVulnerabilities) {
reports, err := a.scanCtl.GetReport(ctx, artifact, []string{mimeType})
if err != nil {
return a.SendError(ctx, err)
}
for _, rp := range reports {
// Resolve scan report data only when it is ready
if len(rp.Report) == 0 {
continue
}
vrp, err := report.ResolveData(rp.MimeType, []byte(rp.Report), report.WithArtifactDigest(rp.Digest))
if err != nil {
return a.SendError(ctx, err)
}
if v, ok := vulnerabilities[rp.MimeType]; ok {
r, err := report.Merge(rp.MimeType, v, vrp)
if err != nil {
return a.SendError(ctx, err)
}
vulnerabilities[rp.MimeType] = r
} else {
vulnerabilities[rp.MimeType] = vrp
}
}
if len(vulnerabilities) != 0 {
break
}
}
content, _ := json.Marshal(vulnerabilities)
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
w.Header().Set("Content-Type", "application/json")
w.Write(content)
})
}
func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAdditionParams) middleware.Responder { func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAdditionParams) middleware.Responder {
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceArtifactAddition); err != nil { if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceArtifactAddition); err != nil {
return a.SendError(ctx, err) return a.SendError(ctx, err)
@ -345,13 +396,7 @@ func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAddit
return a.SendError(ctx, err) return a.SendError(ctx, err)
} }
var addition *processor.Addition addition, err := a.artCtl.GetAddition(ctx, artifact.ID, strings.ToUpper(params.Addition))
if params.Addition == vulnerabilitiesAddition {
addition, err = resolveVulnerabilitiesAddition(ctx, artifact)
} else {
addition, err = a.artCtl.GetAddition(ctx, artifact.ID, strings.ToUpper(params.Addition))
}
if err != nil { if err != nil {
return a.SendError(ctx, err) return a.SendError(ctx, err)
} }
@ -393,7 +438,7 @@ func (a *artifactAPI) RemoveLabel(ctx context.Context, params operation.RemoveLa
func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artifact.Option { func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artifact.Option {
option := &artifact.Option{ option := &artifact.Option{
WithTag: true, // return the tag by default WithTag: true, // return the tag by default
WithLabel: boolValue(withLabel), WithLabel: lib.BoolValue(withLabel),
} }
if withTag != nil { if withTag != nil {
@ -402,8 +447,8 @@ func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artif
if option.WithTag { if option.WithTag {
option.TagOption = &tag.Option{ option.TagOption = &tag.Option{
WithImmutableStatus: boolValue(withImmutableStatus), WithImmutableStatus: lib.BoolValue(withImmutableStatus),
WithSignature: boolValue(withSignature), WithSignature: lib.BoolValue(withSignature),
} }
} }

View File

@ -15,9 +15,20 @@
package handler package handler
import ( import (
"testing"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/server/v2.0/restapi"
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
scantesting "github.com/goharbor/harbor/src/testing/controller/scan"
"github.com/goharbor/harbor/src/testing/mock"
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing" "github.com/stretchr/testify/suite"
) )
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
@ -50,3 +61,134 @@ func TestParse(t *testing.T) {
repository, reference, err = parse(input) repository, reference, err = parse(input)
require.NotNil(t, err) require.NotNil(t, err)
} }
type ArtifactTestSuite struct {
htesting.Suite
artCtl *artifacttesting.Controller
scanCtl *scantesting.Controller
report1 *scan.Report
report2 *scan.Report
}
func (suite *ArtifactTestSuite) SetupSuite() {
suite.artCtl = &artifacttesting.Controller{}
suite.scanCtl = &scantesting.Controller{}
suite.Config = &restapi.Config{
ArtifactAPI: &artifactAPI{
artCtl: suite.artCtl,
scanCtl: suite.scanCtl,
},
}
suite.Suite.SetupSuite()
mock.OnAnything(projectCtlMock, "GetByName").Return(&project.Project{ProjectID: 1}, nil)
suite.report1 = &scan.Report{
MimeType: v1.MimeTypeNativeReport,
Report: "{}",
}
suite.report2 = &scan.Report{
MimeType: v1.MimeTypeGenericVulnerabilityReport,
Report: "{}",
}
}
func (suite *ArtifactTestSuite) onGetReport(mimeType string, reports ...*scan.Report) {
suite.scanCtl.On("GetReport", mock.Anything, mock.Anything, []string{mimeType}).Return(reports, nil).Once()
}
func (suite *ArtifactTestSuite) TestGetVulnerabilitiesAddition() {
times := 6
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
mock.OnAnything(suite.Security, "Can").Return(true).Times(times)
mock.OnAnything(suite.artCtl, "GetByReference").Return(&artifact.Artifact{}, nil).Times(times)
url := "/projects/library/repositories/photon/artifacts/2.0/additions/vulnerabilities"
{
// report not found for the default X-Accept-Vulnerabilities
suite.onGetReport(v1.MimeTypeNativeReport)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body)
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.Empty(body)
}
{
// report found for the default X-Accept-Vulnerabilities
suite.onGetReport(v1.MimeTypeNativeReport, suite.report1)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body)
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.NotEmpty(body)
suite.Contains(body, v1.MimeTypeNativeReport)
}
{
// report found for the X-Accept-Vulnerabilities of "application/vnd.security.vulnerability.report; version=1.1"
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport, suite.report2)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport})
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.NotEmpty(body)
suite.Contains(body, v1.MimeTypeGenericVulnerabilityReport)
}
{
// report found for "application/vnd.security.vulnerability.report; version=1.1"
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport, suite.report2)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.NotEmpty(body)
suite.Contains(body, v1.MimeTypeGenericVulnerabilityReport)
}
{
// report not found for "application/vnd.security.vulnerability.report; version=1.1"
// report found for "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport)
suite.onGetReport(v1.MimeTypeNativeReport, suite.report1)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.NotEmpty(body)
suite.Contains(body, v1.MimeTypeNativeReport)
}
{
// report not found for "application/vnd.security.vulnerability.report; version=1.1"
// report not found for "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport)
suite.onGetReport(v1.MimeTypeNativeReport)
var body map[string]interface{}
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
suite.NoError(err)
suite.Equal(200, res.StatusCode)
suite.Empty(body)
}
}
func TestArtifactTestSuite(t *testing.T) {
suite.Run(t, &ArtifactTestSuite{})
}

View File

@ -20,7 +20,6 @@ import (
"github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/server/v2.0/handler/model" "github.com/goharbor/harbor/src/server/v2.0/handler/model"
) )
@ -29,12 +28,13 @@ const (
) )
// NewVulAssembler returns vul assembler // NewVulAssembler returns vul assembler
func NewVulAssembler(withScanOverview bool) *VulAssembler { func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler {
return &VulAssembler{ return &VulAssembler{
scanChecker: scan.NewChecker(), scanChecker: scan.NewChecker(),
scanCtl: scan.DefaultController, scanCtl: scan.DefaultController,
withScanOverview: withScanOverview, withScanOverview: withScanOverview,
mimeTypes: mimeTypes,
} }
} }
@ -45,6 +45,7 @@ type VulAssembler struct {
artifacts []*model.Artifact artifacts []*model.Artifact
withScanOverview bool withScanOverview bool
mimeTypes []string
} }
// WithArtifacts set artifacts for the assembler // WithArtifacts set artifacts for the assembler
@ -72,11 +73,14 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
artifact.SetAdditionLink(vulnerabilitiesAddition, version) artifact.SetAdditionLink(vulnerabilitiesAddition, version)
if assembler.withScanOverview { if assembler.withScanOverview {
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}) for _, mimeType := range assembler.mimeTypes {
if err != nil { overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType})
log.Warningf("get scan summary of artifact %s@%s failed, error:%v", artifact.RepositoryName, artifact.Digest, err) if err != nil {
} else if len(overview) > 0 { log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, mimeType, err)
artifact.ScanOverview = overview } else if len(overview) > 0 {
artifact.ScanOverview = overview
break
}
} }
} }
} }

View File

@ -36,6 +36,7 @@ func (suite *VulAssemblerTestSuite) TestScannable() {
scanChecker: checker, scanChecker: checker,
scanCtl: scanCtl, scanCtl: scanCtl,
withScanOverview: true, withScanOverview: true,
mimeTypes: []string{"mimeType"},
} }
mock.OnAnything(checker, "IsScannable").Return(true, nil) mock.OnAnything(checker, "IsScannable").Return(true, nil)

View File

@ -36,6 +36,10 @@ import (
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
) )
var (
baseProjectCtl = project.Ctl
)
// BaseAPI base API handler // BaseAPI base API handler
type BaseAPI struct{} type BaseAPI struct{}
@ -68,7 +72,7 @@ func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName inte
} }
if projectName != "" { if projectName != "" {
p, err := project.Ctl.GetByName(ctx, projectName) p, err := baseProjectCtl.GetByName(ctx, projectName)
if err != nil { if err != nil {
log.Errorf("failed to get project %s: %v", projectName, err) log.Errorf("failed to get project %s: %v", projectName, err)
return false return false

View File

@ -15,9 +15,17 @@
package handler package handler
import ( import (
"github.com/stretchr/testify/suite"
"net/url" "net/url"
"os"
"testing" "testing"
"github.com/goharbor/harbor/src/controller/project"
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
"github.com/stretchr/testify/suite"
)
var (
projectCtlMock *projecttesting.Controller
) )
type baseHandlerTestSuite struct { type baseHandlerTestSuite struct {
@ -108,3 +116,15 @@ func (b *baseHandlerTestSuite) TestLinks() {
func TestBaseHandler(t *testing.T) { func TestBaseHandler(t *testing.T) {
suite.Run(t, &baseHandlerTestSuite{}) suite.Run(t, &baseHandlerTestSuite{})
} }
func TestMain(m *testing.M) {
projectCtlMock = &projecttesting.Controller{}
baseProjectCtl = projectCtlMock
exitVal := m.Run()
baseProjectCtl = project.Ctl
os.Exit(exitVal)
}

View File

@ -15,19 +15,14 @@
package handler package handler
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/artifact/processor"
"github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/scan/report"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
) )
@ -46,49 +41,24 @@ const (
ScheduleNone = "None" ScheduleNone = "None"
) )
func boolValue(v *bool) bool { func parseScanReportMimeTypes(header *string) []string {
if v != nil { var mimeTypes []string
return *v
}
return false if header != nil {
} for _, mimeType := range strings.Split(*header, ",") {
mimeType = strings.TrimSpace(mimeType)
func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Artifact) (*processor.Addition, error) { switch mimeType {
reports, err := scan.DefaultController.GetReport(ctx, artifact, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}) case v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport:
if err != nil { mimeTypes = append(mimeTypes, mimeType)
return nil, err
}
vulnerabilities := make(map[string]interface{})
for _, rp := range reports {
// Resolve scan report data only when it is ready
if len(rp.Report) == 0 {
continue
}
vrp, err := report.ResolveData(rp.MimeType, []byte(rp.Report), report.WithArtifactDigest(rp.Digest))
if err != nil {
return nil, err
}
if v, ok := vulnerabilities[rp.MimeType]; ok {
r, err := report.Merge(rp.MimeType, v, vrp)
if err != nil {
return nil, err
} }
vulnerabilities[rp.MimeType] = r
} else {
vulnerabilities[rp.MimeType] = vrp
} }
} }
content, _ := json.Marshal(vulnerabilities) if len(mimeTypes) == 0 {
mimeTypes = append(mimeTypes, v1.MimeTypeNativeReport)
}
return &processor.Addition{ return mimeTypes
Content: content,
ContentType: "application/json",
}, nil
} }
func unescapePathParams(params interface{}, fieldNames ...string) error { func unescapePathParams(params interface{}, fieldNames ...string) error {

View File

@ -63,34 +63,39 @@ func (suite *Suite) TearDownSuite() {
} }
// DoReq ... // DoReq ...
func (suite *Suite) DoReq(method string, url string, body io.Reader, contentTypes ...string) (*http.Response, error) { func (suite *Suite) DoReq(method string, url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
req, err := http.NewRequest(method, suite.ts.URL+"/api/v2.0"+url, body) req, err := http.NewRequest(method, suite.ts.URL+"/api/v2.0"+url, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
contentType := "application/json" if len(headers) > 0 {
if len(contentTypes) > 0 { for key, value := range headers[0] {
contentType = contentTypes[0] req.Header.Set(key, value)
}
}
contentType := req.Header.Get("Content-Type")
if contentType == "" {
req.Header.Set("Content-Type", "application/json")
} }
req.Header.Set("Content-Type", contentType)
return suite.tc.Do(req) return suite.tc.Do(req)
} }
// Delete ... // Delete ...
func (suite *Suite) Delete(url string, contentTypes ...string) (*http.Response, error) { func (suite *Suite) Delete(url string, headers ...map[string]string) (*http.Response, error) {
return suite.DoReq(http.MethodDelete, url, nil, contentTypes...) return suite.DoReq(http.MethodDelete, url, nil, headers...)
} }
// Get ... // Get ...
func (suite *Suite) Get(url string, contentTypes ...string) (*http.Response, error) { func (suite *Suite) Get(url string, headers ...map[string]string) (*http.Response, error) {
return suite.DoReq(http.MethodGet, url, nil, contentTypes...) return suite.DoReq(http.MethodGet, url, nil, headers...)
} }
// GetJSON ... // GetJSON ...
func (suite *Suite) GetJSON(url string, js interface{}) (*http.Response, error) { func (suite *Suite) GetJSON(url string, js interface{}, headers ...map[string]string) (*http.Response, error) {
res, err := suite.Get(url) res, err := suite.Get(url, headers...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,8 +118,8 @@ func (suite *Suite) GetJSON(url string, js interface{}) (*http.Response, error)
} }
// Patch ... // Patch ...
func (suite *Suite) Patch(url string, body io.Reader, contentTypes ...string) (*http.Response, error) { func (suite *Suite) Patch(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
return suite.DoReq(http.MethodPatch, url, body, contentTypes...) return suite.DoReq(http.MethodPatch, url, body, headers...)
} }
// PatchJSON ... // PatchJSON ...
@ -128,8 +133,8 @@ func (suite *Suite) PatchJSON(url string, js interface{}) (*http.Response, error
} }
// Post ... // Post ...
func (suite *Suite) Post(url string, body io.Reader, contentTypes ...string) (*http.Response, error) { func (suite *Suite) Post(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
return suite.DoReq(http.MethodPost, url, body, contentTypes...) return suite.DoReq(http.MethodPost, url, body, headers...)
} }
// PostJSON ... // PostJSON ...
@ -143,8 +148,8 @@ func (suite *Suite) PostJSON(url string, js interface{}) (*http.Response, error)
} }
// Put ... // Put ...
func (suite *Suite) Put(url string, body io.Reader, contentTypes ...string) (*http.Response, error) { func (suite *Suite) Put(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
return suite.DoReq(http.MethodPut, url, body, contentTypes...) return suite.DoReq(http.MethodPut, url, body, headers...)
} }
// PutJSON ... // PutJSON ...