refactor(project): add more methods to project controller and manager

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2020-07-31 17:55:35 +00:00
parent 9ce29c9dc8
commit df1bdc1020
53 changed files with 2434 additions and 390 deletions

View File

@ -120,6 +120,9 @@ func ClearTable(table string) error {
if table == models.UserTable {
sql = fmt.Sprintf("delete from %s where user_id > 2", table)
}
if table == "project_member" { // make sure admin in library
sql = fmt.Sprintf("delete from %s where id > 1", table)
}
if table == "project_metadata" { // make sure library is public
sql = fmt.Sprintf("delete from %s where id > 1", table)
}

View File

@ -68,7 +68,7 @@ func TestMain(m *testing.M) {
"delete from project where name='group_project_private'",
"delete from harbor_user where username='member_test_01' or username='pm_sample' or username='grouptestu09'",
"delete from user_group",
"delete from project_member",
"delete from project_member where id > 1",
}
dao.ExecuteBatchSQL(initSqls)
defer dao.ExecuteBatchSQL(clearSqls)

View File

@ -16,11 +16,12 @@ package project
import (
"fmt"
"os"
"testing"
"github.com/goharbor/harbor/src/common/dao/group"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
@ -67,7 +68,7 @@ func TestMain(m *testing.M) {
"delete from project where name='member_test_01' or name='member_test_02'",
"delete from harbor_user where username='member_test_01' or username='member_test_02' or username='pm_sample'",
"delete from user_group",
"delete from project_member",
"delete from project_member where id > 1",
}
dao.PrepareTestData(clearSqls, initSqls)
cfg.Init()

View File

@ -25,14 +25,6 @@ type Member struct {
EntityType string `orm:"column(entity_type)" json:"entity_type"`
}
// UserMember ...
type UserMember struct {
ID int `orm:"pk;column(user_id)" json:"user_id"`
Username string `json:"username"`
Rolename string `json:"role_name"`
Role int `json:"role_id"`
}
// MemberReq - Create Project Member Request
type MemberReq struct {
ProjectID int64 `json:"project_id"`

View File

@ -36,5 +36,4 @@ type ProjectMetadata struct {
Value string `orm:"column(value)" json:"value"`
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"`
Deleted bool `orm:"column(deleted)" json:"deleted"`
}

View File

@ -15,9 +15,14 @@
package models
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/quota/types"
"github.com/goharbor/harbor/src/replication/model"
)
@ -121,9 +126,67 @@ func (p *Project) AutoScan() bool {
return isTrue(auto)
}
func isTrue(value string) bool {
return strings.ToLower(value) == "true" ||
strings.ToLower(value) == "1"
// FilterByPublic returns orm.QuerySeter with public filter
func (p *Project) FilterByPublic(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
subQuery := `SELECT project_id FROM project_metadata WHERE name = 'public' AND value = '%s'`
if isTrue(value) {
subQuery = fmt.Sprintf(subQuery, "true")
} else {
subQuery = fmt.Sprintf(subQuery, "false")
}
return qs.FilterRaw("project_id", fmt.Sprintf("IN (%s)", subQuery))
}
// FilterByOwner returns orm.QuerySeter with owner filter
func (p *Project) FilterByOwner(ctx context.Context, qs orm.QuerySeter, key string, value string) orm.QuerySeter {
return qs.FilterRaw("owner_id", fmt.Sprintf("IN (SELECT user_id FROM harbor_user WHERE username = '%s')", value))
}
// FilterByMember returns orm.QuerySeter with member filter
func (p *Project) FilterByMember(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
query, ok := value.(*MemberQuery)
if !ok {
return qs
}
subQuery := `SELECT project_id FROM project_member pm, harbor_user u WHERE pm.entity_id = u.user_id AND pm.entity_type = 'u' AND u.username = '%s'`
subQuery = fmt.Sprintf(subQuery, escape(query.Name))
if query.Role > 0 {
subQuery = fmt.Sprintf("%s AND pm.role = %d", subQuery, query.Role)
}
if len(query.GroupIDs) > 0 {
var elems []string
for _, groupID := range query.GroupIDs {
elems = append(elems, strconv.Itoa(groupID))
}
tpl := "(%s) UNION (SELECT project_id FROM project_member pm, user_group ug WHERE pm.entity_id = ug.id AND pm.entity_type = 'g' AND ug.id IN (%s))"
subQuery = fmt.Sprintf(tpl, subQuery, strings.TrimSpace(strings.Join(elems, ", ")))
}
return qs.FilterRaw("project_id", fmt.Sprintf("IN (%s)", subQuery))
}
func isTrue(i interface{}) bool {
switch value := i.(type) {
case bool:
return value
case string:
v := strings.ToLower(value)
return v == "true" || v == "1"
default:
return false
}
}
func escape(str string) string {
// TODO: remove this function when resolve the cycle import between lib/orm, common/dao and common/models.
// We need to move the function to registry the database from common/dao to lib/database,
// then lib/orm no need to depend the PrepareTestForPostgresSQL method in the common/dao
str = strings.Replace(str, `\`, `\\`, -1)
str = strings.Replace(str, `%`, `\%`, -1)
str = strings.Replace(str, `_`, `\_`, -1)
return str
}
// ProjectQueryParam can be used to set query parameters when listing projects.
@ -147,6 +210,36 @@ type ProjectQueryParam struct {
ProjectIDs []int64 // project ID list
}
// ToQuery returns q.Query from param
func (param *ProjectQueryParam) ToQuery() *q.Query {
kw := q.KeyWords{}
if param.Name != "" {
kw["name"] = q.FuzzyMatchValue{Value: param.Name}
}
if param.Owner != "" {
kw["owner"] = param.Owner
}
if param.Public != nil {
kw["public"] = *param.Public
}
if param.RegistryID != 0 {
kw["registry_id"] = param.RegistryID
}
if len(param.ProjectIDs) > 0 {
kw["project_id__in"] = param.ProjectIDs
}
if param.Member != nil {
kw["member"] = param.Member
}
query := q.New(kw)
if param.Pagination != nil {
query.PageNumber = param.Pagination.Page
query.PageSize = param.Pagination.Size
}
return query
}
// MemberQuery filter by member's username and role
type MemberQuery struct {
Name string // the username of member

View File

@ -15,6 +15,9 @@
package proxycachesecret
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
@ -31,14 +34,16 @@ const (
// SecurityContext is the security context for proxy cache secret
type SecurityContext struct {
repository string
mgr project.Manager
getProject func(interface{}) (*models.Project, error)
}
// NewSecurityContext returns an instance of the proxy cache secret security context
func NewSecurityContext(repository string) *SecurityContext {
func NewSecurityContext(ctx context.Context, repository string) *SecurityContext {
return &SecurityContext{
repository: repository,
mgr: project.Mgr,
getProject: func(i interface{}) (*models.Project, error) {
return project.Mgr.Get(ctx, i)
},
}
}
@ -78,7 +83,7 @@ func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool
log.Debugf("got no namespace from the resource %s", resource)
return false
}
project, err := s.mgr.Get(namespace.Identity())
project, err := s.getProject(namespace.Identity())
if err != nil {
log.Errorf("failed to get project %v: %v", namespace.Identity(), err)
return false

View File

@ -15,6 +15,7 @@
package proxycachesecret
import (
"context"
"testing"
"github.com/goharbor/harbor/src/common/models"
@ -27,14 +28,16 @@ import (
type proxyCacheSecretTestSuite struct {
suite.Suite
sc *SecurityContext
mgr *project.FakeManager
mgr *project.Manager
}
func (p *proxyCacheSecretTestSuite) SetupTest() {
p.mgr = &project.FakeManager{}
p.mgr = &project.Manager{}
p.sc = &SecurityContext{
repository: "library/hello-world",
mgr: p.mgr,
getProject: func(i interface{}) (*models.Project, error) {
return p.mgr.Get(context.TODO(), i)
},
}
}
@ -72,7 +75,7 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
// the requested project not found
action = rbac.ActionPull
resource = rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(nil, nil)
p.mgr.On("Get", mock.Anything, mock.Anything).Return(nil, nil)
p.False(p.sc.Can(action, resource))
p.mgr.AssertExpectations(p.T())
@ -82,7 +85,7 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
// pass for action pull
action = rbac.ActionPull
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(&models.Project{
p.mgr.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
ProjectID: 1,
Name: "library",
}, nil)
@ -95,7 +98,7 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
// pass for action push
action = rbac.ActionPush
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(&models.Project{
p.mgr.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
ProjectID: 1,
Name: "library",
}, nil)

View File

@ -5,12 +5,12 @@ import (
"strings"
registry_token "github.com/docker/distribution/registry/auth/token"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/project/models"
)
// tokenSecurityCtx is used for check permission of an internal signed token.
@ -20,7 +20,7 @@ type tokenSecurityCtx struct {
logger *log.Logger
name string
accessMap map[string]map[types.Action]struct{}
pm project.Manager
getProject func(int64) (*models.Project, error)
}
func (t *tokenSecurityCtx) Name() string {
@ -65,7 +65,7 @@ func (t *tokenSecurityCtx) Can(action types.Action, resource types.Resource) boo
t.logger.Warningf("Failed to get project id from namespace: %s", ns)
return false
}
p, err := t.pm.Get(pid)
p, err := t.getProject(pid)
if err != nil {
t.logger.Warningf("Failed to get project, id: %d, error: %v", pid, err)
return false
@ -109,10 +109,13 @@ func New(ctx context.Context, name string, access []*registry_token.ResourceActi
}
m[l[0]] = actionMap
}
return &tokenSecurityCtx{
logger: logger,
name: name,
accessMap: m,
pm: project.New(),
getProject: func(id int64) (*models.Project, error) {
return project.Mgr.Get(ctx, id)
},
}
}

View File

@ -1,22 +1,38 @@
// 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 v2token
import (
"context"
"testing"
"github.com/docker/distribution/registry/auth/token"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/project/models"
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestAll(t *testing.T) {
mgr := &project.FakeManager{}
mgr.On("Get", int64(1)).Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
mgr.On("Get", int64(2)).Return(&models.Project{ProjectID: 2, Name: "test"}, nil)
mgr.On("Get", int64(3)).Return(&models.Project{ProjectID: 3, Name: "development"}, nil)
ctx := context.TODO()
mgr := &project.Manager{}
mgr.On("Get", ctx, int64(1)).Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
mgr.On("Get", ctx, int64(2)).Return(&models.Project{ProjectID: 2, Name: "test"}, nil)
mgr.On("Get", ctx, int64(3)).Return(&models.Project{ProjectID: 3, Name: "development"}, nil)
access := []*token.ResourceActions{
{
@ -47,7 +63,9 @@ func TestAll(t *testing.T) {
}
sc := New(context.Background(), "jack", access)
tsc := sc.(*tokenSecurityCtx)
tsc.pm = mgr
tsc.getProject = func(id int64) (*models.Project, error) {
return mgr.Get(ctx, id)
}
cases := []struct {
resource types.Resource

View File

@ -1,7 +1,20 @@
// 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 artifact
import (
"context"
"testing"
"time"
@ -15,6 +28,8 @@ import (
"github.com/goharbor/harbor/src/replication"
daoModels "github.com/goharbor/harbor/src/replication/dao/models"
"github.com/goharbor/harbor/src/replication/model"
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -31,9 +46,6 @@ type fakedReplicationMgr struct {
type fakedReplicationRegistryMgr struct {
}
type fakedProjectCtl struct {
}
func (f *fakedNotificationPolicyMgr) Create(*models.NotificationPolicy) (int64, error) {
return 0, nil
}
@ -196,16 +208,6 @@ func (f *fakedReplicationRegistryMgr) HealthCheck() error {
return nil
}
func (f *fakedProjectCtl) Get(ctx context.Context, projectID int64, options ...project.Option) (*models.Project, error) {
return &models.Project{ProjectID: 1}, nil
}
func (f *fakedProjectCtl) GetByName(ctx context.Context, projectName string, options ...project.Option) (*models.Project, error) {
return &models.Project{ProjectID: 1}, nil
}
func (f *fakedProjectCtl) List(ctx context.Context, query *models.ProjectQueryParam, options ...project.Option) ([]*models.Project, error) {
return nil, nil
}
func TestReplicationHandler_Handle(t *testing.T) {
common_dao.PrepareTestForPostgresSQL()
config.Init()
@ -227,7 +229,10 @@ func TestReplicationHandler_Handle(t *testing.T) {
replication.OperationCtl = &fakedReplicationMgr{}
replication.PolicyCtl = &fakedReplicationPolicyMgr{}
replication.RegistryMgr = &fakedReplicationRegistryMgr{}
project.Ctl = &fakedProjectCtl{}
projectCtl := &projecttesting.Controller{}
project.Ctl = projectCtl
mock.OnAnything(projectCtl, "GetByName").Return(&models.Project{ProjectID: 1}, nil)
handler := &ReplicationHandler{}

View File

@ -17,12 +17,15 @@ package project
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/promgr/metamgr"
"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/project"
"github.com/goharbor/harbor/src/pkg/project/metadata"
"github.com/goharbor/harbor/src/pkg/project/models"
"github.com/goharbor/harbor/src/pkg/scan/allowlist"
"github.com/goharbor/harbor/src/pkg/user"
)
var (
@ -32,6 +35,12 @@ var (
// Controller defines the operations related with blobs
type Controller interface {
// Create create project instance
Create(ctx context.Context, project *models.Project) (int64, error)
// Count returns the total count of projects according to the query
Count(ctx context.Context, query *q.Query) (int64, error)
// Delete delete the project by project id
Delete(ctx context.Context, id int64) error
// Get get the project by project id
Get(ctx context.Context, projectID int64, options ...Option) (*models.Project, error)
// GetByName get the project by project name
@ -44,27 +53,69 @@ type Controller interface {
func NewController() Controller {
return &controller{
projectMgr: project.Mgr,
metaMgr: metamgr.NewDefaultProjectMetadataManager(),
metaMgr: metadata.Mgr,
allowlistMgr: allowlist.NewDefaultManager(),
userMgr: user.Mgr,
}
}
type controller struct {
projectMgr project.Manager
metaMgr metamgr.ProjectMetadataManager
metaMgr metadata.Manager
allowlistMgr allowlist.Manager
userMgr user.Manager
}
func (c *controller) Create(ctx context.Context, project *models.Project) (int64, error) {
var projectID int64
h := func(ctx context.Context) (err error) {
projectID, err = c.projectMgr.Create(ctx, project)
if err != nil {
return err
}
if err := c.allowlistMgr.CreateEmpty(projectID); err != nil {
log.Errorf("failed to create CVE allowlist for project %s: %v", project.Name, err)
return err
}
if len(project.Metadata) > 0 {
if err = c.metaMgr.Add(ctx, projectID, project.Metadata); err != nil {
log.Errorf("failed to add metadata for project %s: %v", project.Name, err)
return err
}
}
return nil
}
if err := orm.WithTransaction(h)(ctx); err != nil {
return 0, err
}
return projectID, nil
}
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.projectMgr.Count(ctx, query)
}
func (c *controller) Delete(ctx context.Context, id int64) error {
return c.projectMgr.Delete(ctx, id)
}
func (c *controller) Get(ctx context.Context, projectID int64, options ...Option) (*models.Project, error) {
p, err := c.projectMgr.Get(projectID)
p, err := c.projectMgr.Get(ctx, projectID)
if err != nil {
return nil, err
}
if p == nil {
return nil, errors.NotFoundError(nil).WithMessage("project %d not found", projectID)
}
return c.assembleProject(ctx, p, newOptions(options...))
opts := newOptions(options...)
if opts.WithOwner {
if err := c.loadOwners(ctx, models.Projects{p}); err != nil {
return nil, err
}
}
return c.assembleProject(ctx, p, opts)
}
func (c *controller) GetByName(ctx context.Context, projectName string, options ...Option) (*models.Project, error) {
@ -72,24 +123,33 @@ func (c *controller) GetByName(ctx context.Context, projectName string, options
return nil, errors.BadRequestError(nil).WithMessage("project name required")
}
p, err := c.projectMgr.Get(projectName)
if err != nil {
return nil, err
}
if p == nil {
return nil, errors.NotFoundError(nil).WithMessage("project %s not found", projectName)
}
return c.assembleProject(ctx, p, newOptions(options...))
}
func (c *controller) List(ctx context.Context, query *models.ProjectQueryParam, options ...Option) ([]*models.Project, error) {
projects, err := c.projectMgr.List(query)
p, err := c.projectMgr.Get(ctx, projectName)
if err != nil {
return nil, err
}
opts := newOptions(options...)
if opts.WithOwner {
if err := c.loadOwners(ctx, models.Projects{p}); err != nil {
return nil, err
}
}
return c.assembleProject(ctx, p, newOptions(options...))
}
func (c *controller) List(ctx context.Context, query *models.ProjectQueryParam, options ...Option) ([]*models.Project, error) {
projects, err := c.projectMgr.List(ctx, query)
if err != nil {
return nil, err
}
opts := newOptions(options...)
if opts.WithOwner {
if err := c.loadOwners(ctx, projects); err != nil {
return nil, err
}
}
for _, p := range projects {
if _, err := c.assembleProject(ctx, p, opts); err != nil {
return nil, err
@ -99,9 +159,28 @@ func (c *controller) List(ctx context.Context, query *models.ProjectQueryParam,
return projects, nil
}
func (c *controller) loadOwners(ctx context.Context, projects models.Projects) error {
owners, err := c.userMgr.List(ctx, q.New(q.KeyWords{"user_id__in": projects.OwnerIDs()}))
if err != nil {
return err
}
m := owners.MapByUserID()
for _, p := range projects {
owner, ok := m[p.OwnerID]
if !ok {
log.G(ctx).Warningf("the owner of project %s is not found, owner id is %d", p.Name, p.OwnerID)
continue
}
p.OwnerName = owner.Username
}
return nil
}
func (c *controller) assembleProject(ctx context.Context, p *models.Project, opts *Options) (*models.Project, error) {
if opts.Metadata {
meta, err := c.metaMgr.Get(p.ProjectID)
meta, err := c.metaMgr.Get(ctx, p.ProjectID)
if err != nil {
return nil, err
}

View File

@ -19,9 +19,17 @@ import (
"fmt"
"testing"
"github.com/goharbor/harbor/src/common/models"
commonmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/project/models"
usermodels "github.com/goharbor/harbor/src/pkg/user/models"
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/goharbor/harbor/src/testing/pkg/project/metadata"
"github.com/goharbor/harbor/src/testing/pkg/scan/allowlist"
"github.com/goharbor/harbor/src/testing/pkg/user"
"github.com/stretchr/testify/suite"
)
@ -29,34 +37,115 @@ type ControllerTestSuite struct {
suite.Suite
}
func (suite *ControllerTestSuite) TestGetByName() {
mgr := &project.FakeManager{}
mgr.On("Get", "library").Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
mgr.On("Get", "test").Return(nil, nil)
mgr.On("Get", "oops").Return(nil, fmt.Errorf("oops"))
func (suite *ControllerTestSuite) TestCreate() {
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
mgr := &project.Manager{}
c := controller{projectMgr: mgr}
allowlistMgr := &allowlist.Manager{}
allowlistMgr.On("CreateEmpty", mock.Anything).Return(nil)
metadataMgr := &metadata.Manager{}
c := controller{projectMgr: mgr, allowlistMgr: allowlistMgr, metaMgr: metadataMgr}
{
p, err := c.GetByName(context.TODO(), "library", Metadata(false))
metadataMgr.On("Add", ctx, mock.Anything, mock.Anything).Return(nil).Once()
mgr.On("Create", ctx, mock.Anything).Return(int64(2), nil).Once()
projectID, err := c.Create(ctx, &models.Project{OwnerID: 1, Metadata: map[string]string{"public": "true"}})
suite.Nil(err)
suite.Equal(int64(2), projectID)
}
{
metadataMgr.On("Add", ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("oops")).Once()
mgr.On("Create", ctx, mock.Anything).Return(int64(2), nil).Once()
projectID, err := c.Create(ctx, &models.Project{OwnerID: 1, Metadata: map[string]string{"public": "true"}})
suite.Error(err)
suite.Equal(int64(0), projectID)
}
}
func (suite *ControllerTestSuite) TestGetByName() {
ctx := context.TODO()
mgr := &project.Manager{}
mgr.On("Get", ctx, "library").Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
mgr.On("Get", ctx, "test").Return(nil, errors.NotFoundError(nil))
mgr.On("Get", ctx, "oops").Return(nil, fmt.Errorf("oops"))
allowlistMgr := &allowlist.Manager{}
metadataMgr := &metadata.Manager{}
metadataMgr.On("Get", ctx, mock.Anything).Return(map[string]string{"public": "true"}, nil)
c := controller{projectMgr: mgr, allowlistMgr: allowlistMgr, metaMgr: metadataMgr}
{
p, err := c.GetByName(ctx, "library")
suite.Nil(err)
suite.Equal("library", p.Name)
suite.Equal(int64(1), p.ProjectID)
}
{
p, err := c.GetByName(context.TODO(), "test", Metadata(false))
p, err := c.GetByName(ctx, "test")
suite.Error(err)
suite.True(errors.IsNotFoundErr(err))
suite.Nil(p)
}
{
p, err := c.GetByName(context.TODO(), "oops", Metadata(false))
p, err := c.GetByName(ctx, "oops")
suite.Error(err)
suite.False(errors.IsNotFoundErr(err))
suite.Nil(p)
}
{
allowlistMgr.On("GetSys").Return(&commonmodels.CVEAllowlist{}, nil)
p, err := c.GetByName(ctx, "library", CVEAllowlist(true))
suite.Nil(err)
suite.Equal("library", p.Name)
suite.Equal(p.ProjectID, p.CVEAllowlist.ProjectID)
}
}
func (suite *ControllerTestSuite) TestWithOwner() {
ctx := context.TODO()
mgr := &project.Manager{}
mgr.On("Get", ctx, int64(1)).Return(&models.Project{ProjectID: 1, OwnerID: 1, Name: "library"}, nil)
mgr.On("Get", ctx, "library").Return(&models.Project{ProjectID: 1, OwnerID: 1, Name: "library"}, nil)
mgr.On("List", ctx, mock.Anything).Return([]*models.Project{
{ProjectID: 1, OwnerID: 1, Name: "library"},
}, nil)
userMgr := &user.Manager{}
userMgr.On("List", ctx, mock.Anything).Return(usermodels.Users{
&usermodels.User{UserID: 1, Username: "admin"},
}, nil)
c := controller{projectMgr: mgr, userMgr: userMgr}
{
project, err := c.Get(ctx, int64(1), Metadata(false), WithOwner())
suite.Nil(err)
suite.Equal("admin", project.OwnerName)
}
{
project, err := c.GetByName(ctx, "library", Metadata(false), WithOwner())
suite.Nil(err)
suite.Equal("admin", project.OwnerName)
}
{
param := &models.ProjectQueryParam{ProjectIDs: []int64{1}}
projects, err := c.List(ctx, param, Metadata(false), WithOwner())
suite.Nil(err)
suite.Len(projects, 1)
suite.Equal("admin", projects[0].OwnerName)
}
}
func TestControllerTestSuite(t *testing.T) {

View File

@ -21,6 +21,7 @@ type Option func(*Options)
type Options struct {
CVEAllowlist bool // get project with cve allowlist
Metadata bool // get project with metadata
WithOwner bool
}
// CVEAllowlist set CVEAllowlist for the Options
@ -37,6 +38,13 @@ func Metadata(metadata bool) Option {
}
}
// WithOwner set WithOwner for the Options
func WithOwner() Option {
return func(opts *Options) {
opts.WithOwner = true
}
}
func newOptions(options ...Option) *Options {
opts := &Options{
Metadata: true, // default get project with metadata

View File

@ -43,7 +43,7 @@ func getProjectsBatchFn(ctx context.Context, keys dataloader.Keys) []*dataloader
projectIDs = append(projectIDs, id)
}
projects, err := project.Mgr.List(&models.ProjectQueryParam{ProjectIDs: projectIDs})
projects, err := project.Mgr.List(ctx, &models.ProjectQueryParam{ProjectIDs: projectIDs})
if err != nil {
return handleError(err)
}

View File

@ -87,7 +87,7 @@ func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, erro
// the repository doesn't exist, create it first
projectName, _ := utils.ParseRepository(name)
project, err := c.proMgr.Get(projectName)
project, err := c.proMgr.Get(ctx, projectName)
if err != nil {
return false, 0, err
}

View File

@ -15,6 +15,8 @@
package repository
import (
"testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/lib/errors"
@ -26,20 +28,19 @@ import (
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/goharbor/harbor/src/testing/pkg/repository"
"github.com/stretchr/testify/suite"
"testing"
)
type controllerTestSuite struct {
suite.Suite
ctl *controller
proMgr *project.FakeManager
proMgr *project.Manager
repoMgr *repository.FakeManager
argMgr *arttesting.FakeManager
artCtl *artifacttesting.Controller
}
func (c *controllerTestSuite) SetupTest() {
c.proMgr = &project.FakeManager{}
c.proMgr = &project.Manager{}
c.repoMgr = &repository.FakeManager{}
c.argMgr = &arttesting.FakeManager{}
c.artCtl = &artifacttesting.Controller{}
@ -69,7 +70,7 @@ func (c *controllerTestSuite) TestEnsure() {
// doesn't exist
c.repoMgr.On("GetByName").Return(nil, errors.NotFoundError(nil))
c.proMgr.On("Get", "library").Return(&models.Project{
c.proMgr.On("Get", mock.AnythingOfType("*context.valueCtx"), "library").Return(&models.Project{
ProjectID: 1,
}, nil)
c.repoMgr.On("Create").Return(1, nil)

View File

@ -220,7 +220,7 @@ func TestMain(m *testing.M) {
"delete from harbor_label",
"delete from robot",
"delete from user_group",
"delete from project_member",
"delete from project_member where id > 1",
})
ret := m.Run()

View File

@ -17,7 +17,6 @@ package api
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"net/http"
"regexp"
"strconv"
@ -33,12 +32,14 @@ import (
"github.com/goharbor/harbor/src/common/utils"
errutil "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/controller/quota"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/quota/types"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/scan/vuln"
"github.com/goharbor/harbor/src/replication"
)
@ -331,12 +332,13 @@ func (p *ProjectAPI) Delete() {
return
}
if err = p.ProjectMgr.Delete(p.project.ProjectID); err != nil {
ctx := p.Ctx.Request.Context()
if err := project.Ctl.Delete(ctx, p.project.ProjectID); err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to delete project %d", p.project.ProjectID), err)
return
}
ctx := p.Ctx.Request.Context()
referenceID := quota.ReferenceID(p.project.ProjectID)
q, err := quota.Ctl.GetByRef(ctx, quota.ProjectReference, referenceID)
if err != nil {

View File

@ -15,15 +15,16 @@ package api
import (
"fmt"
"net/http"
"strconv"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/testing/apitests/apilib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"strconv"
"testing"
)
var addProject *apilib.ProjectReq

View File

@ -91,7 +91,7 @@ func TestMain(m *testing.M) {
"delete from project where name='member_test_02'",
"delete from harbor_user where username='member_test_01' or username='pm_sample'",
"delete from user_group",
"delete from project_member",
"delete from project_member where id > 1",
}
dao.ExecuteBatchSQL(initSqls)
defer dao.ExecuteBatchSQL(clearSqls)

View File

@ -22,6 +22,11 @@ import (
"github.com/goharbor/harbor/src/lib/log"
)
// RegisterModel ...
func RegisterModel(models ...interface{}) {
orm.RegisterModel(models...)
}
type ormKey struct{}
// FromContext returns orm from context

View File

@ -21,60 +21,68 @@ import (
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/suite"
)
func addProject(ctx context.Context, project models.Project) (int64, error) {
type Foo struct {
ID int64 `orm:"pk;auto;column(id)"`
Name string `orm:"column(name)"`
}
func (*Foo) TableName() string {
return "foo"
}
func addFoo(ctx context.Context, foo Foo) (int64, error) {
o, err := FromContext(ctx)
if err != nil {
return 0, err
}
return o.Insert(&project)
return o.Insert(&foo)
}
func readProject(ctx context.Context, id int64) (*models.Project, error) {
func readFoo(ctx context.Context, id int64) (*Foo, error) {
o, err := FromContext(ctx)
if err != nil {
return nil, err
}
project := &models.Project{
ProjectID: id,
foo := &Foo{
ID: id,
}
if err := o.Read(project, "project_id"); err != nil {
if err := o.Read(foo, "id"); err != nil {
return nil, err
}
return project, nil
return foo, nil
}
func deleteProject(ctx context.Context, id int64) error {
func deleteFoo(ctx context.Context, id int64) error {
o, err := FromContext(ctx)
if err != nil {
return err
}
project := &models.Project{
ProjectID: id,
foo := &Foo{
ID: id,
}
_, err = o.Delete(project, "project_id")
_, err = o.Delete(foo, "id")
return err
}
func existProject(ctx context.Context, id int64) bool {
func existFoo(ctx context.Context, id int64) bool {
o, err := FromContext(ctx)
if err != nil {
return false
}
project := &models.Project{
ProjectID: id,
foo := &Foo{
ID: id,
}
if err := o.Read(project, "project_id"); err != nil {
if err := o.Read(foo, "id"); err != nil {
return false
}
@ -88,7 +96,40 @@ type OrmSuite struct {
// SetupSuite ...
func (suite *OrmSuite) SetupSuite() {
RegisterModel(&Foo{})
dao.PrepareTestForPostgresSQL()
o, err := FromContext(Context())
if err != nil {
suite.Fail("got error %v", err)
}
sql := `
CREATE TABLE IF NOT EXISTS foo (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR (30),
UNIQUE (name)
)
`
_, err = o.Raw(sql).Exec()
if err != nil {
suite.Fail("got error %v", err)
}
}
func (suite *OrmSuite) TearDownSuite() {
o, err := FromContext(Context())
if err != nil {
suite.Fail("got error %v", err)
}
sql := `DROP TABLE foo`
_, err = o.Raw(sql).Exec()
if err != nil {
suite.Fail("got error %v", err)
}
}
func (suite *OrmSuite) TestContext() {
@ -107,13 +148,13 @@ func (suite *OrmSuite) TestWithTransaction() {
var id int64
t1 := WithTransaction(func(ctx context.Context) (err error) {
id, err = addProject(ctx, models.Project{Name: "t1", OwnerID: 1})
id, err = addFoo(ctx, Foo{Name: "t1"})
return err
})
suite.Nil(t1(ctx))
suite.True(existProject(ctx, id))
suite.Nil(deleteProject(ctx, id))
suite.True(existFoo(ctx, id))
suite.Nil(deleteFoo(ctx, id))
}
func (suite *OrmSuite) TestSequentialTransactions() {
@ -122,50 +163,50 @@ func (suite *OrmSuite) TestSequentialTransactions() {
var id1, id2 int64
t1 := func(ctx context.Context, retErr error) error {
return WithTransaction(func(ctx context.Context) (err error) {
id1, err = addProject(ctx, models.Project{Name: "t1", OwnerID: 1})
id1, err = addFoo(ctx, Foo{Name: "t1"})
if err != nil {
return err
}
// Ensure t1 created success
suite.True(existProject(ctx, id1))
suite.True(existFoo(ctx, id1))
return retErr
})(ctx)
}
t2 := func(ctx context.Context, retErr error) error {
return WithTransaction(func(ctx context.Context) (err error) {
id2, _ = addProject(ctx, models.Project{Name: "t2", OwnerID: 1})
id2, _ = addFoo(ctx, Foo{Name: "t2"})
if err != nil {
return err
}
// Ensure t2 created success
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id2))
return retErr
})(ctx)
}
if suite.Nil(t1(ctx, nil)) {
suite.True(existProject(ctx, id1))
suite.True(existFoo(ctx, id1))
}
if suite.Nil(t2(ctx, nil)) {
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id2))
}
// delete project t1 and t2 in db
suite.Nil(deleteProject(ctx, id1))
suite.Nil(deleteProject(ctx, id2))
// delete foo t1 and t2 in db
suite.Nil(deleteFoo(ctx, id1))
suite.Nil(deleteFoo(ctx, id2))
if suite.Error(t1(ctx, errors.New("oops"))) {
suite.False(existProject(ctx, id1))
suite.False(existFoo(ctx, id1))
}
if suite.Nil(t2(ctx, nil)) {
suite.True(existProject(ctx, id2))
suite.Nil(deleteProject(ctx, id2))
suite.True(existFoo(ctx, id2))
suite.Nil(deleteFoo(ctx, id2))
}
}
@ -174,11 +215,11 @@ func (suite *OrmSuite) TestNestedTransaction() {
var id1, id2 int64
nt1 := WithTransaction(func(ctx context.Context) (err error) {
id1, err = addProject(ctx, models.Project{Name: "nt1", OwnerID: 1})
id1, err = addFoo(ctx, Foo{Name: "nt1"})
return err
})
nt2 := WithTransaction(func(ctx context.Context) (err error) {
id2, err = addProject(ctx, models.Project{Name: "nt2", OwnerID: 1})
id2, err = addFoo(ctx, Foo{Name: "nt2"})
return err
})
@ -193,36 +234,36 @@ func (suite *OrmSuite) TestNestedTransaction() {
}
// Ensure nt1 and nt2 created success
suite.True(existProject(ctx, id1))
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id1))
suite.True(existFoo(ctx, id2))
return retErr
})(ctx)
}
if suite.Nil(nt(ctx, nil)) {
suite.True(existProject(ctx, id1))
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id1))
suite.True(existFoo(ctx, id2))
// delete project nt1 and nt2 in db
suite.Nil(deleteProject(ctx, id1))
suite.Nil(deleteProject(ctx, id2))
suite.False(existProject(ctx, id1))
suite.False(existProject(ctx, id2))
// delete foo nt1 and nt2 in db
suite.Nil(deleteFoo(ctx, id1))
suite.Nil(deleteFoo(ctx, id2))
suite.False(existFoo(ctx, id1))
suite.False(existFoo(ctx, id2))
}
if suite.Error(nt(ctx, errors.New("oops"))) {
suite.False(existProject(ctx, id1))
suite.False(existProject(ctx, id2))
suite.False(existFoo(ctx, id1))
suite.False(existFoo(ctx, id2))
}
// test nt1 failed but we skip it and nt2 success
suite.Nil(nt1(ctx))
suite.True(existProject(ctx, id1))
suite.True(existFoo(ctx, id1))
// delete nt1 here because id1 will overwrite in the following transaction
defer func(id int64) {
suite.Nil(deleteProject(ctx, id))
suite.Nil(deleteFoo(ctx, id))
}(id1)
t := WithTransaction(func(ctx context.Context) error {
@ -233,16 +274,16 @@ func (suite *OrmSuite) TestNestedTransaction() {
}
// Ensure t2 created success
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id2))
return nil
})
if suite.Nil(t(ctx)) {
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id2))
// delete project t2 in db
suite.Nil(deleteProject(ctx, id2))
// delete foo t2 in db
suite.Nil(deleteFoo(ctx, id2))
}
}
@ -251,11 +292,11 @@ func (suite *OrmSuite) TestNestedSavepoint() {
var id1, id2 int64
ns1 := WithTransaction(func(ctx context.Context) (err error) {
id1, err = addProject(ctx, models.Project{Name: "ns1", OwnerID: 1})
id1, err = addFoo(ctx, Foo{Name: "ns1"})
return err
})
ns2 := WithTransaction(func(ctx context.Context) (err error) {
id2, err = addProject(ctx, models.Project{Name: "ns2", OwnerID: 1})
id2, err = addFoo(ctx, Foo{Name: "ns2"})
return err
})
@ -270,8 +311,8 @@ func (suite *OrmSuite) TestNestedSavepoint() {
}
// Ensure nt1 and nt2 created success
suite.True(existProject(ctx, id1))
suite.True(existProject(ctx, id2))
suite.True(existFoo(ctx, id1))
suite.True(existFoo(ctx, id2))
return retErr
})(ctx)
@ -287,25 +328,25 @@ func (suite *OrmSuite) TestNestedSavepoint() {
// transaction commit and s1s2 commit
suite.Nil(t(ctx, nil, nil))
// Ensure nt1 and nt2 created success
suite.True(existProject(ctx, id1))
suite.True(existProject(ctx, id2))
// delete project nt1 and nt2 in db
suite.Nil(deleteProject(ctx, id1))
suite.Nil(deleteProject(ctx, id2))
suite.False(existProject(ctx, id1))
suite.False(existProject(ctx, id2))
suite.True(existFoo(ctx, id1))
suite.True(existFoo(ctx, id2))
// delete foo nt1 and nt2 in db
suite.Nil(deleteFoo(ctx, id1))
suite.Nil(deleteFoo(ctx, id2))
suite.False(existFoo(ctx, id1))
suite.False(existFoo(ctx, id2))
// transaction commit and s1s2 rollback
suite.Nil(t(ctx, nil, errors.New("oops")))
// Ensure nt1 and nt2 created failed
suite.False(existProject(ctx, id1))
suite.False(existProject(ctx, id2))
suite.False(existFoo(ctx, id1))
suite.False(existFoo(ctx, id2))
// transaction rollback and s1s2 commit
suite.Error(t(ctx, errors.New("oops"), nil))
// Ensure nt1 and nt2 created failed
suite.False(existProject(ctx, id1))
suite.False(existProject(ctx, id2))
suite.False(existFoo(ctx, id1))
suite.False(existFoo(ctx, id2))
}
func TestRunOrmSuite(t *testing.T) {

View File

@ -18,15 +18,63 @@ import (
"context"
"reflect"
"strings"
"sync"
"unicode"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
)
// QuerySetter generates the query setter according to the query. "ignoredCols" is used to set the
// columns that will not be queried
// Params ...
type Params = orm.Params
// QuerySeter ...
type QuerySeter = orm.QuerySeter
// Escape special characters
func Escape(str string) string {
return dao.Escape(str)
}
// ParamPlaceholderForIn returns a string that contains placeholders for sql keyword "in"
// e.g. n=3, returns "?,?,?"
func ParamPlaceholderForIn(n int) string {
placeholders := []string{}
for i := 0; i < n; i++ {
placeholders = append(placeholders, "?")
}
return strings.Join(placeholders, ",")
}
// QuerySetter generates the query setter according to the query. "ignoredCols" is used to set the columns that will not be queried.
// Currently, it supports two ways to generate the query setter, the first one is to generate by the fields of the model,
// and the second one is to generate by the methods their name begins with `FilterBy` of the model.
// e.g. for the following model the queriable fields are :
// "Field2", "customized_field2", "Field3", "field3", "Field4" (or "field4") and "Field5" (or "field5").
// type Foo struct{
// Field1 string `orm:"-"`
// Field2 string `orm:"column(customized_field2)"`
// Field3 string
// }
//
// func (f *Foo) FilterByField4(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
// // The value is the raw value of key in q.Query
// return qs
// }
//
// func (f *Foo) FilterByField5(ctx context.Context, qs orm.QuerySeter, key, value string) orm.QuerySeter {
// // The value string is the value of key in q.Query which is escaped by `Escape`
// return qs
// }
func QuerySetter(ctx context.Context, model interface{}, query *q.Query, ignoredCols ...string) (orm.QuerySeter, error) {
val := reflect.ValueOf(model)
if val.Kind() != reflect.Ptr {
return nil, errors.Errorf("<orm.QuerySetter> cannot use non-ptr model struct `%s`", getFullName(reflect.Indirect(val).Type()))
}
ormer, err := FromContext(ctx)
if err != nil {
return nil, err
@ -36,53 +84,26 @@ func QuerySetter(ctx context.Context, model interface{}, query *q.Query, ignored
return qs, nil
}
// the program will panic when querying the columns that doesn't exist
// list the supported columns first to avoid the panic
cols := listQueriableCols(model, ignoredCols...)
ignored := map[string]bool{}
for _, col := range ignoredCols {
ignored[col] = true
}
columns := queriableColumns(model)
methods := queriableMethods(model)
for k, v := range query.Keywords {
col := strings.SplitN(k, orm.ExprSep, 2)[0]
if _, exist := cols[col]; !exist {
field := strings.SplitN(k, orm.ExprSep, 2)[0]
if ignored[field] {
continue
}
// fuzzy match
f, ok := v.(*q.FuzzyMatchValue)
if ok {
qs = qs.Filter(k+"__icontains", f.Value)
continue
if columns[field] {
qs = queryByColumn(qs, k, v)
} else if method, ok := methods[snakeCase(field)]; ok {
qs = queryByMethod(ctx, qs, k, v, method, val)
}
}
// range
r, ok := v.(*q.Range)
if ok {
if r.Min != nil {
qs = qs.Filter(k+"__gte", r.Min)
}
if r.Max != nil {
qs = qs.Filter(k+"__lte", r.Max)
}
continue
}
// or list
ol, ok := v.(*q.OrList)
if ok {
if len(ol.Values) > 0 {
qs = qs.Filter(k+"__in", ol.Values...)
}
continue
}
// and list
_, ok = v.(*q.AndList)
if ok {
// do nothing as and list needs to be handled by the logic of DAO
continue
}
// exact match
qs = qs.Filter(k, v)
}
if query.PageSize > 0 {
qs = qs.Limit(query.PageSize)
if query.PageNumber > 0 {
@ -92,7 +113,88 @@ func QuerySetter(ctx context.Context, model interface{}, query *q.Query, ignored
return qs, nil
}
// list the columns that can be queried
// get reflect.Type name with package path.
func getFullName(typ reflect.Type) string {
return typ.PkgPath() + "." + typ.Name()
}
// convert string to snake case
func snakeCase(str string) string {
delim := '_'
runes := []rune(str)
var out []rune
for i := 0; i < len(runes); i++ {
if i > 0 &&
(unicode.IsUpper(runes[i])) &&
((i+1 < len(runes) && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
out = append(out, delim)
}
out = append(out, unicode.ToLower(runes[i]))
}
return string(out)
}
func queryByColumn(qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
// fuzzy match
if f, ok := value.(*q.FuzzyMatchValue); ok {
return qs.Filter(key+"__icontains", f.Value)
}
// range
if r, ok := value.(*q.Range); ok {
if r.Min != nil {
qs = qs.Filter(key+"__gte", r.Min)
}
if r.Max != nil {
qs = qs.Filter(key+"__lte", r.Max)
}
return qs
}
// or list
if ol, ok := value.(*q.OrList); ok {
if len(ol.Values) > 0 {
qs = qs.Filter(key+"__in", ol.Values...)
}
return qs
}
// and list
if _, ok := value.(*q.AndList); ok {
// do nothing as and list needs to be handled by the logic of DAO
return qs
}
// exact match
return qs.Filter(key, value)
}
func queryByMethod(ctx context.Context, qs orm.QuerySeter, key string, value interface{}, methodName string, reflectVal reflect.Value) orm.QuerySeter {
if mv := reflectVal.MethodByName(methodName); mv.IsValid() {
switch method := mv.Interface().(type) {
case func(context.Context, orm.QuerySeter, string, interface{}) orm.QuerySeter:
return method(ctx, qs, key, value)
case func(context.Context, orm.QuerySeter, string, string) orm.QuerySeter:
if str, ok := value.(string); ok {
return method(ctx, qs, key, Escape(str))
}
log.Warningf("expected string type for the value of method %s, but actual is %T", methodName, value)
default:
return qs
}
}
return qs
}
var (
cache = sync.Map{}
)
// get model fields which are columns in orm
// e.g. for the following model the columns that can be queried are:
// "Field2", "customized_field2", "Field3" and "field3"
// type model struct{
@ -100,20 +202,22 @@ func QuerySetter(ctx context.Context, model interface{}, query *q.Query, ignored
// Field2 string `orm:"column(customized_field2)"`
// Field3 string
// }
//
// set "ignoredCols" to ignore the specified columns
func listQueriableCols(model interface{}, ignoredCols ...string) map[string]struct{} {
if model == nil {
return nil
func queriableColumns(model interface{}) map[string]bool {
typ := reflect.Indirect(reflect.ValueOf(model)).Type()
key := getFullName(typ) + "-columns"
value, ok := cache.Load(key)
if ok {
return value.(map[string]bool)
}
ignored := map[string]struct{}{}
for _, ig := range ignoredCols {
ignored[ig] = struct{}{}
}
cols := map[string]struct{}{}
t := reflect.Indirect(reflect.ValueOf(model)).Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
cols := map[string]bool{}
defer func() {
cache.Store(key, cols)
}()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
orm := field.Tag.Get("orm")
if orm == "-" {
continue
@ -129,34 +233,46 @@ func listQueriableCols(model interface{}, ignoredCols ...string) map[string]stru
}
}
}
if len(colName) == 0 {
// TODO convert the field.Name to snake_case
if colName == "" {
colName = snakeCase(field.Name)
}
if _, exist := ignored[colName]; exist {
continue
}
if _, exist := ignored[field.Name]; exist {
continue
}
if len(colName) != 0 {
cols[colName] = struct{}{}
}
cols[field.Name] = struct{}{}
cols[colName] = true
cols[field.Name] = true
}
return cols
}
// ParamPlaceholderForIn returns a string that contains placeholders for sql keyword "in"
// e.g. n=3, returns "?,?,?"
func ParamPlaceholderForIn(n int) string {
placeholders := []string{}
for i := 0; i < n; i++ {
placeholders = append(placeholders, "?")
}
return strings.Join(placeholders, ",")
// get model methods which begin with `FilterBy`
func queriableMethods(model interface{}) map[string]string {
val := reflect.ValueOf(model)
key := getFullName(reflect.Indirect(val).Type()) + "-methods"
value, ok := cache.Load(key)
if ok {
return value.(map[string]string)
}
// Escape special characters
func Escape(str string) string {
return dao.Escape(str)
methods := map[string]string{}
defer func() {
cache.Store(key, methods)
}()
prefix := "FilterBy"
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
name := typ.Method(i).Name
if !strings.HasPrefix(name, prefix) {
continue
}
field := snakeCase(strings.TrimPrefix(name, prefix))
if field != "" {
methods[field] = name
}
}
return methods
}

View File

@ -15,47 +15,95 @@
package orm
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"reflect"
"testing"
)
func TestListQueriableCols(t *testing.T) {
type model struct {
Field1 string `orm:"column(field1)" json:"field1"`
func Test_snakeCase(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want string
}{
{"ProjectID", args{"ProjectID"}, "project_id"},
{"project_id", args{"project_id"}, "project_id"},
{"RepositoryName", args{"RepositoryName"}, "repository_name"},
{"repository_name", args{"repository_name"}, "repository_name"},
{"ProfileURL", args{"ProfileURL"}, "profile_url"},
{"City", args{"City"}, "city"},
{"Address1", args{"Address1"}, "address1"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := snakeCase(tt.args.str); got != tt.want {
t.Errorf("snakeCase() = %v, want %v", got, tt.want)
}
})
}
}
type Bar struct {
Field1 string `orm:"-"`
Field2 string `orm:"column(customized_field2)"`
Field3 string
Field4 string `orm:"column(field4)"`
FirstName string
}
// without ignoring columns
cols := listQueriableCols(&model{})
require.Len(t, cols, 7)
_, exist := cols["Field1"]
assert.True(t, exist)
_, exist = cols["field1"]
assert.True(t, exist)
_, exist = cols["Field2"]
assert.True(t, exist)
_, exist = cols["customized_field2"]
assert.True(t, exist)
_, exist = cols["Field3"]
assert.True(t, exist)
_, exist = cols["Field4"]
assert.True(t, exist)
_, exist = cols["field4"]
assert.True(t, exist)
// with ignoring columns
cols = listQueriableCols(&model{}, "Field4")
require.Len(t, cols, 5)
_, exist = cols["Field1"]
assert.True(t, exist)
_, exist = cols["field1"]
assert.True(t, exist)
_, exist = cols["Field2"]
assert.True(t, exist)
_, exist = cols["customized_field2"]
assert.True(t, exist)
_, exist = cols["Field3"]
assert.True(t, exist)
func (Bar) Foo() {}
func (bar *Bar) FilterBy() {}
func (bar *Bar) FilterByField4() {}
func Test_queriableColumns(t *testing.T) {
toWant := func(fields ...string) map[string]bool {
want := map[string]bool{}
for _, field := range fields {
want[field] = true
}
return want
}
type args struct {
model interface{}
}
tests := []struct {
name string
args args
want map[string]bool
}{
{"bar", args{&Bar{}}, toWant("Field2", "customized_field2", "Field3", "field3", "FirstName", "first_name")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := queriableColumns(tt.args.model); !reflect.DeepEqual(got, tt.want) {
t.Errorf("queriableColumns() = %v, want %v", got, tt.want)
}
})
}
}
func Test_queriableMethods(t *testing.T) {
type args struct {
model interface{}
}
tests := []struct {
name string
args args
want map[string]string
}{
{"bar", args{&Bar{}}, map[string]string{"field4": "FilterByField4"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := queriableMethods(tt.args.model); !reflect.DeepEqual(got, tt.want) {
t.Errorf("queriableMethods() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -34,6 +34,16 @@ func New(kw KeyWords) *Query {
return &Query{Keywords: kw}
}
// MustClone returns the clone of query when it's not nil
// or returns a new Query instance
func MustClone(query *Query) *Query {
if query != nil {
clone := *query
return &clone
}
return New(KeyWords{})
}
// Range query
type Range struct {
Min interface{}

41
src/lib/q/query_test.go Normal file
View File

@ -0,0 +1,41 @@
// 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 q
import (
"reflect"
"testing"
)
func TestMustClone(t *testing.T) {
type args struct {
query *Query
}
tests := []struct {
name string
args args
want *Query
}{
{"ptr", args{New(KeyWords{"public": "true"})}, New(KeyWords{"public": "true"})},
{"nil", args{nil}, New(KeyWords{})},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MustClone(tt.args.query); !reflect.DeepEqual(got, tt.want) {
t.Errorf("MustClone() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -16,6 +16,7 @@ package migration
import (
"context"
art "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
@ -26,7 +27,7 @@ import (
func upgradeData(ctx context.Context) error {
abstractor := art.NewAbstractor()
pros, err := project.Mgr.List()
pros, err := project.Mgr.List(ctx)
if err != nil {
return err
}

View File

@ -39,6 +39,9 @@ func (s *ControllerTestSuite) TestImmutableRule() {
Name: "TestImmutableRule",
OwnerID: 1,
})
if s.Nil(err) {
defer dao.DeleteProject(projectID)
}
rule := &model.Metadata{
ProjectID: projectID,

169
src/pkg/project/dao/dao.go Normal file
View File

@ -0,0 +1,169 @@
// 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 dao
import (
"context"
"fmt"
"time"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/project/models"
)
// DAO is the data access object interface for project
type DAO interface {
// Create create a project instance
Create(ctx context.Context, project *models.Project) (int64, error)
// Count returns the total count of projects according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// Delete delete the project instance by id
Delete(ctx context.Context, id int64) error
// Get get project instance by id
Get(ctx context.Context, id int64) (*models.Project, error)
// GetByName get project instance by name
GetByName(ctx context.Context, name string) (*models.Project, error)
// List list projects
List(ctx context.Context, query *q.Query) ([]*models.Project, error)
}
// New returns an instance of the default DAO
func New() DAO {
return &dao{}
}
type dao struct{}
// Create create a project instance
func (d *dao) Create(ctx context.Context, project *models.Project) (int64, error) {
var projectID int64
h := func(ctx context.Context) error {
o, err := orm.FromContext(ctx)
if err != nil {
return err
}
now := time.Now()
project.CreationTime = now
project.UpdateTime = now
projectID, err = o.Insert(project)
if err != nil {
return orm.WrapConflictError(err, "The project named %s already exists", project.Name)
}
member := &Member{
ProjectID: projectID,
EntityID: project.OwnerID,
Role: common.RoleProjectAdmin,
EntityType: common.UserMember,
CreationTime: now,
UpdateTime: now,
}
if _, err := o.Insert(member); err != nil {
return err
}
return nil
}
if err := orm.WithTransaction(h)(ctx); err != nil {
return 0, err
}
return projectID, nil
}
// Count returns the total count of artifacts according to the query
func (d *dao) Count(ctx context.Context, query *q.Query) (total int64, err error) {
query = q.MustClone(query)
query.Keywords["deleted"] = false
query.PageNumber = 0
query.PageSize = 0
qs, err := orm.QuerySetter(ctx, &models.Project{}, query)
if err != nil {
return 0, err
}
return qs.Count()
}
// Delete delete the project instance by id
func (d *dao) Delete(ctx context.Context, id int64) error {
project, err := d.Get(ctx, id)
if err != nil {
return err
}
project.Deleted = true
project.Name = fmt.Sprintf("%s#%d", project.Name, project.ProjectID)
o, err := orm.FromContext(ctx)
if err != nil {
return err
}
_, err = o.Update(project, "deleted", "name")
return err
}
// Get get project instance by id
func (d *dao) Get(ctx context.Context, id int64) (*models.Project, error) {
o, err := orm.FromContext(ctx)
if err != nil {
return nil, err
}
project := &models.Project{ProjectID: id, Deleted: false}
if err = o.Read(project, "project_id", "deleted"); err != nil {
return nil, orm.WrapNotFoundError(err, "project %d not found", id)
}
return project, nil
}
// GetByName get project instance by name
func (d *dao) GetByName(ctx context.Context, name string) (*models.Project, error) {
o, err := orm.FromContext(ctx)
if err != nil {
return nil, err
}
project := &models.Project{Name: name, Deleted: false}
if err := o.Read(project, "name", "deleted"); err != nil {
return nil, orm.WrapNotFoundError(err, "project %s not found", name)
}
return project, nil
}
func (d *dao) List(ctx context.Context, query *q.Query) ([]*models.Project, error) {
query = q.MustClone(query)
query.Keywords["deleted"] = false
qs, err := orm.QuerySetter(ctx, &models.Project{}, query)
if err != nil {
return nil, err
}
projects := []*models.Project{}
if _, err := qs.All(&projects); err != nil {
return nil, err
}
return projects, nil
}

View File

@ -0,0 +1,299 @@
// 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 dao
import (
"fmt"
"testing"
"github.com/goharbor/harbor/src/common"
"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/project/models"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
)
type DaoTestSuite struct {
htesting.Suite
dao DAO
}
func (suite *DaoTestSuite) SetupSuite() {
suite.Suite.SetupSuite()
suite.dao = New()
}
func (suite *DaoTestSuite) WithUser(f func(int64, string), usernames ...string) {
var username string
if len(usernames) > 0 {
username = usernames[0]
} else {
username = suite.RandString(5)
}
o, err := orm.FromContext(orm.Context())
if err != nil {
suite.Fail("got error %v", err)
}
var userID int64
email := fmt.Sprintf("%s@example.com", username)
sql := "INSERT INTO harbor_user (username, realname, email, password) VALUES (?, ?, ?, 'Harbor12345') RETURNING user_id"
suite.Nil(o.Raw(sql, username, username, email).QueryRow(&userID))
defer func() {
o.Raw("UPDATE harbor_user SET deleted=True WHERE user_id = ?", userID).Exec()
}()
f(userID, username)
}
func (suite *DaoTestSuite) WithUserGroup(f func(int64, string), groupNames ...string) {
var groupName string
if len(groupNames) > 0 {
groupName = groupNames[0]
} else {
groupName = suite.RandString(5)
}
o, err := orm.FromContext(orm.Context())
if err != nil {
suite.Fail("got error %v", err)
}
var groupID int64
groupDN := fmt.Sprintf("cn=%s,dc=goharbor,dc=io", groupName)
suite.Nil(o.Raw("INSERT INTO user_group (group_name, ldap_group_dn) VALUES (?, ?) RETURNING id", groupName, groupDN).QueryRow(&groupID))
defer func() {
o.Raw("DELETE FROM user_group WHERE id = ?", groupID).Exec()
}()
f(groupID, groupName)
}
func (suite *DaoTestSuite) TestCreate() {
{
project := &models.Project{
Name: "foobar",
OwnerID: 1,
}
projectID, err := suite.dao.Create(orm.Context(), project)
suite.Nil(err)
suite.dao.Delete(orm.Context(), projectID)
}
{
// project name duplicated
project := &models.Project{
Name: "library",
OwnerID: 1,
}
projectID, err := suite.dao.Create(orm.Context(), project)
suite.Error(err)
suite.True(errors.IsConflictErr(err))
suite.Equal(int64(0), projectID)
}
}
func (suite *DaoTestSuite) TestCount() {
{
count, err := suite.dao.Count(orm.Context(), q.New(q.KeyWords{"project_id": 1}))
suite.Nil(err)
suite.Equal(int64(1), count)
}
}
func (suite *DaoTestSuite) TestDelete() {
project := &models.Project{
Name: "foobar",
OwnerID: 1,
}
projectID, err := suite.dao.Create(orm.Context(), project)
suite.Nil(err)
p1, err := suite.dao.Get(orm.Context(), projectID)
suite.Nil(err)
suite.Equal("foobar", p1.Name)
suite.dao.Delete(orm.Context(), projectID)
p2, err := suite.dao.Get(orm.Context(), projectID)
suite.Error(err)
suite.True(errors.IsNotFoundErr(err))
suite.Nil(p2)
}
func (suite *DaoTestSuite) TestGet() {
{
project, err := suite.dao.Get(orm.Context(), 1)
suite.Nil(err)
suite.Equal("library", project.Name)
}
{
// not found
project, err := suite.dao.Get(orm.Context(), 10000)
suite.Error(err)
suite.True(errors.IsNotFoundErr(err))
suite.Nil(project)
}
}
func (suite *DaoTestSuite) TestGetByName() {
{
project, err := suite.dao.GetByName(orm.Context(), "library")
suite.Nil(err)
suite.Equal("library", project.Name)
}
{
// not found
project, err := suite.dao.GetByName(orm.Context(), "project10000")
suite.Error(err)
suite.True(errors.IsNotFoundErr(err))
suite.Nil(project)
}
}
func (suite *DaoTestSuite) TestList() {
projectNames := []string{"foo1", "foo2", "foo3"}
var projectIDs []int64
for _, projectName := range projectNames {
project := &models.Project{
Name: projectName,
OwnerID: 1,
}
projectID, err := suite.dao.Create(orm.Context(), project)
if suite.Nil(err) {
projectIDs = append(projectIDs, projectID)
}
}
defer func() {
for _, projectID := range projectIDs {
suite.dao.Delete(orm.Context(), projectID)
}
}()
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"project_id__in": projectIDs}))
suite.Nil(err)
suite.Len(projects, len(projectNames))
}
func (suite *DaoTestSuite) TestListByPublic() {
{
// default library project
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"public": true}))
suite.Nil(err)
suite.Len(projects, 1)
}
{
// default library project
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"public": "true"}))
suite.Nil(err)
suite.Len(projects, 1)
}
{
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"public": false}))
suite.Nil(err)
suite.Len(projects, 0)
}
{
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"public": "false"}))
suite.Nil(err)
suite.Len(projects, 0)
}
}
func (suite *DaoTestSuite) TestListByOwner() {
{
// default library project
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"owner": "admin"}))
suite.Nil(err)
suite.Len(projects, 1)
}
{
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"owner": "owner-not-found"}))
suite.Nil(err)
suite.Len(projects, 0)
}
}
func (suite *DaoTestSuite) TestListByMember() {
{
// project admin
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"member": &models.MemberQuery{Name: "admin", Role: common.RoleProjectAdmin}}))
suite.Nil(err)
suite.Len(projects, 1)
}
{
// guest
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"member": &models.MemberQuery{Name: "admin", Role: common.RoleGuest}}))
suite.Nil(err)
suite.Len(projects, 0)
}
{
suite.WithUser(func(userID int64, username string) {
project := &models.Project{
Name: "project-with-user-group",
OwnerID: int(userID),
}
projectID, err := suite.dao.Create(orm.Context(), project)
suite.Nil(err)
defer suite.dao.Delete(orm.Context(), projectID)
suite.WithUserGroup(func(groupID int64, groupName string) {
o, err := orm.FromContext(orm.Context())
if err != nil {
suite.Fail("got error %v", err)
}
var pid int64
suite.Nil(o.Raw("INSERT INTO project_member (project_id, entity_id, role, entity_type) values (?, ?, ?, ?) RETURNING id", projectID, groupID, common.RoleGuest, "g").QueryRow(&pid))
defer o.Raw("DELETE FROM project_member WHERE id = ?", pid)
memberQuery := &models.MemberQuery{
Name: "admin",
Role: common.RoleProjectAdmin,
GroupIDs: []int{int(groupID)},
}
projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"member": memberQuery}))
suite.Nil(err)
suite.Len(projects, 2)
})
})
}
}
func TestDaoTestSuite(t *testing.T) {
suite.Run(t, &DaoTestSuite{})
}

View File

@ -0,0 +1,43 @@
// 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 dao
import (
"time"
"github.com/goharbor/harbor/src/lib/orm"
)
func init() {
orm.RegisterModel(
new(Member),
)
}
// Member holds the details of a member.
type Member struct {
ID int `orm:"pk;auto;column(id)" json:"id"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
Role int `orm:"column(role)" json:"role_id"`
EntityID int `orm:"column(entity_id)" json:"entity_id"`
EntityType string `orm:"column(entity_type)" json:"entity_type"`
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 (*Member) TableName() string {
return "project_member"
}

View File

@ -15,10 +15,12 @@
package project
import (
"fmt"
"context"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/project/dao"
"github.com/goharbor/harbor/src/pkg/project/models"
)
var (
@ -27,40 +29,72 @@ var (
)
// Manager is used for project management
// currently, the interface only defines the methods needed for tag retention
// will expand it when doing refactor
type Manager interface {
// List projects according to the query
List(...*models.ProjectQueryParam) ([]*models.Project, error)
// Create create project instance
Create(ctx context.Context, project *models.Project) (int64, error)
// Count returns the total count of projects according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// Delete delete the project instance by id
Delete(ctx context.Context, id int64) error
// Get the project specified by the ID or name
Get(interface{}) (*models.Project, error)
Get(ctx context.Context, idOrName interface{}) (*models.Project, error)
// List projects according to the query
List(ctx context.Context, query ...*models.ProjectQueryParam) ([]*models.Project, error)
}
// New returns a default implementation of Manager
func New() Manager {
return &manager{}
return &manager{dao: dao.New()}
}
type manager struct{}
// List projects according to the query
func (m *manager) List(query ...*models.ProjectQueryParam) ([]*models.Project, error) {
var q *models.ProjectQueryParam
if len(query) > 0 {
q = query[0]
type manager struct {
dao dao.DAO
}
return dao.GetProjects(q)
// Create create project instance
func (m *manager) Create(ctx context.Context, project *models.Project) (int64, error) {
if project.OwnerID <= 0 {
return 0, errors.BadRequestError(nil).WithMessage("Owner is missing when creating project %s", project.Name)
}
return m.dao.Create(ctx, project)
}
// Count returns the total count of projects according to the query
func (m *manager) Count(ctx context.Context, query *q.Query) (total int64, err error) {
return m.dao.Count(ctx, query)
}
// Delete delete the project instance by id
func (m *manager) Delete(ctx context.Context, id int64) error {
return m.dao.Delete(ctx, id)
}
// Get the project specified by the ID
func (m *manager) Get(idOrName interface{}) (*models.Project, error) {
func (m *manager) Get(ctx context.Context, idOrName interface{}) (*models.Project, error) {
id, ok := idOrName.(int64)
if ok {
return dao.GetProjectByID(id)
return m.dao.Get(ctx, id)
}
name, ok := idOrName.(string)
if ok {
return dao.GetProjectByName(name)
return m.dao.GetByName(ctx, name)
}
return nil, fmt.Errorf("invalid parameter: %v, should be ID(int64) or name(string)", idOrName)
return nil, errors.Errorf("invalid parameter: %v, should be ID(int64) or name(string)", idOrName)
}
// List projects according to the query
func (m *manager) List(ctx context.Context, query ...*models.ProjectQueryParam) ([]*models.Project, error) {
var param *models.ProjectQueryParam
if len(query) > 0 {
param = query[0]
}
if param == nil {
return m.dao.List(ctx, nil)
}
return m.dao.List(ctx, param.ToQuery())
}

View File

@ -0,0 +1,104 @@
// 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 dao
import (
"context"
"time"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/project/metadata/models"
)
// DAO is the data access object interface for project metadata
type DAO interface {
// Create create metadata instances for the project
Create(ctx context.Context, projectID int64, name, value string) (int64, error)
// Delete delete metadata interfaces filtered the query
Delete(ctx context.Context, query *q.Query) error
// Update update the value of metadata instance
Update(ctx context.Context, projectID int64, name, value string) error
// List returns project metadata instances
List(ctx context.Context, query *q.Query) ([]*models.ProjectMetadata, error)
}
// New returns an instance of the default DAO
func New() DAO {
return &dao{}
}
type dao struct{}
// Create create metadata instances for the project
func (d *dao) Create(ctx context.Context, projectID int64, name, value string) (int64, error) {
o, err := orm.FromContext(ctx)
if err != nil {
return 0, err
}
now := time.Now()
md := &models.ProjectMetadata{
ProjectID: projectID,
Name: name,
Value: value,
CreationTime: now,
UpdateTime: now,
}
return o.Insert(md)
}
// Delete delete metadata interfaces filtered the query
func (d *dao) Delete(ctx context.Context, query *q.Query) error {
qs, err := orm.QuerySetter(ctx, &models.ProjectMetadata{}, query)
if err != nil {
return err
}
_, err = qs.Delete()
return err
}
// Update update the metadata instance
func (d *dao) Update(ctx context.Context, projectID int64, name, value string) error {
qs, err := orm.QuerySetter(ctx, &models.ProjectMetadata{}, nil)
if err != nil {
return err
}
qs = qs.Filter("project_id", projectID).Filter("name", name)
_, err = qs.Update(orm.Params{"value": value})
return err
}
// List returns project metadata instances
func (d *dao) List(ctx context.Context, query *q.Query) ([]*models.ProjectMetadata, error) {
qs, err := orm.QuerySetter(ctx, &models.ProjectMetadata{}, query)
if err != nil {
return nil, err
}
mds := []*models.ProjectMetadata{}
if _, err := qs.All(&mds); err != nil {
return nil, err
}
return mds, nil
}

View File

@ -0,0 +1,70 @@
// 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 dao
import (
"testing"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
)
type DaoTestSuite struct {
htesting.Suite
dao DAO
}
func (suite *DaoTestSuite) SetupSuite() {
suite.Suite.SetupSuite()
suite.dao = New()
}
func (suite *DaoTestSuite) TestCreate() {
id, err := suite.dao.Create(orm.Context(), 1, "foo", "bar")
if suite.Nil(err) {
defer func() {
suite.Nil(suite.dao.Delete(orm.Context(), q.New(q.KeyWords{"id": id})))
}()
}
mds, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"id": id}))
suite.Nil(err)
suite.Len(mds, 1)
suite.Equal("foo", mds[0].Name)
suite.Equal("bar", mds[0].Value)
}
func (suite *DaoTestSuite) TestUpdate() {
id, err := suite.dao.Create(orm.Context(), 1, "foo", "bar")
if suite.Nil(err) {
defer func() {
suite.Nil(suite.dao.Delete(orm.Context(), q.New(q.KeyWords{"id": id})))
}()
}
suite.Nil(suite.dao.Update(orm.Context(), 1, "foo", "Bar"))
mds, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"id": id}))
suite.Nil(err)
suite.Len(mds, 1)
suite.Equal("foo", mds[0].Name)
suite.Equal("Bar", mds[0].Value)
}
func TestDaoTestSuite(t *testing.T) {
suite.Run(t, &DaoTestSuite{})
}

View File

@ -0,0 +1,128 @@
// 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 metadata
import (
"context"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/project/metadata/dao"
"github.com/goharbor/harbor/src/pkg/project/metadata/models"
)
var (
// Mgr is the global project metadata manager
Mgr = New()
)
// Manager defines the operations that a project metadata manager should implement
type Manager interface {
// Add metadatas for project specified by projectID
Add(ctx context.Context, projectID int64, meta map[string]string) error
// Delete metadatas whose keys are specified in parameter meta, if it is absent, delete all
Delete(ctx context.Context, projectID int64, meta ...string) error
// Update metadatas
Update(ctx context.Context, projectID int64, meta map[string]string) error
// Get metadatas whose keys are specified in parameter meta, if it is absent, get all
Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error)
// List metadata according to the name and value
List(ctx context.Context, name, value string) ([]*models.ProjectMetadata, error)
}
// New returns a default implementation of Manager
func New() Manager {
return &manager{dao: dao.New()}
}
type manager struct {
dao dao.DAO
}
// Add metadatas for project specified by projectID
func (m *manager) Add(ctx context.Context, projectID int64, meta map[string]string) error {
h := func(ctx context.Context) error {
for name, value := range meta {
if _, err := m.dao.Create(ctx, projectID, name, value); err != nil {
return err
}
}
return nil
}
return orm.WithTransaction(h)(ctx)
}
// Delete metadatas whose keys are specified in parameter meta, if it is absent, delete all
func (m *manager) Delete(ctx context.Context, projectID int64, meta ...string) error {
return m.dao.Delete(ctx, makeQuery(projectID, meta...))
}
// Update metadatas
func (m *manager) Update(ctx context.Context, projectID int64, meta map[string]string) error {
if len(meta) == 0 {
return nil
}
h := func(ctx context.Context) error {
for name, value := range meta {
if err := m.dao.Update(ctx, projectID, name, value); err != nil {
return err
}
}
return nil
}
return orm.WithTransaction(h)(ctx)
}
// Get metadatas whose keys are specified in parameter meta, if it is absent, get all
func (m *manager) Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error) {
mds, err := m.dao.List(ctx, makeQuery(projectID, meta...))
if err != nil {
return nil, err
}
data := map[string]string{}
for _, md := range mds {
data[md.Name] = md.Value
}
return data, nil
}
// List metadata according to the name and value
func (m *manager) List(ctx context.Context, name string, value string) ([]*models.ProjectMetadata, error) {
return m.dao.List(ctx, q.New(q.KeyWords{"name": name, "value": value}))
}
func makeQuery(projectID int64, meta ...string) *q.Query {
kw := q.KeyWords{
"project_id": projectID,
}
if len(meta) > 0 {
var names []string
for _, name := range meta {
names = append(names, name)
}
kw["name__in"] = names
}
return q.New(kw)
}

View File

@ -0,0 +1,22 @@
// 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 models
import (
"github.com/goharbor/harbor/src/common/models"
)
// ProjectMetadata ...
type ProjectMetadata = models.ProjectMetadata

View File

@ -0,0 +1,43 @@
// 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 models
import (
"github.com/goharbor/harbor/src/common/models"
)
// Project ...
type Project = models.Project
// Projects the connection for Project
type Projects []*models.Project
// OwnerIDs returns all the owner ids from the projects
func (projects Projects) OwnerIDs() []int {
var ownerIDs []int
for _, project := range projects {
ownerIDs = append(ownerIDs, project.OwnerID)
}
return ownerIDs
}
// Member ...
type Member = models.Member
// ProjectQueryParam ...
type ProjectQueryParam = models.ProjectQueryParam
// MemberQuery ...
type MemberQuery = models.MemberQuery

View File

@ -1,15 +1,31 @@
// 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 retention
import (
"context"
"strings"
"testing"
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
"github.com/goharbor/harbor/src/pkg/scheduler"
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/goharbor/harbor/src/testing/pkg/repository"
"github.com/stretchr/testify/suite"
"strings"
"testing"
)
type ControllerTestSuite struct {
@ -29,7 +45,7 @@ func TestController(t *testing.T) {
}
func (s *ControllerTestSuite) TestPolicy() {
projectMgr := &fakeProjectManager{}
projectMgr := &project.Manager{}
repositoryMgr := &repository.FakeManager{}
retentionScheduler := &fakeRetentionScheduler{}
retentionLauncher := &fakeLauncher{}
@ -127,7 +143,7 @@ func (s *ControllerTestSuite) TestPolicy() {
}
func (s *ControllerTestSuite) TestExecution() {
projectMgr := &fakeProjectManager{}
projectMgr := &project.Manager{}
repositoryMgr := &repository.FakeManager{}
retentionScheduler := &fakeRetentionScheduler{}
retentionLauncher := &fakeLauncher{}

View File

@ -16,10 +16,11 @@ package retention
import (
"fmt"
"time"
beegoorm "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/selector"
"time"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/selector/selectors/index"
@ -325,7 +326,7 @@ func launcherError(err error) error {
}
func getProjects(projectMgr project.Manager) ([]*selector.Candidate, error) {
projects, err := projectMgr.List()
projects, err := projectMgr.List(orm.Context())
if err != nil {
return nil, err
}

View File

@ -15,7 +15,8 @@
package retention
import (
"fmt"
"testing"
"github.com/goharbor/harbor/src/common/job"
"github.com/goharbor/harbor/src/common/models"
_ "github.com/goharbor/harbor/src/lib/selector/selectors/doublestar"
@ -24,42 +25,14 @@ import (
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
"github.com/goharbor/harbor/src/pkg/retention/q"
hjob "github.com/goharbor/harbor/src/testing/job"
"github.com/goharbor/harbor/src/testing/mock"
projecttesting "github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/goharbor/harbor/src/testing/pkg/repository"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"testing"
)
type fakeProjectManager struct {
projects []*models.Project
}
func (f *fakeProjectManager) List(...*models.ProjectQueryParam) ([]*models.Project, error) {
return f.projects, nil
}
func (f *fakeProjectManager) Get(idOrName interface{}) (*models.Project, error) {
id, ok := idOrName.(int64)
if ok {
for _, pro := range f.projects {
if pro.ProjectID == id {
return pro, nil
}
}
return nil, nil
}
name, ok := idOrName.(string)
if ok {
for _, pro := range f.projects {
if pro.Name == name {
return pro, nil
}
}
return nil, nil
}
return nil, fmt.Errorf("invalid parameter: %v, should be ID(int64) or name(string)", idOrName)
}
type fakeRetentionManager struct{}
func (f *fakeRetentionManager) GetTotalOfRetentionExecs(policyID int64) (int64, error) {
@ -145,10 +118,11 @@ func (l *launchTestSuite) SetupTest() {
ProjectID: 2,
Name: "test",
}
l.projectMgr = &fakeProjectManager{
projects: []*models.Project{
projectMgr := &projecttesting.Manager{}
mock.OnAnything(projectMgr, "List").Return([]*models.Project{
pro1, pro2,
}}
}, nil)
l.projectMgr = projectMgr
l.repositoryMgr = &repository.FakeManager{}
l.retentionMgr = &fakeRetentionManager{}
l.jobserviceClient = &hjob.MockJobClient{

74
src/pkg/user/dao/dao.go Normal file
View File

@ -0,0 +1,74 @@
// 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 dao
import (
"context"
"strings"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/user/models"
)
// DAO is the data access object interface for user
type DAO interface {
// List list users
List(ctx context.Context, query *q.Query) ([]*models.User, error)
}
// New returns an instance of the default DAO
func New() DAO {
return &dao{}
}
type dao struct{}
// List list users
func (d *dao) List(ctx context.Context, query *q.Query) ([]*models.User, error) {
query = q.MustClone(query)
if query.Sorting == "" {
query.Sorting = "username"
}
excludeAdmin := true
for key := range query.Keywords {
str := strings.ToLower(key)
if str == "user_id__in" {
excludeAdmin = false
break
} else if str == "user_id" {
excludeAdmin = false
break
}
}
if excludeAdmin {
// Exclude admin account when not filter by UserIDs, see https://github.com/goharbor/harbor/issues/2527
query.Keywords["user_id__gt"] = 1
}
qs, err := orm.QuerySetter(ctx, &models.User{}, query)
if err != nil {
return nil, err
}
users := []*models.User{}
if _, err := qs.OrderBy(query.Sorting).All(&users); err != nil {
return nil, err
}
return users, nil
}

View File

@ -0,0 +1,46 @@
// 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 dao
import (
"testing"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
)
type DaoTestSuite struct {
htesting.Suite
dao DAO
}
func (suite *DaoTestSuite) SetupSuite() {
suite.Suite.SetupSuite()
suite.dao = New()
}
func (suite *DaoTestSuite) TestList() {
{
users, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"user_id": 1}))
suite.Nil(err)
suite.Len(users, 1)
}
}
func TestDaoTestSuite(t *testing.T) {
suite.Run(t, &DaoTestSuite{})
}

48
src/pkg/user/manager.go Normal file
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 user
import (
"context"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/user/dao"
"github.com/goharbor/harbor/src/pkg/user/models"
)
var (
// Mgr is the global project manager
Mgr = New()
)
// Manager is used for user management
type Manager interface {
// List users according to the query
List(ctx context.Context, query *q.Query) (models.Users, error)
}
// New returns a default implementation of Manager
func New() Manager {
return &manager{dao: dao.New()}
}
type manager struct {
dao dao.DAO
}
// List users according to the query
func (m *manager) List(ctx context.Context, query *q.Query) (models.Users, error) {
return m.dao.List(ctx, query)
}

View File

@ -0,0 +1,35 @@
// 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 models
import (
"github.com/goharbor/harbor/src/common/models"
)
// User ...
type User = models.User
// Users the collection for User
type Users []*User
// MapByUserID returns map which key is UserID of the user and value is the user itself
func (users Users) MapByUserID() map[int]*User {
m := map[int]*User{}
for _, user := range users {
m[user.UserID] = user
}
return m
}

View File

@ -16,12 +16,13 @@ package repoproxy
import (
"context"
"testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
securitySecret "github.com/goharbor/harbor/src/common/security/secret"
"github.com/goharbor/harbor/src/core/config"
"testing"
)
func TestIsProxyProject(t *testing.T) {
@ -60,7 +61,7 @@ func TestIsProxySession(t *testing.T) {
sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore)
otherCtx := security.NewContext(context.Background(), sc1)
sc2 := proxycachesecret.NewSecurityContext("library/hello-world")
sc2 := proxycachesecret.NewSecurityContext(context.Background(), "library/hello-world")
proxyCtx := security.NewContext(context.Background(), sc2)
cases := []struct {
name string

View File

@ -38,5 +38,5 @@ func (p *proxyCacheSecret) Generate(req *http.Request) security.Context {
return nil
}
log.Debugf("a proxy cache secret security context generated for request %s %s", req.Method, req.URL.Path)
return proxycachesecret.NewSecurityContext(artifact.Repository)
return proxycachesecret.NewSecurityContext(req.Context(), artifact.Repository)
}

View File

@ -9,6 +9,8 @@ import (
mock "github.com/stretchr/testify/mock"
project "github.com/goharbor/harbor/src/controller/project"
q "github.com/goharbor/harbor/src/lib/q"
)
// Controller is an autogenerated mock type for the Controller type
@ -16,6 +18,62 @@ type Controller struct {
mock.Mock
}
// 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)
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, _a1
func (_m *Controller) Create(ctx context.Context, _a1 *models.Project) (int64, error) {
ret := _m.Called(ctx, _a1)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *models.Project) int64); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.Project) error); ok {
r1 = rf(ctx, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// 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
}
// Get provides a mock function with given fields: ctx, projectID, options
func (_m *Controller) Get(ctx context.Context, projectID int64, options ...project.Option) (*models.Project, error) {
_va := make([]interface{}, len(options))

View File

@ -16,9 +16,13 @@ package pkg
//go:generate mockery --case snake --dir ../../pkg/blob --name Manager --output ./blob --outpkg blob
//go:generate mockery --case snake --dir ../../vendor/github.com/docker/distribution --name Manifest --output ./distribution --outpkg distribution
//go:generate mockery --case snake --dir ../../pkg/project --name Manager --output ./project --outpkg project
//go:generate mockery --case snake --dir ../../pkg/project/metadata --name Manager --output ./project/metadata --outpkg metadata
//go:generate mockery --case snake --dir ../../pkg/quota --name Manager --output ./quota --outpkg quota
//go:generate mockery --case snake --dir ../../pkg/quota/driver --name Driver --output ./quota/driver --outpkg driver
//go:generate mockery --case snake --dir ../../pkg/scan/allowlist --name Manager --output ./scan/allowlist --outpkg allowlist
//go:generate mockery --case snake --dir ../../pkg/scan/report --name Manager --output ./scan/report --outpkg report
//go:generate mockery --case snake --dir ../../pkg/scan/rest/v1 --all --output ./scan/rest/v1 --outpkg v1
//go:generate mockery --case snake --dir ../../pkg/scan/scanner --all --output ./scan/scanner --outpkg scanner
//go:generate mockery --case snake --dir ../../pkg/scheduler --name Scheduler --output ./scheduler --outpkg scheduler
//go:generate mockery --case snake --dir ../../pkg/user --name Manager --output ./user --outpkg user

View File

@ -1,36 +1,84 @@
// 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 v2.1.0. DO NOT EDIT.
package project
import (
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/mock"
context "context"
models "github.com/goharbor/harbor/src/common/models"
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/lib/q"
)
// FakeManager is an autogenerated mock type for the FakeManager type
type FakeManager struct {
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// Get provides a mock function with given fields: _a0
func (_m *FakeManager) Get(_a0 interface{}) (*models.Project, error) {
ret := _m.Called(_a0)
// 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, _a1
func (_m *Manager) Create(ctx context.Context, _a1 *models.Project) (int64, error) {
ret := _m.Called(ctx, _a1)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *models.Project) int64); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.Project) error); ok {
r1 = rf(ctx, _a1)
} 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
}
// Get provides a mock function with given fields: ctx, idOrName
func (_m *Manager) Get(ctx context.Context, idOrName interface{}) (*models.Project, error) {
ret := _m.Called(ctx, idOrName)
var r0 *models.Project
if rf, ok := ret.Get(0).(func(interface{}) *models.Project); ok {
r0 = rf(_a0)
if rf, ok := ret.Get(0).(func(context.Context, interface{}) *models.Project); ok {
r0 = rf(ctx, idOrName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Project)
@ -38,8 +86,8 @@ func (_m *FakeManager) Get(_a0 interface{}) (*models.Project, error) {
}
var r1 error
if rf, ok := ret.Get(1).(func(interface{}) error); ok {
r1 = rf(_a0)
if rf, ok := ret.Get(1).(func(context.Context, interface{}) error); ok {
r1 = rf(ctx, idOrName)
} else {
r1 = ret.Error(1)
}
@ -47,19 +95,20 @@ func (_m *FakeManager) Get(_a0 interface{}) (*models.Project, error) {
return r0, r1
}
// List provides a mock function with given fields: _a0
func (_m *FakeManager) List(_a0 ...*models.ProjectQueryParam) ([]*models.Project, error) {
_va := make([]interface{}, len(_a0))
for _i := range _a0 {
_va[_i] = _a0[_i]
// List provides a mock function with given fields: ctx, query
func (_m *Manager) List(ctx context.Context, query ...*models.ProjectQueryParam) ([]*models.Project, error) {
_va := make([]interface{}, len(query))
for _i := range query {
_va[_i] = query[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 []*models.Project
if rf, ok := ret.Get(0).(func(...*models.ProjectQueryParam) []*models.Project); ok {
r0 = rf(_a0...)
if rf, ok := ret.Get(0).(func(context.Context, ...*models.ProjectQueryParam) []*models.Project); ok {
r0 = rf(ctx, query...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Project)
@ -67,8 +116,8 @@ func (_m *FakeManager) List(_a0 ...*models.ProjectQueryParam) ([]*models.Project
}
var r1 error
if rf, ok := ret.Get(1).(func(...*models.ProjectQueryParam) error); ok {
r1 = rf(_a0...)
if rf, ok := ret.Get(1).(func(context.Context, ...*models.ProjectQueryParam) error); ok {
r1 = rf(ctx, query...)
} else {
r1 = ret.Error(1)
}

View File

@ -0,0 +1,118 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package metadata
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "github.com/goharbor/harbor/src/common/models"
)
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// Add provides a mock function with given fields: ctx, projectID, meta
func (_m *Manager) Add(ctx context.Context, projectID int64, meta map[string]string) error {
ret := _m.Called(ctx, projectID, meta)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok {
r0 = rf(ctx, projectID, meta)
} else {
r0 = ret.Error(0)
}
return r0
}
// Delete provides a mock function with given fields: ctx, projectID, meta
func (_m *Manager) Delete(ctx context.Context, projectID int64, meta ...string) error {
_va := make([]interface{}, len(meta))
for _i := range meta {
_va[_i] = meta[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, projectID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) error); ok {
r0 = rf(ctx, projectID, meta...)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: ctx, projectID, meta
func (_m *Manager) Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error) {
_va := make([]interface{}, len(meta))
for _i := range meta {
_va[_i] = meta[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, projectID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 map[string]string
if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) map[string]string); ok {
r0 = rf(ctx, projectID, meta...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64, ...string) error); ok {
r1 = rf(ctx, projectID, meta...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, name, value
func (_m *Manager) List(ctx context.Context, name string, value string) ([]*models.ProjectMetadata, error) {
ret := _m.Called(ctx, name, value)
var r0 []*models.ProjectMetadata
if rf, ok := ret.Get(0).(func(context.Context, string, string) []*models.ProjectMetadata); ok {
r0 = rf(ctx, name, value)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.ProjectMetadata)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, name, value)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, projectID, meta
func (_m *Manager) Update(ctx context.Context, projectID int64, meta map[string]string) error {
ret := _m.Called(ctx, projectID, meta)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok {
r0 = rf(ctx, projectID, meta)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,101 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package allowlist
import (
models "github.com/goharbor/harbor/src/common/models"
mock "github.com/stretchr/testify/mock"
)
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// CreateEmpty provides a mock function with given fields: projectID
func (_m *Manager) CreateEmpty(projectID int64) error {
ret := _m.Called(projectID)
var r0 error
if rf, ok := ret.Get(0).(func(int64) error); ok {
r0 = rf(projectID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: projectID
func (_m *Manager) Get(projectID int64) (*models.CVEAllowlist, error) {
ret := _m.Called(projectID)
var r0 *models.CVEAllowlist
if rf, ok := ret.Get(0).(func(int64) *models.CVEAllowlist); ok {
r0 = rf(projectID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.CVEAllowlist)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(projectID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetSys provides a mock function with given fields:
func (_m *Manager) GetSys() (*models.CVEAllowlist, error) {
ret := _m.Called()
var r0 *models.CVEAllowlist
if rf, ok := ret.Get(0).(func() *models.CVEAllowlist); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.CVEAllowlist)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Set provides a mock function with given fields: projectID, list
func (_m *Manager) Set(projectID int64, list models.CVEAllowlist) error {
ret := _m.Called(projectID, list)
var r0 error
if rf, ok := ret.Get(0).(func(int64, models.CVEAllowlist) error); ok {
r0 = rf(projectID, list)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetSys provides a mock function with given fields: list
func (_m *Manager) SetSys(list models.CVEAllowlist) error {
ret := _m.Called(list)
var r0 error
if rf, ok := ret.Get(0).(func(models.CVEAllowlist) error); ok {
r0 = rf(list)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,40 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package user
import (
context "context"
models "github.com/goharbor/harbor/src/pkg/user/models"
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
}
// List provides a mock function with given fields: ctx, query
func (_m *Manager) List(ctx context.Context, query *q.Query) (models.Users, error) {
ret := _m.Called(ctx, query)
var r0 models.Users
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) models.Users); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.Users)
}
}
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
}