feat: add stop scan & stop scan-all feature

Signed-off-by: Shengwen Yu <yshengwen@vmware.com>
This commit is contained in:
Shengwen Yu 2021-08-15 17:21:39 +08:00
parent cdb13f5191
commit e2e3bcca1c
25 changed files with 460 additions and 7 deletions

View File

@ -1166,6 +1166,31 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/stop:
post:
summary: Cancelling a scan job for a particular artifact
description: Cancelling a scan job for a particular artifact
tags:
- scan
operationId: stopScanArtifact
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
- $ref: '#/parameters/reference'
responses:
'202':
$ref: '#/responses/202'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/{report_id}/log:
get:
summary: Get the log of the scan report
@ -4167,6 +4192,26 @@ paths:
$ref: '#/responses/412'
'500':
$ref: '#/responses/500'
/system/scanAll/stop:
post:
summary: Stop scanAll job execution
description: Stop scanAll job execution
parameters:
- $ref: '#/parameters/requestId'
tags:
- scanAll
operationId: stopScanAll
responses:
'202':
$ref: '#/responses/202'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'500':
$ref: '#/responses/500'
/ping:
get:
operationId: getPing

View File

@ -30,6 +30,7 @@ const (
ActionOperate = Action("operate")
ActionScannerPull = Action("scanner-pull") // for robot account created by scanner to pull image, bypass the policy check
ActionStop = Action("stop") // for stop scan/scan-all execution
)
// const resource variables

View File

