Merge branch 'main' into update_mockery_2.42.2

This commit is contained in:
MinerYang 2024-04-11 10:03:18 +08:00 committed by GitHub
commit f78dab0f3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 533 additions and 38 deletions

View File

@ -85,11 +85,11 @@ func (p *processor) AbstractAddition(_ context.Context, artifact *artifact.Artif
if err != nil {
return nil, err
}
defer blob.Close()
content, err := io.ReadAll(blob)
if err != nil {
return nil, err
}
blob.Close()
chartDetails, err := p.chartOperator.GetDetails(content)
if err != nil {
return nil, err

View File

@ -0,0 +1,89 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sbom
import (
"context"
"encoding/json"
"io"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/goharbor/harbor/src/controller/artifact/processor"
"github.com/goharbor/harbor/src/controller/artifact/processor/base"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/artifact"
)
const (
// processorArtifactTypeSBOM is the artifact type for SBOM, it's scope is only used in the processor
processorArtifactTypeSBOM = "SBOM"
// processorMediaType is the media type for SBOM, it's scope is only used to register the processor
processorMediaType = "application/vnd.goharbor.harbor.sbom.v1"
)
func init() {
pc := &Processor{}
pc.ManifestProcessor = base.NewManifestProcessor()
if err := processor.Register(pc, processorMediaType); err != nil {
log.Errorf("failed to register processor for media type %s: %v", processorMediaType, err)
return
}
}
// Processor is the processor for SBOM
type Processor struct {
*base.ManifestProcessor
}
// AbstractAddition returns the addition for SBOM
func (m *Processor) AbstractAddition(_ context.Context, art *artifact.Artifact, _ string) (*processor.Addition, error) {
man, _, err := m.RegCli.PullManifest(art.RepositoryName, art.Digest)
if err != nil {
return nil, errors.Wrap(err, "failed to pull manifest")
}
_, payload, err := man.Payload()
if err != nil {
return nil, errors.Wrap(err, "failed to get payload")
}
manifest := &v1.Manifest{}
if err := json.Unmarshal(payload, manifest); err != nil {
return nil, err
}
// SBOM artifact should only have one layer
if len(manifest.Layers) != 1 {
return nil, errors.New(nil).WithCode(errors.NotFoundCode).WithMessage("The sbom is not found")
}
layerDgst := manifest.Layers[0].Digest.String()
_, blob, err := m.RegCli.PullBlob(art.RepositoryName, layerDgst)
if err != nil {
return nil, errors.Wrap(err, "failed to pull the blob")
}
defer blob.Close()
content, err := io.ReadAll(blob)
if err != nil {
return nil, err
}
return &processor.Addition{
Content: content,
ContentType: processorMediaType,
}, nil
}
// GetArtifactType the artifact type is used to display the artifact type in the UI
func (m *Processor) GetArtifactType(_ context.Context, _ *artifact.Artifact) string {
return processorArtifactTypeSBOM
}

View File

@ -0,0 +1,166 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sbom
import (
"context"
"fmt"
"io"
"strings"
"testing"
"github.com/docker/distribution"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/goharbor/harbor/src/controller/artifact/processor/base"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/pkg/registry"
)
type SBOMProcessorTestSuite struct {
suite.Suite
processor *Processor
regCli *registry.Client
}
func (suite *SBOMProcessorTestSuite) SetupSuite() {
suite.regCli = &registry.Client{}
suite.processor = &Processor{
&base.ManifestProcessor{
RegCli: suite.regCli,
},
}
}
func (suite *SBOMProcessorTestSuite) TearDownSuite() {
}
func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNormal() {
manContent := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:abc"
}]
}`
sbomContent := "this is a sbom content"
reader := strings.NewReader(sbomContent)
blobReader := io.NopCloser(reader)
mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent))
suite.Require().NoError(err)
suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once()
suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), blobReader, nil).Once()
addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom")
suite.Nil(err)
suite.Equal(sbomContent, string(addition.Content))
}
func (suite *SBOMProcessorTestSuite) TestAbstractAdditionMultiLayer() {
manContent := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:abc"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 843,
"digest": "sha256:def"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 531,
"digest": "sha256:123"
}
]
}`
mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent))
suite.Require().NoError(err)
suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once()
_, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom")
suite.NotNil(err)
}
func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullBlobError() {
manContent := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:abc"
}
]
}`
mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent))
suite.Require().NoError(err)
suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once()
suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), nil, errors.NotFoundError(fmt.Errorf("not found"))).Once()
addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom")
suite.NotNil(err)
suite.Nil(addition)
}
func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNoSBOMLayer() {
manContent := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
}
}`
mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent))
suite.Require().NoError(err)
suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once()
_, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom")
suite.NotNil(err)
}
func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullManifestError() {
suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(nil, "sha256:123", errors.NotFoundError(fmt.Errorf("not found"))).Once()
_, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom")
suite.NotNil(err)
}
func (suite *SBOMProcessorTestSuite) TestGetArtifactType() {
suite.Equal(processorArtifactTypeSBOM, suite.processor.GetArtifactType(context.Background(), &artifact.Artifact{}))
}
func TestSBOMProcessorTestSuite(t *testing.T) {
suite.Run(t, &SBOMProcessorTestSuite{})
}

