mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
[cherry-pick] Add sbom_report table to store sbom related information (#20482)
Add sbom_report table to store sbom related information fixes #20445 Refactor scan/base_controller.go Move MakeReportPlaceholder, GetReportPlaceholder, GetSummary to vul and sbom scanHandler Signed-off-by: stonezdj <stone.zhang@broadcom.com>
This commit is contained in:
parent
a763d6b54c
commit
e2826868ee
@ -28,4 +28,16 @@ then set column artifact_type as not null
|
||||
*/
|
||||
UPDATE artifact SET artifact_type = media_type WHERE artifact_type IS NULL;
|
||||
|
||||
ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL;
|
||||
ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sbom_report
|
||||
(
|
||||
id SERIAL PRIMARY KEY NOT NULL,
|
||||
uuid VARCHAR(64) UNIQUE NOT NULL,
|
||||
artifact_id INT NOT NULL,
|
||||
registration_uuid VARCHAR(64) NOT NULL,
|
||||
mime_type VARCHAR(256) NOT NULL,
|
||||
media_type VARCHAR(256) NOT NULL,
|
||||
report JSON,
|
||||
UNIQUE(artifact_id, registration_uuid, mime_type, media_type)
|
||||
);
|
@ -24,7 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/artifact/processor/sbom"
|
||||
sbomprocessor "github.com/goharbor/harbor/src/controller/artifact/processor/sbom"
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/controller/event/operator"
|
||||
"github.com/goharbor/harbor/src/controller/repository"
|
||||
@ -38,6 +38,7 @@ import (
|
||||
pkgArt "github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/sbom"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
)
|
||||
|
||||
@ -74,6 +75,8 @@ type ArtifactEventHandler struct {
|
||||
execMgr task.ExecutionManager
|
||||
// reportMgr for managing scan reports
|
||||
reportMgr report.Manager
|
||||
// sbomReportMgr
|
||||
sbomReportMgr sbom.Manager
|
||||
// artMgr for managing artifacts
|
||||
artMgr pkgArt.Manager
|
||||
|
||||
@ -321,9 +324,15 @@ func (a *ArtifactEventHandler) onDelete(ctx context.Context, event *event.Artifa
|
||||
log.Errorf("failed to delete scan reports of artifact %v, error: %v", unrefDigests, err)
|
||||
}
|
||||
|
||||
if event.Artifact.Type == sbom.ArtifactTypeSBOM && len(event.Artifact.Digest) > 0 {
|
||||
if err := reportMgr.DeleteByExtraAttr(ctx, v1.MimeTypeSBOMReport, "sbom_digest", event.Artifact.Digest); err != nil {
|
||||
log.Errorf("failed to delete scan reports of with sbom digest %v, error: %v", event.Artifact.Digest, err)
|
||||
// delete sbom_report when the subject artifact is deleted
|
||||
if err := sbom.Mgr.DeleteByArtifactID(ctx, event.Artifact.ID); err != nil {
|
||||
log.Errorf("failed to delete sbom reports of artifact ID %v, error: %v", event.Artifact.ID, err)
|
||||
}
|
||||
|
||||
// delete sbom_report when the accessory artifact is deleted
|
||||
if event.Artifact.Type == sbomprocessor.ArtifactTypeSBOM && len(event.Artifact.Digest) > 0 {
|
||||
if err := sbom.Mgr.DeleteByExtraAttr(ctx, v1.MimeTypeSBOMReport, "sbom_digest", event.Artifact.Digest); err != nil {
|
||||
log.Errorf("failed to delete sbom reports of with sbom digest %v, error: %v", event.Artifact.Digest, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -144,7 +144,7 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent,
|
||||
|
||||
scanSummaries := map[string]interface{}{}
|
||||
if event.ScanType == v1.ScanTypeVulnerability {
|
||||
scanSummaries, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
||||
scanSummaries, err = scan.DefaultController.GetSummary(ctx, art, event.ScanType, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "construct scan payload")
|
||||
}
|
||||
@ -152,7 +152,7 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent,
|
||||
|
||||
sbomOverview := map[string]interface{}{}
|
||||
if event.ScanType == v1.ScanTypeSbom {
|
||||
sbomOverview, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeSBOMReport})
|
||||
sbomOverview, err = scan.DefaultController.GetSummary(ctx, art, event.ScanType, []string{v1.MimeTypeSBOMReport})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "construct scan payload")
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ package scan
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -49,7 +48,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/scan/postprocessors"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
)
|
||||
@ -275,8 +273,9 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti
|
||||
errs []error
|
||||
launchScanJobParams []*launchScanJobParam
|
||||
)
|
||||
handler := sca.GetScanHandler(opts.GetScanType())
|
||||
for _, art := range artifacts {
|
||||
reports, err := bc.makeReportPlaceholder(ctx, r, art, opts)
|
||||
reports, err := handler.MakePlaceHolder(ctx, art, r)
|
||||
if err != nil {
|
||||
if errors.IsConflictErr(err) {
|
||||
errs = append(errs, err)
|
||||
@ -566,63 +565,6 @@ func (bc *basicController) startScanAll(ctx context.Context, executionID int64)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact, opts *Options) ([]*scan.Report, error) {
|
||||
mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType())
|
||||
oldReports, err := bc.manager.GetBy(bc.cloneCtx(ctx), art.Digest, r.UUID, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := bc.deleteArtifactAccessories(ctx, oldReports); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bc.assembleReports(ctx, oldReports...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(oldReports) > 0 {
|
||||
for _, oldReport := range oldReports {
|
||||
if !job.Status(oldReport.Status).Final() {
|
||||
return nil, errors.ConflictError(nil).WithMessage("a previous scan process is %s", oldReport.Status)
|
||||
}
|
||||
}
|
||||
|
||||
for _, oldReport := range oldReports {
|
||||
if err := bc.manager.Delete(ctx, oldReport.UUID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var reports []*scan.Report
|
||||
|
||||
for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType()) {
|
||||
report := &scan.Report{
|
||||
Digest: art.Digest,
|
||||
RegistrationUUID: r.UUID,
|
||||
MimeType: pm,
|
||||
}
|
||||
|
||||
create := func(ctx context.Context) error {
|
||||
reportUUID, err := bc.manager.Create(ctx, report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report.UUID = reportUUID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := orm.WithTransaction(create)(orm.SetTransactionOpNameToContext(ctx, "tx-make-report-placeholder")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reports = append(reports, report)
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
// GetReport ...
|
||||
func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) ([]*scan.Report, error) {
|
||||
if artifact == nil {
|
||||
@ -697,95 +639,10 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries := make(map[string]interface{}, len(rps))
|
||||
for _, rp := range rps {
|
||||
sum, err := report.GenerateSummary(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s, ok := summaries[rp.MimeType]; ok {
|
||||
r, err := report.MergeSummary(rp.MimeType, s, sum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries[rp.MimeType] = r
|
||||
} else {
|
||||
summaries[rp.MimeType] = sum
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
result := map[string]interface{}{}
|
||||
if len(reportContent) == 0 {
|
||||
status := bc.retrieveStatusFromTask(ctx, reports[0].UUID)
|
||||
if len(status) > 0 {
|
||||
result[sbomModel.ReportID] = reports[0].UUID
|
||||
result[sbomModel.ScanStatus] = status
|
||||
}
|
||||
log.Debug("no content for current report")
|
||||
return result, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(reportContent), &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// retrieve the status from task
|
||||
func (bc *basicController) retrieveStatusFromTask(ctx context.Context, reportID string) string {
|
||||
if len(reportID) == 0 {
|
||||
return ""
|
||||
}
|
||||
tasks, err := bc.taskMgr.ListScanTasksByReportUUID(ctx, reportID)
|
||||
if err != nil {
|
||||
log.Warningf("can not find the task with report UUID %v, error %v", reportID, err)
|
||||
return ""
|
||||
}
|
||||
if len(tasks) > 0 {
|
||||
return tasks[0].Status
|
||||
}
|
||||
return ""
|
||||
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, scanType string, mimeTypes []string) (map[string]interface{}, error) {
|
||||
handler := sca.GetScanHandler(scanType)
|
||||
return handler.GetSummary(ctx, artifact, mimeTypes)
|
||||
}
|
||||
|
||||
// GetScanLog ...
|
||||
@ -821,7 +678,7 @@ func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact
|
||||
if !scanTaskForArtifacts(t, artifactMap) {
|
||||
return nil, errors.NotFoundError(nil).WithMessage("scan log with uuid: %s not found", uuid)
|
||||
}
|
||||
for _, reportUUID := range getReportUUIDs(t.ExtraAttrs) {
|
||||
for _, reportUUID := range GetReportUUIDs(t.ExtraAttrs) {
|
||||
reportUUIDToTasks[reportUUID] = t
|
||||
}
|
||||
}
|
||||
@ -902,14 +759,6 @@ func scanTaskForArtifacts(task *task.Task, artifactMap map[int64]interface{}) bo
|
||||
return exist
|
||||
}
|
||||
|
||||
// DeleteReports ...
|
||||
func (bc *basicController) DeleteReports(ctx context.Context, digests ...string) error {
|
||||
if err := bc.manager.DeleteByDigests(ctx, digests...); err != nil {
|
||||
return errors.Wrap(err, "scan controller: delete reports")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *basicController) GetVulnerable(ctx context.Context, artifact *ar.Artifact, allowlist allowlist.CVESet, allowlistIsExpired bool) (*Vulnerable, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get vulnerable for nil artifact")
|
||||
@ -1204,7 +1053,7 @@ func (bc *basicController) assembleReports(ctx context.Context, reports ...*scan
|
||||
|
||||
reportUUIDToTasks := map[string]*task.Task{}
|
||||
for _, task := range tasks {
|
||||
for _, reportUUID := range getReportUUIDs(task.ExtraAttrs) {
|
||||
for _, reportUUID := range GetReportUUIDs(task.ExtraAttrs) {
|
||||
reportUUIDToTasks[reportUUID] = task
|
||||
}
|
||||
}
|
||||
@ -1275,7 +1124,8 @@ func getArtifactTag(extraAttrs map[string]interface{}) string {
|
||||
return tag
|
||||
}
|
||||
|
||||
func getReportUUIDs(extraAttrs map[string]interface{}) []string {
|
||||
// GetReportUUIDs returns the report UUIDs from the extra attributes
|
||||
func GetReportUUIDs(extraAttrs map[string]interface{}) []string {
|
||||
var reportUUIDs []string
|
||||
|
||||
if extraAttrs != nil {
|
||||
@ -1314,48 +1164,3 @@ func parseOptions(options ...Option) (*Options, error) {
|
||||
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
// deleteArtifactAccessories delete the accessory in reports, only delete sbom accessory
|
||||
func (bc *basicController) deleteArtifactAccessories(ctx context.Context, reports []*scan.Report) error {
|
||||
for _, rpt := range reports {
|
||||
if rpt.MimeType != v1.MimeTypeSBOMReport {
|
||||
continue
|
||||
}
|
||||
if err := bc.deleteArtifactAccessory(ctx, rpt.Report); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteArtifactAccessory check if current report has accessory info, if there is, delete it
|
||||
func (bc *basicController) deleteArtifactAccessory(ctx context.Context, report string) error {
|
||||
if len(report) == 0 {
|
||||
return nil
|
||||
}
|
||||
sbomSummary := sbomModel.Summary{}
|
||||
if err := json.Unmarshal([]byte(report), &sbomSummary); err != nil {
|
||||
// it could be a non sbom report, just skip
|
||||
log.Debugf("fail to unmarshal %v, skip to delete sbom report", err)
|
||||
return nil
|
||||
}
|
||||
repo, dgst := sbomSummary.SBOMAccArt()
|
||||
if len(repo) == 0 || len(dgst) == 0 {
|
||||
return nil
|
||||
}
|
||||
art, err := bc.ar.GetByReference(ctx, repo, dgst, nil)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if art == nil {
|
||||
return nil
|
||||
}
|
||||
err = bc.ar.Delete(ctx, art.ID)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
_ "github.com/goharbor/harbor/src/pkg/scan/vulnerability"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
robottesting "github.com/goharbor/harbor/src/testing/controller/robot"
|
||||
@ -55,6 +54,7 @@ import (
|
||||
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory"
|
||||
scanTest "github.com/goharbor/harbor/src/testing/pkg/scan"
|
||||
postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors"
|
||||
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
|
||||
tasktesting "github.com/goharbor/harbor/src/testing/pkg/task"
|
||||
@ -64,6 +64,8 @@ import (
|
||||
type ControllerTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
scanHandler *scanTest.Handler
|
||||
|
||||
artifactCtl *artifacttesting.Controller
|
||||
accessoryMgr *accessorytesting.Manager
|
||||
originalArtifactCtl artifact.Controller
|
||||
@ -91,6 +93,8 @@ func TestController(t *testing.T) {
|
||||
|
||||
// SetupSuite ...
|
||||
func (suite *ControllerTestSuite) SetupSuite() {
|
||||
suite.scanHandler = &scanTest.Handler{}
|
||||
sca.RegisterScanHanlder(v1.ScanTypeVulnerability, suite.scanHandler)
|
||||
suite.originalArtifactCtl = artifact.Ctl
|
||||
suite.artifactCtl = &artifacttesting.Controller{}
|
||||
artifact.Ctl = suite.artifactCtl
|
||||
@ -212,7 +216,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil)
|
||||
mgr.On("GetBy", mock.Anything, suite.wrongArtifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(emptySBOMReport, 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("Update", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil)
|
||||
mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil)
|
||||
suite.reportMgr = mgr
|
||||
|
||||
@ -347,6 +351,19 @@ func (suite *ControllerTestSuite) TearDownSuite() {
|
||||
|
||||
// TestScanControllerScan ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerScan() {
|
||||
rpts := []*scan.Report{
|
||||
{UUID: "uuid"},
|
||||
}
|
||||
requiredPermission := []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionPull,
|
||||
},
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionScannerPull,
|
||||
},
|
||||
}
|
||||
{
|
||||
// artifact not provieded
|
||||
suite.Require().Error(suite.c.Scan(context.TODO(), nil))
|
||||
@ -369,6 +386,8 @@ func (suite *ControllerTestSuite) TestScanControllerScan() {
|
||||
|
||||
mock.OnAnything(suite.execMgr, "Create").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.taskMgr, "Create").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.scanHandler, "MakePlaceHolder").Return(rpts, nil).Once()
|
||||
mock.OnAnything(suite.scanHandler, "RequiredPermissions").Return(requiredPermission).Once()
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||
|
||||
@ -388,7 +407,10 @@ func (suite *ControllerTestSuite) TestScanControllerScan() {
|
||||
}, nil).Once()
|
||||
|
||||
mock.OnAnything(suite.reportMgr, "Delete").Return(fmt.Errorf("delete failed")).Once()
|
||||
|
||||
mock.OnAnything(suite.scanHandler, "MakePlaceHolder").Return(rpts, nil).Once()
|
||||
mock.OnAnything(suite.scanHandler, "RequiredPermissions").Return(requiredPermission).Once()
|
||||
mock.OnAnything(suite.execMgr, "Create").Return(int64(1), nil).Once()
|
||||
mock.OnAnything(suite.taskMgr, "Create").Return(int64(0), fmt.Errorf("failed to create task")).Once()
|
||||
suite.Require().Error(suite.c.Scan(context.TODO(), suite.artifact))
|
||||
}
|
||||
|
||||
@ -403,7 +425,9 @@ func (suite *ControllerTestSuite) TestScanControllerScan() {
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return([]*task.Task{
|
||||
{ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), Status: "Running"},
|
||||
}, nil).Once()
|
||||
|
||||
mock.OnAnything(suite.scanHandler, "MakePlaceHolder").Return(rpts, nil).Once()
|
||||
mock.OnAnything(suite.scanHandler, "RequiredPermissions").Return(requiredPermission).Once()
|
||||
mock.OnAnything(suite.execMgr, "Create").Return(int64(0), fmt.Errorf("failed to create execution")).Once()
|
||||
suite.Require().Error(suite.c.Scan(context.TODO(), suite.artifact))
|
||||
}
|
||||
}
|
||||
@ -465,21 +489,6 @@ func (suite *ControllerTestSuite) TestScanControllerGetReport() {
|
||||
assert.Equal(suite.T(), 1, len(rep))
|
||||
}
|
||||
|
||||
// TestScanControllerGetSummary ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerGetSummary() {
|
||||
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
|
||||
mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once()
|
||||
mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once()
|
||||
mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {
|
||||
walkFn := args.Get(2).(func(*artifact.Artifact) error)
|
||||
walkFn(suite.artifact)
|
||||
}).Once()
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return(nil, nil).Once()
|
||||
sum, err := suite.c.GetSummary(ctx, suite.artifact, []string{v1.MimeTypeNativeReport})
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(sum))
|
||||
}
|
||||
|
||||
// TestScanControllerGetScanLog ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerGetScanLog() {
|
||||
mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once()
|
||||
@ -493,6 +502,13 @@ func (suite *ControllerTestSuite) TestScanControllerGetScanLog() {
|
||||
|
||||
mock.OnAnything(suite.taskMgr, "GetLog").Return([]byte("log"), nil).Once()
|
||||
|
||||
mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {
|
||||
walkFn := args.Get(2).(func(*artifact.Artifact) error)
|
||||
walkFn(suite.artifact)
|
||||
}).Once()
|
||||
|
||||
mock.OnAnything(suite.accessoryMgr, "List").Return(nil, nil)
|
||||
|
||||
bytes, err := suite.c.GetScanLog(ctx, &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, "rp-uuid-001")
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Condition(suite.T(), func() (success bool) {
|
||||
@ -566,6 +582,21 @@ func (suite *ControllerTestSuite) TestScanAll() {
|
||||
{
|
||||
// no artifacts found when scan all
|
||||
executionID := int64(1)
|
||||
rpts := []*scan.Report{
|
||||
{UUID: "uuid"},
|
||||
}
|
||||
requiredPermission := []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionPull,
|
||||
},
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionScannerPull,
|
||||
},
|
||||
}
|
||||
mock.OnAnything(suite.scanHandler, "MakePlaceHolder").Return(rpts, nil).Once()
|
||||
mock.OnAnything(suite.scanHandler, "RequiredPermissions").Return(requiredPermission).Once()
|
||||
mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once()
|
||||
suite.execMgr.On(
|
||||
"Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE",
|
||||
@ -607,8 +638,6 @@ func (suite *ControllerTestSuite) TestScanAll() {
|
||||
walkFn(suite.artifact)
|
||||
}).Once()
|
||||
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return(nil, nil).Once()
|
||||
|
||||
mock.OnAnything(suite.reportMgr, "Delete").Return(nil).Once()
|
||||
mock.OnAnything(suite.reportMgr, "Create").Return("uuid", nil).Once()
|
||||
mock.OnAnything(suite.taskMgr, "Create").Return(int64(0), fmt.Errorf("failed")).Once()
|
||||
@ -635,16 +664,6 @@ func (suite *ControllerTestSuite) TestStopScanAll() {
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ControllerTestSuite) TestDeleteReports() {
|
||||
suite.reportMgr.On("DeleteByDigests", context.TODO(), "digest").Return(nil).Once()
|
||||
|
||||
suite.NoError(suite.c.DeleteReports(context.TODO(), "digest"))
|
||||
|
||||
suite.reportMgr.On("DeleteByDigests", context.TODO(), "digest").Return(fmt.Errorf("failed")).Once()
|
||||
|
||||
suite.Error(suite.c.DeleteReports(context.TODO(), "digest"))
|
||||
}
|
||||
|
||||
func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs ...string) map[string]interface{} {
|
||||
b, _ := json.Marshal(map[string]interface{}{reportUUIDsKey: reportUUIDs})
|
||||
|
||||
@ -654,57 +673,3 @@ 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)
|
||||
tasks := []*task.Task{{Status: "Error"}}
|
||||
suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once()
|
||||
sum2, err := suite.c.GetSummary(context.TODO(), suite.wrongArtifact, []string{v1.MimeTypeSBOMReport})
|
||||
suite.Nil(err)
|
||||
suite.NotNil(sum2)
|
||||
|
||||
}
|
||||
|
||||
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{}))
|
||||
}
|
||||
|
||||
func (suite *ControllerTestSuite) TestDeleteArtifactAccessories() {
|
||||
// artifact not provided
|
||||
suite.Nil(suite.c.deleteArtifactAccessories(context.TODO(), nil))
|
||||
|
||||
// artifact is provided
|
||||
art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1, RepositoryName: "library/photon"}}
|
||||
mock.OnAnything(suite.ar, "GetByReference").Return(art, nil).Once()
|
||||
mock.OnAnything(suite.ar, "Delete").Return(nil).Once()
|
||||
reportContent := `{"sbom_digest":"sha256:12345", "scan_status":"Success", "duration":3, "sbom_repository":"library/photon"}`
|
||||
emptyReportContent := ``
|
||||
reports := []*scan.Report{
|
||||
{Report: reportContent},
|
||||
{Report: emptyReportContent},
|
||||
}
|
||||
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
|
||||
suite.NoError(suite.c.deleteArtifactAccessories(ctx, reports))
|
||||
}
|
||||
|
||||
func (suite *ControllerTestSuite) TestRetrieveStatusFromTask() {
|
||||
tasks := []*task.Task{{Status: "Error"}}
|
||||
suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once()
|
||||
status := suite.c.retrieveStatusFromTask(nil, "rp-uuid-004")
|
||||
suite.Equal("Error", status)
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ type Controller interface {
|
||||
// Returns:
|
||||
// map[string]interface{} : report summaries indexed by mime types
|
||||
// error : non nil error if any errors occurred
|
||||
GetSummary(ctx context.Context, artifact *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error)
|
||||
GetSummary(ctx context.Context, artifact *artifact.Artifact, scanType string, mimeTypes []string) (map[string]interface{}, error)
|
||||
|
||||
// Get the scan log for the specified artifact with the given digest
|
||||
//
|
||||
@ -96,15 +96,6 @@ type Controller interface {
|
||||
// error : non nil error if any errors occurred
|
||||
GetScanLog(ctx context.Context, art *artifact.Artifact, uuid string) ([]byte, error)
|
||||
|
||||
// Delete the reports related with the specified digests
|
||||
//
|
||||
// Arguments:
|
||||
// digests ...string : specify one or more digests whose reports will be deleted
|
||||
//
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
DeleteReports(ctx context.Context, digests ...string) error
|
||||
|
||||
// Scan all the artifacts
|
||||
//
|
||||
// Arguments:
|
||||
|
@ -54,14 +54,6 @@ func (suite *ReportTestSuite) SetupTest() {
|
||||
MimeType: v1.MimeTypeNativeReport,
|
||||
}
|
||||
suite.create(r)
|
||||
sbomReport := &Report{
|
||||
UUID: "uuid3",
|
||||
Digest: "digest1003",
|
||||
RegistrationUUID: "ruuid",
|
||||
MimeType: v1.MimeTypeSBOMReport,
|
||||
Report: `{"sbom_digest": "sha256:abc"}`,
|
||||
}
|
||||
suite.create(sbomReport)
|
||||
}
|
||||
|
||||
// TearDownTest clears enf for test case.
|
||||
@ -113,17 +105,6 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() {
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestDeleteReportBySBOMDigest() {
|
||||
l, err := suite.dao.List(orm.Context(), nil)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(2, len(l))
|
||||
err = suite.dao.DeleteByExtraAttr(orm.Context(), v1.MimeTypeSBOMReport, "sbom_digest", "sha256:abc")
|
||||
suite.Require().NoError(err)
|
||||
l2, err := suite.dao.List(orm.Context(), nil)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(1, len(l2))
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) create(r *Report) {
|
||||
id, err := suite.dao.Create(orm.Context(), r)
|
||||
suite.Require().NoError(err)
|
||||
|
@ -15,12 +15,15 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
@ -44,8 +47,21 @@ type Handler interface {
|
||||
RequiredPermissions() []*types.Policy
|
||||
// RequestParameters defines the parameters for scan request
|
||||
RequestParameters() map[string]interface{}
|
||||
// ReportURLParameter defines the parameters for scan report
|
||||
ReportURLParameter(sr *v1.ScanRequest) (string, error)
|
||||
// PostScan defines the operation after scan
|
||||
PostScan(ctx job.Context, sr *v1.ScanRequest, rp *scan.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error)
|
||||
ReportHandler
|
||||
}
|
||||
|
||||
// ReportHandler handler for scan report, it could be sbom report or vulnerability report
|
||||
type ReportHandler interface {
|
||||
// URLParameter defines the parameters for scan report
|
||||
URLParameter(sr *v1.ScanRequest) (string, error)
|
||||
// Update update the report data in the database by UUID
|
||||
Update(ctx context.Context, uuid string, report string) error
|
||||
// MakePlaceHolder make the report place holder, if exist, delete it and create a new one
|
||||
MakePlaceHolder(ctx context.Context, art *artifact.Artifact, r *scanner.Registration) (rps []*scan.Report, err error)
|
||||
// GetPlaceHolder get the the report place holder
|
||||
GetPlaceHolder(ctx context.Context, artRepo string, artDigest string, scannerUUID string, mimeType string) (rp *scan.Report, err error)
|
||||
// GetSummary get the summary of the report
|
||||
GetSummary(ctx context.Context, ar *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package scan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -35,7 +34,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
@ -243,7 +241,7 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
|
||||
myLogger.Debugf("check scan report for mime %s at %s", m, t.Format("2006/01/02 15:04:05"))
|
||||
|
||||
reportURLParameter, err := handler.ReportURLParameter(req)
|
||||
reportURLParameter, err := handler.URLParameter(req)
|
||||
if err != nil {
|
||||
errs[i] = errors.Wrap(err, "scan job: get report url")
|
||||
return
|
||||
@ -298,7 +296,7 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
}
|
||||
|
||||
for i, mimeType := range mimeTypes {
|
||||
rp, err := getReportPlaceholder(ctx.SystemContext(), req.Artifact.Digest, r.UUID, mimeType, myLogger)
|
||||
rp, err := handler.GetPlaceHolder(ctx.SystemContext(), req.Artifact.Repository, req.Artifact.Digest, r.UUID, mimeType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -314,30 +312,16 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
// this is required since the top level layers relay on the vuln.Report struct that
|
||||
// contains additional metadata within the report which if stored in the new columns within the scan_report table
|
||||
// would be redundant
|
||||
if err := report.Mgr.UpdateReportData(ctx.SystemContext(), rp.UUID, reportData); err != nil {
|
||||
if err := handler.Update(ctx.SystemContext(), rp.UUID, reportData); err != nil {
|
||||
myLogger.Errorf("Failed to update report data for report %s, error %v", rp.UUID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
myLogger.Debugf("Converted report ID %s to the new V2 schema", rp.UUID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReportPlaceholder(ctx context.Context, digest string, reportUUID string, mimeType string, logger logger.Interface) (*scan.Report, error) {
|
||||
reports, err := report.Mgr.GetBy(ctx, digest, reportUUID, []string{mimeType})
|
||||
if err != nil {
|
||||
logger.Error("Failed to get report for artifact %s of mimetype %s, error %v", digest, mimeType, err)
|
||||
return nil, err
|
||||
}
|
||||
if len(reports) == 0 {
|
||||
logger.Errorf("No report found for artifact %s of mimetype %s, error %v", digest, mimeType, err)
|
||||
return nil, errors.NotFoundError(nil).WithMessage("no report found to update data")
|
||||
}
|
||||
return reports[0], nil
|
||||
}
|
||||
|
||||
func fetchScanReportFromScanner(client v1.Client, requestID string, mimType string, urlParameter string) (rawReport string, err error) {
|
||||
rawReport, err = client.GetScanReport(requestID, mimType, urlParameter)
|
||||
if err != nil {
|
||||
|
@ -161,26 +161,6 @@ func (suite *JobTestSuite) TestJob() {
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *JobTestSuite) TestgetReportPlaceholder() {
|
||||
dgst := "sha256:mydigest"
|
||||
uuid := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e`
|
||||
scannerUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15f`
|
||||
rpt := &scan.Report{
|
||||
UUID: uuid,
|
||||
RegistrationUUID: scannerUUID,
|
||||
Digest: dgst,
|
||||
MimeType: v1.MimeTypeDockerArtifact,
|
||||
}
|
||||
ctx := suite.Context()
|
||||
rptID, err := report.Mgr.Create(ctx, rpt)
|
||||
suite.reportIDs = append(suite.reportIDs, rptID)
|
||||
require.NoError(suite.T(), err)
|
||||
jobLogger := &mockjobservice.MockJobLogger{}
|
||||
report, err := getReportPlaceholder(ctx, dgst, scannerUUID, v1.MimeTypeDockerArtifact, jobLogger)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), report)
|
||||
}
|
||||
|
||||
func (suite *JobTestSuite) TestfetchScanReportFromScanner() {
|
||||
vulnRpt := &vuln.Report{
|
||||
GeneratedAt: time.Now().UTC().String(),
|
||||
|
126
src/pkg/scan/sbom/dao/dao.go
Normal file
126
src/pkg/scan/sbom/dao/dao.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(model.Report))
|
||||
}
|
||||
|
||||
// DAO is the data access object interface for sbom report
|
||||
type DAO interface {
|
||||
// Create creates new report
|
||||
Create(ctx context.Context, r *model.Report) (int64, error)
|
||||
// DeleteMany delete the reports according to the query
|
||||
DeleteMany(ctx context.Context, query q.Query) (int64, error)
|
||||
// List lists the reports with given query parameters.
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Report, error)
|
||||
// UpdateReportData only updates the `report` column with conditions matched.
|
||||
UpdateReportData(ctx context.Context, uuid string, report string) error
|
||||
// Update update report
|
||||
Update(ctx context.Context, r *model.Report, cols ...string) error
|
||||
// DeleteByExtraAttr delete the scan_report by mimeType and extra attribute
|
||||
DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error
|
||||
}
|
||||
|
||||
// New returns an instance of the default DAO
|
||||
func New() DAO {
|
||||
return &dao{}
|
||||
}
|
||||
|
||||
type dao struct{}
|
||||
|
||||
// Create creates new sbom report
|
||||
func (d *dao) Create(ctx context.Context, r *model.Report) (int64, error) {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return o.Insert(r)
|
||||
}
|
||||
|
||||
func (d *dao) DeleteMany(ctx context.Context, query q.Query) (int64, error) {
|
||||
if len(query.Keywords) == 0 {
|
||||
return 0, errors.New("delete all sbom reports at once is not allowed")
|
||||
}
|
||||
|
||||
qs, err := orm.QuerySetter(ctx, &model.Report{}, &query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return qs.Delete()
|
||||
}
|
||||
|
||||
func (d *dao) List(ctx context.Context, query *q.Query) ([]*model.Report, error) {
|
||||
qs, err := orm.QuerySetter(ctx, &model.Report{}, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reports := []*model.Report{}
|
||||
if _, err = qs.All(&reports); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
// UpdateReportData only updates the `report` column with conditions matched.
|
||||
func (d *dao) UpdateReportData(ctx context.Context, uuid string, report string) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qt := o.QueryTable(new(model.Report))
|
||||
|
||||
data := make(orm.Params)
|
||||
data["report"] = report
|
||||
|
||||
_, err = qt.Filter("uuid", uuid).Update(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dao) Update(ctx context.Context, r *model.Report, cols ...string) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := o.Update(r, cols...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dao) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delReportSQL := "delete from sbom_report where mime_type = ? and report::jsonb @> ?"
|
||||
dgstJSONStr := fmt.Sprintf(`{"%s":"%s"}`, attrName, attrValue)
|
||||
_, err = o.Raw(delReportSQL, mimeType, dgstJSONStr).Exec()
|
||||
return err
|
||||
}
|
133
src/pkg/scan/sbom/dao/dao_test.go
Normal file
133
src/pkg/scan/sbom/dao/dao_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
)
|
||||
|
||||
// ReportTestSuite is test suite of testing report DAO.
|
||||
type ReportTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
}
|
||||
|
||||
// TestReport is the entry of ReportTestSuite.
|
||||
func TestReport(t *testing.T) {
|
||||
suite.Run(t, &ReportTestSuite{})
|
||||
}
|
||||
|
||||
// SetupSuite prepares env for test suite.
|
||||
func (suite *ReportTestSuite) SetupSuite() {
|
||||
suite.Suite.SetupSuite()
|
||||
suite.dao = New()
|
||||
}
|
||||
|
||||
// SetupTest prepares env for test case.
|
||||
func (suite *ReportTestSuite) SetupTest() {
|
||||
sbomReport := &model.Report{
|
||||
UUID: "uuid",
|
||||
ArtifactID: 111,
|
||||
RegistrationUUID: "ruuid",
|
||||
MimeType: v1.MimeTypeSBOMReport,
|
||||
ReportSummary: `{"sbom_digest": "sha256:abc"}`,
|
||||
}
|
||||
suite.create(sbomReport)
|
||||
}
|
||||
|
||||
// TearDownTest clears enf for test case.
|
||||
func (suite *ReportTestSuite) TearDownTest() {
|
||||
_, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}})
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestDeleteReportBySBOMDigest() {
|
||||
l, err := suite.dao.List(orm.Context(), nil)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(1, len(l))
|
||||
err = suite.dao.DeleteByExtraAttr(orm.Context(), v1.MimeTypeSBOMReport, "sbom_digest", "sha256:abc")
|
||||
suite.Require().NoError(err)
|
||||
l2, err := suite.dao.List(orm.Context(), nil)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(0, len(l2))
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) create(r *model.Report) {
|
||||
id, err := suite.dao.Create(orm.Context(), r)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Condition(func() (success bool) {
|
||||
success = id > 0
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// TestReportUpdateReportData tests update the report data.
|
||||
func (suite *ReportTestSuite) TestReportUpdateReportData() {
|
||||
err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
l, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"uuid": "uuid"}))
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(1, len(l))
|
||||
suite.Equal("{}", l[0].ReportSummary)
|
||||
|
||||
err = suite.dao.UpdateReportData(orm.Context(), "uuid", "{\"a\": 900}")
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestUpdate() {
|
||||
err := suite.dao.Update(orm.Context(), &model.Report{
|
||||
UUID: "uuid",
|
||||
ArtifactID: 111,
|
||||
RegistrationUUID: "ruuid",
|
||||
MimeType: v1.MimeTypeSBOMReport,
|
||||
ReportSummary: `{"sbom_digest": "sha256:abc"}`,
|
||||
}, "report")
|
||||
suite.Require().NoError(err)
|
||||
query1 := &q.Query{
|
||||
PageSize: 1,
|
||||
PageNumber: 1,
|
||||
Keywords: map[string]interface{}{
|
||||
"artifact_id": 111,
|
||||
"registration_uuid": "ruuid",
|
||||
"mime_type": v1.MimeTypeSBOMReport,
|
||||
},
|
||||
}
|
||||
l, err := suite.dao.List(orm.Context(), query1)
|
||||
suite.Require().Equal(1, len(l))
|
||||
suite.Equal(l[0].ReportSummary, `{"sbom_digest": "sha256:abc"}`)
|
||||
}
|
||||
|
||||
// TestReportList tests list reports with query parameters.
|
||||
func (suite *ReportTestSuite) TestReportList() {
|
||||
query1 := &q.Query{
|
||||
PageSize: 1,
|
||||
PageNumber: 1,
|
||||
Keywords: map[string]interface{}{
|
||||
"artifact_id": 111,
|
||||
"registration_uuid": "ruuid",
|
||||
"mime_type": v1.MimeTypeSBOMReport,
|
||||
},
|
||||
}
|
||||
l, err := suite.dao.List(orm.Context(), query1)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(1, len(l))
|
||||
|
||||
query2 := &q.Query{
|
||||
PageSize: 1,
|
||||
PageNumber: 1,
|
||||
Keywords: map[string]interface{}{
|
||||
"artifact_id": 222,
|
||||
},
|
||||
}
|
||||
l, err = suite.dao.List(orm.Context(), query2)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(0, len(l))
|
||||
}
|
203
src/pkg/scan/sbom/manager.go
Normal file
203
src/pkg/scan/sbom/manager.go
Normal file
@ -0,0 +1,203 @@
|
||||
// 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"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/sbom/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// Mgr is the global sbom report manager
|
||||
Mgr = NewManager()
|
||||
)
|
||||
|
||||
// Manager is used to manage the sbom reports.
|
||||
type Manager interface {
|
||||
// Create a new report record.
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// r *scan.Report : report model to be created
|
||||
//
|
||||
// Returns:
|
||||
// string : uuid of the new report
|
||||
// error : non nil error if any errors occurred
|
||||
//
|
||||
Create(ctx context.Context, r *model.Report) (string, error)
|
||||
|
||||
// Delete delete report by uuid
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// uuid string : uuid of the report to delete
|
||||
//
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
//
|
||||
Delete(ctx context.Context, uuid string) error
|
||||
|
||||
// UpdateReportData update the report data (with JSON format) of the given report.
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// uuid string : uuid to identify the report
|
||||
// report string : report JSON data
|
||||
//
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
//
|
||||
UpdateReportData(ctx context.Context, uuid string, report string) error
|
||||
|
||||
// GetBy the reports for the given digest by other properties.
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// artifact_id int64 : the artifact id
|
||||
// registrationUUID string : [optional] the report generated by which registration.
|
||||
// If it is empty, reports by all the registrations are retrieved.
|
||||
// mimeTypes []string : [optional] mime types of the reports requiring
|
||||
// If empty array is specified, reports with all the supported mimes are retrieved.
|
||||
//
|
||||
// Returns:
|
||||
// []*Report : sbom report list
|
||||
// error : non nil error if any errors occurred
|
||||
GetBy(ctx context.Context, artifactID int64, registrationUUID string, mimeType string, mediaType string) ([]*model.Report, error)
|
||||
// List reports according to the query
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// query *q.Query : the query to list the reports
|
||||
//
|
||||
// Returns:
|
||||
// []*scan.Report : report list
|
||||
// error : non nil error if any errors occurred
|
||||
List(ctx context.Context, query *q.Query) ([]*model.Report, error)
|
||||
|
||||
// Update update report information
|
||||
Update(ctx context.Context, r *model.Report, cols ...string) error
|
||||
// DeleteByExtraAttr delete scan_report by sbom_digest
|
||||
DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error
|
||||
// DeleteByArtifactID delete sbom report by artifact id
|
||||
DeleteByArtifactID(ctx context.Context, artifactID int64) error
|
||||
}
|
||||
|
||||
// basicManager is a default implementation of report manager.
|
||||
type basicManager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
// NewManager news basic manager.
|
||||
func NewManager() Manager {
|
||||
return &basicManager{
|
||||
dao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (bm *basicManager) Create(ctx context.Context, r *model.Report) (string, error) {
|
||||
// Validate report object
|
||||
if r == nil {
|
||||
return "", errors.New("nil sbom report object")
|
||||
}
|
||||
|
||||
if r.ArtifactID == 0 || len(r.RegistrationUUID) == 0 || len(r.MimeType) == 0 || len(r.MediaType) == 0 {
|
||||
return "", errors.New("malformed sbom report object")
|
||||
}
|
||||
|
||||
r.UUID = uuid.New().String()
|
||||
|
||||
// Insert
|
||||
if _, err := bm.dao.Create(ctx, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return r.UUID, nil
|
||||
}
|
||||
|
||||
func (bm *basicManager) Delete(ctx context.Context, uuid string) error {
|
||||
query := q.Query{Keywords: q.KeyWords{"uuid": uuid}}
|
||||
count, err := bm.dao.DeleteMany(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.Errorf("no report with uuid %s deleted", uuid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBy ...
|
||||
func (bm *basicManager) GetBy(ctx context.Context, artifactID int64, registrationUUID string,
|
||||
mimeType string, mediaType string) ([]*model.Report, error) {
|
||||
if artifactID == 0 {
|
||||
return nil, errors.New("no artifact id to get sbom report data")
|
||||
}
|
||||
|
||||
kws := make(map[string]interface{})
|
||||
kws["artifact_id"] = artifactID
|
||||
if len(registrationUUID) > 0 {
|
||||
kws["registration_uuid"] = registrationUUID
|
||||
}
|
||||
if len(mimeType) > 0 {
|
||||
kws["mine_type"] = mimeType
|
||||
}
|
||||
if len(mediaType) > 0 {
|
||||
kws["media_type"] = mediaType
|
||||
}
|
||||
// Query all
|
||||
query := &q.Query{
|
||||
PageNumber: 0,
|
||||
Keywords: kws,
|
||||
}
|
||||
|
||||
return bm.dao.List(ctx, query)
|
||||
}
|
||||
|
||||
// UpdateReportData ...
|
||||
func (bm *basicManager) UpdateReportData(ctx context.Context, uuid string, report string) error {
|
||||
if len(uuid) == 0 {
|
||||
return errors.New("missing uuid")
|
||||
}
|
||||
|
||||
if len(report) == 0 {
|
||||
return errors.New("missing report JSON data")
|
||||
}
|
||||
|
||||
return bm.dao.UpdateReportData(ctx, uuid, report)
|
||||
}
|
||||
|
||||
func (bm *basicManager) List(ctx context.Context, query *q.Query) ([]*model.Report, error) {
|
||||
return bm.dao.List(ctx, query)
|
||||
}
|
||||
|
||||
func (bm *basicManager) Update(ctx context.Context, r *model.Report, cols ...string) error {
|
||||
return bm.dao.Update(ctx, r, cols...)
|
||||
}
|
||||
|
||||
func (bm *basicManager) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error {
|
||||
return bm.dao.DeleteByExtraAttr(ctx, mimeType, attrName, attrValue)
|
||||
}
|
||||
|
||||
func (bm *basicManager) DeleteByArtifactID(ctx context.Context, artifactID int64) error {
|
||||
_, err := bm.dao.DeleteMany(ctx, *q.New(q.KeyWords{"ArtifactID": artifactID}))
|
||||
return err
|
||||
}
|
46
src/pkg/scan/sbom/model/report.go
Normal file
46
src/pkg/scan/sbom/model/report.go
Normal file
@ -0,0 +1,46 @@
|
||||
// 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 v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
|
||||
// Report sbom report.
|
||||
// Identified by the `artifact_id`, `registration_uuid` and `mime_type`.
|
||||
type Report struct {
|
||||
ID int64 `orm:"pk;auto;column(id)"`
|
||||
UUID string `orm:"unique;column(uuid)"`
|
||||
ArtifactID int64 `orm:"column(artifact_id)"`
|
||||
RegistrationUUID string `orm:"column(registration_uuid)"`
|
||||
MimeType string `orm:"column(mime_type)"`
|
||||
MediaType string `orm:"column(media_type)"`
|
||||
ReportSummary string `orm:"column(report);type(json)"`
|
||||
}
|
||||
|
||||
// TableName for sbom report
|
||||
func (r *Report) TableName() string {
|
||||
return "sbom_report"
|
||||
}
|
||||
|
||||
// RawSBOMReport the original report of the sbom report get from scanner
|
||||
type RawSBOMReport struct {
|
||||
// Time of generating this report
|
||||
GeneratedAt string `json:"generated_at"`
|
||||
// Scanner of generating this report
|
||||
Scanner *v1.Scanner `json:"scanner"`
|
||||
// MediaType the media type of the report, e.g. application/spdx+json
|
||||
MediaType string `json:"media_type"`
|
||||
// SBOM sbom content
|
||||
SBOM map[string]interface{} `json:"sbom,omitempty"`
|
||||
}
|
@ -23,18 +23,26 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
scanModel "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
sbom "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
scanCtl "github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan"
|
||||
scanModel "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
sbom "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
|
||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,32 +51,72 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
scan.RegisterScanHanlder(v1.ScanTypeSbom, &scanHandler{GenAccessoryFunc: scan.GenAccessoryArt, RegistryServer: registry})
|
||||
scan.RegisterScanHanlder(v1.ScanTypeSbom, &scanHandler{
|
||||
GenAccessoryFunc: scan.GenAccessoryArt,
|
||||
RegistryServer: registry,
|
||||
SBOMMgrFunc: func() Manager { return Mgr },
|
||||
TaskMgrFunc: func() task.Manager { return task.Mgr },
|
||||
ArtifactControllerFunc: func() artifact.Controller { return artifact.Ctl },
|
||||
ScanControllerFunc: func() scanCtl.Controller { return scanCtl.DefaultController },
|
||||
ScannerControllerFunc: func() sc.Controller { return sc.DefaultController },
|
||||
cloneCtx: orm.Clone,
|
||||
})
|
||||
}
|
||||
|
||||
// ScanHandler defines the Handler to generate sbom
|
||||
// scanHandler defines the Handler to generate sbom
|
||||
type scanHandler struct {
|
||||
GenAccessoryFunc func(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error)
|
||||
RegistryServer func(ctx context.Context) (string, bool)
|
||||
GenAccessoryFunc func(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error)
|
||||
RegistryServer func(ctx context.Context) (string, bool)
|
||||
SBOMMgrFunc func() Manager
|
||||
TaskMgrFunc func() task.Manager
|
||||
ArtifactControllerFunc func() artifact.Controller
|
||||
ScanControllerFunc func() scanCtl.Controller
|
||||
ScannerControllerFunc func() sc.Controller
|
||||
cloneCtx func(ctx context.Context) context.Context
|
||||
}
|
||||
|
||||
// RequestProducesMineTypes defines the mine types produced by the scan handler
|
||||
func (v *scanHandler) RequestProducesMineTypes() []string {
|
||||
func (h *scanHandler) RequestProducesMineTypes() []string {
|
||||
return []string{v1.MimeTypeSBOMReport}
|
||||
}
|
||||
|
||||
// RequestParameters defines the parameters for scan request
|
||||
func (v *scanHandler) RequestParameters() map[string]interface{} {
|
||||
func (h *scanHandler) RequestParameters() map[string]interface{} {
|
||||
return map[string]interface{}{"sbom_media_types": []string{sbomMediaTypeSpdx}}
|
||||
}
|
||||
|
||||
// ReportURLParameter defines the parameters for scan report url
|
||||
func (v *scanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) {
|
||||
// PostScan defines task specific operations after the scan is complete
|
||||
func (h *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) {
|
||||
sbomContent, s, err := retrieveSBOMContent(rawReport)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
scanReq := v1.ScanRequest{
|
||||
Registry: sr.Registry,
|
||||
Artifact: sr.Artifact,
|
||||
}
|
||||
// the registry server url is core by default, need to replace it with real registry server url
|
||||
scanReq.Registry.URL, scanReq.Registry.Insecure = h.RegistryServer(ctx.SystemContext())
|
||||
if len(scanReq.Registry.URL) == 0 {
|
||||
return "", fmt.Errorf("empty registry server")
|
||||
}
|
||||
myLogger := ctx.GetLogger()
|
||||
myLogger.Debugf("Pushing accessory artifact to %s/%s", scanReq.Registry.URL, scanReq.Artifact.Repository)
|
||||
dgst, err := h.GenAccessoryFunc(scanReq, sbomContent, h.annotations(), sbomMimeType, robot)
|
||||
if err != nil {
|
||||
myLogger.Errorf("error when create accessory from image %v", err)
|
||||
return "", err
|
||||
}
|
||||
return h.generateReport(startTime, sr.Artifact.Repository, dgst, "Success", s)
|
||||
}
|
||||
|
||||
// URLParameter defines the parameters for scan report url
|
||||
func (h *scanHandler) URLParameter(_ *v1.ScanRequest) (string, error) {
|
||||
return fmt.Sprintf("sbom_media_type=%s", url.QueryEscape(sbomMediaTypeSpdx)), nil
|
||||
}
|
||||
|
||||
// RequiredPermissions defines the permission used by the scan robot account
|
||||
func (v *scanHandler) RequiredPermissions() []*types.Policy {
|
||||
func (h *scanHandler) RequiredPermissions() []*types.Policy {
|
||||
return []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
@ -85,33 +133,8 @@ func (v *scanHandler) RequiredPermissions() []*types.Policy {
|
||||
}
|
||||
}
|
||||
|
||||
// PostScan defines task specific operations after the scan is complete
|
||||
func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) {
|
||||
sbomContent, s, err := retrieveSBOMContent(rawReport)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
scanReq := v1.ScanRequest{
|
||||
Registry: sr.Registry,
|
||||
Artifact: sr.Artifact,
|
||||
}
|
||||
// the registry server url is core by default, need to replace it with real registry server url
|
||||
scanReq.Registry.URL, scanReq.Registry.Insecure = v.RegistryServer(ctx.SystemContext())
|
||||
if len(scanReq.Registry.URL) == 0 {
|
||||
return "", fmt.Errorf("empty registry server")
|
||||
}
|
||||
myLogger := ctx.GetLogger()
|
||||
myLogger.Debugf("Pushing accessory artifact to %s/%s", scanReq.Registry.URL, scanReq.Artifact.Repository)
|
||||
dgst, err := v.GenAccessoryFunc(scanReq, sbomContent, v.annotations(), sbomMimeType, robot)
|
||||
if err != nil {
|
||||
myLogger.Errorf("error when create accessory from image %v", err)
|
||||
return "", err
|
||||
}
|
||||
return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success", s)
|
||||
}
|
||||
|
||||
// annotations defines the annotations for the accessory artifact
|
||||
func (v *scanHandler) annotations() map[string]string {
|
||||
func (h *scanHandler) annotations() map[string]string {
|
||||
t := time.Now().Format(time.RFC3339)
|
||||
return map[string]string{
|
||||
"created": t,
|
||||
@ -121,7 +144,7 @@ func (v *scanHandler) annotations() map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string, scanner *v1.Scanner) (string, error) {
|
||||
func (h *scanHandler) generateReport(startTime time.Time, repository, digest, status string, scanner *v1.Scanner) (string, error) {
|
||||
summary := sbom.Summary{}
|
||||
endTime := time.Now()
|
||||
summary[sbom.StartTime] = startTime
|
||||
@ -138,6 +161,14 @@ func (v *scanHandler) generateReport(startTime time.Time, repository, digest, st
|
||||
return string(rep), nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) Update(ctx context.Context, uuid string, report string) error {
|
||||
mgr := h.SBOMMgrFunc()
|
||||
if err := mgr.UpdateReportData(ctx, uuid, report); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extract server name from config, and remove the protocol prefix
|
||||
func registry(ctx context.Context) (string, bool) {
|
||||
cfgMgr, ok := config.FromContext(ctx)
|
||||
@ -153,7 +184,7 @@ func registry(ctx context.Context) (string, bool) {
|
||||
|
||||
// retrieveSBOMContent retrieves the "sbom" field from the raw report
|
||||
func retrieveSBOMContent(rawReport string) ([]byte, *v1.Scanner, error) {
|
||||
rpt := vuln.Report{}
|
||||
rpt := sbom.RawSBOMReport{}
|
||||
err := json.Unmarshal([]byte(rawReport), &rpt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -164,3 +195,153 @@ func retrieveSBOMContent(rawReport string) ([]byte, *v1.Scanner, error) {
|
||||
}
|
||||
return sbomContent, rpt.Scanner, nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) MakePlaceHolder(ctx context.Context, art *artifact.Artifact, r *scanner.Registration) (rps []*scanModel.Report, err error) {
|
||||
var reports []*scanModel.Report
|
||||
mgr := h.SBOMMgrFunc()
|
||||
mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType, v1.ScanTypeSbom)
|
||||
if len(mimeTypes) == 0 {
|
||||
return nil, errors.New("no mime types to make report placeholders")
|
||||
}
|
||||
sbomReports, err := mgr.GetBy(h.cloneCtx(ctx), art.ID, r.UUID, mimeTypes[0], sbomMediaTypeSpdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := h.deleteSBOMAccessories(ctx, sbomReports); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, mt := range mimeTypes {
|
||||
report := &sbom.Report{
|
||||
ArtifactID: art.ID,
|
||||
RegistrationUUID: r.UUID,
|
||||
MimeType: mt,
|
||||
MediaType: sbomMediaTypeSpdx,
|
||||
}
|
||||
|
||||
create := func(ctx context.Context) error {
|
||||
reportUUID, err := mgr.Create(ctx, report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report.UUID = reportUUID
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := orm.WithTransaction(create)(orm.SetTransactionOpNameToContext(ctx, "tx-make-report-placeholder-sbom")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reports = append(reports, &scanModel.Report{
|
||||
RegistrationUUID: r.UUID,
|
||||
MimeType: mt,
|
||||
UUID: report.UUID,
|
||||
})
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
// deleteSBOMAccessories delete the sbom accessory in reports
|
||||
func (h *scanHandler) deleteSBOMAccessories(ctx context.Context, reports []*sbom.Report) error {
|
||||
mgr := h.SBOMMgrFunc()
|
||||
for _, rpt := range reports {
|
||||
if rpt.MimeType != v1.MimeTypeSBOMReport {
|
||||
continue
|
||||
}
|
||||
if err := h.deleteSBOMAccessory(ctx, rpt.ReportSummary); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mgr.Delete(ctx, rpt.UUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteSBOMAccessory check if current report has sbom accessory info, if there is, delete it
|
||||
func (h *scanHandler) deleteSBOMAccessory(ctx context.Context, report string) error {
|
||||
if len(report) == 0 {
|
||||
return nil
|
||||
}
|
||||
sbomSummary := sbom.Summary{}
|
||||
if err := json.Unmarshal([]byte(report), &sbomSummary); err != nil {
|
||||
// it could be a non sbom report, just skip
|
||||
log.Debugf("fail to unmarshal %v, skip to delete sbom report", err)
|
||||
return nil
|
||||
}
|
||||
repo, dgst := sbomSummary.SBOMAccArt()
|
||||
if len(repo) == 0 || len(dgst) == 0 {
|
||||
return nil
|
||||
}
|
||||
artifactCtl := h.ArtifactControllerFunc()
|
||||
art, err := artifactCtl.GetByReference(ctx, repo, dgst, nil)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if art == nil {
|
||||
return nil
|
||||
}
|
||||
err = artifactCtl.Delete(ctx, art.ID)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *scanHandler) GetPlaceHolder(ctx context.Context, artRepo string, artDigest, scannerUUID string, mimeType string) (rp *scanModel.Report, err error) {
|
||||
artifactCtl := h.ArtifactControllerFunc()
|
||||
a, err := artifactCtl.GetByReference(ctx, artRepo, artDigest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgr := h.SBOMMgrFunc()
|
||||
rpts, err := mgr.GetBy(ctx, a.ID, scannerUUID, mimeType, sbomMediaTypeSpdx)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get report for artifact %s@%s of mimetype %s, error %v", artRepo, artDigest, mimeType, err)
|
||||
return nil, err
|
||||
}
|
||||
if len(rpts) == 0 {
|
||||
logger.Errorf("No report found for artifact %s@%s of mimetype %s, error %v", artRepo, artDigest, mimeType, err)
|
||||
return nil, errors.NotFoundError(nil).WithMessage("no report found to update data")
|
||||
}
|
||||
return &scanModel.Report{
|
||||
UUID: rpts[0].UUID,
|
||||
MimeType: rpts[0].MimeType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) GetSummary(ctx context.Context, art *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
if len(mimeTypes) == 0 {
|
||||
return nil, errors.New("no mime types to get report summaries")
|
||||
}
|
||||
if art == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
ds := h.ScannerControllerFunc()
|
||||
r, err := ds.GetRegistrationByProject(ctx, art.ProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get sbom summary failed")
|
||||
}
|
||||
reports, err := h.SBOMMgrFunc().GetBy(ctx, art.ID, r.UUID, mimeTypes[0], sbomMediaTypeSpdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(reports) == 0 {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
reportContent := reports[0].ReportSummary
|
||||
result := map[string]interface{}{}
|
||||
if len(reportContent) == 0 {
|
||||
status := h.TaskMgrFunc().RetrieveStatusFromTask(ctx, reports[0].UUID)
|
||||
if len(status) > 0 {
|
||||
result[sbom.ReportID] = reports[0].UUID
|
||||
result[sbom.ScanStatus] = status
|
||||
}
|
||||
log.Debug("no content for current report")
|
||||
return result, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(reportContent), &result)
|
||||
return result, err
|
||||
}
|
||||
|
@ -6,15 +6,42 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sc "github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
art "github.com/goharbor/harbor/src/pkg/artifact"
|
||||
sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
artifactTest "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
scanTest "github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
scannerTest "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/testing/jobservice"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
sbomTest "github.com/goharbor/harbor/src/testing/pkg/scan/sbom"
|
||||
taskTest "github.com/goharbor/harbor/src/testing/pkg/task"
|
||||
)
|
||||
|
||||
var registeredScanner = &scanner.Registration{
|
||||
UUID: "uuid",
|
||||
Metadata: &v1.ScannerAdapterMetadata{
|
||||
Capabilities: []*v1.ScannerCapability{
|
||||
{Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeGenericVulnerabilityReport}},
|
||||
{Type: v1.ScanTypeSbom, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeSBOMReport}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func Test_scanHandler_ReportURLParameter(t *testing.T) {
|
||||
type args struct {
|
||||
in0 *v1.ScanRequest
|
||||
@ -30,13 +57,13 @@ func Test_scanHandler_ReportURLParameter(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &scanHandler{}
|
||||
got, err := v.ReportURLParameter(tt.args.in0)
|
||||
got, err := v.URLParameter(tt.args.in0)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReportURLParameter() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("URLParameter() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ReportURLParameter() got = %v, want %v", got, tt.want)
|
||||
t.Errorf("URLParameter() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -97,22 +124,53 @@ func mockGenAccessory(scanRep v1.ScanRequest, sbomContent []byte, labels map[str
|
||||
return "sha256:1234567890", nil
|
||||
}
|
||||
|
||||
type ExampleTestSuite struct {
|
||||
handler *scanHandler
|
||||
suite.Suite
|
||||
type SBOMTestSuite struct {
|
||||
htesting.Suite
|
||||
handler *scanHandler
|
||||
sbomManager *sbomTest.Manager
|
||||
taskMgr *taskTest.Manager
|
||||
artifactCtl *artifactTest.Controller
|
||||
artifact *artifact.Artifact
|
||||
wrongArtifact *artifact.Artifact
|
||||
scanController *scanTest.Controller
|
||||
scannerController *scannerTest.Controller
|
||||
}
|
||||
|
||||
func (suite *ExampleTestSuite) SetupSuite() {
|
||||
func (suite *SBOMTestSuite) SetupSuite() {
|
||||
suite.sbomManager = &sbomTest.Manager{}
|
||||
suite.taskMgr = &taskTest.Manager{}
|
||||
suite.artifactCtl = &artifactTest.Controller{}
|
||||
suite.scannerController = &scannerTest.Controller{}
|
||||
suite.scanController = &scanTest.Controller{}
|
||||
|
||||
suite.handler = &scanHandler{
|
||||
GenAccessoryFunc: mockGenAccessory,
|
||||
RegistryServer: mockGetRegistry,
|
||||
GenAccessoryFunc: mockGenAccessory,
|
||||
RegistryServer: mockGetRegistry,
|
||||
SBOMMgrFunc: func() Manager { return suite.sbomManager },
|
||||
TaskMgrFunc: func() task.Manager { return suite.taskMgr },
|
||||
ArtifactControllerFunc: func() artifact.Controller { return suite.artifactCtl },
|
||||
ScanControllerFunc: func() sc.Controller { return suite.scanController },
|
||||
ScannerControllerFunc: func() scanner.Controller { return suite.scannerController },
|
||||
cloneCtx: func(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
suite.artifact = &artifact.Artifact{Artifact: art.Artifact{ID: 1}}
|
||||
suite.artifact.Type = "IMAGE"
|
||||
suite.artifact.ProjectID = 1
|
||||
suite.artifact.RepositoryName = "library/photon"
|
||||
suite.artifact.Digest = "digest-code"
|
||||
suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact
|
||||
|
||||
suite.wrongArtifact = &artifact.Artifact{Artifact: art.Artifact{ID: 2, ProjectID: 1}}
|
||||
suite.wrongArtifact.Digest = "digest-wrong"
|
||||
}
|
||||
|
||||
func (suite *ExampleTestSuite) TearDownSuite() {
|
||||
func (suite *SBOMTestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
func (suite *ExampleTestSuite) TestPostScan() {
|
||||
func (suite *SBOMTestSuite) TestPostScan() {
|
||||
req := &v1.ScanRequest{
|
||||
Registry: &v1.Registry{
|
||||
URL: "myregistry.example.com",
|
||||
@ -134,6 +192,62 @@ func (suite *ExampleTestSuite) TestPostScan() {
|
||||
suite.Require().NotEmpty(accessory)
|
||||
}
|
||||
|
||||
func TestExampleTestSuite(t *testing.T) {
|
||||
suite.Run(t, &ExampleTestSuite{})
|
||||
func (suite *SBOMTestSuite) TestMakeReportPlaceHolder() {
|
||||
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
|
||||
art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, Digest: "digest", ManifestMediaType: v1.MimeTypeDockerArtifact}}
|
||||
r := &scanner.Registration{
|
||||
UUID: "uuid",
|
||||
Metadata: &v1.ScannerAdapterMetadata{
|
||||
Capabilities: []*v1.ScannerCapability{
|
||||
{Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeGenericVulnerabilityReport}},
|
||||
{Type: v1.ScanTypeSbom, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeSBOMReport}},
|
||||
},
|
||||
},
|
||||
}
|
||||
mock.OnAnything(suite.sbomManager, "GetBy").Return([]*sbomModel.Report{{UUID: "uuid"}}, nil).Once()
|
||||
mock.OnAnything(suite.sbomManager, "Create").Return("uuid", nil).Once()
|
||||
mock.OnAnything(suite.sbomManager, "Delete").Return(nil).Once()
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return([]*task.Task{{Status: "Success"}}, nil)
|
||||
rps, err := suite.handler.MakePlaceHolder(ctx, art, r)
|
||||
require.NoError(suite.T(), err)
|
||||
suite.Equal(1, len(rps))
|
||||
}
|
||||
|
||||
func (suite *SBOMTestSuite) TestGetSBOMSummary() {
|
||||
r := registeredScanner
|
||||
rpts := []*sbomModel.Report{
|
||||
{UUID: "rp-uuid-004", MimeType: v1.MimeTypeSBOMReport, ReportSummary: `{"scan_status":"Success", "sbom_digest": "sha256:1234567890"}`},
|
||||
}
|
||||
mock.OnAnything(suite.scannerController, "GetRegistrationByProject").Return(r, nil)
|
||||
mock.OnAnything(suite.sbomManager, "GetBy").Return(rpts, nil)
|
||||
sum, err := suite.handler.GetSummary(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)
|
||||
tasks := []*task.Task{{Status: "Error"}}
|
||||
suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once()
|
||||
sum2, err := suite.handler.GetSummary(context.TODO(), suite.wrongArtifact, []string{v1.MimeTypeSBOMReport})
|
||||
suite.Nil(err)
|
||||
suite.NotNil(sum2)
|
||||
|
||||
}
|
||||
|
||||
func (suite *SBOMTestSuite) TestGetReportPlaceHolder() {
|
||||
mock.OnAnything(suite.sbomManager, "GetBy").Return([]*sbomModel.Report{{UUID: "uuid"}}, nil).Once()
|
||||
mock.OnAnything(suite.artifactCtl, "GetByReference").Return(suite.artifact, nil).Twice()
|
||||
rp, err := suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
|
||||
require.NoError(suite.T(), err)
|
||||
suite.Equal("uuid", rp.UUID)
|
||||
mock.OnAnything(suite.sbomManager, "GetBy").Return(nil, nil).Once()
|
||||
rp, err = suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
|
||||
require.Error(suite.T(), err)
|
||||
}
|
||||
|
||||
func TestExampleTestSuite(t *testing.T) {
|
||||
suite.Run(t, &SBOMTestSuite{})
|
||||
}
|
||||
|
@ -15,38 +15,227 @@
|
||||
package vulnerability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
scanCtl "github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
scanJob "github.com/goharbor/harbor/src/pkg/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/postprocessors"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
)
|
||||
|
||||
func init() {
|
||||
scanJob.RegisterScanHanlder(v1.ScanTypeVulnerability, &ScanHandler{})
|
||||
scanJob.RegisterScanHanlder(v1.ScanTypeVulnerability, &scanHandler{
|
||||
reportConverter: postprocessors.Converter,
|
||||
ReportMgrFunc: func() report.Manager { return report.Mgr },
|
||||
TaskMgrFunc: func() task.Manager { return task.Mgr },
|
||||
ScanControllerFunc: func() scanCtl.Controller { return scanCtl.DefaultController },
|
||||
cloneCtx: orm.Clone,
|
||||
})
|
||||
}
|
||||
|
||||
// ScanHandler defines the handler for scan vulnerability
|
||||
type ScanHandler struct {
|
||||
// scanHandler defines the handler for scan vulnerability
|
||||
type scanHandler struct {
|
||||
reportConverter postprocessors.NativeScanReportConverter
|
||||
ReportMgrFunc func() report.Manager
|
||||
TaskMgrFunc func() task.Manager
|
||||
ScanControllerFunc func() scanCtl.Controller
|
||||
cloneCtx func(ctx context.Context) context.Context
|
||||
}
|
||||
|
||||
func (h *scanHandler) MakePlaceHolder(ctx context.Context, art *artifact.Artifact,
|
||||
r *scanner.Registration) (rps []*scan.Report, err error) {
|
||||
mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType, v1.ScanTypeVulnerability)
|
||||
reportMgr := h.ReportMgrFunc()
|
||||
oldReports, err := reportMgr.GetBy(h.cloneCtx(ctx), art.Digest, r.UUID, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := h.assembleReports(ctx, oldReports...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(oldReports) > 0 {
|
||||
for _, oldReport := range oldReports {
|
||||
if !job.Status(oldReport.Status).Final() {
|
||||
return nil, errors.ConflictError(nil).WithMessage("a previous scan process is %s", oldReport.Status)
|
||||
}
|
||||
}
|
||||
|
||||
for _, oldReport := range oldReports {
|
||||
if err := reportMgr.Delete(ctx, oldReport.UUID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var reports []*scan.Report
|
||||
|
||||
for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType, v1.ScanTypeVulnerability) {
|
||||
rpt := &scan.Report{
|
||||
Digest: art.Digest,
|
||||
RegistrationUUID: r.UUID,
|
||||
MimeType: pm,
|
||||
}
|
||||
|
||||
create := func(ctx context.Context) error {
|
||||
reportUUID, err := reportMgr.Create(ctx, rpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rpt.UUID = reportUUID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := orm.WithTransaction(create)(orm.SetTransactionOpNameToContext(ctx, "tx-make-report-placeholder")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reports = append(reports, rpt)
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) assembleReports(ctx context.Context, reports ...*scan.Report) error {
|
||||
reportUUIDs := make([]string, len(reports))
|
||||
for i, report := range reports {
|
||||
reportUUIDs[i] = report.UUID
|
||||
}
|
||||
|
||||
tasks, err := h.listScanTasks(ctx, reportUUIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reportUUIDToTasks := map[string]*task.Task{}
|
||||
for _, task := range tasks {
|
||||
for _, reportUUID := range scanCtl.GetReportUUIDs(task.ExtraAttrs) {
|
||||
reportUUIDToTasks[reportUUID] = task
|
||||
}
|
||||
}
|
||||
|
||||
for _, report := range reports {
|
||||
if task, ok := reportUUIDToTasks[report.UUID]; ok {
|
||||
report.Status = task.Status
|
||||
report.StartTime = task.StartTime
|
||||
report.EndTime = task.EndTime
|
||||
} else {
|
||||
report.Status = job.ErrorStatus.String()
|
||||
}
|
||||
|
||||
completeReport, err := h.reportConverter.FromRelationalSchema(ctx, report.UUID, report.Digest, report.Report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report.Report = completeReport
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listScanTasks returns the tasks of the reports
|
||||
func (h *scanHandler) listScanTasks(ctx context.Context, reportUUIDs []string) ([]*task.Task, error) {
|
||||
if len(reportUUIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tasks := make([]*task.Task, len(reportUUIDs))
|
||||
errs := make([]error, len(reportUUIDs))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i, reportUUID := range reportUUIDs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(ix int, reportUUID string) {
|
||||
defer wg.Done()
|
||||
|
||||
task, err := h.getScanTask(h.cloneCtx(ctx), reportUUID)
|
||||
if err == nil {
|
||||
tasks[ix] = task
|
||||
} else if !errors.IsNotFoundErr(err) {
|
||||
errs[ix] = err
|
||||
} else {
|
||||
log.G(ctx).Warningf("task for the scan report %s not found", reportUUID)
|
||||
}
|
||||
}(i, reportUUID)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var results []*task.Task
|
||||
for _, task := range tasks {
|
||||
if task != nil {
|
||||
results = append(results, task)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) getScanTask(ctx context.Context, reportUUID string) (*task.Task, error) {
|
||||
// NOTE: the method uses the postgres' unique operations and should consider here if support other database in the future.
|
||||
taskMgr := h.TaskMgrFunc()
|
||||
tasks, err := taskMgr.ListScanTasksByReportUUID(ctx, reportUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
return nil, errors.NotFoundError(nil).WithMessage("task for report %s not found", reportUUID)
|
||||
}
|
||||
|
||||
return tasks[0], nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) GetPlaceHolder(ctx context.Context, _ string, artDigest, scannerUUID string,
|
||||
mimeType string) (rp *scan.Report, err error) {
|
||||
reportMgr := h.ReportMgrFunc()
|
||||
reports, err := reportMgr.GetBy(ctx, artDigest, scannerUUID, []string{mimeType})
|
||||
if err != nil {
|
||||
logger.Errorf("failed to get report for artifact %s of mimetype %s, error %v", artDigest, mimeType, err)
|
||||
return nil, err
|
||||
}
|
||||
if len(reports) == 0 {
|
||||
logger.Errorf("no report found for artifact %s of mimetype %s, error %v", artDigest, mimeType, err)
|
||||
return nil, errors.NotFoundError(nil).WithMessage("no report found to update data")
|
||||
}
|
||||
return reports[0], nil
|
||||
}
|
||||
|
||||
// RequestProducesMineTypes returns the produces mime types
|
||||
func (v *ScanHandler) RequestProducesMineTypes() []string {
|
||||
func (h *scanHandler) RequestProducesMineTypes() []string {
|
||||
return []string{v1.MimeTypeGenericVulnerabilityReport}
|
||||
}
|
||||
|
||||
// RequestParameters defines the parameters for scan request
|
||||
func (v *ScanHandler) RequestParameters() map[string]interface{} {
|
||||
func (h *scanHandler) RequestParameters() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequiredPermissions defines the permission used by the scan robot account
|
||||
func (v *ScanHandler) RequiredPermissions() []*types.Policy {
|
||||
func (h *scanHandler) RequiredPermissions() []*types.Policy {
|
||||
return []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
@ -59,14 +248,56 @@ func (v *ScanHandler) RequiredPermissions() []*types.Policy {
|
||||
}
|
||||
}
|
||||
|
||||
// ReportURLParameter vulnerability doesn't require any scan report parameters
|
||||
func (v *ScanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) {
|
||||
// PostScan ...
|
||||
func (h *scanHandler) PostScan(ctx job.Context, _ *v1.ScanRequest, origRp *scan.Report, rawReport string,
|
||||
_ time.Time, _ *model.Robot) (string, error) {
|
||||
// use a new ormer here to use the short db connection
|
||||
_, refreshedReport, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), origRp.UUID,
|
||||
origRp.RegistrationUUID, origRp.Digest, rawReport)
|
||||
return refreshedReport, err
|
||||
}
|
||||
|
||||
// URLParameter vulnerability doesn't require any scan report parameters
|
||||
func (h *scanHandler) URLParameter(_ *v1.ScanRequest) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// PostScan ...
|
||||
func (v *ScanHandler) PostScan(ctx job.Context, _ *v1.ScanRequest, origRp *scan.Report, rawReport string, _ time.Time, _ *model.Robot) (string, error) {
|
||||
// use a new ormer here to use the short db connection
|
||||
_, refreshedReport, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), origRp.UUID, origRp.RegistrationUUID, origRp.Digest, rawReport)
|
||||
return refreshedReport, err
|
||||
func (h *scanHandler) Update(ctx context.Context, uuid string, rpt string) error {
|
||||
reportMgr := h.ReportMgrFunc()
|
||||
if err := reportMgr.UpdateReportData(ctx, uuid, rpt); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *scanHandler) GetSummary(ctx context.Context, ar *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
bc := h.ScanControllerFunc()
|
||||
if ar == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
// Get reports first
|
||||
rps, err := bc.GetReport(ctx, ar, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summaries := make(map[string]interface{}, len(rps))
|
||||
for _, rp := range rps {
|
||||
sum, err := report.GenerateSummary(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s, ok := summaries[rp.MimeType]; ok {
|
||||
r, err := report.MergeSummary(rp.MimeType, s, sum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries[rp.MimeType] = r
|
||||
} else {
|
||||
summaries[rp.MimeType] = sum
|
||||
}
|
||||
}
|
||||
|
||||
return summaries, nil
|
||||
}
|
||||
|
@ -1,25 +1,44 @@
|
||||
package vulnerability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
scanCtl "github.com/goharbor/harbor/src/controller/scan"
|
||||
art "github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
scanCtlTest "github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory"
|
||||
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
|
||||
tasktesting "github.com/goharbor/harbor/src/testing/pkg/task"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
accessoryModel "github.com/goharbor/harbor/src/pkg/accessory/model"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/postprocessors"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/testing/jobservice"
|
||||
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
|
||||
postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors"
|
||||
)
|
||||
|
||||
func TestRequiredPermissions(t *testing.T) {
|
||||
v := &ScanHandler{}
|
||||
v := &scanHandler{}
|
||||
expected := []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
@ -37,7 +56,7 @@ func TestRequiredPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPostScan(t *testing.T) {
|
||||
v := &ScanHandler{}
|
||||
v := &scanHandler{}
|
||||
ctx := &jobservice.MockJobContext{}
|
||||
artifact := &v1.Artifact{}
|
||||
origRp := &scan.Report{}
|
||||
@ -70,7 +89,7 @@ func TestScanHandler_RequiredPermissions(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &ScanHandler{}
|
||||
v := &scanHandler{}
|
||||
assert.Equalf(t, tt.want, v.RequiredPermissions(), "RequiredPermissions()")
|
||||
})
|
||||
}
|
||||
@ -90,12 +109,12 @@ func TestScanHandler_ReportURLParameter(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &ScanHandler{}
|
||||
got, err := v.ReportURLParameter(tt.args.in0)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("ReportURLParameter(%v)", tt.args.in0)) {
|
||||
v := &scanHandler{}
|
||||
got, err := v.URLParameter(tt.args.in0)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("URLParameter(%v)", tt.args.in0)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "ReportURLParameter(%v)", tt.args.in0)
|
||||
assert.Equalf(t, tt.want, got, "URLParameter(%v)", tt.args.in0)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -109,8 +128,98 @@ func TestScanHandler_RequestProducesMineTypes(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &ScanHandler{}
|
||||
v := &scanHandler{}
|
||||
assert.Equalf(t, tt.want, v.RequestProducesMineTypes(), "RequestProducesMineTypes()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type VulHandlerTestSuite struct {
|
||||
htesting.Suite
|
||||
ar *artifacttesting.Controller
|
||||
accessoryMgr *accessorytesting.Manager
|
||||
artifact *artifact.Artifact
|
||||
taskMgr *tasktesting.Manager
|
||||
reportMgr *reporttesting.Manager
|
||||
scanController *scanCtlTest.Controller
|
||||
handler *scanHandler
|
||||
}
|
||||
|
||||
func (suite *VulHandlerTestSuite) SetupSuite() {
|
||||
suite.ar = &artifacttesting.Controller{}
|
||||
suite.accessoryMgr = &accessorytesting.Manager{}
|
||||
suite.taskMgr = &tasktesting.Manager{}
|
||||
suite.scanController = &scanCtlTest.Controller{}
|
||||
suite.reportMgr = &reporttesting.Manager{}
|
||||
suite.artifact = &artifact.Artifact{Artifact: art.Artifact{ID: 1}}
|
||||
suite.artifact.Type = "IMAGE"
|
||||
suite.artifact.ProjectID = 1
|
||||
suite.artifact.RepositoryName = "library/photon"
|
||||
suite.artifact.Digest = "digest-code"
|
||||
suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact
|
||||
suite.handler = &scanHandler{
|
||||
reportConverter: postprocessors.Converter,
|
||||
ReportMgrFunc: func() report.Manager { return suite.reportMgr },
|
||||
TaskMgrFunc: func() task.Manager { return suite.taskMgr },
|
||||
ScanControllerFunc: func() scanCtl.Controller { return suite.scanController },
|
||||
cloneCtx: func(ctx context.Context) context.Context { return ctx },
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (suite *VulHandlerTestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
func TestExampleTestSuite(t *testing.T) {
|
||||
suite.Run(t, &VulHandlerTestSuite{})
|
||||
}
|
||||
|
||||
// TestScanControllerGetSummary ...
|
||||
func (suite *VulHandlerTestSuite) TestScanControllerGetSummary() {
|
||||
rpts := []*scan.Report{
|
||||
{UUID: "uuid", MimeType: v1.MimeTypeGenericVulnerabilityReport},
|
||||
}
|
||||
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
|
||||
mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once()
|
||||
mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once()
|
||||
mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {
|
||||
walkFn := args.Get(2).(func(*artifact.Artifact) error)
|
||||
walkFn(suite.artifact)
|
||||
}).Once()
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return(nil, nil).Once()
|
||||
mock.OnAnything(suite.scanController, "GetReport").Return(rpts, nil).Once()
|
||||
sum, err := suite.handler.GetSummary(ctx, suite.artifact, []string{v1.MimeTypeGenericVulnerabilityReport})
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(sum))
|
||||
}
|
||||
|
||||
func (suite *VulHandlerTestSuite) TestMakeReportPlaceHolder() {
|
||||
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
|
||||
art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, Digest: "digest", ManifestMediaType: v1.MimeTypeDockerArtifact}}
|
||||
r := &scanner.Registration{
|
||||
UUID: "uuid",
|
||||
Metadata: &v1.ScannerAdapterMetadata{
|
||||
Capabilities: []*v1.ScannerCapability{
|
||||
{Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeGenericVulnerabilityReport}},
|
||||
},
|
||||
},
|
||||
}
|
||||
// mimeTypes := []string{v1.MimeTypeGenericVulnerabilityReport}
|
||||
mock.OnAnything(suite.reportMgr, "GetBy").Return([]*scan.Report{{UUID: "uuid"}}, nil).Once()
|
||||
mock.OnAnything(suite.reportMgr, "Create").Return("uuid", nil).Once()
|
||||
mock.OnAnything(suite.reportMgr, "Delete").Return(nil).Once()
|
||||
mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return([]*task.Task{{Status: "Success"}}, nil)
|
||||
rps, err := suite.handler.MakePlaceHolder(ctx, art, r)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(rps))
|
||||
}
|
||||
|
||||
func (suite *VulHandlerTestSuite) TestGetReportPlaceHolder() {
|
||||
mock.OnAnything(suite.reportMgr, "GetBy").Return([]*scan.Report{{UUID: "uuid"}}, nil).Once()
|
||||
rp, err := suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), "uuid", rp.UUID)
|
||||
mock.OnAnything(suite.reportMgr, "GetBy").Return(nil, fmt.Errorf("not found")).Once()
|
||||
rp, err = suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
|
||||
require.Error(suite.T(), err)
|
||||
}
|
||||
|
@ -257,6 +257,24 @@ func (_m *mockTaskManager) ListScanTasksByReportUUID(ctx context.Context, uuid s
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RetrieveStatusFromTask provides a mock function with given fields: ctx, reportID
|
||||
func (_m *mockTaskManager) RetrieveStatusFromTask(ctx context.Context, reportID string) string {
|
||||
ret := _m.Called(ctx, reportID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveStatusFromTask")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) string); ok {
|
||||
r0 = rf(ctx, reportID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields: ctx, id
|
||||
func (_m *mockTaskManager) Stop(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
@ -67,6 +67,8 @@ type Manager interface {
|
||||
// ListScanTasksByReportUUID lists scan tasks by report uuid, although it's a specific case but it will be
|
||||
// more suitable to support multi database in the future.
|
||||
ListScanTasksByReportUUID(ctx context.Context, uuid string) (tasks []*Task, err error)
|
||||
// RetrieveStatusFromTask retrieve status from task
|
||||
RetrieveStatusFromTask(ctx context.Context, reportID string) string
|
||||
}
|
||||
|
||||
// NewManager creates an instance of the default task manager
|
||||
@ -282,3 +284,18 @@ func (m *manager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType,
|
||||
func (m *manager) GetLogByJobID(_ context.Context, jobID string) (log []byte, err error) {
|
||||
return m.jsClient.GetJobLog(jobID)
|
||||
}
|
||||
|
||||
func (m *manager) RetrieveStatusFromTask(ctx context.Context, reportID string) string {
|
||||
if len(reportID) == 0 {
|
||||
return ""
|
||||
}
|
||||
tasks, err := m.dao.ListScanTasksByReportUUID(ctx, reportID)
|
||||
if err != nil {
|
||||
log.Warningf("can not find the task with report UUID %v, error %v", reportID, err)
|
||||
return ""
|
||||
}
|
||||
if len(tasks) > 0 {
|
||||
return tasks[0].Status
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -160,6 +160,17 @@ func (t *taskManagerTestSuite) TestListScanTasksByReportUUID() {
|
||||
t.dao.AssertExpectations(t.T())
|
||||
}
|
||||
|
||||
func (t *taskManagerTestSuite) TestRetrieveStatusFromTask() {
|
||||
t.dao.On("ListScanTasksByReportUUID", mock.Anything, mock.Anything).Return([]*dao.Task{
|
||||
{
|
||||
ID: 1,
|
||||
Status: "Success",
|
||||
},
|
||||
}, nil)
|
||||
status := t.mgr.RetrieveStatusFromTask(nil, "uuid")
|
||||
t.Equal("Success", status)
|
||||
}
|
||||
|
||||
func TestTaskManagerTestSuite(t *testing.T) {
|
||||
suite.Run(t, &taskManagerTestSuite{})
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
|
||||
const (
|
||||
vulnerabilitiesAddition = "vulnerabilities"
|
||||
sbomAddition = "sbom"
|
||||
)
|
||||
|
||||
// NewScanReportAssembler returns vul assembler
|
||||
@ -80,7 +79,7 @@ func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error {
|
||||
|
||||
if assembler.overviewOption.WithVuln {
|
||||
for _, mimeType := range assembler.mimeTypes {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType})
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, v1.ScanTypeVulnerability, []string{mimeType})
|
||||
if err != nil {
|
||||
log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, mimeType, err)
|
||||
} else if len(overview) > 0 {
|
||||
@ -93,13 +92,17 @@ func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error {
|
||||
// set sbom additional link if it is supported, use the empty digest
|
||||
artifact.SetSBOMAdditionLink("", version)
|
||||
if assembler.overviewOption.WithSBOM {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport})
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, v1.ScanTypeSbom, []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)
|
||||
}
|
||||
if len(overview) == 0 {
|
||||
log.Warningf("overview is empty, retrieve sbom status from execution")
|
||||
query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest, "extra_attrs.enabled_capabilities.type": "sbom"})
|
||||
// Get latest execution with digest, repository, and scan type is sbom, the status is the scan status
|
||||
query := q.New(
|
||||
q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest,
|
||||
"extra_attrs.artifact.repository_name": artifact.RepositoryName,
|
||||
"extra_attrs.enabled_capabilities.type": "sbom"})
|
||||
// sort by ID desc to get the latest execution
|
||||
query.Sorts = []*q.Sort{q.NewSort("ID", true)}
|
||||
execs, err := assembler.executionMgr.List(ctx, query)
|
||||
|
@ -7,13 +7,13 @@ import (
|
||||
|
||||
artifact "github.com/goharbor/harbor/src/controller/artifact"
|
||||
|
||||
daoscan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
controllerscan "github.com/goharbor/harbor/src/controller/scan"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
|
||||
scan "github.com/goharbor/harbor/src/controller/scan"
|
||||
scan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
)
|
||||
|
||||
// Controller is an autogenerated mock type for the Controller type
|
||||
@ -21,49 +21,24 @@ type Controller struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// DeleteReports provides a mock function with given fields: ctx, digests
|
||||
func (_m *Controller) DeleteReports(ctx context.Context, digests ...string) error {
|
||||
_va := make([]interface{}, len(digests))
|
||||
for _i := range digests {
|
||||
_va[_i] = digests[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteReports")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok {
|
||||
r0 = rf(ctx, digests...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetReport provides a mock function with given fields: ctx, _a1, mimeTypes
|
||||
func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) ([]*daoscan.Report, error) {
|
||||
func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) ([]*scan.Report, error) {
|
||||
ret := _m.Called(ctx, _a1, mimeTypes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetReport")
|
||||
}
|
||||
|
||||
var r0 []*daoscan.Report
|
||||
var r0 []*scan.Report
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) ([]*daoscan.Report, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) ([]*scan.Report, error)); ok {
|
||||
return rf(ctx, _a1, mimeTypes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) []*daoscan.Report); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) []*scan.Report); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*daoscan.Report)
|
||||
r0 = ret.Get(0).([]*scan.Report)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,9 +81,9 @@ func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uu
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes
|
||||
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
ret := _m.Called(ctx, _a1, mimeTypes)
|
||||
// GetSummary provides a mock function with given fields: ctx, _a1, scanType, mimeTypes
|
||||
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, scanType string, mimeTypes []string) (map[string]interface{}, error) {
|
||||
ret := _m.Called(ctx, _a1, scanType, mimeTypes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSummary")
|
||||
@ -116,19 +91,19 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi
|
||||
|
||||
var r0 map[string]interface{}
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) (map[string]interface{}, error)); ok {
|
||||
return rf(ctx, _a1, mimeTypes)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string, []string) (map[string]interface{}, error)); ok {
|
||||
return rf(ctx, _a1, scanType, mimeTypes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string, []string) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, _a1, scanType, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string) error); ok {
|
||||
r1 = rf(ctx, _a1, mimeTypes)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, string, []string) error); ok {
|
||||
r1 = rf(ctx, _a1, scanType, mimeTypes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -137,23 +112,23 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi
|
||||
}
|
||||
|
||||
// GetVulnerable provides a mock function with given fields: ctx, _a1, allowlist, allowlistIsExpired
|
||||
func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact, allowlist models.CVESet, allowlistIsExpired bool) (*scan.Vulnerable, error) {
|
||||
func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact, allowlist models.CVESet, allowlistIsExpired bool) (*controllerscan.Vulnerable, error) {
|
||||
ret := _m.Called(ctx, _a1, allowlist, allowlistIsExpired)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetVulnerable")
|
||||
}
|
||||
|
||||
var r0 *scan.Vulnerable
|
||||
var r0 *controllerscan.Vulnerable
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) (*scan.Vulnerable, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) (*controllerscan.Vulnerable, error)); ok {
|
||||
return rf(ctx, _a1, allowlist, allowlistIsExpired)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) *scan.Vulnerable); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) *controllerscan.Vulnerable); ok {
|
||||
r0 = rf(ctx, _a1, allowlist, allowlistIsExpired)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scan.Vulnerable)
|
||||
r0 = ret.Get(0).(*controllerscan.Vulnerable)
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +142,7 @@ func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact,
|
||||
}
|
||||
|
||||
// Scan provides a mock function with given fields: ctx, _a1, options
|
||||
func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options ...scan.Option) error {
|
||||
func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options ...controllerscan.Option) error {
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
@ -182,7 +157,7 @@ func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...scan.Option) error); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...controllerscan.Option) error); ok {
|
||||
r0 = rf(ctx, _a1, options...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
|
@ -74,3 +74,5 @@ package pkg
|
||||
//go:generate mockery --case snake --dir ../../pkg/jobmonitor --name RedisClient --output ./jobmonitor --outpkg jobmonitor
|
||||
//go:generate mockery --case snake --dir ../../pkg/queuestatus --name Manager --output ./queuestatus --outpkg queuestatus
|
||||
//go:generate mockery --case snake --dir ../../pkg/securityhub --name Manager --output ./securityhub --outpkg securityhub
|
||||
//go:generate mockery --case snake --dir ../../pkg/scan/sbom --name Manager --output ./scan/sbom --outpkg sbom
|
||||
//go:generate mockery --case snake --dir ../../pkg/scan --name Handler --output ./scan --outpkg scan
|
||||
|
268
src/testing/pkg/scan/handler.go
Normal file
268
src/testing/pkg/scan/handler.go
Normal file
@ -0,0 +1,268 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
artifact "github.com/goharbor/harbor/src/controller/artifact"
|
||||
|
||||
job "github.com/goharbor/harbor/src/jobservice/job"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
|
||||
scan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
|
||||
scanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
|
||||
time "time"
|
||||
|
||||
types "github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
// Handler is an autogenerated mock type for the Handler type
|
||||
type Handler struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetPlaceHolder provides a mock function with given fields: ctx, artRepo, artDigest, scannerUUID, mimeType
|
||||
func (_m *Handler) GetPlaceHolder(ctx context.Context, artRepo string, artDigest string, scannerUUID string, mimeType string) (*scan.Report, error) {
|
||||
ret := _m.Called(ctx, artRepo, artDigest, scannerUUID, mimeType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPlaceHolder")
|
||||
}
|
||||
|
||||
var r0 *scan.Report
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (*scan.Report, error)); ok {
|
||||
return rf(ctx, artRepo, artDigest, scannerUUID, mimeType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) *scan.Report); ok {
|
||||
r0 = rf(ctx, artRepo, artDigest, scannerUUID, mimeType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scan.Report)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
|
||||
r1 = rf(ctx, artRepo, artDigest, scannerUUID, mimeType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSummary provides a mock function with given fields: ctx, ar, mimeTypes
|
||||
func (_m *Handler) GetSummary(ctx context.Context, ar *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
ret := _m.Called(ctx, ar, mimeTypes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSummary")
|
||||
}
|
||||
|
||||
var r0 map[string]interface{}
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) (map[string]interface{}, error)); ok {
|
||||
return rf(ctx, ar, mimeTypes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, ar, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string) error); ok {
|
||||
r1 = rf(ctx, ar, mimeTypes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MakePlaceHolder provides a mock function with given fields: ctx, art, r
|
||||
func (_m *Handler) MakePlaceHolder(ctx context.Context, art *artifact.Artifact, r *scanner.Registration) ([]*scan.Report, error) {
|
||||
ret := _m.Called(ctx, art, r)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MakePlaceHolder")
|
||||
}
|
||||
|
||||
var r0 []*scan.Report
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, *scanner.Registration) ([]*scan.Report, error)); ok {
|
||||
return rf(ctx, art, r)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, *scanner.Registration) []*scan.Report); ok {
|
||||
r0 = rf(ctx, art, r)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*scan.Report)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, *scanner.Registration) error); ok {
|
||||
r1 = rf(ctx, art, r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// PostScan provides a mock function with given fields: ctx, sr, rp, rawReport, startTime, robot
|
||||
func (_m *Handler) PostScan(ctx job.Context, sr *v1.ScanRequest, rp *scan.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) {
|
||||
ret := _m.Called(ctx, sr, rp, rawReport, startTime, robot)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PostScan")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(job.Context, *v1.ScanRequest, *scan.Report, string, time.Time, *model.Robot) (string, error)); ok {
|
||||
return rf(ctx, sr, rp, rawReport, startTime, robot)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(job.Context, *v1.ScanRequest, *scan.Report, string, time.Time, *model.Robot) string); ok {
|
||||
r0 = rf(ctx, sr, rp, rawReport, startTime, robot)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(job.Context, *v1.ScanRequest, *scan.Report, string, time.Time, *model.Robot) error); ok {
|
||||
r1 = rf(ctx, sr, rp, rawReport, startTime, robot)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RequestParameters provides a mock function with given fields:
|
||||
func (_m *Handler) RequestParameters() map[string]interface{} {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RequestParameters")
|
||||
}
|
||||
|
||||
var r0 map[string]interface{}
|
||||
if rf, ok := ret.Get(0).(func() map[string]interface{}); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RequestProducesMineTypes provides a mock function with given fields:
|
||||
func (_m *Handler) RequestProducesMineTypes() []string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RequestProducesMineTypes")
|
||||
}
|
||||
|
||||
var r0 []string
|
||||
if rf, ok := ret.Get(0).(func() []string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RequiredPermissions provides a mock function with given fields:
|
||||
func (_m *Handler) RequiredPermissions() []*types.Policy {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RequiredPermissions")
|
||||
}
|
||||
|
||||
var r0 []*types.Policy
|
||||
if rf, ok := ret.Get(0).(func() []*types.Policy); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*types.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// URLParameter provides a mock function with given fields: sr
|
||||
func (_m *Handler) URLParameter(sr *v1.ScanRequest) (string, error) {
|
||||
ret := _m.Called(sr)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for URLParameter")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*v1.ScanRequest) (string, error)); ok {
|
||||
return rf(sr)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*v1.ScanRequest) string); ok {
|
||||
r0 = rf(sr)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*v1.ScanRequest) error); ok {
|
||||
r1 = rf(sr)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, uuid, report
|
||||
func (_m *Handler) Update(ctx context.Context, uuid string, report string) error {
|
||||
ret := _m.Called(ctx, uuid, report)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||
r0 = rf(ctx, uuid, report)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewHandler creates a new instance of Handler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewHandler(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Handler {
|
||||
mock := &Handler{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
216
src/testing/pkg/scan/sbom/manager.go
Normal file
216
src/testing/pkg/scan/sbom/manager.go
Normal file
@ -0,0 +1,216 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
package sbom
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, r
|
||||
func (_m *Manager) Create(ctx context.Context, r *model.Report) (string, error) {
|
||||
ret := _m.Called(ctx, r)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Report) (string, error)); ok {
|
||||
return rf(ctx, r)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Report) string); ok {
|
||||
r0 = rf(ctx, r)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *model.Report) error); ok {
|
||||
r1 = rf(ctx, r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, uuid
|
||||
func (_m *Manager) Delete(ctx context.Context, uuid string) error {
|
||||
ret := _m.Called(ctx, uuid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(ctx, uuid)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteByArtifactID provides a mock function with given fields: ctx, artifactID
|
||||
func (_m *Manager) DeleteByArtifactID(ctx context.Context, artifactID int64) error {
|
||||
ret := _m.Called(ctx, artifactID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteByArtifactID")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, artifactID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteByExtraAttr provides a mock function with given fields: ctx, mimeType, attrName, attrValue
|
||||
func (_m *Manager) DeleteByExtraAttr(ctx context.Context, mimeType string, attrName string, attrValue string) error {
|
||||
ret := _m.Called(ctx, mimeType, attrName, attrValue)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteByExtraAttr")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||
r0 = rf(ctx, mimeType, attrName, attrValue)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetBy provides a mock function with given fields: ctx, artifactID, registrationUUID, mimeType, mediaType
|
||||
func (_m *Manager) GetBy(ctx context.Context, artifactID int64, registrationUUID string, mimeType string, mediaType string) ([]*model.Report, error) {
|
||||
ret := _m.Called(ctx, artifactID, registrationUUID, mimeType, mediaType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetBy")
|
||||
}
|
||||
|
||||
var r0 []*model.Report
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string, string, string) ([]*model.Report, error)); ok {
|
||||
return rf(ctx, artifactID, registrationUUID, mimeType, mediaType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string, string, string) []*model.Report); ok {
|
||||
r0 = rf(ctx, artifactID, registrationUUID, mimeType, mediaType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Report)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string, string, string) error); ok {
|
||||
r1 = rf(ctx, artifactID, registrationUUID, mimeType, mediaType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Report, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for List")
|
||||
}
|
||||
|
||||
var r0 []*model.Report
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Report, error)); ok {
|
||||
return rf(ctx, query)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Report); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Report)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, r, cols
|
||||
func (_m *Manager) Update(ctx context.Context, r *model.Report, cols ...string) error {
|
||||
_va := make([]interface{}, len(cols))
|
||||
for _i := range cols {
|
||||
_va[_i] = cols[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, r)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Report, ...string) error); ok {
|
||||
r0 = rf(ctx, r, cols...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateReportData provides a mock function with given fields: ctx, uuid, report
|
||||
func (_m *Manager) UpdateReportData(ctx context.Context, uuid string, report string) error {
|
||||
ret := _m.Called(ctx, uuid, report)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportData")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||
r0 = rf(ctx, uuid, report)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewManager(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Manager {
|
||||
mock := &Manager{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
@ -259,6 +259,24 @@ func (_m *Manager) ListScanTasksByReportUUID(ctx context.Context, uuid string) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RetrieveStatusFromTask provides a mock function with given fields: ctx, reportID
|
||||
func (_m *Manager) RetrieveStatusFromTask(ctx context.Context, reportID string) string {
|
||||
ret := _m.Called(ctx, reportID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveStatusFromTask")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) string); ok {
|
||||
r0 = rf(ctx, reportID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields: ctx, id
|
||||
func (_m *Manager) Stop(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
Loading…
Reference in New Issue
Block a user