add robot mgr

the robot account manager to handle the CRUD

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2020-11-03 22:24:29 -08:00
parent ca8cb87790
commit 3550b5e5e9
9 changed files with 791 additions and 0 deletions

View File

@ -1,4 +1,5 @@
ALTER TABLE schedule ADD COLUMN IF NOT EXISTS cron_type varchar(64);
ALTER TABLE robot ADD COLUMN IF NOT EXISTS secret varchar(2048);
DO $$
DECLARE

139
src/pkg/robot2/dao/dao.go Normal file
View File

@ -0,0 +1,139 @@
package dao
import (
"context"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/robot2/model"
"time"
)
// DAO defines the interface to access the robot data model
type DAO interface {
// Create ...
Create(ctx context.Context, r *model.Robot) (int64, error)
// Update ...
Update(ctx context.Context, r *model.Robot, props ...string) error
// Get ...
Get(ctx context.Context, id int64) (*model.Robot, error)
// Count returns the total count of robots according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List ...
List(ctx context.Context, query *q.Query) ([]*model.Robot, error)
// Delete ...
Delete(ctx context.Context, id int64) error
// DeleteByProjectID ...
DeleteByProjectID(ctx context.Context, projectID int64) error
}
// New creates a default implementation for Dao
func New() DAO {
return &dao{}
}
type dao struct{}
func (d *dao) Create(ctx context.Context, r *model.Robot) (int64, error) {
ormer, err := orm.FromContext(ctx)
if err != nil {
return 0, err
}
r.CreationTime = time.Now()
id, err := ormer.Insert(r)
if err != nil {
return 0, orm.WrapConflictError(err, "robot account %d:%s already exists", r.ProjectID, r.Name)
}
return id, err
}
func (d *dao) Update(ctx context.Context, r *model.Robot, props ...string) error {
ormer, err := orm.FromContext(ctx)
if err != nil {
return err
}
n, err := ormer.Update(r, props...)
if err != nil {
return err
}
if n == 0 {
return errors.NotFoundError(nil).WithMessage("robot %d not found", r.ID)
}
return nil
}
func (d *dao) Get(ctx context.Context, id int64) (*model.Robot, error) {
r := &model.Robot{
ID: id,
}
ormer, err := orm.FromContext(ctx)
if err != nil {
return nil, err
}
if err := ormer.Read(r); err != nil {
return nil, orm.WrapNotFoundError(err, "robot %d not found", id)
}
return r, nil
}
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
query = q.MustClone(query)
query.Sorting = ""
query.PageNumber = 0
query.PageSize = 0
qs, err := orm.QuerySetter(ctx, &model.Robot{}, query)
if err != nil {
return 0, err
}
return qs.Count()
}
func (d *dao) Delete(ctx context.Context, id int64) error {
ormer, err := orm.FromContext(ctx)
if err != nil {
return err
}
n, err := ormer.Delete(&model.Robot{
ID: id,
})
if err != nil {
return err
}
if n == 0 {
return errors.NotFoundError(nil).WithMessage("robot account %d not found", id)
}
return nil
}
func (d *dao) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) {
robots := []*model.Robot{}
qs, err := orm.QuerySetter(ctx, &model.Robot{}, query)
if err != nil {
return nil, err
}
if _, err = qs.All(&robots); err != nil {
return nil, err
}
return robots, nil
}
func (d *dao) DeleteByProjectID(ctx context.Context, projectID int64) error {
qs, err := orm.QuerySetter(ctx, &model.Robot{}, &q.Query{
Keywords: map[string]interface{}{
"project_id": projectID,
},
})
if err != nil {
return err
}
_, err = qs.Delete()
return err
}

View File

