feat(addition-link): only set vuls addition link when artifact scanable (#10892)

1. Add Checker to check the scannable status of the artifact.
2. Only set vulnerabilities addition link when the artifact scanable in the
project.

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2020-03-03 16:41:54 +08:00 committed by GitHub
parent 0d45308fbc
commit 69119b6410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 694 additions and 436 deletions

View File

@ -17,6 +17,7 @@ package artifact
import (
"container/list"
"context"
"errors"
"fmt"
"strings"
"time"
@ -54,6 +55,11 @@ var (
Ctl = NewController()
)
var (
// ErrBreak error to break walk
ErrBreak = errors.New("break")
)
// Controller defines the operations related with artifacts and tags
type Controller interface {
// Ensure the artifact specified by the digest exists under the repository,
@ -453,6 +459,10 @@ func (c *controller) Walk(ctx context.Context, root *Artifact, walkFn func(*Arti
artifact := elem.Value.(*Artifact)
if err := walkFn(artifact); err != nil {
if err == ErrBreak {
return nil
}
return err
}

View File

@ -20,6 +20,7 @@ import (
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/common/models"
artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/goharbor/harbor/src/testing/pkg/repository"
"github.com/stretchr/testify/suite"
@ -30,13 +31,13 @@ type controllerTestSuite struct {
ctl *controller
proMgr *project.FakeManager
repoMgr *repository.FakeManager
artCtl *artifacttesting.FakeController
artCtl *artifacttesting.Controller
}
func (c *controllerTestSuite) SetupTest() {
c.proMgr = &project.FakeManager{}
c.repoMgr = &repository.FakeManager{}
c.artCtl = &artifacttesting.FakeController{}
c.artCtl = &artifacttesting.Controller{}
c.ctl = &controller{
proMgr: c.proMgr,
repoMgr: c.repoMgr,
@ -118,8 +119,8 @@ func (c *controllerTestSuite) TestGetByName() {
func (c *controllerTestSuite) TestDelete() {
art := &artifact.Artifact{}
art.ID = 1
c.artCtl.On("List").Return([]*artifact.Artifact{art}, nil)
c.artCtl.On("Delete").Return(nil)
mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{art}, nil)
mock.OnAnything(c.artCtl, "Delete").Return(nil)
c.repoMgr.On("Delete").Return(nil)
err := c.ctl.Delete(nil, 1)
c.Require().Nil(err)

View File

@ -35,6 +35,7 @@ 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"
scannertesting "github.com/goharbor/harbor/src/testing/api/scanner"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@ -94,7 +95,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
},
}
sc := &MockScannerController{}
sc := &scannertesting.Controller{}
sc.On("GetRegistrationByProject", suite.artifact.NamespaceID).Return(suite.registration, nil)
sc.On("Ping", suite.registration).Return(m, nil)
@ -364,119 +365,6 @@ func (mrm *MockReportManager) GetStats(requester string) (*all.Stats, error) {
return args.Get(0).(*all.Stats), args.Error(1)
}
// MockScannerController ...
type MockScannerController struct {
mock.Mock
}
// ListRegistrations ...
func (msc *MockScannerController) ListRegistrations(query *q.Query) ([]*scanner.Registration, error) {
args := msc.Called(query)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*scanner.Registration), args.Error(1)
}
// CreateRegistration ...
func (msc *MockScannerController) CreateRegistration(registration *scanner.Registration) (string, error) {
args := msc.Called(registration)
return args.String(0), args.Error(1)
}
// GetRegistration ...
func (msc *MockScannerController) GetRegistration(registrationUUID string) (*scanner.Registration, error) {
args := msc.Called(registrationUUID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*scanner.Registration), args.Error(1)
}
// RegistrationExists ...
func (msc *MockScannerController) RegistrationExists(registrationUUID string) bool {
args := msc.Called(registrationUUID)
return args.Bool(0)
}
// UpdateRegistration ...
func (msc *MockScannerController) UpdateRegistration(registration *scanner.Registration) error {
args := msc.Called(registration)
return args.Error(0)
}
// DeleteRegistration ...
func (msc *MockScannerController) DeleteRegistration(registrationUUID string) (*scanner.Registration, error) {
args := msc.Called(registrationUUID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*scanner.Registration), args.Error(1)
}
// SetDefaultRegistration ...
func (msc *MockScannerController) SetDefaultRegistration(registrationUUID string) error {
args := msc.Called(registrationUUID)
return args.Error(0)
}
// SetRegistrationByProject ...
func (msc *MockScannerController) SetRegistrationByProject(projectID int64, scannerID string) error {
args := msc.Called(projectID, scannerID)
return args.Error(0)
}
// GetRegistrationByProject ...
func (msc *MockScannerController) GetRegistrationByProject(projectID int64) (*scanner.Registration, error) {
args := msc.Called(projectID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*scanner.Registration), args.Error(1)
}
// Ping ...
func (msc *MockScannerController) Ping(registration *scanner.Registration) (*v1.ScannerAdapterMetadata, error) {
args := msc.Called(registration)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*v1.ScannerAdapterMetadata), args.Error(1)
}
// GetMetadata ...
func (msc *MockScannerController) GetMetadata(registrationUUID string) (*v1.ScannerAdapterMetadata, error) {
args := msc.Called(registrationUUID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*v1.ScannerAdapterMetadata), args.Error(1)
}
// IsScannerAvailable ...
func (msc *MockScannerController) IsScannerAvailable(projectID int64) (bool, error) {
args := msc.Called(projectID)
return args.Bool(0), args.Error(1)
}
// MockJobServiceClient ...
type MockJobServiceClient struct {
mock.Mock

85
src/api/scan/checker.go Normal file
View File

@ -0,0 +1,85 @@
// 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 scan
import (
"context"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/scanner"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
)
// Checker checker which can check that the artifact is scannable
type Checker interface {
// IsScannable returns true when the artifact is scannable
IsScannable(ctx context.Context, artifact *artifact.Artifact) (bool, error)
}
// NewChecker returns checker
func NewChecker() Checker {
return &checker{
artifactCtl: artifact.Ctl,
scannerCtl: scanner.DefaultController,
scannerMetadatas: map[int64]*v1.ScannerAdapterMetadata{},
}
}
type checker struct {
artifactCtl artifact.Controller
scannerCtl scanner.Controller
scannerMetadatas map[int64]*v1.ScannerAdapterMetadata
}
func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool, error) {
projectID := art.ProjectID
metadata, ok := c.scannerMetadatas[projectID]
if !ok {
registration, err := c.scannerCtl.GetRegistrationByProject(projectID, scanner.WithPing(false))
if err != nil {
return false, err
}
if registration == nil {
return false, nil
}
md, err := c.scannerCtl.Ping(registration)
if err != nil {
return false, err
}
metadata = md
c.scannerMetadatas[projectID] = md
}
var scannable bool
walkFn := func(a *artifact.Artifact) error {
scannable = metadata.HasCapability(a.ManifestMediaType)
if scannable {
return artifact.ErrBreak
}
return nil
}
if err := c.artifactCtl.Walk(ctx, art, walkFn, nil); err != nil {
return false, err
}
return scannable, nil
}

View File

@ -0,0 +1,99 @@
// 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 scan
import (
"context"
"testing"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact"
scannertesting "github.com/goharbor/harbor/src/testing/api/scanner"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/suite"
)
type CheckerTestSuite struct {
suite.Suite
}
func (suite *CheckerTestSuite) new() *checker {
artifactCtl := &artifacttesting.Controller{}
scannerCtl := &scannertesting.Controller{}
return &checker{
artifactCtl: artifactCtl,
scannerCtl: scannerCtl,
scannerMetadatas: map[int64]*v1.ScannerAdapterMetadata{},
}
}
func (suite *CheckerTestSuite) TestScannerNotFound() {
c := suite.new()
{
mock.OnAnything(c.scannerCtl, "GetRegistrationByProject").Return(nil, nil)
isScannable, err := c.IsScannable(context.TODO(), &artifact.Artifact{})
suite.Nil(err)
suite.False(isScannable)
}
}
func (suite *CheckerTestSuite) TestIsScannable() {
c := suite.new()
supportMimeType := "support mime type"
mock.OnAnything(c.scannerCtl, "GetRegistrationByProject").Return(&scanner.Registration{}, nil)
mock.OnAnything(c.scannerCtl, "Ping").Return(&v1.ScannerAdapterMetadata{
Capabilities: []*v1.ScannerCapability{
{ConsumesMimeTypes: []string{supportMimeType}},
},
}, nil)
{
art := &artifact.Artifact{}
mock.OnAnything(c.artifactCtl, "Walk").Return(nil).Once().Run(func(args mock.Arguments) {
walkFn := args.Get(2).(func(*artifact.Artifact) error)
walkFn(art)
})
isScannable, err := c.IsScannable(context.TODO(), art)
suite.Nil(err)
suite.False(isScannable)
}
{
art := &artifact.Artifact{}
art.ManifestMediaType = supportMimeType
mock.OnAnything(c.artifactCtl, "Walk").Return(nil).Once().Run(func(args mock.Arguments) {
walkFn := args.Get(2).(func(*artifact.Artifact) error)
walkFn(art)
})
isScannable, err := c.IsScannable(context.TODO(), art)
suite.Nil(err)
suite.True(isScannable)
}
}
func TestCheckerTestSuite(t *testing.T) {
suite.Run(t, &CheckerTestSuite{})
}

View File

@ -180,7 +180,7 @@ func (bc *basicController) SetRegistrationByProject(projectID int64, registratio
}
// GetRegistrationByProject ...
func (bc *basicController) GetRegistrationByProject(projectID int64) (*scanner.Registration, error) {
func (bc *basicController) GetRegistrationByProject(projectID int64, options ...Option) (*scanner.Registration, error) {
if projectID == 0 {
return nil, errors.New("invalid project ID")
}
@ -222,18 +222,22 @@ func (bc *basicController) GetRegistrationByProject(projectID int64) (*scanner.R
return nil, nil
}
// Get metadata of the configured registration
meta, err := bc.Ping(registration)
if err != nil {
// Not blocked, just logged it
log.Error(errors.Wrap(err, "api controller: get project scanner"))
registration.Health = statusUnhealthy
} else {
registration.Health = statusHealthy
// Fill in some metadata
registration.Adapter = meta.Scanner.Name
registration.Vendor = meta.Scanner.Vendor
registration.Version = meta.Scanner.Version
opts := newOptions(options...)
if opts.Ping {
// Get metadata of the configured registration
meta, err := bc.Ping(registration)
if err != nil {
// Not blocked, just logged it
log.Error(errors.Wrap(err, "api controller: get project scanner"))
registration.Health = statusUnhealthy
} else {
registration.Health = statusHealthy
// Fill in some metadata
registration.Adapter = meta.Scanner.Name
registration.Vendor = meta.Scanner.Vendor
registration.Version = meta.Scanner.Version
}
}
return registration, err

View File

@ -112,7 +112,7 @@ type Controller interface {
// Returns:
// *scanner.Registration : the default scanner registration
// error : non nil error if any errors occurred
GetRegistrationByProject(projectID int64) (*scanner.Registration, error)
GetRegistrationByProject(projectID int64, options ...Option) (*scanner.Registration, error)
// Ping pings Scanner Adapter to test EndpointURL and Authorization settings.
// The implementation is supposed to call the GetMetadata method on scanner.Client.

View File

@ -0,0 +1,48 @@
// 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 scanner
// Options keep the settings/configurations for scanner.
type Options struct {
// Mark the scan triggered by who.
// Identified by the UUID.
Ping bool
}
// Option represents an option item by func template.
// The validation result of the options are marked by nil/non-nil error.
// e.g:
// If the option is required and the input arg is empty,
// then a non nil error should be returned at then.
type Option func(options *Options) error
// WithPing sets the requester option.
func WithPing(ping bool) Option {
return func(options *Options) error {
options.Ping = ping
return nil
}
}
func newOptions(options ...Option) *Options {
opts := &Options{Ping: true}
for _, o := range options {
o(opts)
}
return opts
}

View File

@ -19,12 +19,12 @@ import (
"net/http"
"testing"
sc "github.com/goharbor/harbor/src/api/scanner"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
scannertesting "github.com/goharbor/harbor/src/testing/api/scanner"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sc "github.com/goharbor/harbor/src/api/scanner"
"github.com/stretchr/testify/suite"
)
@ -33,7 +33,7 @@ type ProScannerAPITestSuite struct {
suite.Suite
originC sc.Controller
mockC *MockScannerAPIController
mockC *scannertesting.Controller
}
// TestProScannerAPI is the entry of ProScannerAPITestSuite
@ -44,7 +44,7 @@ func TestProScannerAPI(t *testing.T) {
// SetupSuite prepares testing env
func (suite *ProScannerAPITestSuite) SetupTest() {
suite.originC = sc.DefaultController
m := &MockScannerAPIController{}
m := &scannertesting.Controller{}
sc.DefaultController = m
suite.mockC = m

View File

@ -19,13 +19,11 @@ import (
"net/http"
"testing"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
sc "github.com/goharbor/harbor/src/api/scanner"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
scannertesting "github.com/goharbor/harbor/src/testing/api/scanner"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
@ -39,7 +37,7 @@ type ScannerAPITestSuite struct {
suite.Suite
originC sc.Controller
mockC *MockScannerAPIController
mockC *scannertesting.Controller
}
// TestScannerAPI is the entry of ScannerAPITestSuite
@ -50,7 +48,7 @@ func TestScannerAPI(t *testing.T) {
// SetupSuite prepares testing env
func (suite *ScannerAPITestSuite) SetupTest() {
suite.originC = sc.DefaultController
m := &MockScannerAPIController{}
m := &scannertesting.Controller{}
sc.DefaultController = m
suite.mockC = m
@ -275,99 +273,3 @@ func (suite *ScannerAPITestSuite) mockQuery(r *scanner.Registration) {
}
suite.mockC.On("ListRegistrations", query2).Return(emptyL, nil)
}
// MockScannerAPIController is mock of scanner API controller
type MockScannerAPIController struct {
mock.Mock
}
// ListRegistrations ...
func (m *MockScannerAPIController) ListRegistrations(query *q.Query) ([]*scanner.Registration, error) {
args := m.Called(query)
return args.Get(0).([]*scanner.Registration), args.Error(1)
}
// CreateRegistration ...
func (m *MockScannerAPIController) CreateRegistration(registration *scanner.Registration) (string, error) {
args := m.Called(registration)
return args.String(0), args.Error(1)
}
// GetRegistration ...
func (m *MockScannerAPIController) GetRegistration(registrationUUID string) (*scanner.Registration, error) {
args := m.Called(registrationUUID)
s := args.Get(0)
if s == nil {
return nil, args.Error(1)
}
return s.(*scanner.Registration), args.Error(1)
}
// RegistrationExists ...
func (m *MockScannerAPIController) RegistrationExists(registrationUUID string) bool {
args := m.Called(registrationUUID)
return args.Bool(0)
}
// UpdateRegistration ...
func (m *MockScannerAPIController) UpdateRegistration(registration *scanner.Registration) error {
args := m.Called(registration)
return args.Error(0)
}
// DeleteRegistration ...
func (m *MockScannerAPIController) DeleteRegistration(registrationUUID string) (*scanner.Registration, error) {
args := m.Called(registrationUUID)
s := args.Get(0)
if s == nil {
return nil, args.Error(1)
}
return s.(*scanner.Registration), args.Error(1)
}
// SetDefaultRegistration ...
func (m *MockScannerAPIController) SetDefaultRegistration(registrationUUID string) error {
args := m.Called(registrationUUID)
return args.Error(0)
}
// SetRegistrationByProject ...
func (m *MockScannerAPIController) SetRegistrationByProject(projectID int64, scannerID string) error {
args := m.Called(projectID, scannerID)
return args.Error(0)
}
// GetRegistrationByProject ...
func (m *MockScannerAPIController) GetRegistrationByProject(projectID int64) (*scanner.Registration, error) {
args := m.Called(projectID)
s := args.Get(0)
if s == nil {
return nil, args.Error(1)
}
return s.(*scanner.Registration), args.Error(1)
}
// Ping ...
func (m *MockScannerAPIController) Ping(registration *scanner.Registration) (*v1.ScannerAdapterMetadata, error) {
args := m.Called(registration)
sam := args.Get(0)
if sam == nil {
return nil, args.Error(1)
}
return sam.(*v1.ScannerAdapterMetadata), nil
}
// GetMetadata ...
func (m *MockScannerAPIController) GetMetadata(registrationUUID string) (*v1.ScannerAdapterMetadata, error) {
args := m.Called(registrationUUID)
sam := args.Get(0)
if sam == nil {
return nil, args.Error(1)
}
return sam.(*v1.ScannerAdapterMetadata), nil
}

View File

@ -21,7 +21,6 @@ import (
"time"
"github.com/goharbor/harbor/src/pkg/scan/rest/auth"
"github.com/pkg/errors"
)

View File

@ -65,6 +65,19 @@ type ScannerAdapterMetadata struct {
Properties ScannerProperties `json:"properties"`
}
// HasCapability returns true when mine type of the artifact support by the scanner
func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool {
for _, capability := range md.Capabilities {
for _, mt := range capability.ConsumesMimeTypes {
if mt == mimeType {
return true
}
}
}
return false
}
// Artifact represents an artifact stored in Registry.
type Artifact struct {
// ID of the namespace (project). It will not be sent to scanner adapter.

View File

@ -15,15 +15,17 @@
package registry
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/repository"
ierror "github.com/goharbor/harbor/src/internal/error"
arttesting "github.com/goharbor/harbor/src/testing/api/artifact"
repotesting "github.com/goharbor/harbor/src/testing/api/repository"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/suite"
"net/http"
"net/http/httptest"
"testing"
)
type manifestTestSuite struct {
@ -32,7 +34,7 @@ type manifestTestSuite struct {
originalArtCtl artifact.Controller
originalProxy http.Handler
repoCtl *repotesting.FakeController
artCtl *arttesting.FakeController
artCtl *arttesting.Controller
}
func (m *manifestTestSuite) SetupSuite() {
@ -43,7 +45,7 @@ func (m *manifestTestSuite) SetupSuite() {
func (m *manifestTestSuite) SetupTest() {
m.repoCtl = &repotesting.FakeController{}
m.artCtl = &arttesting.FakeController{}
m.artCtl = &arttesting.Controller{}
repository.Ctl = m.repoCtl
artifact.Ctl = m.artCtl
}
@ -63,7 +65,7 @@ func (m *manifestTestSuite) TestGetManifest() {
req := httptest.NewRequest(http.MethodGet, "/v2/library/hello-world/manifests/latest", nil)
w := &httptest.ResponseRecorder{}
m.artCtl.On("GetByReference").Return(nil, ierror.New(nil).WithCode(ierror.NotFoundCode))
mock.OnAnything(m.artCtl, "GetByReference").Return(nil, ierror.New(nil).WithCode(ierror.NotFoundCode))
getManifest(w, req)
m.Equal(http.StatusNotFound, w.Code)
@ -85,7 +87,7 @@ func (m *manifestTestSuite) TestGetManifest() {
art := &artifact.Artifact{}
art.Digest = "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180"
m.artCtl.On("GetByReference").Return(art, nil)
mock.OnAnything(m.artCtl, "GetByReference").Return(art, nil)
getManifest(w, req)
m.Equal(http.StatusOK, w.Code)
}
@ -95,7 +97,7 @@ func (m *manifestTestSuite) TestDeleteManifest() {
req := httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil)
w := &httptest.ResponseRecorder{}
m.artCtl.On("GetByReference").Return(nil, ierror.New(nil).WithCode(ierror.NotFoundCode))
mock.OnAnything(m.artCtl, "GetByReference").Return(nil, ierror.New(nil).WithCode(ierror.NotFoundCode))
deleteManifest(w, req)
m.Equal(http.StatusNotFound, w.Code)
@ -116,8 +118,8 @@ func (m *manifestTestSuite) TestDeleteManifest() {
})
req = httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil)
w = &httptest.ResponseRecorder{}
m.artCtl.On("GetByReference").Return(&artifact.Artifact{}, nil)
m.artCtl.On("Delete").Return(nil)
mock.OnAnything(m.artCtl, "GetByReference").Return(&artifact.Artifact{}, nil)
mock.OnAnything(m.artCtl, "Delete").Return(nil)
deleteManifest(w, req)
m.Equal(http.StatusAccepted, w.Code)
}
@ -152,7 +154,7 @@ func (m *manifestTestSuite) TestPutManifest() {
req = httptest.NewRequest(http.MethodPut, "/v2/library/hello-world/manifests/latest", nil)
w = &httptest.ResponseRecorder{}
m.repoCtl.On("Ensure").Return(false, 1, nil)
m.artCtl.On("Ensure").Return(true, 1, nil)
mock.OnAnything(m.artCtl, "Ensure").Return(true, int64(1), nil)
putManifest(w, req)
m.Equal(http.StatusCreated, w.Code)
}

View File

@ -18,7 +18,6 @@ import (
"context"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/api/scanner"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
@ -32,40 +31,22 @@ const (
// NewVulAssembler returns vul assembler
func NewVulAssembler(withScanOverview bool) *VulAssembler {
return &VulAssembler{
scanChecker: scan.NewChecker(),
scanCtl: scan.DefaultController,
withScanOverview: withScanOverview,
scanCtl: scan.DefaultController,
scannerCtl: scanner.DefaultController,
scanners: map[int64]bool{},
}
}
// VulAssembler vul assembler
type VulAssembler struct {
artifacts []*model.Artifact
scanCtl scan.Controller
scannerCtl scanner.Controller
scanners map[int64]bool
scanChecker scan.Checker
scanCtl scan.Controller
artifacts []*model.Artifact
withScanOverview bool
}
func (assembler *VulAssembler) hasScanner(ctx context.Context, projectID int64) bool {
value, ok := assembler.scanners[projectID]
if !ok {
scanner, err := assembler.scannerCtl.GetRegistrationByProject(projectID)
if err != nil {
log.Warningf("get scanner for project %d failed, error: %v", projectID, err)
return false
}
value = scanner != nil
assembler.scanners[projectID] = value
}
return value
}
// WithArtifacts set artifacts for the assembler
func (assembler *VulAssembler) WithArtifacts(artifacts ...*model.Artifact) *VulAssembler {
assembler.artifacts = artifacts
@ -78,9 +59,13 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
version := internal.GetAPIVersion(ctx)
for _, artifact := range assembler.artifacts {
hasScanner := assembler.hasScanner(ctx, artifact.ProjectID)
isScannable, err := assembler.scanChecker.IsScannable(ctx, &artifact.Artifact)
if err != nil {
log.Errorf("check the scannable status of %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err)
continue
}
if !hasScanner {
if !isScannable {
continue
}

View File

@ -16,14 +16,11 @@ package assembler
import (
"context"
"fmt"
"testing"
models "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
"github.com/goharbor/harbor/src/testing/api/scan"
"github.com/goharbor/harbor/src/testing/api/scanner"
"github.com/stretchr/testify/mock"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/suite"
)
@ -31,75 +28,48 @@ type VulAssemblerTestSuite struct {
suite.Suite
}
func (suite *VulAssemblerTestSuite) newVulAssembler(withScanOverview bool) (*VulAssembler, *scan.Controller, *scanner.Controller) {
vulAssembler := NewVulAssembler(withScanOverview)
func (suite *VulAssemblerTestSuite) TestScannable() {
checker := &scan.Checker{}
scanCtl := &scan.Controller{}
scannerCtl := &scanner.Controller{}
vulAssembler.scanCtl = scanCtl
vulAssembler.scannerCtl = scannerCtl
assembler := VulAssembler{
scanChecker: checker,
scanCtl: scanCtl,
withScanOverview: true,
}
return vulAssembler, scanCtl, scannerCtl
mock.OnAnything(checker, "IsScannable").Return(true, nil)
summary := map[string]interface{}{"key": "value"}
mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil)
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 1)
suite.Equal(artifact.ScanOverview, summary)
}
func (suite *VulAssemblerTestSuite) TestNotHasScanner() {
{
assembler, _, scannerCtl := suite.newVulAssembler(true)
scannerCtl.On("GetRegistrationByProject", mock.AnythingOfType("int64")).Return(nil, nil)
func (suite *VulAssemblerTestSuite) TestNotScannable() {
checker := &scan.Checker{}
scanCtl := &scan.Controller{}
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 0)
assembler := VulAssembler{
scanChecker: checker,
scanCtl: scanCtl,
withScanOverview: true,
}
{
assembler, _, scannerCtl := suite.newVulAssembler(true)
scannerCtl.On("GetRegistrationByProject", mock.AnythingOfType("int64")).Return(nil, fmt.Errorf("error"))
mock.OnAnything(checker, "IsScannable").Return(false, nil)
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 0)
}
}
summary := map[string]interface{}{"key": "value"}
mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil)
func (suite *VulAssemblerTestSuite) TestHasScanner() {
{
assembler, scanCtl, scannerCtl := suite.newVulAssembler(true)
scannerCtl.On("GetRegistrationByProject", mock.AnythingOfType("int64")).Return(&models.Registration{}, nil)
var art model.Artifact
summary := map[string]interface{}{"key": "value"}
scanCtl.On("GetSummary", mock.AnythingOfType("*v1.Artifact"), mock.AnythingOfType("[]string")).Return(summary, nil)
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 1)
suite.Equal(artifact.ScanOverview, summary)
}
{
assembler, scanCtl, scannerCtl := suite.newVulAssembler(false)
scannerCtl.On("GetRegistrationByProject", mock.AnythingOfType("int64")).Return(&models.Registration{}, nil)
summary := map[string]interface{}{"key": "value"}
scanCtl.On("GetSummary", mock.AnythingOfType("*v1.Artifact"), mock.AnythingOfType("[]string")).Return(summary, nil)
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 1)
suite.Nil(artifact.ScanOverview)
}
{
assembler, scanCtl, scannerCtl := suite.newVulAssembler(true)
scannerCtl.On("GetRegistrationByProject", mock.AnythingOfType("int64")).Return(&models.Registration{}, nil)
scanCtl.On("GetSummary", mock.AnythingOfType("*v1.Artifact"), mock.AnythingOfType("[]string")).Return(nil, fmt.Errorf("error"))
var artifact model.Artifact
suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO()))
suite.Len(artifact.AdditionLinks, 1)
suite.Nil(artifact.ScanOverview)
}
suite.Nil(assembler.WithArtifacts(&art).Assemble(context.TODO()))
suite.Len(art.AdditionLinks, 0)
scanCtl.AssertNotCalled(suite.T(), "GetSummary")
}
func TestVulAssemblerTestSuite(t *testing.T) {

View File

@ -14,5 +14,7 @@
package api
//go:generate mockery -case snake -dir ../../api/artifact -name Controller -output ./artifact -outpkg artifact
//go:generate mockery -case snake -dir ../../api/scan -name Controller -output ./scan -outpkg scan
//go:generate mockery -case snake -dir ../../api/scan -name Checker -output ./scan -outpkg scan
//go:generate mockery -case snake -dir ../../api/scanner -name Controller -output ./scanner -outpkg scanner

View File

@ -1,118 +1,261 @@
// 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.
// Code generated by mockery v1.0.0. DO NOT EDIT.
package artifact
import (
"context"
"time"
context "context"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/pkg/q"
"github.com/stretchr/testify/mock"
artifact "github.com/goharbor/harbor/src/api/artifact"
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/pkg/q"
resolver "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
time "time"
)
// FakeController is a fake artifact controller that implement src/api/artifact.Controller interface
type FakeController struct {
// Controller is an autogenerated mock type for the Controller type
type Controller struct {
mock.Mock
}
// Ensure ...
func (f *FakeController) Ensure(ctx context.Context, repository, digest string, tags ...string) (bool, int64, error) {
args := f.Called()
return args.Bool(0), int64(args.Int(1)), args.Error(2)
}
// AddLabel provides a mock function with given fields: ctx, artifactID, labelID
func (_m *Controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) error {
ret := _m.Called(ctx, artifactID, labelID)
// Count ...
func (f *FakeController) Count(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// List ...
func (f *FakeController) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) {
args := f.Called()
var artifacts []*artifact.Artifact
if args.Get(0) != nil {
artifacts = args.Get(0).([]*artifact.Artifact)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, artifactID, labelID)
} else {
r0 = ret.Error(0)
}
return artifacts, args.Error(1)
return r0
}
// Get ...
func (f *FakeController) Get(ctx context.Context, id int64, option *artifact.Option) (*artifact.Artifact, error) {
args := f.Called()
var art *artifact.Artifact
if args.Get(0) != nil {
art = args.Get(0).(*artifact.Artifact)
// Copy provides a mock function with given fields: ctx, srcRepo, reference, dstRepo
func (_m *Controller) Copy(ctx context.Context, srcRepo string, reference string, dstRepo string) (int64, error) {
ret := _m.Called(ctx, srcRepo, reference, dstRepo)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) int64); ok {
r0 = rf(ctx, srcRepo, reference, dstRepo)
} else {
r0 = ret.Get(0).(int64)
}
return art, args.Error(1)
}
// GetByReference ...
func (f *FakeController) GetByReference(ctx context.Context, repository, reference string, option *artifact.Option) (*artifact.Artifact, error) {
args := f.Called()
var art *artifact.Artifact
if args.Get(0) != nil {
art = args.Get(0).(*artifact.Artifact)
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
r1 = rf(ctx, srcRepo, reference, dstRepo)
} else {
r1 = ret.Error(1)
}
return art, args.Error(1)
return r0, r1
}
// Delete ...
func (f *FakeController) Delete(ctx context.Context, id int64) (err error) {
args := f.Called()
return args.Error(0)
}
// Count provides a mock function with given fields: ctx, query
func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
// Copy ...
func (f *FakeController) Copy(ctx context.Context, srcRepo, ref, dstRepo string) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
// UpdatePullTime ...
func (f *FakeController) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, time time.Time) error {
args := f.Called()
return args.Error(0)
}
// GetAddition ...
func (f *FakeController) GetAddition(ctx context.Context, artifactID int64, addition string) (*resolver.Addition, error) {
args := f.Called()
var res *resolver.Addition
if args.Get(0) != nil {
res = args.Get(0).(*resolver.Addition)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Get(0).(int64)
}
return res, args.Error(1)
var r1 error
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
}
// AddLabel ...
func (f *FakeController) AddLabel(ctx context.Context, artifactID int64, labelID int64) error {
args := f.Called()
return args.Error(0)
// Delete provides a mock function with given fields: ctx, id
func (_m *Controller) Delete(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveLabel ...
func (f *FakeController) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error {
args := f.Called()
return args.Error(0)
// Ensure provides a mock function with given fields: ctx, repository, digest, tags
func (_m *Controller) Ensure(ctx context.Context, repository string, digest string, tags ...string) (bool, int64, error) {
_va := make([]interface{}, len(tags))
for _i := range tags {
_va[_i] = tags[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, repository, digest)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 bool
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...string) bool); ok {
r0 = rf(ctx, repository, digest, tags...)
} else {
r0 = ret.Get(0).(bool)
}
var r1 int64
if rf, ok := ret.Get(1).(func(context.Context, string, string, ...string) int64); ok {
r1 = rf(ctx, repository, digest, tags...)
} else {
r1 = ret.Get(1).(int64)
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, string, ...string) error); ok {
r2 = rf(ctx, repository, digest, tags...)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Walk ...
func (f *FakeController) Walk(ctx context.Context, root *artifact.Artifact, workFn func(*artifact.Artifact) error, option *artifact.Option) error {
args := f.Called()
return args.Error(0)
// Get provides a mock function with given fields: ctx, id, option
func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option) (*artifact.Artifact, error) {
ret := _m.Called(ctx, id, option)
var r0 *artifact.Artifact
if rf, ok := ret.Get(0).(func(context.Context, int64, *artifact.Option) *artifact.Artifact); ok {
r0 = rf(ctx, id, option)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*artifact.Artifact)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64, *artifact.Option) error); ok {
r1 = rf(ctx, id, option)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetAddition provides a mock function with given fields: ctx, artifactID, additionType
func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additionType string) (*resolver.Addition, error) {
ret := _m.Called(ctx, artifactID, additionType)
var r0 *resolver.Addition
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *resolver.Addition); ok {
r0 = rf(ctx, artifactID, additionType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resolver.Addition)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
r1 = rf(ctx, artifactID, additionType)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetByReference provides a mock function with given fields: ctx, repository, reference, option
func (_m *Controller) GetByReference(ctx context.Context, repository string, reference string, option *artifact.Option) (*artifact.Artifact, error) {
ret := _m.Called(ctx, repository, reference, option)
var r0 *artifact.Artifact
if rf, ok := ret.Get(0).(func(context.Context, string, string, *artifact.Option) *artifact.Artifact); ok {
r0 = rf(ctx, repository, reference, option)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*artifact.Artifact)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, *artifact.Option) error); ok {
r1 = rf(ctx, repository, reference, option)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, query, option
func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) {
ret := _m.Called(ctx, query, option)
var r0 []*artifact.Artifact
if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *artifact.Option) []*artifact.Artifact); ok {
r0 = rf(ctx, query, option)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*artifact.Artifact)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query, *artifact.Option) error); ok {
r1 = rf(ctx, query, option)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveLabel provides a mock function with given fields: ctx, artifactID, labelID
func (_m *Controller) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error {
ret := _m.Called(ctx, artifactID, labelID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, artifactID, labelID)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdatePullTime provides a mock function with given fields: ctx, artifactID, tagID, _a3
func (_m *Controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, _a3 time.Time) error {
ret := _m.Called(ctx, artifactID, tagID, _a3)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64, time.Time) error); ok {
r0 = rf(ctx, artifactID, tagID, _a3)
} else {
r0 = ret.Error(0)
}
return r0
}
// Walk provides a mock function with given fields: ctx, root, walkFn, option
func (_m *Controller) Walk(ctx context.Context, root *artifact.Artifact, walkFn func(*artifact.Artifact) error, option *artifact.Option) error {
ret := _m.Called(ctx, root, walkFn, option)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, func(*artifact.Artifact) error, *artifact.Option) error); ok {
r0 = rf(ctx, root, walkFn, option)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,37 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package scan
import (
context "context"
artifact "github.com/goharbor/harbor/src/api/artifact"
mock "github.com/stretchr/testify/mock"
)
// Checker is an autogenerated mock type for the Checker type
type Checker struct {
mock.Mock
}
// IsScannable provides a mock function with given fields: ctx, _a1
func (_m *Checker) IsScannable(ctx context.Context, _a1 *artifact.Artifact) (bool, error) {
ret := _m.Called(ctx, _a1)
var r0 bool
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) bool); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact) error); ok {
r1 = rf(ctx, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -3,9 +3,11 @@
package scanner
import (
q "github.com/goharbor/harbor/src/pkg/q"
apiscanner "github.com/goharbor/harbor/src/api/scanner"
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/pkg/q"
scanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
@ -106,13 +108,20 @@ func (_m *Controller) GetRegistration(registrationUUID string) (*scanner.Registr
return r0, r1
}
// GetRegistrationByProject provides a mock function with given fields: projectID
func (_m *Controller) GetRegistrationByProject(projectID int64) (*scanner.Registration, error) {
ret := _m.Called(projectID)
// GetRegistrationByProject provides a mock function with given fields: projectID, options
func (_m *Controller) GetRegistrationByProject(projectID int64, options ...apiscanner.Option) (*scanner.Registration, error) {
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
}
var _ca []interface{}
_ca = append(_ca, projectID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *scanner.Registration
if rf, ok := ret.Get(0).(func(int64) *scanner.Registration); ok {
r0 = rf(projectID)
if rf, ok := ret.Get(0).(func(int64, ...apiscanner.Option) *scanner.Registration); ok {
r0 = rf(projectID, options...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*scanner.Registration)
@ -120,8 +129,8 @@ func (_m *Controller) GetRegistrationByProject(projectID int64) (*scanner.Regist
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(projectID)
if rf, ok := ret.Get(1).(func(int64, ...apiscanner.Option) error); ok {
r1 = rf(projectID, options...)
} else {
r1 = ret.Error(1)
}

61
src/testing/mock/mock.go Normal file
View File

@ -0,0 +1,61 @@
// 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 mock
import (
"fmt"
"reflect"
"github.com/stretchr/testify/mock"
)
const (
// Anything anything alias of mock.Anything
Anything = mock.Anything
)
var (
// AnythingOfType func alias of mock.AnythingOfType
AnythingOfType = mock.AnythingOfType
)
// Arguments type alias of mock.Arguments
type Arguments = mock.Arguments
type mockable interface {
On(methodName string, arguments ...interface{}) *mock.Call
}
// OnAnything mock method on obj which match any args
func OnAnything(obj interface{}, methodName string) *mock.Call {
m, ok := obj.(mockable)
if !ok {
panic("obj not mockable")
}
v := reflect.ValueOf(obj).MethodByName(methodName)
fnType := v.Type()
if fnType.Kind() != reflect.Func {
panic(fmt.Sprintf("assert: arguments: %s is not a func", v))
}
args := []interface{}{}
for i := 0; i < fnType.NumIn(); i++ {
args = append(args, mock.Anything)
}
return m.On(methodName, args...)
}