mirror of https://github.com/goharbor/harbor.git
Compare commits
3 Commits
c7e5a3d691
...
d3da41b8c2
Author | SHA1 | Date |
---|---|---|
stonezdj(Daojun Zhang) | d3da41b8c2 | |
stonezdj(Daojun Zhang) | fba4c40c65 | |
stonezdj | f10b060eef |
|
@ -24,6 +24,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/controller/artifact"
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact/processor/sbom"
|
||||||
"github.com/goharbor/harbor/src/controller/event"
|
"github.com/goharbor/harbor/src/controller/event"
|
||||||
"github.com/goharbor/harbor/src/controller/event/operator"
|
"github.com/goharbor/harbor/src/controller/event/operator"
|
||||||
"github.com/goharbor/harbor/src/controller/repository"
|
"github.com/goharbor/harbor/src/controller/repository"
|
||||||
|
@ -36,6 +37,7 @@ import (
|
||||||
"github.com/goharbor/harbor/src/pkg"
|
"github.com/goharbor/harbor/src/pkg"
|
||||||
pkgArt "github.com/goharbor/harbor/src/pkg/artifact"
|
pkgArt "github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
"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"
|
"github.com/goharbor/harbor/src/pkg/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -319,6 +321,11 @@ func (a *ArtifactEventHandler) onDelete(ctx context.Context, event *event.Artifa
|
||||||
log.Errorf("failed to delete scan reports of artifact %v, error: %v", unrefDigests, err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,7 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent,
|
||||||
RepoFullName: event.Artifact.Repository,
|
RepoFullName: event.Artifact.Repository,
|
||||||
RepoType: repoType,
|
RepoType: repoType,
|
||||||
},
|
},
|
||||||
|
ScanType: event.ScanType,
|
||||||
},
|
},
|
||||||
Operator: event.Operator,
|
Operator: event.Operator,
|
||||||
}
|
}
|
||||||
|
@ -138,17 +139,29 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent,
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add scan overview
|
scanSummaries := map[string]interface{}{}
|
||||||
summaries, err := scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
if event.ScanType == v1.ScanTypeVulnerability {
|
||||||
if err != nil {
|
scanSummaries, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
||||||
return nil, errors.Wrap(err, "construct scan payload")
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "construct scan payload")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sbomOverview := map[string]interface{}{}
|
||||||
|
if event.ScanType == v1.ScanTypeSbom {
|
||||||
|
sbomOverview, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeSBOMReport})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "construct scan payload")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add scan overview and sbom overview
|
||||||
resource := &model.Resource{
|
resource := &model.Resource{
|
||||||
Tag: event.Artifact.Tag,
|
Tag: event.Artifact.Tag,
|
||||||
Digest: event.Artifact.Digest,
|
Digest: event.Artifact.Digest,
|
||||||
ResourceURL: resURL,
|
ResourceURL: resURL,
|
||||||
ScanOverview: summaries,
|
ScanOverview: scanSummaries,
|
||||||
|
SBOMOverview: sbomOverview,
|
||||||
}
|
}
|
||||||
payload.EventData.Resources = append(payload.EventData.Resources, resource)
|
payload.EventData.Resources = append(payload.EventData.Resources, resource)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
// ScanImageMetaData defines meta data of image scanning event
|
// ScanImageMetaData defines meta data of image scanning event
|
||||||
type ScanImageMetaData struct {
|
type ScanImageMetaData struct {
|
||||||
Artifact *v1.Artifact
|
Artifact *v1.Artifact
|
||||||
|
ScanType string
|
||||||
Status string
|
Status string
|
||||||
Operator string
|
Operator string
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,7 @@ func (si *ScanImageMetaData) Resolve(evt *event.Event) error {
|
||||||
Artifact: si.Artifact,
|
Artifact: si.Artifact,
|
||||||
OccurAt: time.Now(),
|
OccurAt: time.Now(),
|
||||||
Operator: si.Operator,
|
Operator: si.Operator,
|
||||||
|
ScanType: si.ScanType,
|
||||||
}
|
}
|
||||||
|
|
||||||
evt.Topic = topic
|
evt.Topic = topic
|
||||||
|
|
|
@ -289,6 +289,7 @@ func (d *DeleteTagEvent) String() string {
|
||||||
// ScanImageEvent is scanning image related event data to publish
|
// ScanImageEvent is scanning image related event data to publish
|
||||||
type ScanImageEvent struct {
|
type ScanImageEvent struct {
|
||||||
EventType string
|
EventType string
|
||||||
|
ScanType string
|
||||||
Artifact *v1.Artifact
|
Artifact *v1.Artifact
|
||||||
OccurAt time.Time
|
OccurAt time.Time
|
||||||
Operator string
|
Operator string
|
||||||
|
|
|
@ -120,6 +120,13 @@ func scanTaskStatusChange(ctx context.Context, taskID int64, status string) (err
|
||||||
if operator, ok := exec.ExtraAttrs["operator"].(string); ok {
|
if operator, ok := exec.ExtraAttrs["operator"].(string); ok {
|
||||||
e.Operator = operator
|
e.Operator = operator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extract ScanType if exist in ExtraAttrs
|
||||||
|
if c, ok := exec.ExtraAttrs["enabled_capabilities"].(map[string]interface{}); ok {
|
||||||
|
if Type, ok := c["type"].(string); ok {
|
||||||
|
e.ScanType = Type
|
||||||
|
}
|
||||||
|
}
|
||||||
// fire event
|
// fire event
|
||||||
notification.AddEvent(ctx, e)
|
notification.AddEvent(ctx, e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ type EventData struct {
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
Replication *model.Replication `json:"replication,omitempty"`
|
Replication *model.Replication `json:"replication,omitempty"`
|
||||||
Retention *model.Retention `json:"retention,omitempty"`
|
Retention *model.Retention `json:"retention,omitempty"`
|
||||||
|
ScanType string `json:"scan_type,omitempty"`
|
||||||
Custom map[string]string `json:"custom_attributes,omitempty"`
|
Custom map[string]string `json:"custom_attributes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ type Resource struct {
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
ResourceURL string `json:"resource_url,omitempty"`
|
ResourceURL string `json:"resource_url,omitempty"`
|
||||||
ScanOverview map[string]interface{} `json:"scan_overview,omitempty"`
|
ScanOverview map[string]interface{} `json:"scan_overview,omitempty"`
|
||||||
|
SBOMOverview map[string]interface{} `json:"sbom_overview,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository info of notification event
|
// Repository info of notification event
|
||||||
|
|
|
@ -16,6 +16,7 @@ package scan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"github.com/goharbor/harbor/src/lib/orm"
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
|
@ -38,6 +39,8 @@ type DAO interface {
|
||||||
UpdateReportData(ctx context.Context, uuid string, report string) error
|
UpdateReportData(ctx context.Context, uuid string, report string) error
|
||||||
// Update update report
|
// Update update report
|
||||||
Update(ctx context.Context, r *Report, cols ...string) error
|
Update(ctx context.Context, r *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
|
// New returns an instance of the default DAO
|
||||||
|
@ -110,3 +113,14 @@ func (d *dao) Update(ctx context.Context, r *Report, cols ...string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 scan_report where mime_type = ? and report::jsonb @> ?"
|
||||||
|
dgstJSONStr := fmt.Sprintf(`{"%s":"%s"}`, attrName, attrValue)
|
||||||
|
_, err = o.Raw(delReportSQL, mimeType, dgstJSONStr).Exec()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -53,14 +53,23 @@ func (suite *ReportTestSuite) SetupTest() {
|
||||||
RegistrationUUID: "ruuid",
|
RegistrationUUID: "ruuid",
|
||||||
MimeType: v1.MimeTypeNativeReport,
|
MimeType: v1.MimeTypeNativeReport,
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.create(r)
|
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.
|
// TearDownTest clears enf for test case.
|
||||||
func (suite *ReportTestSuite) TearDownTest() {
|
func (suite *ReportTestSuite) TearDownTest() {
|
||||||
_, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}})
|
_, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}})
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
_, err = suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid3"}})
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReportList tests list reports with query parameters.
|
// TestReportList tests list reports with query parameters.
|
||||||
|
@ -95,7 +104,7 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() {
|
||||||
err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}")
|
err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}")
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
l, err := suite.dao.List(orm.Context(), nil)
|
l, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"uuid": "uuid"}))
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
suite.Require().Equal(1, len(l))
|
suite.Require().Equal(1, len(l))
|
||||||
suite.Equal("{}", l[0].Report)
|
suite.Equal("{}", l[0].Report)
|
||||||
|
@ -104,6 +113,17 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() {
|
||||||
suite.Require().NoError(err)
|
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) {
|
func (suite *ReportTestSuite) create(r *Report) {
|
||||||
id, err := suite.dao.Create(orm.Context(), r)
|
id, err := suite.dao.Create(orm.Context(), r)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
|
@ -104,6 +104,8 @@ type Manager interface {
|
||||||
|
|
||||||
// Update update report information
|
// Update update report information
|
||||||
Update(ctx context.Context, r *scan.Report, cols ...string) error
|
Update(ctx context.Context, r *scan.Report, cols ...string) error
|
||||||
|
// DeleteByExtraAttr delete scan_report by sbom_digest
|
||||||
|
DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// basicManager is a default implementation of report manager.
|
// basicManager is a default implementation of report manager.
|
||||||
|
@ -226,3 +228,7 @@ func (bm *basicManager) List(ctx context.Context, query *q.Query) ([]*scan.Repor
|
||||||
func (bm *basicManager) Update(ctx context.Context, r *scan.Report, cols ...string) error {
|
func (bm *basicManager) Update(ctx context.Context, r *scan.Report, cols ...string) error {
|
||||||
return bm.dao.Update(ctx, r, cols...)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -87,6 +87,24 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error
|
||||||
return r0
|
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, digest, registrationUUID, mimeTypes
|
// GetBy provides a mock function with given fields: ctx, digest, registrationUUID, mimeTypes
|
||||||
func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) {
|
func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) {
|
||||||
ret := _m.Called(ctx, digest, registrationUUID, mimeTypes)
|
ret := _m.Called(ctx, digest, registrationUUID, mimeTypes)
|
||||||
|
|
Loading…
Reference in New Issue