View File

@ -17,6 +17,7 @@ package scan
import (
"bytes"
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
@ -674,12 +675,23 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact,
return reports, nil
}
func isSBOMMimeTypes(mimeTypes []string) bool {
for _, mimeType := range mimeTypes {
if mimeType == v1.MimeTypeSBOMReport {
return true
}
}
return false
}
// GetSummary ...
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) {
if artifact == nil {
return nil, errors.New("no way to get report summaries for nil artifact")
}
if isSBOMMimeTypes(mimeTypes) {
return bc.GetSBOMSummary(ctx, artifact, mimeTypes)
}
// Get reports first
rps, err := bc.GetReport(ctx, artifact, mimeTypes)
if err != nil {
@ -708,6 +720,30 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact
return summaries, nil
}
func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) {
if art == nil {
return nil, errors.New("no way to get report summaries for nil artifact")
}
r, err := bc.sc.GetRegistrationByProject(ctx, art.ProjectID)
if err != nil {
return nil, errors.Wrap(err, "scan controller: get sbom summary")
}
reports, err := bc.manager.GetBy(ctx, art.Digest, r.UUID, mimeTypes)
if err != nil {
return nil, err
}
if len(reports) == 0 {
return map[string]interface{}{}, nil
}
reportContent := reports[0].Report
if len(reportContent) == 0 {
log.Warning("no content for current report")
}
result := map[string]interface{}{}
err = json.Unmarshal([]byte(reportContent), &result)
return result, err
}
// GetScanLog ...
func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) {
if len(uuid) == 0 {

View File

@ -78,7 +78,7 @@ type ControllerTestSuite struct {
taskMgr *tasktesting.Manager
reportMgr *reporttesting.Manager
ar artifact.Controller
c Controller
c *basicController
reportConverter *postprocessorstesting.ScanReportV1ToV2Converter
cache *mockcache.Cache
}
@ -180,7 +180,19 @@ func (suite *ControllerTestSuite) SetupSuite() {
},
}
sbomReport := []*scan.Report{
{
ID: 12,
UUID: "rp-uuid-002",
Digest: "digest-code",
RegistrationUUID: "uuid001",
MimeType: "application/vnd.scanner.adapter.sbom.report.harbor+json; version=1.0",
Status: "Success",
Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`,
},
}
mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil)
mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil)
mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil)
mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil)
mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil)
@ -620,3 +632,26 @@ func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs .
return extraAttrs
}
func (suite *ControllerTestSuite) TestGenerateSBOMSummary() {
sum, err := suite.c.GetSBOMSummary(context.TODO(), suite.artifact, []string{v1.MimeTypeSBOMReport})
suite.Nil(err)
suite.NotNil(sum)
status := sum["scan_status"]
suite.NotNil(status)
dgst := sum["sbom_digest"]
suite.NotNil(dgst)
suite.Equal("Success", status)
suite.Equal("sha256:1234567890", dgst)
}
func TestIsSBOMMimeTypes(t *testing.T) {
// Test with a slice containing the SBOM mime type
assert.True(t, isSBOMMimeTypes([]string{v1.MimeTypeSBOMReport}))
// Test with a slice not containing the SBOM mime type
assert.False(t, isSBOMMimeTypes([]string{"application/vnd.oci.image.manifest.v1+json"}))
// Test with an empty slice
assert.False(t, isSBOMMimeTypes([]string{}))
}

View File

@ -21,12 +21,11 @@ import (
"github.com/goharbor/harbor/src/lib/errors"
)
const (
// ScanTypeVulnerability the scan type for vulnerability
ScanTypeVulnerability = "vulnerability"
// ScanTypeSbom the scan type for sbom
ScanTypeSbom = "sbom"
)
var supportedMimeTypes = []string{
MimeTypeNativeReport,
MimeTypeGenericVulnerabilityReport,
MimeTypeSBOMReport,
}
// Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of
// scanning a given Artifact stored in its registry and making sure that it can interpret a
@ -105,7 +104,7 @@ func (md *ScannerAdapterMetadata) Validate() error {
// either of v1.MimeTypeNativeReport OR v1.MimeTypeGenericVulnerabilityReport is required
found = false
for _, pm := range ca.ProducesMimeTypes {
if pm == MimeTypeNativeReport || pm == MimeTypeGenericVulnerabilityReport {
if isSupportedMimeType(pm) {
found = true
break
}
@ -119,6 +118,15 @@ func (md *ScannerAdapterMetadata) Validate() error {
return nil
}
func isSupportedMimeType(mimeType string) bool {
for _, mt := range supportedMimeTypes {
if mt == mimeType {
return true
}
}
return false
}
// HasCapability returns true when mine type of the artifact support by the scanner
func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool {
for _, capability := range md.Capabilities {

View File

@ -0,0 +1,15 @@
package v1
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsSupportedMimeType(t *testing.T) {
// Test with a supported mime type
assert.True(t, isSupportedMimeType(MimeTypeSBOMReport), "isSupportedMimeType should return true for supported mime types")
// Test with an unsupported mime type
assert.False(t, isSupportedMimeType("unsupported/mime-type"), "isSupportedMimeType should return false for unsupported mime types")
}

View File

@ -39,9 +39,14 @@ const (
MimeTypeScanRequest = "application/vnd.scanner.adapter.scan.request+json; version=1.0"
// MimeTypeScanResponse defines the mime type for scan response
MimeTypeScanResponse = "application/vnd.scanner.adapter.scan.response+json; version=1.0"
// MimeTypeSBOMReport
MimeTypeSBOMReport = "application/vnd.security.sbom.report+json; version=1.0"
// MimeTypeGenericVulnerabilityReport defines the MIME type for the generic report with enhanced information
MimeTypeGenericVulnerabilityReport = "application/vnd.security.vulnerability.report; version=1.1"
ScanTypeVulnerability = "vulnerability"
ScanTypeSbom = "sbom"
apiPrefix = "/api/v1"
)

View File

@ -107,8 +107,8 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
if err != nil {
return a.SendError(ctx, err)
}
assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities))
overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview)))
assembler := assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities))
var artifacts []*models.Artifact
for _, art := range arts {
artifact := &model.Artifact{}
@ -138,8 +138,9 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif
}
art := &model.Artifact{}
art.Artifact = *artifact
overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview)))
err = assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
err = assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
if err != nil {
log.Warningf("failed to assemble vulnerabilities with artifact, error: %v", err)
}

View File

@ -20,43 +20,48 @@ import (
"github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/lib"
"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"
)
const (
vulnerabilitiesAddition = "vulnerabilities"
startTime = "start_time"
endTime = "end_time"
scanStatus = "scan_status"
sbomDigest = "sbom_digest"
duration = "duration"
)
// NewVulAssembler returns vul assembler
func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler {
return &VulAssembler{
scanChecker: scan.NewChecker(),
scanCtl: scan.DefaultController,
withScanOverview: withScanOverview,
mimeTypes: mimeTypes,
// NewScanReportAssembler returns vul assembler
func NewScanReportAssembler(option *model.OverviewOptions, mimeTypes []string) *ScanReportAssembler {
return &ScanReportAssembler{
overviewOption: option,
scanChecker: scan.NewChecker(),
scanCtl: scan.DefaultController,
mimeTypes: mimeTypes,
}
}
// VulAssembler vul assembler
type VulAssembler struct {
// ScanReportAssembler vul assembler
type ScanReportAssembler struct {
scanChecker scan.Checker
scanCtl scan.Controller
artifacts []*model.Artifact
withScanOverview bool
mimeTypes []string
artifacts []*model.Artifact
mimeTypes []string
overviewOption *model.OverviewOptions
}
// WithArtifacts set artifacts for the assembler
func (assembler *VulAssembler) WithArtifacts(artifacts ...*model.Artifact) *VulAssembler {
func (assembler *ScanReportAssembler) WithArtifacts(artifacts ...*model.Artifact) *ScanReportAssembler {
assembler.artifacts = artifacts
return assembler
}
// Assemble assemble vul for the artifacts
func (assembler *VulAssembler) Assemble(ctx context.Context) error {
func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error {
version := lib.GetAPIVersion(ctx)
for _, artifact := range assembler.artifacts {
@ -72,7 +77,7 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
artifact.SetAdditionLink(vulnerabilitiesAddition, version)
if assembler.withScanOverview {
if assembler.overviewOption.WithVuln {
for _, mimeType := range assembler.mimeTypes {
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType})
if err != nil {
@ -83,6 +88,20 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
}
}
}
if assembler.overviewOption.WithSBOM {
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport})
if err != nil {
log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err)
} else if len(overview) > 0 {
artifact.SBOMOverView = map[string]interface{}{
startTime: overview[startTime],
endTime: overview[endTime],
scanStatus: overview[scanStatus],
sbomDigest: overview[sbomDigest],
duration: overview[duration],
}
}
}
}
return nil

View File

@ -20,6 +20,7 @@ import (
"github.com/stretchr/testify/suite"
v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
"github.com/goharbor/harbor/src/testing/controller/scan"
"github.com/goharbor/harbor/src/testing/mock"
@ -33,11 +34,11 @@ func (suite *VulAssemblerTestSuite) TestScannable() {
checker := &scan.Checker{}
scanCtl := &scan.Controller{}
assembler := VulAssembler{
scanChecker: checker,
scanCtl: scanCtl,
withScanOverview: true,
mimeTypes: []string{"mimeType"},
assembler := ScanReportAssembler{
scanChecker: checker,
scanCtl: scanCtl,
overviewOption: model.NewOverviewOptions(model.WithVuln(true)),
mimeTypes: []string{"mimeType"},
}
mock.OnAnything(checker, "IsScannable").Return(true, nil)
@ -56,10 +57,10 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() {
checker := &scan.Checker{}
scanCtl := &scan.Controller{}
assembler := VulAssembler{
scanChecker: checker,
scanCtl: scanCtl,
withScanOverview: true,
assembler := ScanReportAssembler{
scanChecker: checker,
scanCtl: scanCtl,
overviewOption: model.NewOverviewOptions(model.WithVuln(true)),
}
mock.OnAnything(checker, "IsScannable").Return(false, nil)
@ -74,6 +75,32 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() {
scanCtl.AssertNotCalled(suite.T(), "GetSummary")
}
func (suite *VulAssemblerTestSuite) TestAssembleSBOMOverview() {
checker := &scan.Checker{}
scanCtl := &scan.Controller{}
assembler := ScanReportAssembler{
scanChecker: checker,
scanCtl: scanCtl,
overviewOption: model.NewOverviewOptions(model.WithSBOM(true)),
mimeTypes: []string{v1sq.MimeTypeSBOMReport},
}
mock.OnAnything(checker, "IsScannable").Return(true, nil)
overview := map[string]interface{}{
"sbom_digest": "sha256:123456",
"scan_status": "Success",
}
mock.OnAnything(scanCtl, "GetSummary").Return(overview, nil)
var artifact model.Artifact
err := assembler.WithArtifacts(&artifact).Assemble(context.TODO())
suite.Nil(err)
suite.Equal(artifact.SBOMOverView["sbom_digest"], "sha256:123456")
suite.Equal(artifact.SBOMOverView["scan_status"], "Success")
}
func TestVulAssemblerTestSuite(t *testing.T) {
suite.Run(t, &VulAssemblerTestSuite{})
}

View File

@ -28,7 +28,9 @@ import (
// Artifact model
type Artifact struct {
artifact.Artifact
// TODO: rename to VulOverview
ScanOverview map[string]interface{} `json:"scan_overview"`
SBOMOverView map[string]interface{} `json:"sbom_overview"`
}
// ToSwagger converts the artifact to the swagger model
@ -84,6 +86,18 @@ func (a *Artifact) ToSwagger() *models.Artifact {
art.ScanOverview[key] = summary
}
}
if len(a.SBOMOverView) > 0 {
js, err := json.Marshal(a.SBOMOverView)
if err != nil {
log.Warningf("convert sbom summary failed, error: %v", err)
}
sbomOverview := &models.SBOMOverview{}
err = json.Unmarshal(js, sbomOverview)
if err != nil {
log.Warningf("failed to get sbom summary: error: %v", err)
}
art.SbomOverview = sbomOverview
}
return art
}

View File

@ -0,0 +1,47 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
// OverviewOptions define the option to query overview info
type OverviewOptions struct {
WithVuln bool
WithSBOM bool
}
// Option define the func to build options
type Option func(*OverviewOptions)
// NewOverviewOptions create a new OverviewOptions
func NewOverviewOptions(options ...Option) *OverviewOptions {
opts := &OverviewOptions{}
for _, f := range options {
f(opts)
}
return opts
}
// WithVuln set the option to query vulnerability info
func WithVuln(enable bool) Option {
return func(o *OverviewOptions) {
o.WithVuln = enable
}
}
// WithSBOM set the option to query SBOM info
func WithSBOM(enable bool) Option {
return func(o *OverviewOptions) {
o.WithSBOM = enable
}
}

View File

@ -0,0 +1,33 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOverviewOptions(t *testing.T) {
// Test NewOverviewOptions with WithVuln and WithSBOM
opts := NewOverviewOptions(WithVuln(true), WithSBOM(true))
assert.True(t, opts.WithVuln)
assert.True(t, opts.WithSBOM)
// Test NewOverviewOptions with WithVuln and WithSBOM set to false
opts = NewOverviewOptions(WithVuln(false), WithSBOM(false))
assert.False(t, opts.WithVuln)
assert.False(t, opts.WithSBOM)
}