@ -0,0 +1,156 @@
package dao
import (
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/robot2/model"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
"testing"
)
type DaoTestSuite struct {
htesting.Suite
dao DAO
robotID1 int64
robotID2 int64
robotID3 int64
robotID4 int64
}
func (suite *DaoTestSuite) SetupSuite() {
suite.Suite.SetupSuite()
suite.dao = New()
suite.Suite.ClearTables = []string{"robot"}
suite.robots()
}
func (suite *DaoTestSuite) robots() {
var err error
suite.robotID1, err = suite.dao.Create(orm.Context(), &model.Robot{
Name: "test1",
Description: "test1 description",
ProjectID: 1,
Secret: suite.RandString(10),
})
suite.Nil(err)
suite.robotID2, err = suite.dao.Create(orm.Context(), &model.Robot{
Name: "test2",
Description: "test2 description",
ProjectID: 1,
Secret: suite.RandString(10),
})
suite.Nil(err)
suite.robotID3, err = suite.dao.Create(orm.Context(), &model.Robot{
Name: "test3",
Description: "test3 description",
ProjectID: 1,
Secret: suite.RandString(10),
})
suite.Nil(err)
suite.robotID4, err = suite.dao.Create(orm.Context(), &model.Robot{
Name: "test4",
Description: "test4 description",
ProjectID: 2,
Secret: suite.RandString(10),
})
suite.Nil(err)
}
func (suite *DaoTestSuite) TestCreate() {
r := &model.Robot{
Name: "test1",
Description: "test1 description",
ProjectID: 1,
Secret: suite.RandString(10),
}
_, err := suite.dao.Create(orm.Context(), r)
suite.NotNil(err)
suite.True(errors.IsErr(err, errors.ConflictCode))
}
func (suite *DaoTestSuite) TestDelete() {
err := suite.dao.Delete(orm.Context(), 1234)
suite.Require().NotNil(err)
suite.True(errors.IsErr(err, errors.NotFoundCode))
err = suite.dao.Delete(orm.Context(), suite.robotID2)
suite.Nil(err)
}
func (suite *DaoTestSuite) TestList() {
robots, err := suite.dao.List(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"name": "test3",
},
})
suite.Require().Nil(err)
suite.Equal(suite.robotID3, robots[0].ID)
}
func (suite *DaoTestSuite) TestGet() {
_, err := suite.dao.Get(orm.Context(), 1234)
suite.Require().NotNil(err)
suite.True(errors.IsErr(err, errors.NotFoundCode))
r, err := suite.dao.Get(orm.Context(), suite.robotID3)
suite.Nil(err)
suite.Equal("test3", r.Name)
}
func (suite *DaoTestSuite) TestCount() {
// nil query
total, err := suite.dao.Count(orm.Context(), nil)
suite.Nil(err)
suite.True(total > 0)
// query by name
total, err = suite.dao.Count(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"name": "test3",
},
})
suite.Nil(err)
suite.Equal(int64(1), total)
}
func (suite *DaoTestSuite) TestUpdate() {
r := &model.Robot{
ID: suite.robotID3,
Description: "after test3 update",
}
err := suite.dao.Update(orm.Context(), r)
suite.Nil(err)
r1, err := suite.dao.Get(orm.Context(), r.ID)
suite.Equal("after test3 update", r1.Description)
}
func (suite *DaoTestSuite) TestDeleteByProjectID() {
robots, err := suite.dao.List(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"project_id": 2,
},
})
suite.Equal(1, len(robots))
err = suite.dao.DeleteByProjectID(orm.Context(), 2)
suite.Nil(err)
robots, err = suite.dao.List(orm.Context(), &q.Query{
Keywords: map[string]interface{}{
"project_id": 2,
},
})
suite.Equal(0, len(robots))
}
func TestDaoTestSuite(t *testing.T) {
suite.Run(t, &DaoTestSuite{})
}

85
src/pkg/robot2/manager.go Normal file
View File

