mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-18 13:41:21 +01:00
Merge pull request #12604 from heww/project-enhancement
refactor(project): add more methods to project controller and manager
This commit is contained in:
commit
44e5c58c30
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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"`
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -5,22 +5,22 @@ 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.
|
||||
// The intention for this guy is only for support CLI push/pull. It should not be used in other scenario without careful review
|
||||
// Each request should have a different instance of tokenSecurityCtx
|
||||
type tokenSecurityCtx struct {
|
||||
logger *log.Logger
|
||||
name string
|
||||
accessMap map[string]map[types.Action]struct{}
|
||||
pm project.Manager
|
||||
logger *log.Logger
|
||||
name string
|
||||
accessMap map[string]map[types.Action]struct{}
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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...)
|
||||
for k, v := range query.Keywords {
|
||||
col := strings.SplitN(k, orm.ExprSep, 2)[0]
|
||||
if _, exist := cols[col]; !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
// fuzzy match
|
||||
f, ok := v.(*q.FuzzyMatchValue)
|
||||
if ok {
|
||||
qs = qs.Filter(k+"__icontains", f.Value)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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)
|
||||
ignored := map[string]bool{}
|
||||
for _, col := range ignoredCols {
|
||||
ignored[col] = true
|
||||
}
|
||||
|
||||
columns := queriableColumns(model)
|
||||
methods := queriableMethods(model)
|
||||
for k, v := range query.Keywords {
|
||||
field := strings.SplitN(k, orm.ExprSep, 2)[0]
|
||||
if ignored[field] {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Escape special characters
|
||||
func Escape(str string) string {
|
||||
return dao.Escape(str)
|
||||
key := getFullName(reflect.Indirect(val).Type()) + "-methods"
|
||||
value, ok := cache.Load(key)
|
||||
if ok {
|
||||
return value.(map[string]string)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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"`
|
||||
Field2 string `orm:"column(customized_field2)"`
|
||||
Field3 string
|
||||
Field4 string `orm:"column(field4)"`
|
||||
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
|
||||
FirstName string
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
@ -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
41
src/lib/q/query_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
169
src/pkg/project/dao/dao.go
Normal 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
|
||||
}
|
299
src/pkg/project/dao/dao_test.go
Normal file
299
src/pkg/project/dao/dao_test.go
Normal 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{})
|
||||
}
|
43
src/pkg/project/dao/model.go
Normal file
43
src/pkg/project/dao/model.go
Normal 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"
|
||||
}
|
@ -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{}
|
||||
type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
// 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]
|
||||
// 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 dao.GetProjects(q)
|
||||
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())
|
||||
}
|
||||
|
104
src/pkg/project/metadata/dao/dao.go
Normal file
104
src/pkg/project/metadata/dao/dao.go
Normal 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
|
||||
}
|
70
src/pkg/project/metadata/dao/dao_test.go
Normal file
70
src/pkg/project/metadata/dao/dao_test.go
Normal 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{})
|
||||
}
|
128
src/pkg/project/metadata/manager.go
Normal file
128
src/pkg/project/metadata/manager.go
Normal 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)
|
||||
}
|
22
src/pkg/project/metadata/models/metadata.go
Normal file
22
src/pkg/project/metadata/models/metadata.go
Normal 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
|
43
src/pkg/project/models/project.go
Normal file
43
src/pkg/project/models/project.go
Normal 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
|
@ -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{}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{
|
||||
pro1, pro2,
|
||||
}}
|
||||
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
74
src/pkg/user/dao/dao.go
Normal 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
|
||||
}
|
46
src/pkg/user/dao/dao_test.go
Normal file
46
src/pkg/user/dao/dao_test.go
Normal 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
48
src/pkg/user/manager.go
Normal 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)
|
||||
}
|
35
src/pkg/user/models/user.go
Normal file
35
src/pkg/user/models/user.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
118
src/testing/pkg/project/metadata/manager.go
Normal file
118
src/testing/pkg/project/metadata/manager.go
Normal 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
|
||||
}
|
101
src/testing/pkg/scan/allowlist/manager.go
Normal file
101
src/testing/pkg/scan/allowlist/manager.go
Normal 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
|
||||
}
|
40
src/testing/pkg/user/manager.go
Normal file
40
src/testing/pkg/user/manager.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user