mirror of https://github.com/goharbor/harbor.git
Merge branch 'main' into dependabot/go_modules/src/github.com/tencentcloud/tencentcloud-sdk-go-3.0.233incompatible
This commit is contained in:
commit
fc8b6d3b3f
|
@ -8450,6 +8450,12 @@ definitions:
|
|||
ScannerCapability:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: |
|
||||
Specify the type of scanner capability, like vulnerability or sbom
|
||||
x-omitempty: false
|
||||
example: "sbom"
|
||||
consumes_mime_types:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -356,7 +356,16 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces
|
|||
for _, acc := range art.Accessories {
|
||||
// only hard ref accessory should be removed
|
||||
if acc.IsHard() {
|
||||
if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, true, true); err != nil {
|
||||
// if this acc artifact has parent(is child), set isRoot to false
|
||||
parents, err := c.artMgr.ListReferences(ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
"ChildID": acc.GetData().ArtifactID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, len(parents) == 0, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -369,7 +378,12 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces
|
|||
!errors.IsErr(err, errors.NotFoundCode) {
|
||||
return err
|
||||
}
|
||||
if err = c.deleteDeeply(ctx, reference.ChildID, false, false); err != nil {
|
||||
// if the child artifact is an accessory, set isAccessory to true
|
||||
accs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": reference.ChildID}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.deleteDeeply(ctx, reference.ChildID, false, len(accs) > 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,11 +154,8 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy,
|
|||
func (c *controller) markError(ctx context.Context, executionID int64, err error) {
|
||||
logger := log.GetLogger(ctx)
|
||||
// try to stop the execution first in case that some tasks are already created
|
||||
if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil {
|
||||
logger.Errorf("failed to stop the execution %d: %v", executionID, err)
|
||||
}
|
||||
if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil {
|
||||
logger.Errorf("failed to mark error for the execution %d: %v", executionID, err)
|
||||
if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil {
|
||||
logger.Errorf("failed to stop the execution %d: %v", executionID, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,8 +75,7 @@ func (r *replicationTestSuite) TestStart() {
|
|||
// got error when running the replication flow
|
||||
r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil)
|
||||
r.execMgr.On("StopAndWait", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
r.execMgr.On("StopAndWaitWithError", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
|
||||
r.ormCreator.On("Create").Return(nil)
|
||||
id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true}, nil, task.ExecutionTriggerManual)
|
||||
|
|
|
@ -280,12 +280,8 @@ func (r *defaultController) TriggerRetentionExec(ctx context.Context, policyID i
|
|||
if num, err := r.launcher.Launch(ctx, p, id, dryRun); err != nil {
|
||||
logger.Errorf("failed to launch the retention jobs, err: %v", err)
|
||||
|
||||
if err = r.execMgr.StopAndWait(ctx, id, 10*time.Second); err != nil {
|
||||
logger.Errorf("failed to stop the retention execution %d: %v", id, err)
|
||||
}
|
||||
|
||||
if err = r.execMgr.MarkError(ctx, id, err.Error()); err != nil {
|
||||
logger.Errorf("failed to mark error for the retention execution %d: %v", id, err)
|
||||
if e := r.execMgr.StopAndWaitWithError(ctx, id, 10*time.Second, err); e != nil {
|
||||
logger.Errorf("failed to stop the retention execution %d: %v", id, e)
|
||||
}
|
||||
} else if num == 0 {
|
||||
// no candidates, mark the execution as done directly
|
||||
|
|
|
@ -119,11 +119,8 @@ func (c *controller) createCleanupTask(ctx context.Context, jobParams job.Parame
|
|||
|
||||
func (c *controller) markError(ctx context.Context, executionID int64, err error) {
|
||||
// try to stop the execution first in case that some tasks are already created
|
||||
if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil {
|
||||
log.Errorf("failed to stop the execution %d: %v", executionID, err)
|
||||
}
|
||||
if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil {
|
||||
log.Errorf("failed to mark error for the execution %d: %v", executionID, err)
|
||||
if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil {
|
||||
log.Errorf("failed to stop the execution %d: %v", executionID, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ func (suite *SystemArtifactCleanupTestSuite) TestStartCleanupErrorDuringTaskCrea
|
|||
suite.taskMgr.On("Create", ctx, executionID, mock.Anything).Return(taskId, errors.New("test error")).Once()
|
||||
|
||||
suite.execMgr.On("MarkError", ctx, executionID, mock.Anything).Return(nil).Once()
|
||||
suite.execMgr.On("StopAndWait", ctx, executionID, mock.Anything).Return(nil).Once()
|
||||
suite.execMgr.On("StopAndWaitWithError", ctx, executionID, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
err := suite.ctl.Start(ctx, false, "SCHEDULE")
|
||||
suite.Error(err)
|
||||
|
|
|
@ -56,7 +56,7 @@ func (m *manager) Create(ctx context.Context, jobLog *models.JobLog) (id int64,
|
|||
return m.dao.Create(ctx, jobLog)
|
||||
}
|
||||
|
||||
// DeleteJobLogsBefore ...
|
||||
// DeleteBefore ...
|
||||
func (m *manager) DeleteBefore(ctx context.Context, t time.Time) (id int64, err error) {
|
||||
return m.dao.DeleteBefore(ctx, t)
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ func (suite *ClientTestSuite) TestClientMetadata() {
|
|||
require.NotNil(suite.T(), m)
|
||||
|
||||
assert.Equal(suite.T(), m.Scanner.Name, "Trivy")
|
||||
assert.Equal(suite.T(), m.Capabilities[0].Type, "sbom")
|
||||
}
|
||||
|
||||
// TestClientSubmitScan tests the scan submission of client
|
||||
|
@ -119,6 +120,7 @@ func (mh *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
Version: "0.1.0",
|
||||
},
|
||||
Capabilities: []*ScannerCapability{{
|
||||
Type: "sbom",
|
||||
ConsumesMimeTypes: []string{
|
||||
MimeTypeOCIArtifact,
|
||||
MimeTypeDockerArtifact,
|
||||
|
|
|
@ -37,6 +37,7 @@ type Scanner struct {
|
|||
// report MIME types. For example, a scanner capable of analyzing Docker images and producing
|
||||
// a vulnerabilities report recognizable by Harbor web console might be represented with the
|
||||
// following capability:
|
||||
// - type: vulnerability
|
||||
// - consumes MIME types:
|
||||
// -- application/vnd.oci.image.manifest.v1+json
|
||||
// -- application/vnd.docker.distribution.manifest.v2+json
|
||||
|
@ -44,6 +45,8 @@ type Scanner struct {
|
|||
// -- application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0
|
||||
// -- application/vnd.scanner.adapter.vuln.report.raw
|
||||
type ScannerCapability struct {
|
||||
// The type of the scanner capability, vulnerability or sbom
|
||||
Type string `json:"type"`
|
||||
// The set of MIME types of the artifacts supported by the scanner to produce the reports
|
||||
// specified in the "produces_mime_types". A given mime type should only be present in one
|
||||
// capability item.
|
||||
|
|
|
@ -25,6 +25,8 @@ import (
|
|||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TaskDAO is the data access object interface for task
|
||||
|
@ -91,22 +93,33 @@ func (t *taskDAO) List(ctx context.Context, query *q.Query) ([]*Task, error) {
|
|||
return tasks, nil
|
||||
}
|
||||
|
||||
func isValidUUID(id string) bool {
|
||||
if len(id) == 0 {
|
||||
return false
|
||||
}
|
||||
if _, err := uuid.Parse(id); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *taskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*Task, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tasks := []*Task{}
|
||||
// Due to the limitation of the beego's orm, the SQL cannot be converted by orm framework,
|
||||
// so we can only execute the query by raw SQL, the SQL filters the task contains the report uuid in the column extra_attrs,
|
||||
// consider from performance side which can using indexes to speed up queries.
|
||||
sql := fmt.Sprintf(`SELECT * FROM task WHERE extra_attrs::jsonb->'report_uuids' @> '["%s"]'`, uuid)
|
||||
_, err = ormer.Raw(sql).QueryRows(&tasks)
|
||||
if !isValidUUID(uuid) {
|
||||
return nil, errors.BadRequestError(fmt.Errorf("invalid UUID %v", uuid))
|
||||
}
|
||||
|
||||
var tasks []*Task
|
||||
param := fmt.Sprintf(`{"report_uuids":["%s"]}`, uuid)
|
||||
sql := `SELECT * FROM task WHERE extra_attrs::jsonb @> cast( ? as jsonb )`
|
||||
_, err = ormer.Raw(sql, param).QueryRows(&tasks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -113,8 +113,9 @@ func (t *taskDAOTestSuite) TestList() {
|
|||
}
|
||||
|
||||
func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() {
|
||||
reportUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e`
|
||||
// should not exist if non set
|
||||
tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid")
|
||||
tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID)
|
||||
t.Require().Nil(err)
|
||||
t.Require().Len(tasks, 0)
|
||||
// create one with report uuid
|
||||
|
@ -122,12 +123,12 @@ func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() {
|
|||
ExecutionID: t.executionID,
|
||||
Status: "success",
|
||||
StatusCode: 1,
|
||||
ExtraAttrs: `{"report_uuids": ["fake-report-uuid"]}`,
|
||||
ExtraAttrs: fmt.Sprintf(`{"report_uuids": ["%s"]}`, reportUUID),
|
||||
})
|
||||
t.Require().Nil(err)
|
||||
defer t.taskDAO.Delete(t.ctx, taskID)
|
||||
// should exist as created
|
||||
tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid")
|
||||
tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID)
|
||||
t.Require().Nil(err)
|
||||
t.Require().Len(tasks, 1)
|
||||
t.Equal(taskID, tasks[0].ID)
|
||||
|
@ -299,6 +300,29 @@ func (t *taskDAOTestSuite) TestExecutionIDsByVendorAndStatus() {
|
|||
defer t.taskDAO.Delete(t.ctx, tid)
|
||||
}
|
||||
|
||||
func TestIsValidUUID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
uuid string
|
||||
expected bool
|
||||
}{
|
||||
{"Valid UUID", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f", true},
|
||||
{"Invalid UUID - Short", "7f20b1b9-6117-4a2e-820b", false},
|
||||
{"Invalid UUID - Long", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f-extra", false},
|
||||
{"Invalid UUID - Invalid Characters", "7f20b1b9-6117-4z2e-820b-e4cc0401f15f", false},
|
||||
{"Empty String", "", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := isValidUUID(test.uuid)
|
||||
if result != test.expected {
|
||||
t.Errorf("Expected isValidUUID(%s) to be %t, got %t", test.uuid, test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskDAOSuite(t *testing.T) {
|
||||
suite.Run(t, &taskDAOTestSuite{})
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ type ExecutionManager interface {
|
|||
// StopAndWait stops all linked tasks of the specified execution and waits until all tasks are stopped
|
||||
// or get an error
|
||||
StopAndWait(ctx context.Context, id int64, timeout time.Duration) (err error)
|
||||
// StopAndWaitWithError calls the StopAndWait first, if it doesn't return error, then it call MarkError if the origError is not empty
|
||||
StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) (err error)
|
||||
// Delete the specified execution and its tasks
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// Delete all executions and tasks of the specific vendor. They can be deleted only when all the executions/tasks
|
||||
|
@ -250,6 +252,16 @@ func (e *executionManager) StopAndWait(ctx context.Context, id int64, timeout ti
|
|||
}
|
||||
}
|
||||
|
||||
func (e *executionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error {
|
||||
if err := e.StopAndWait(ctx, id, timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
if origError != nil {
|
||||
return e.MarkError(ctx, id, origError.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executionManager) Delete(ctx context.Context, id int64) error {
|
||||
tasks, err := e.taskDAO.List(ctx, &q.Query{
|
||||
Keywords: map[string]interface{}{
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -145,6 +145,8 @@ func deleteManifest(w http.ResponseWriter, req *http.Request) {
|
|||
// add parse digest here is to return ErrDigestInvalidFormat before GetByReference throws an NOT_FOUND(404)
|
||||
// Do not add the logic into GetByReference as it's a shared method for PUT/GET/DELETE/Internal call,
|
||||
// and NOT_FOUND satisfy PUT/GET/Internal call.
|
||||
// According to https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-tags
|
||||
// If tag deletion is disabled, the registry MUST respond with either a 400 Bad Request or a 405 Method Not Allowed
|
||||
if _, err := digest.Parse(reference); err != nil {
|
||||
lib_http.SendError(w, errors.Wrapf(err, "unsupported digest %s", reference).WithCode(errors.UNSUPPORTED))
|
||||
return
|
||||
|
|
|
@ -74,6 +74,7 @@ func (s *ScannerMetadata) ToSwagger(_ context.Context) *models.ScannerAdapterMet
|
|||
var capabilities []*models.ScannerCapability
|
||||
for _, c := range s.Capabilities {
|
||||
capabilities = append(capabilities, &models.ScannerCapability{
|
||||
Type: c.Type,
|
||||
ConsumesMimeTypes: c.ConsumesMimeTypes,
|
||||
ProducesMimeTypes: c.ProducesMimeTypes,
|
||||
})
|
||||
|
|
|
@ -57,7 +57,7 @@ func (n *WebhookPolicy) ToTargets() []*models.WebhookTargetObject {
|
|||
return results
|
||||
}
|
||||
|
||||
// NewNotifiactionPolicy ...
|
||||
// NewWebhookPolicy ...
|
||||
func NewWebhookPolicy(p *model.Policy) *WebhookPolicy {
|
||||
return &WebhookPolicy{
|
||||
Policy: p,
|
||||
|
|
|
@ -209,6 +209,20 @@ func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout t
|
|||
return r0
|
||||
}
|
||||
|
||||
// StopAndWaitWithError provides a mock function with given fields: ctx, id, timeout, origError
|
||||
func (_m *ExecutionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error {
|
||||
ret := _m.Called(ctx, id, timeout, origError)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration, error) error); ok {
|
||||
r0 = rf(ctx, id, timeout, origError)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateExtraAttrs provides a mock function with given fields: ctx, id, extraAttrs
|
||||
func (_m *ExecutionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error {
|
||||
ret := _m.Called(ctx, id, extraAttrs)
|
||||
|
|
|
@ -66,6 +66,7 @@ Vulnerability Not Ready Project Hint
|
|||
|
||||
Switch To Scanners Page
|
||||
Retry Element Click xpath=//clr-main-container//clr-vertical-nav//a[contains(.,'Interrogation')]
|
||||
Retry Element Click xpath=//app-interrogation-services//a[normalize-space()='Scanners']
|
||||
Retry Wait Until Page Contains Element ${set_default_scanner}
|
||||
|
||||
Should Display The Default Trivy Scanner
|
||||
|
|
Loading…
Reference in New Issue