@ -0,0 +1,85 @@
package robot2
import (
"context"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/robot2/dao"
"github.com/goharbor/harbor/src/pkg/robot2/model"
)
var (
// Mgr is a global variable for the default robot account manager implementation
Mgr = NewManager()
)
// Manager ...
type Manager interface {
// Get ...
Get(ctx context.Context, id int64) (*model.Robot, error)
// Count returns the total count of robots according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// Create ...
Create(ctx context.Context, m *model.Robot) (int64, error)
// Delete ...
Delete(ctx context.Context, id int64) error
// DeleteByProjectID ...
DeleteByProjectID(ctx context.Context, projectID int64) error
// Update ...
Update(ctx context.Context, m *model.Robot) error
// List ...
List(ctx context.Context, query *q.Query) ([]*model.Robot, error)
}
var _ Manager = &manager{}
type manager struct {
dao dao.DAO
}
// NewManager return a new instance of defaultRobotManager
func NewManager() Manager {
return &manager{
dao: dao.New(),
}
}
// Get ...
func (m *manager) Get(ctx context.Context, id int64) (*model.Robot, error) {
return m.dao.Get(ctx, id)
}
// Count ...
func (m *manager) Count(ctx context.Context, query *q.Query) (total int64, err error) {
return m.dao.Count(ctx, query)
}
// Create ...
func (m *manager) Create(ctx context.Context, r *model.Robot) (int64, error) {
return m.dao.Create(ctx, r)
}
// Delete ...
func (m *manager) Delete(ctx context.Context, id int64) error {
return m.dao.Delete(ctx, id)
}
// DeleteByProjectID ...
func (m *manager) DeleteByProjectID(ctx context.Context, projectID int64) error {
return m.dao.DeleteByProjectID(ctx, projectID)
}
// Update ...
func (m *manager) Update(ctx context.Context, r *model.Robot) error {
return m.dao.Update(ctx, r)
}
// List ...
func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) {
return m.dao.List(ctx, query)
}

View File

