diff --git a/src/controller/event/handler/internal/artifact.go b/src/controller/event/handler/internal/artifact.go index 9218db95a..0e9b3f5bb 100644 --- a/src/controller/event/handler/internal/artifact.go +++ b/src/controller/event/handler/internal/artifact.go @@ -24,6 +24,7 @@ import ( "time" "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/operator" "github.com/goharbor/harbor/src/controller/repository" @@ -36,6 +37,7 @@ import ( "github.com/goharbor/harbor/src/pkg" 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/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) } + 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 } diff --git a/src/pkg/scan/dao/scan/report.go b/src/pkg/scan/dao/scan/report.go index f30b36ddb..8da0e67af 100644 --- a/src/pkg/scan/dao/scan/report.go +++ b/src/pkg/scan/dao/scan/report.go @@ -16,6 +16,7 @@ package scan import ( "context" + "fmt" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/orm" @@ -38,6 +39,8 @@ type DAO interface { UpdateReportData(ctx context.Context, uuid string, report string) error // Update update report 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 @@ -110,3 +113,14 @@ func (d *dao) Update(ctx context.Context, r *Report, cols ...string) error { } 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 +} diff --git a/src/pkg/scan/dao/scan/report_test.go b/src/pkg/scan/dao/scan/report_test.go index ccda8e02b..ab4662289 100644 --- a/src/pkg/scan/dao/scan/report_test.go +++ b/src/pkg/scan/dao/scan/report_test.go @@ -53,14 +53,23 @@ func (suite *ReportTestSuite) SetupTest() { RegistrationUUID: "ruuid", 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. func (suite *ReportTestSuite) TearDownTest() { _, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}}) 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. @@ -95,7 +104,7 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() { err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}") 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().Equal(1, len(l)) suite.Equal("{}", l[0].Report) @@ -104,6 +113,17 @@ 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) diff --git a/src/pkg/scan/report/manager.go b/src/pkg/scan/report/manager.go index fa6415ed0..3bc2de1f1 100644 --- a/src/pkg/scan/report/manager.go +++ b/src/pkg/scan/report/manager.go @@ -104,6 +104,8 @@ type Manager interface { // Update update report information 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. @@ -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 { 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) +} diff --git a/src/testing/pkg/scan/report/manager.go b/src/testing/pkg/scan/report/manager.go index 4a50114f4..046ac253e 100644 --- a/src/testing/pkg/scan/report/manager.go +++ b/src/testing/pkg/scan/report/manager.go @@ -87,6 +87,24 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error 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 func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) { ret := _m.Called(ctx, digest, registrationUUID, mimeTypes)