diff --git a/src/core/main.go b/src/core/main.go index 2d32c2d57b..854d823878 100755 --- a/src/core/main.go +++ b/src/core/main.go @@ -217,12 +217,10 @@ func main() { log.Fatalf("failed to initialize clair database: %v", err) } - // TODO: change to be internal adapter reg := &scanner.Registration{ Name: "Clair", Description: "The clair scanner adapter", URL: config.ClairAdapterEndpoint(), - IsDefault: true, UseInternalAddr: true, Immutable: true, } diff --git a/src/pkg/scan/init.go b/src/pkg/scan/init.go index cbc14c7953..fdf22848b5 100644 --- a/src/pkg/scan/init.go +++ b/src/pkg/scan/init.go @@ -15,10 +15,16 @@ package scan import ( + "fmt" + "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/pkg/q" - sc "github.com/goharbor/harbor/src/pkg/scan/api/scanner" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + sc "github.com/goharbor/harbor/src/pkg/scan/scanner" +) + +var ( + scannerManager = sc.New() ) // EnsureScanner ensure the scanner which specially name exists in the system @@ -27,13 +33,22 @@ func EnsureScanner(registration *scanner.Registration) error { Keywords: map[string]interface{}{"url": registration.URL}, } - registrations, err := sc.DefaultController.ListRegistrations(q) + // Check if the registration with the url already existing. + registrations, err := scannerManager.List(q) if err != nil { return err } if len(registrations) == 0 { - if _, err := sc.DefaultController.CreateRegistration(registration); err != nil { + defaultReg, err := scannerManager.GetDefault() + if err != nil { + return fmt.Errorf("failed to get the default scanner, error: %v", err) + } + + // Set the registration to be default one when no default registration exist in the system + registration.IsDefault = defaultReg == nil + + if _, err := scannerManager.Create(registration); err != nil { return err } diff --git a/src/pkg/scan/init_test.go b/src/pkg/scan/init_test.go new file mode 100644 index 0000000000..c2a0a08693 --- /dev/null +++ b/src/pkg/scan/init_test.go @@ -0,0 +1,178 @@ +// 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 ( + "testing" + + "github.com/goharbor/harbor/src/pkg/q" + "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + sc "github.com/goharbor/harbor/src/pkg/scan/scanner" + "github.com/goharbor/harbor/src/pkg/scan/scanner/mocks" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type managerOptions struct { + registrations []*scanner.Registration + listError error + getError error + getDefaultError error + createError error +} + +func newManager(opts *managerOptions) sc.Manager { + if opts == nil { + opts = &managerOptions{} + } + + data := map[string]*scanner.Registration{} + for _, reg := range opts.registrations { + data[reg.URL] = reg + } + + mgr := &mocks.Manager{} + + listFn := func(query *q.Query) []*scanner.Registration { + if opts.listError != nil { + return nil + } + + url := query.Keywords["url"] + + var results []*scanner.Registration + for key, reg := range data { + if url == key { + results = append(results, reg) + } + } + + return results + } + + getFn := func(url string) *scanner.Registration { + if opts.getError != nil { + return nil + } + + return data[url] + } + + getDefaultFn := func() *scanner.Registration { + if opts.getDefaultError != nil { + return nil + } + + for _, reg := range data { + if reg.IsDefault { + return reg + } + } + + return nil + } + + createFn := func(reg *scanner.Registration) string { + if opts.createError != nil { + return "" + } + + data[reg.URL] = reg + + return reg.URL + } + + mgr.On("List", mock.AnythingOfType("*q.Query")).Return(listFn, opts.listError) + mgr.On("Get", mock.AnythingOfType("string")).Return(getFn, opts.getError) + mgr.On("GetDefault").Return(getDefaultFn, opts.getDefaultError) + mgr.On("Create", mock.AnythingOfType("*scanner.Registration")).Return(createFn, opts.createError) + + return mgr +} + +func TestEnsureScanner(t *testing.T) { + assert := assert.New(t) + + registrations := []*scanner.Registration{ + {URL: "reg1"}, + } + + // registration with the url exist in the system + scannerManager = newManager( + &managerOptions{ + registrations: registrations, + }, + ) + assert.Nil(EnsureScanner(&scanner.Registration{URL: "reg1"})) + + // list registrations got error + scannerManager = newManager( + &managerOptions{ + listError: errors.New("list registrations internal error"), + }, + ) + assert.Error(EnsureScanner(&scanner.Registration{URL: "reg1"})) + + // create registration got error + scannerManager = newManager( + &managerOptions{ + createError: errors.New("create registration internal error"), + }, + ) + assert.Error(EnsureScanner(&scanner.Registration{URL: "reg1"})) + + // get default registration got error + scannerManager = newManager( + &managerOptions{ + getDefaultError: errors.New("get default registration internal error"), + }, + ) + assert.Error(EnsureScanner(&scanner.Registration{URL: "reg1"})) + + // create registration when no registrations in the system + scannerManager = newManager(nil) + assert.Nil(EnsureScanner(&scanner.Registration{URL: "reg1"})) + reg1, err := scannerManager.Get("reg1") + assert.Nil(err) + assert.NotNil(reg1) + assert.True(reg1.IsDefault) + + // create registration when there are registrations in the system + scannerManager = newManager( + &managerOptions{ + registrations: registrations, + }, + ) + assert.Nil(EnsureScanner(&scanner.Registration{URL: "reg2"})) + reg2, err := scannerManager.Get("reg2") + assert.Nil(err) + assert.NotNil(reg2) + assert.True(reg2.IsDefault) + + // create registration when there are registrations in the system and the default registration exist + scannerManager = newManager( + &managerOptions{ + registrations: []*scanner.Registration{ + {URL: "reg1", IsDefault: true}, + }, + }, + ) + assert.Nil(EnsureScanner(&scanner.Registration{URL: "reg3"})) + reg3, err := scannerManager.Get("reg3") + assert.Nil(err) + assert.NotNil(reg3) + assert.False(reg3.IsDefault) +} diff --git a/src/pkg/scan/scanner/mocks/manager.go b/src/pkg/scan/scanner/mocks/manager.go new file mode 100644 index 0000000000..15731b986d --- /dev/null +++ b/src/pkg/scan/scanner/mocks/manager.go @@ -0,0 +1,147 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + q "github.com/goharbor/harbor/src/pkg/q" + mock "github.com/stretchr/testify/mock" + + scanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" +) + +// Manager is an autogenerated mock type for the Manager type +type Manager struct { + mock.Mock +} + +// Create provides a mock function with given fields: registration +func (_m *Manager) Create(registration *scanner.Registration) (string, error) { + ret := _m.Called(registration) + + var r0 string + if rf, ok := ret.Get(0).(func(*scanner.Registration) string); ok { + r0 = rf(registration) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(*scanner.Registration) error); ok { + r1 = rf(registration) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: registrationUUID +func (_m *Manager) Delete(registrationUUID string) error { + ret := _m.Called(registrationUUID) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(registrationUUID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Get provides a mock function with given fields: registrationUUID +func (_m *Manager) Get(registrationUUID string) (*scanner.Registration, error) { + ret := _m.Called(registrationUUID) + + var r0 *scanner.Registration + if rf, ok := ret.Get(0).(func(string) *scanner.Registration); ok { + r0 = rf(registrationUUID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*scanner.Registration) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(registrationUUID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDefault provides a mock function with given fields: +func (_m *Manager) GetDefault() (*scanner.Registration, error) { + ret := _m.Called() + + var r0 *scanner.Registration + if rf, ok := ret.Get(0).(func() *scanner.Registration); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*scanner.Registration) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// List provides a mock function with given fields: query +func (_m *Manager) List(query *q.Query) ([]*scanner.Registration, error) { + ret := _m.Called(query) + + var r0 []*scanner.Registration + if rf, ok := ret.Get(0).(func(*q.Query) []*scanner.Registration); ok { + r0 = rf(query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*scanner.Registration) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*q.Query) error); ok { + r1 = rf(query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetAsDefault provides a mock function with given fields: registrationUUID +func (_m *Manager) SetAsDefault(registrationUUID string) error { + ret := _m.Called(registrationUUID) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(registrationUUID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: registration +func (_m *Manager) Update(registration *scanner.Registration) error { + ret := _m.Called(registration) + + var r0 error + if rf, ok := ret.Get(0).(func(*scanner.Registration) error); ok { + r0 = rf(registration) + } else { + r0 = ret.Error(0) + } + + return r0 +}