@ -98,6 +98,7 @@ var (
{Resource: rbac.ResourceScan, Action: rbac.ActionCreate},
{Resource: rbac.ResourceScan, Action: rbac.ActionRead},
{Resource: rbac.ResourceScan, Action: rbac.ActionStop},
{Resource: rbac.ResourceScanner, Action: rbac.ActionRead},
{Resource: rbac.ResourceScanner, Action: rbac.ActionCreate},
@ -185,6 +186,7 @@ var (
{Resource: rbac.ResourceScan, Action: rbac.ActionCreate},
{Resource: rbac.ResourceScan, Action: rbac.ActionRead},
{Resource: rbac.ResourceScan, Action: rbac.ActionStop},
{Resource: rbac.ResourceScanner, Action: rbac.ActionRead},

View File

@ -58,6 +58,7 @@ var (
{Resource: rbac.ResourceScanAll, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionDelete},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionList},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionStop},
{Resource: rbac.ResourceSystemVolumes, Action: rbac.ActionRead},

View File

@ -30,6 +30,7 @@ func init() {
notifier.Subscribe(event.TopicQuotaExceed, &quota.Handler{})
notifier.Subscribe(event.TopicQuotaWarning, &quota.Handler{})
notifier.Subscribe(event.TopicScanningFailed, &scan.Handler{})
notifier.Subscribe(event.TopicScanningStopped, &scan.Handler{})
notifier.Subscribe(event.TopicScanningCompleted, &scan.Handler{})
notifier.Subscribe(event.TopicDeleteArtifact, &scan.DelArtHandler{})
notifier.Subscribe(event.TopicReplication, &artifact.ReplicationHandler{})

View File

@ -29,7 +29,10 @@ func (si *ScanImageMetaData) Resolve(evt *event.Event) error {
case job.SuccessStatus:
eventType = event2.TopicScanningCompleted
topic = event2.TopicScanningCompleted
case job.ErrorStatus, job.StoppedStatus:
case job.StoppedStatus:
eventType = event2.TopicScanningStopped
topic = event2.TopicScanningStopped
case job.ErrorStatus:
eventType = event2.TopicScanningFailed
topic = event2.TopicScanningFailed
default:

View File

@ -49,6 +49,48 @@ func (r *scanEventTestSuite) TestResolveOfScanImageEventMetadata() {
r.Equal("library/hello-world", data.Artifact.Repository)
}
func (r *scanEventTestSuite) TestResolveOfStopScanImageEventMetadata() {
e := &event.Event{}
metadata := &ScanImageMetaData{
Artifact: &v1.Artifact{
NamespaceID: 0,
Repository: "library/hello-world",
Tag: "latest",
Digest: "sha256:absdfd87123",
MimeType: "docker.chart",
},
Status: job.StoppedStatus.String(),
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicScanningStopped, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.ScanImageEvent)
r.Require().True(ok)
r.Equal("library/hello-world", data.Artifact.Repository)
}
func (r *scanEventTestSuite) TestResolveOfFailedScanImageEventMetadata() {
e := &event.Event{}
metadata := &ScanImageMetaData{
Artifact: &v1.Artifact{
NamespaceID: 0,
Repository: "library/hello-world",
Tag: "latest",
Digest: "sha256:absdfd87123",
MimeType: "docker.chart",
},
Status: job.ErrorStatus.String(),
}
err := metadata.Resolve(e)
r.Require().Nil(err)
r.Equal(event2.TopicScanningFailed, e.Topic)
r.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.ScanImageEvent)
r.Require().True(ok)
r.Equal("library/hello-world", data.Artifact.Repository)
}
func TestScanEventTestSuite(t *testing.T) {
suite.Run(t, &scanEventTestSuite{})
}

View File

@ -38,6 +38,7 @@ const (
TopicCreateTag = "CREATE_TAG"
TopicDeleteTag = "DELETE_TAG"
TopicScanningFailed = "SCANNING_FAILED"
TopicScanningStopped = "SCANNING_STOPPED"
TopicScanningCompleted = "SCANNING_COMPLETED"
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
TopicQuotaWarning = "QUOTA_WARNING"

View File

@ -313,6 +313,24 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti
return nil
}
// Stop scan job of a given artifact
func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact) error {
if artifact == nil {
return errors.New("nil artifact to stop scan")
}
query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest})
executions, err := bc.execMgr.List(ctx, query)
if err != nil {
return err
}
if len(executions) == 0 {
message := fmt.Sprintf("no scan job for artifact digest=%v", artifact.Digest)
return errors.BadRequestError(nil).WithMessage(message)
}
execution := executions[0]
return bc.execMgr.Stop(ctx, execution.ID)
}
func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bool) (int64, error) {
executionID, err := bc.execMgr.Create(ctx, VendorTypeScanAll, 0, trigger)
if err != nil {

View File

@ -358,6 +358,45 @@ func (suite *ControllerTestSuite) TestScanControllerScan() {
}
}
// TestScanControllerStop ...
func (suite *ControllerTestSuite) TestScanControllerStop() {
{
// artifact not provieded
suite.Require().Error(suite.c.Stop(context.TODO(), nil))
}
{
// success
mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{
{ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), Status: "Running"},
}, nil).Once()
mock.OnAnything(suite.execMgr, "Stop").Return(nil).Once()
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
suite.Require().NoError(suite.c.Stop(ctx, suite.artifact))
}
{
// failed due to no execution returned by List
mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{}, nil).Once()
mock.OnAnything(suite.execMgr, "Stop").Return(nil).Once()
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
suite.Require().Error(suite.c.Stop(ctx, suite.artifact))
}
{
// failed due to execMgr.List() errored out
mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{}, fmt.Errorf("failed to call execMgr.List()")).Once()
ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
suite.Require().Error(suite.c.Stop(ctx, suite.artifact))
}
}
// TestScanControllerGetReport ...
func (suite *ControllerTestSuite) TestScanControllerGetReport() {
mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {

View File

@ -50,6 +50,16 @@ type Controller interface {
// error : non nil error if any errors occurred
Scan(ctx context.Context, artifact *artifact.Artifact, options ...Option) error
// Stop scan job of the given artifact
//
// Arguments:
// ctx context.Context : the context for this method
// artifact *artifact.Artifact : the artifact whose scan job to be stopped
//
// Returns:
// error : non nil error if any errors occurred
Stop(ctx context.Context, artifact *artifact.Artifact) error
// GetReport gets the reports for the given artifact identified by the digest
//
// Arguments:

View File

@ -58,6 +58,7 @@ func initSupportedNotifyType() {
event.TopicQuotaExceed,
event.TopicQuotaWarning,
event.TopicScanningFailed,
event.TopicScanningStopped,
event.TopicScanningCompleted,
event.TopicReplication,
event.TopicTagRetention,

View File

@ -146,6 +146,16 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
// Get logger
myLogger := ctx.GetLogger()
// shouldStop checks if the job should be stopped
shouldStop := func() bool {
if cmd, ok := ctx.OPCommand(); ok && cmd == job.StopCommand {
myLogger.Info("scan job being stopped")
return true
}
return false
}
// Ignore errors as they have been validated already
r, _ := extractRegistration(params)
req, _ := ExtractScanReq(params)
@ -156,6 +166,10 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
printJSONParameter(JobParameterRequest, removeAuthInfo(req), myLogger)
myLogger.Infof("Report mime types: %v\n", mimeTypes)
if shouldStop() {
return nil
}
// Submit scan request to the scanner adapter
client, err := r.Client(v1.DefaultClientPool)
if err != nil {
@ -182,6 +196,10 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
logAndWrapError(myLogger, err, "scan job: make authorization")
}
if shouldStop() {
return nil
}
req.Registry.Authorization = authorization
resp, err := client.SubmitScan(req)
if err != nil {
@ -210,6 +228,10 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
for {
select {
case t := <-tm.C:
if shouldStop() {
return
}
myLogger.Debugf("check scan report for mime %s at %s", m, t.Format("2006/01/02 15:04:05"))
rawReport, err := client.GetScanReport(resp.ID, m)
@ -250,6 +272,10 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
// Wait for all the retrieving routines are completed
wg.Wait()
if shouldStop() {
return nil
}
// Merge errors
for _, e := range errs {
if e != nil {

View File

@ -65,6 +65,7 @@ func (suite *JobTestSuite) TestJob() {
lg := &mockjobservice.MockJobLogger{}
ctx.On("GetLogger").Return(lg)
ctx.On("OPCommand").Return(job.NilCommand, false)
r := &scanner.Registration{
ID: 0,

View File

@ -113,9 +113,8 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
sum.TotalCount = 1
// If the status is not success/stopped, there will not be any report.
if r.Status != job.SuccessStatus.String() &&
r.Status != job.StoppedStatus.String() {
// If the status is not success, there will not be any report.
if r.Status != job.SuccessStatus.String() {
return sum, nil
}

View File

@ -85,6 +85,8 @@ func MergeScanStatus(s1, s2 string) string {
if j1 == job.RunningStatus || j2 == job.RunningStatus {
return job.RunningStatus.String()
} else if j1 == job.StoppedStatus || j2 == job.StoppedStatus {
return job.StoppedStatus.String()
} else if j1 == job.SuccessStatus || j2 == job.SuccessStatus {
// the scan status of the image index will be treated as a success when one of its children is success
return job.SuccessStatus.String()

View File

@ -71,6 +71,7 @@ func Test_mergeScanStatus(t *testing.T) {
errorStatus := job.ErrorStatus.String()
runningStatus := job.RunningStatus.String()
successStatus := job.SuccessStatus.String()
stoppedStatus := job.StoppedStatus.String()
type args struct {
s1 string
@ -88,6 +89,9 @@ func Test_mergeScanStatus(t *testing.T) {
{"success and success", args{successStatus, successStatus}, successStatus},
{"error and error", args{errorStatus, errorStatus}, errorStatus},
{"error and empty string", args{errorStatus, ""}, errorStatus},
{"running and stopped", args{runningStatus, stoppedStatus}, runningStatus},
{"success and stopped", args{successStatus, stoppedStatus}, stoppedStatus},
{"stopped and stopped", args{stoppedStatus, stoppedStatus}, stoppedStatus},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -329,11 +329,25 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que
break
}
}
if len(keyPrefix) == 0 {
if len(keyPrefix) == 0 || keyPrefix == key {
return qs, nil
}
inClause, err := orm.CreateInClause(ctx, "select id from execution where extra_attrs->>?=?",
strings.TrimPrefix(key, keyPrefix), value)
// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data
// examples:
// key = extra_attrs.id,
// ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value}
// key = extra_attrs.artifact.digest
// ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value}
// key = extra_attrs.a.b.c
// ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value}
keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".")
var args []interface{}
for _, item := range keys {
args = append(args, item)
}
args = append(args, value)
inClause, err := orm.CreateInClause(ctx, buildInClauseSqlForExtraAttrs(keys), args...)
if err != nil {
return nil, err
}
@ -342,3 +356,23 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que
return qs, nil
}
// Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix
func buildInClauseSqlForExtraAttrs(keys []string) string {
switch len(keys) {
case 0:
// won't fall into this case, as the if condition on "keyPrefix == key"
// act as a place holder to ensure "default" is equivalent to "len(keys) >= 2"
return ""
case 1:
return fmt.Sprintf("select id from execution where extra_attrs->>?=?")
default:
// len(keys) >= 2
elements := make([]string, len(keys)-1)
for i := range elements {
elements[i] = "?"
}
s := strings.Join(elements, "->")
return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s)
}
}

View File

@ -329,3 +329,26 @@ func (e *executionDAOTestSuite) TestRefreshStatus() {
func TestExecutionDAOSuite(t *testing.T) {
suite.Run(t, &executionDAOTestSuite{})
}
func Test_buildInClauseSqlForExtraAttrs(t *testing.T) {
type args struct {
keys []string
}
tests := []struct {
name string
args args
want string
}{
{"extra_attrs.", args{[]string{}}, ""},
{"extra_attrs.id", args{[]string{"id"}}, "select id from execution where extra_attrs->>?=?"},
{"extra_attrs.artifact.digest", args{[]string{"artifact", "digest"}}, "select id from execution where extra_attrs->?->>?=?"},
{"extra_attrs.a.b.c", args{[]string{"a", "b", "c"}}, "select id from execution where extra_attrs->?->?->>?=?"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildInClauseSqlForExtraAttrs(tt.args.keys); got != tt.want {
t.Errorf("buildInClauseSqlForExtraAttrs() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -24,6 +24,7 @@ const EVENT_TYPES_TEXT_MAP = {
'QUOTA_EXCEED': 'Quota exceed',
'QUOTA_WARNING': 'Quota near threshold',
'SCANNING_FAILED': 'Scanning failed',
'SCANNING_STOPPED': 'Scanning stopped',
'SCANNING_COMPLETED': 'Scanning finished',
'TAG_RETENTION': 'Tag retention finished',
};

View File

@ -48,6 +48,24 @@ func (s *scanAPI) Prepare(ctx context.Context, operation string, params interfac
return nil
}
func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopScanArtifactParams) middleware.Responder {
if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionStop, rbac.ResourceScan); err != nil {
return s.SendError(ctx, err)
}
// get the artifact
curArtifact, err := s.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil)
if err != nil {
return s.SendError(ctx, err)
}
if err := s.scanCtl.Stop(ctx, curArtifact); err != nil {
return s.SendError(ctx, err)
}
return operation.NewStopScanArtifactAccepted()
}
func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifactParams) middleware.Responder {
if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, rbac.ResourceScan); err != nil {
return s.SendError(ctx, err)

View File

@ -25,6 +25,8 @@ import (
"github.com/goharbor/harbor/src/controller/scanner"
"github.com/goharbor/harbor/src/jobservice/job"
"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/lib/q"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/pkg/task"
@ -54,6 +56,30 @@ func (s *scanAllAPI) Prepare(ctx context.Context, operation string, params inter
return nil
}
// StopScanAll stops the execution of scan all artifacts.
func (s *scanAllAPI) StopScanAll(ctx context.Context, params operation.StopScanAllParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionStop); err != nil {
return s.SendError(ctx, err)
}
execution, err := s.getLatestScanAllExecution(ctx)
if err != nil {
return s.SendError(ctx, err)
}
if execution == nil {
message := fmt.Sprintf("no scan all job is found currently")
return s.SendError(ctx, errors.BadRequestError(nil).WithMessage(message))
}
go func(ctx context.Context, eid int64) {
err := s.execMgr.Stop(ctx, eid)
if err != nil {
log.Errorf("failed to stop the execution of executionID=%+v", execution.ID)
}
}(orm.Context(), execution.ID)
return operation.NewStopScanAllAccepted()
}
func (s *scanAllAPI) CreateScanAllSchedule(ctx context.Context, params operation.CreateScanAllScheduleParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionCreate); err != nil {
return s.SendError(ctx, err)

View File

@ -23,6 +23,7 @@ import (
"testing"
"time"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/pkg/task"
@ -51,6 +52,9 @@ type ScanAllTestSuite struct {
}
func (suite *ScanAllTestSuite) SetupSuite() {
// this is because orm.Context()
dao.PrepareTestForPostgresSQL()
suite.execution = &task.Execution{
Status: "Running",
Metrics: &taskdao.Metrics{
@ -238,6 +242,41 @@ func (suite *ScanAllTestSuite) TestGetLatestScheduledScanAllMetrics() {
}
}
func (suite *ScanAllTestSuite) TestStopScanAll() {
times := 3
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{
// create stop scan all but get latest scan all execution failed
mock.OnAnything(suite.execMgr, "List").Return(nil, fmt.Errorf("list executions failed")).Once()
res, err := suite.Post("/system/scanAll/stop", nil)
suite.NoError(err)
suite.Equal(500, res.StatusCode)
}
{
// create stop scan all but no latest scan all execution
mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{}, nil).Once()
res, err := suite.Post("/system/scanAll/stop", nil)
suite.NoError(err)
suite.Equal(400, res.StatusCode)
}
{
// successfully stop scan all
mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{suite.execution}, nil).Once()
mock.OnAnything(suite.execMgr, "Stop").Return(nil).Once()
res, err := suite.Post("/system/scanAll/stop", nil)
suite.NoError(err)
suite.Equal(202, res.StatusCode)
}
}
func (suite *ScanAllTestSuite) TestCreateScanAllSchedule() {
times := 11
suite.Security.On("IsAuthenticated").Return(true).Times(times)

View File

@ -0,0 +1,102 @@
// 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 handler
import (
"fmt"
"testing"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/pkg/task"
"github.com/goharbor/harbor/src/server/v2.0/restapi"
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
scantesting "github.com/goharbor/harbor/src/testing/controller/scan"
"github.com/goharbor/harbor/src/testing/mock"
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
"github.com/stretchr/testify/suite"
)
type ScanTestSuite struct {
htesting.Suite
artifactCtl *artifacttesting.Controller
scanCtl *scantesting.Controller
execution *task.Execution
projectCtlMock *projecttesting.Controller
}
func (suite *ScanTestSuite) SetupSuite() {
suite.execution = &task.Execution{
Status: "Running",
}
suite.scanCtl = &scantesting.Controller{}
suite.artifactCtl = &artifacttesting.Controller{}
suite.Config = &restapi.Config{
ScanAPI: &scanAPI{
artCtl: suite.artifactCtl,
scanCtl: suite.scanCtl,
},
}
suite.Suite.SetupSuite()
mock.OnAnything(projectCtlMock, "GetByName").Return(&project.Project{ProjectID: 1}, nil)
}
func (suite *ScanTestSuite) TestStopScan() {
times := 3
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
url := "/projects/library/repositories/nginx/artifacts/sha256:e4f0474a75c510f40b37b6b7dc2516241ffa8bde5a442bde3d372c9519c84d90/scan/stop"
{
// failed to get artifact by reference
mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, fmt.Errorf("failed to get artifact by reference")).Once()
res, err := suite.Post(url, nil)
suite.NoError(err)
suite.Equal(500, res.StatusCode)
}
{
// get nil artifact by reference
mock.OnAnything(suite.artifactCtl, "GetByReference").Return(nil, nil).Once()
mock.OnAnything(suite.scanCtl, "Stop").Return(fmt.Errorf("nil artifact to stop scan")).Once()
res, err := suite.Post(url, nil)
suite.NoError(err)
suite.Equal(500, res.StatusCode)
}
{
// successfully stop scan artifact
mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, nil).Once()
mock.OnAnything(suite.scanCtl, "Stop").Return(nil).Once()
res, err := suite.Post(url, nil)
suite.NoError(err)
suite.Equal(202, res.StatusCode)
}
}
func TestScanTestSuite(t *testing.T) {
suite.Run(t, &ScanTestSuite{})
}

View File

@ -175,3 +175,17 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) (
return r0, r1
}
// Stop provides a mock function with given fields: ctx, _a1
func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact) error {
ret := _m.Called(ctx, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) error); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}