@ -0,0 +1,76 @@
package robot2
import (
"context"
"github.com/goharbor/harbor/src/pkg/robot2/model"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/robot2/dao"
"github.com/stretchr/testify/suite"
"testing"
)
type managerTestSuite struct {
suite.Suite
mgr *manager
dao *dao.DAO
}
func (m *managerTestSuite) SetupTest() {
m.dao = &dao.DAO{}
m.mgr = &manager{
dao: m.dao,
}
}
func (m *managerTestSuite) TestCreate() {
m.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
_, err := m.mgr.Create(context.Background(), &model.Robot{})
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestCount() {
m.dao.On("Count", mock.Anything, mock.Anything).Return(int64(1), nil)
n, err := m.mgr.Count(context.Background(), nil)
m.Nil(err)
m.Equal(int64(1), n)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestDelete() {
m.dao.On("Delete", mock.Anything, mock.Anything).Return(nil)
err := m.mgr.Delete(context.Background(), 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestDeleteByProjectID() {
m.dao.On("DeleteByProjectID", mock.Anything, mock.Anything).Return(nil)
err := m.mgr.DeleteByProjectID(context.Background(), 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestUpdate() {
m.dao.On("Update", mock.Anything, mock.Anything).Return(nil)
err := m.mgr.Update(context.Background(), &model.Robot{})
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestList() {
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Robot{
{
ID: 1,
Name: "robot",
},
}, nil)
rpers, err := m.mgr.List(context.Background(), nil)
m.Nil(err)
m.Equal(1, len(rpers))
m.dao.AssertExpectations(m.T())
}
func TestManager(t *testing.T) {
suite.Run(t, &managerTestSuite{})
}

View File

@ -0,0 +1,30 @@
package model
import (
"time"
"github.com/astaxie/beego/orm"
)
func init() {
orm.RegisterModel(&Robot{})
}
// Robot holds the details of a robot.
type Robot struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Name string `orm:"column(name)" json:"name"`
Description string `orm:"column(description)" json:"description"`
Secret string `orm:"column(secret)" json:"secret"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
ExpiresAt int64 `orm:"column(expiresat)" json:"expires_at"`
Disabled bool `orm:"column(disabled)" json:"disabled"`
Visible bool `orm:"column(visible)" json:"-"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
}
// TableName ...
func (r *Robot) TableName() string {
return "robot"
}

View File

@ -31,3 +31,5 @@ package pkg
//go:generate mockery --case snake --dir ../../pkg/robot/dao --name RobotAccountDao --output ./robot/dao --outpkg dao
//go:generate mockery --case snake --dir ../../pkg/rbac --name Manager --output ./rbac --outpkg rbac
//go:generate mockery --case snake --dir ../../pkg/rbac/dao --name DAO --output ./rbac/dao --outpkg dao
//go:generate mockery --case snake --dir ../../pkg/robot2 --name Manager --output ./robot2 --outpkg robot2
//go:generate mockery --case snake --dir ../../pkg/robot2/dao --name DAO --output ./robot2/dao --outpkg dao

View File

@ -0,0 +1,155 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package dao
import (
context "context"
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/pkg/robot2/model"
q "github.com/goharbor/harbor/src/lib/q"
)
// DAO is an autogenerated mock type for the DAO type
type DAO struct {
mock.Mock
}
// Count provides a mock function with given fields: ctx, query
func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
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)
}
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
}
// Create provides a mock function with given fields: ctx, r
func (_m *DAO) Create(ctx context.Context, r *model.Robot) (int64, error) {
ret := _m.Called(ctx, r)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) int64); ok {
r0 = rf(ctx, r)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *model.Robot) error); ok {
r1 = rf(ctx, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, id
func (_m *DAO) 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
}
// DeleteByProjectID provides a mock function with given fields: ctx, projectID
func (_m *DAO) DeleteByProjectID(ctx context.Context, projectID int64) error {
ret := _m.Called(ctx, projectID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, projectID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: ctx, id
func (_m *DAO) Get(ctx context.Context, id int64) (*model.Robot, error) {
ret := _m.Called(ctx, id)
var r0 *model.Robot
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Robot); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Robot)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, query
func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) {
ret := _m.Called(ctx, query)
var r0 []*model.Robot
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Robot); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Robot)
}
}
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
}
// Update provides a mock function with given fields: ctx, r, props
func (_m *DAO) Update(ctx context.Context, r *model.Robot, props ...string) error {
_va := make([]interface{}, len(props))
for _i := range props {
_va[_i] = props[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, r)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *model.Robot, ...string) error); ok {
r0 = rf(ctx, r, props...)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,147 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package robot2
import (
context "context"
model "github.com/goharbor/harbor/src/pkg/robot2/model"
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/lib/q"
)
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// Count provides a mock function with given fields: ctx, query
func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
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)
}
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
}
// Create provides a mock function with given fields: ctx, m
func (_m *Manager) Create(ctx context.Context, m *model.Robot) (int64, error) {
ret := _m.Called(ctx, m)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) int64); ok {
r0 = rf(ctx, m)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *model.Robot) error); ok {
r1 = rf(ctx, m)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, id
func (_m *Manager) 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
}
// DeleteByProjectID provides a mock function with given fields: ctx, projectID
func (_m *Manager) DeleteByProjectID(ctx context.Context, projectID int64) error {
ret := _m.Called(ctx, projectID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, projectID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: ctx, id
func (_m *Manager) Get(ctx context.Context, id int64) (*model.Robot, error) {
ret := _m.Called(ctx, id)
var r0 *model.Robot
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Robot); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Robot)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, query
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) {
ret := _m.Called(ctx, query)
var r0 []*model.Robot
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Robot); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Robot)
}
}
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
}
// Update provides a mock function with given fields: ctx, m
func (_m *Manager) Update(ctx context.Context, m *model.Robot) error {
ret := _m.Called(ctx, m)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) error); ok {
r0 = rf(ctx, m)
} else {
r0 = ret.Error(0)
}
return r0
}