Add interfaces to implement project level policy (#3271)

* add interfaces to implement project level policy
This commit is contained in:
Wenkai Yin 2017-09-26 16:41:08 +08:00 committed by GitHub
parent 0982dff6ed
commit e79334a445
28 changed files with 614 additions and 297 deletions

View File

@ -0,0 +1,39 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 (
"time"
)
// keys of project metadata
const (
ProMetaPublic = "public"
ProMetaEnableContentTrust = "enable_content_trust"
ProMetaPreventVul = "prevent_vul"
ProMetaSeverity = "severity"
ProMetaAutoScan = "auto_scan"
)
// ProjectMetadata holds the metadata of a project.
type ProjectMetadata struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
Name string `orm:"column(name)" json:"name"`
Value string `orm:"column(value)" json:"value"`
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time)" json:"update_time"`
Deleted int `orm:"column(deleted)" json:"deleted"`
}

View File

@ -21,24 +21,25 @@ import (
// Project holds the details of a project.
// TODO remove useless attrs
type Project struct {
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
Name string `orm:"column(name)" json:"name"`
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
CreationTimeStr string `orm:"-" json:"creation_time_str"`
Deleted int `orm:"column(deleted)" json:"deleted"`
//UserID int `json:"UserId"`
OwnerName string `orm:"-" json:"owner_name"`
Public int `orm:"column(public)" json:"public"`
//This field does not have correspondent column in DB, this is just for UI to disable button
Togglable bool `orm:"-"`
UpdateTime time.Time `orm:"update_time" json:"update_time"`
Role int `orm:"-" json:"current_user_role_id"`
RepoCount int `orm:"-" json:"repo_count"`
EnableContentTrust bool `orm:"-" json:"enable_content_trust"`
PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"`
PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"`
AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"`
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
Name string `orm:"column(name)" json:"name"`
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
UpdateTime time.Time `orm:"update_time" json:"update_time"`
Deleted int `orm:"column(deleted)" json:"deleted"`
CreationTimeStr string `orm:"-" json:"creation_time_str"`
OwnerName string `orm:"-" json:"owner_name"`
Togglable bool `orm:"-"`
Role int `orm:"-" json:"current_user_role_id"`
RepoCount int `orm:"-" json:"repo_count"`
Metadata map[string]interface{} `orm:"-" json:"metadata"`
// TODO remove
Public int `orm:"column(public)" json:"public"`
EnableContentTrust bool `orm:"-" json:"enable_content_trust"`
PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"`
PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"`
AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"`
}
// ProjectSorter holds an array of projects
@ -109,3 +110,9 @@ type ProjectRequest struct {
PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"`
AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"`
}
// ProjectQueryResult ...
type ProjectQueryResult struct {
Total int64
Projects []*Project
}

View File

@ -26,11 +26,11 @@ import (
// auth context and project manager
type SecurityContext struct {
ctx *authcontext.AuthContext
pm promgr.ProMgr
pm promgr.ProjectManager
}
// NewSecurityContext ...
func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProMgr) *SecurityContext {
func NewSecurityContext(ctx *authcontext.AuthContext, pm promgr.ProjectManager) *SecurityContext {
return &SecurityContext{
ctx: ctx,
pm: pm,

View File

@ -25,11 +25,11 @@ import (
// SecurityContext implements security.Context interface based on database
type SecurityContext struct {
user *models.User
pm promgr.ProMgr
pm promgr.ProjectManager
}
// NewSecurityContext ...
func NewSecurityContext(user *models.User, pm promgr.ProMgr) *SecurityContext {
func NewSecurityContext(user *models.User, pm promgr.ProjectManager) *SecurityContext {
return &SecurityContext{
user: user,
pm: pm,

View File

@ -25,6 +25,7 @@ import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
)
@ -47,7 +48,7 @@ var (
Email: "guestUser@vmware.com",
}
pm = &local.ProjectManager{}
pm = promgr.NewDefaultProjectManager(local.NewDriver(), true)
)
func TestMain(m *testing.M) {

View File

@ -159,19 +159,10 @@ func ParseProjectIDOrName(value interface{}) (int64, string, error) {
case int:
i := value.(int)
id = int64(i)
if id == 0 {
return 0, "", fmt.Errorf("invalid ID: 0")
}
case int64:
id = value.(int64)
if id == 0 {
return 0, "", fmt.Errorf("invalid ID: 0")
}
case string:
name = value.(string)
if len(name) == 0 {
return 0, "", fmt.Errorf("empty name")
}
default:
return 0, "", fmt.Errorf("unsupported type")
}

View File

@ -217,14 +217,6 @@ func TestParseHarborIDOrName(t *testing.T) {
id, name, err := ParseProjectIDOrName(nil)
assert.NotNil(t, err)
// invalid ID
id, name, err = ParseProjectIDOrName(0)
assert.NotNil(t, err)
// invalid name
id, name, err = ParseProjectIDOrName("")
assert.NotNil(t, err)
// valid int ID
id, name, err = ParseProjectIDOrName(1)
assert.Nil(t, err)

View File

@ -31,7 +31,7 @@ type BaseController struct {
SecurityCtx security.Context
// ProjectMgr is the project manager which abstracts the operations
// related to projects
ProjectMgr promgr.ProMgr
ProjectMgr promgr.ProjectManager
}
const (

View File

@ -109,7 +109,7 @@ func (p *ProjectAPI) Post() {
return
}
exist, err := p.ProjectMgr.Exist(pro.Name)
exist, err := p.ProjectMgr.Exists(pro.Name)
if err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
pro.Name), err)
@ -325,19 +325,13 @@ func (p *ProjectAPI) List() {
}
}
total, err := p.ProjectMgr.GetTotal(query, base)
result, err := p.ProjectMgr.List(query, base)
if err != nil {
p.ParseAndHandleError("failed to get total of projects", err)
p.ParseAndHandleError("failed to list projects", err)
return
}
projects, err := p.ProjectMgr.GetAll(query, base)
if err != nil {
p.ParseAndHandleError("failed to get projects", err)
return
}
for _, project := range projects {
for _, project := range result.Projects {
if p.SecurityCtx.IsAuthenticated() {
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
if len(roles) != 0 {
@ -359,8 +353,8 @@ func (p *ProjectAPI) List() {
project.RepoCount = len(repos)
}
p.SetPaginationHeader(total, page, size)
p.Data["json"] = projects
p.SetPaginationHeader(result.Total, page, size)
p.Data["json"] = result.Projects
p.ServeJSON()
}
@ -385,7 +379,9 @@ func (p *ProjectAPI) ToggleProjectPublic() {
if err := p.ProjectMgr.Update(p.project.ProjectID,
&models.Project{
Public: req.Public,
Metadata: map[string]interface{}{
models.ProMetaPublic: req.Public,
},
}); err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to update project %d",
p.project.ProjectID), err)

View File

@ -84,7 +84,7 @@ func (ra *RepositoryAPI) Get() {
return
}
exist, err := ra.ProjectMgr.Exist(projectID)
exist, err := ra.ProjectMgr.Exists(projectID)
if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %d",
projectID), err)
@ -335,7 +335,7 @@ func (ra *RepositoryAPI) GetTags() {
repoName := ra.GetString(":splat")
projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName)
exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err)
@ -478,7 +478,7 @@ func (ra *RepositoryAPI) GetManifests() {
}
projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName)
exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err)
@ -610,7 +610,7 @@ func (ra *RepositoryAPI) GetSignatures() {
repoName := ra.GetString(":splat")
projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName)
exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err)
@ -651,7 +651,7 @@ func (ra *RepositoryAPI) ScanImage() {
repoName := ra.GetString(":splat")
tag := ra.GetString(":tag")
projectName, _ := utils.ParseRepository(repoName)
exist, err := ra.ProjectMgr.Exist(projectName)
exist, err := ra.ProjectMgr.Exists(projectName)
if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s",
projectName), err)
@ -794,7 +794,7 @@ func getSignatures(username, repository string) (map[string][]notary.Target, err
func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) {
project, _ := utils.ParseRepository(repository)
exist, err := ra.ProjectMgr.Exist(project)
exist, err := ra.ProjectMgr.Exists(project)
if err != nil {
return false, "", err
}

View File

@ -48,11 +48,12 @@ func (s *SearchAPI) Get() {
var err error
if isSysAdmin {
projects, err = s.ProjectMgr.GetAll(nil)
result, err := s.ProjectMgr.List(nil)
if err != nil {
s.ParseAndHandleError("failed to get projects", err)
return
}
projects = result.Projects
} else {
projects, err = s.ProjectMgr.GetPublic()
if err != nil {

View File

@ -77,15 +77,15 @@ func (s *StatisticAPI) Get() {
statistic[PubRC] = n
if s.SecurityCtx.IsSysAdmin() {
n, err := s.ProjectMgr.GetTotal(nil)
result, err := s.ProjectMgr.List(nil)
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
}
statistic[TPC] = n
statistic[PriPC] = n - statistic[PubPC]
statistic[TPC] = result.Total
statistic[PriPC] = result.Total - statistic[PubPC]
n, err = dao.GetTotalOfRepositories("")
n, err := dao.GetTotalOfRepositories("")
if err != nil {
log.Errorf("failed to get total of repositories: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
@ -94,7 +94,7 @@ func (s *StatisticAPI) Get() {
statistic[PriRC] = n - statistic[PubRC]
} else {
value := false
projects, err := s.ProjectMgr.GetAll(&models.ProjectQueryParam{
result, err := s.ProjectMgr.List(&models.ProjectQueryParam{
Public: &value,
Member: &models.MemberQuery{
Name: s.username,
@ -106,10 +106,10 @@ func (s *StatisticAPI) Get() {
return
}
statistic[PriPC] = (int64)(len(projects))
statistic[PriPC] = result.Total
ids := []int64{}
for _, p := range projects {
for _, p := range result.Projects {
ids = append(ids, p.ProjectID)
}

View File

@ -166,7 +166,7 @@ func postReplicationAction(policyID int64, acton string) error {
}
// SyncRegistry syncs the repositories of registry with database.
func SyncRegistry(pm promgr.ProMgr) error {
func SyncRegistry(pm promgr.ProjectManager) error {
log.Infof("Start syncing repositories from registry to DB... ")
@ -254,7 +254,7 @@ func catalog() ([]string, error) {
}
func diffRepos(reposInRegistry []string, reposInDB []string,
pm promgr.ProMgr) ([]string, []string, error) {
pm promgr.ProjectManager) ([]string, []string, error) {
var needsAdd []string
var needsDel []string
@ -359,9 +359,9 @@ func diffRepos(reposInRegistry []string, reposInDB []string,
return needsAdd, needsDel, nil
}
func projectExists(pm promgr.ProMgr, repository string) (bool, error) {
func projectExists(pm promgr.ProjectManager, repository string) (bool, error) {
project, _ := utils.ParseRepository(repository)
return pm.Exist(project)
return pm.Exists(project)
}
func initRegistryClient() (r *registry.Registry, err error) {

View File

@ -15,7 +15,7 @@
package config
import (
"crypto/tls"
//"crypto/tls"
"encoding/json"
"fmt"
"net/http"
@ -30,6 +30,7 @@ import (
"github.com/vmware/harbor/src/common/secret"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/local"
)
@ -46,7 +47,7 @@ var (
// AdminserverClient is a client for adminserver
AdminserverClient client.Client
// GlobalProjectMgr is initialized based on the deploy mode
GlobalProjectMgr promgr.ProMgr
GlobalProjectMgr promgr.ProjectManager
mg *comcfg.Manager
keyProvider comcfg.KeyProvider
// AdmiralClient is initialized only under integration deploy mode
@ -105,34 +106,41 @@ func initSecretStore() {
}
func initProjectManager() {
if !WithAdmiral() {
var driver pmsdriver.PMSDriver
if WithAdmiral() {
// TODO add support for admiral
/*
// integration with admiral
log.Info("initializing the project manager based on PMS...")
// TODO read ca/cert file and pass it to the TLS config
AdmiralClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
path := os.Getenv("SERVICE_TOKEN_FILE_PATH")
if len(path) == 0 {
path = defaultTokenFilePath
}
log.Infof("service token file path: %s", path)
TokenReader = &admiral.FileTokenReader{
Path: path,
}
GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient,
AdmiralEndpoint(), TokenReader)
*/
GlobalProjectMgr = nil
} else {
// standalone
log.Info("initializing the project manager based on database...")
GlobalProjectMgr = &local.ProjectManager{}
return
log.Info("initializing the project manager based on local database...")
driver = local.NewDriver()
// TODO move the statement out of the else block when admiral driver is completed
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
}
// integration with admiral
log.Info("initializing the project manager based on PMS...")
// TODO read ca/cert file and pass it to the TLS config
AdmiralClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
path := os.Getenv("SERVICE_TOKEN_FILE_PATH")
if len(path) == 0 {
path = defaultTokenFilePath
}
log.Infof("service token file path: %s", path)
TokenReader = &admiral.FileTokenReader{
Path: path,
}
GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient,
AdmiralEndpoint(), TokenReader)
}
// Load configurations
@ -379,6 +387,7 @@ func AdmiralEndpoint() string {
log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err)
return ""
}
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
return ""
}

View File

@ -33,7 +33,7 @@ import (
"github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/promgr"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
//"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
)
type key string
@ -264,11 +264,16 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
return false
}
log.Debug("creating PMS project manager...")
pm := admiral.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), &admiral.RawTokenReader{
Token: token,
})
/*
log.Debug("creating PMS project manager...")
pm := admiral.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), &admiral.RawTokenReader{
Token: token,
})
*/
// TODO create the DefaultProjectManager with the real admiral PMSDriver
pm := promgr.NewDefaultProjectManager(nil, false)
log.Debug("creating admiral security context...")
securCtx := admr.NewSecurityContext(authContext, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
@ -283,12 +288,16 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
log.Debug("user information is nil")
var securCtx security.Context
var pm promgr.ProMgr
var pm promgr.ProjectManager
if config.WithAdmiral() {
// integration with admiral
log.Debug("creating PMS project manager...")
pm = admiral.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), nil)
/*
log.Debug("creating PMS project manager...")
pm = admiral.NewProjectManager(config.AdmiralClient,
config.AdmiralEndpoint(), nil)
*/
// TODO create the DefaultProjectManager with the real admiral PMSDriver
pm = promgr.NewDefaultProjectManager(nil, false)
log.Debug("creating admiral security context...")
securCtx = admr.NewSecurityContext(nil, pm)
} else {
@ -302,7 +311,7 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
return true
}
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProMgr) {
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) {
addToReqContext(req, securCtxKey, ctx)
addToReqContext(req, pmKey, pm)
}
@ -331,7 +340,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) {
}
// GetProjectManager tries to get project manager from request and returns it
func GetProjectManager(req *http.Request) (promgr.ProMgr, error) {
func GetProjectManager(req *http.Request) (promgr.ProjectManager, error) {
if req == nil {
return nil, fmt.Errorf("request is nil")
}
@ -341,7 +350,7 @@ func GetProjectManager(req *http.Request) (promgr.ProMgr, error) {
return nil, fmt.Errorf("the project manager got from request is nil")
}
p, ok := pm.(promgr.ProMgr)
p, ok := pm.(promgr.ProjectManager)
if !ok {
return nil, fmt.Errorf("the variable got from request is not project manager type")
}

View File

@ -316,9 +316,9 @@ func TestGetProjectManager(t *testing.T) {
req, err = http.NewRequest("", "", nil)
assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(),
pmKey, &driver_local.ProjectManager{}))
pmKey, promgr.NewDefaultProjectManager(driver_local.NewDriver(), true)))
pm, err = GetProjectManager(req)
assert.Nil(t, err)
_, ok := pm.(promgr.ProMgr)
_, ok := pm.(promgr.ProjectManager)
assert.True(t, ok)
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 metamgr
import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
)
// ProjectMetadataManaegr defines the operations that a project metadata manager should
// implement
type ProjectMetadataManaegr interface {
// Add metadatas for project specified by projectID
Add(projectID int64, meta map[string]interface{}) error
// Delete metadatas whose keys are specified in parameter meta, if it
// is absent, delete all
Delete(projecdtID int64, meta ...[]string) error
// Update metadatas
Update(projectID int64, meta map[string]interface{}) error
// Get metadatas whose keys are specified in parameter meta, if it is
// absent, get all
Get(projectID int64, meta ...[]string) (map[string]interface{}, error)
}
type defaultProjectMetadataManaegr struct{}
// NewDefaultProjectMetadataManager ...
func NewDefaultProjectMetadataManager() ProjectMetadataManaegr {
return &defaultProjectMetadataManaegr{}
}
// TODO add implement
func (d *defaultProjectMetadataManaegr) Add(projectID int64, meta map[string]interface{}) error {
return nil
}
func (d *defaultProjectMetadataManaegr) Delete(projectID int64, meta ...[]string) error {
return nil
}
func (d *defaultProjectMetadataManaegr) Update(projectID int64, meta map[string]interface{}) error {
// TODO remove the logic
public, ok := meta[models.ProMetaPublic]
if ok {
return dao.ToggleProjectPublicity(projectID, public.(int))
}
return nil
}
func (d *defaultProjectMetadataManaegr) Get(projectID int64, meta ...[]string) (map[string]interface{}, error) {
return nil, nil
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 metamgr
// TODO add test cases

View File

@ -196,7 +196,7 @@ func TestGet(t *testing.T) {
// get by invalid ID
project, err := pm.Get(int64(0))
assert.NotNil(t, err)
assert.Nil(t, err)
assert.Nil(t, project)
// get by invalid name

View File

@ -0,0 +1,36 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 pmsdriver
import (
"github.com/vmware/harbor/src/common/models"
)
// PMSDriver defines the operations that a project management service driver
// should implement
type PMSDriver interface {
// Get a project by ID or name
Get(projectIDOrName interface{}) (*models.Project, error)
// Create a project
Create(*models.Project) (int64, error)
// Delete a project by ID or name
Delete(projectIDOrName interface{}) error
// Update the properties of a project
Update(projectIDOrName interface{}, project *models.Project) error
// List lists projects according to the query conditions
// TODO remove base
List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
}

View File

@ -21,61 +21,39 @@ import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils"
errutil "github.com/vmware/harbor/src/common/utils/error"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
)
const dupProjectPattern = `Duplicate entry '\w+' for key 'name'`
// ProjectManager implements pm.PM interface based on database
type ProjectManager struct{}
type driver struct {
}
// NewDriver returns an instance of driver
func NewDriver() pmsdriver.PMSDriver {
return &driver{}
}
// Get ...
func (p *ProjectManager) Get(projectIDOrName interface{}) (
func (d *driver) Get(projectIDOrName interface{}) (
*models.Project, error) {
switch projectIDOrName.(type) {
case string:
return dao.GetProjectByName(projectIDOrName.(string))
case int64:
return dao.GetProjectByID(projectIDOrName.(int64))
default:
return nil, fmt.Errorf("unsupported type of %v, must be string or int64", projectIDOrName)
}
}
// Exist ...
func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) {
project, err := p.Get(projectIDOrName)
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
if err != nil {
return false, err
}
return project != nil, nil
}
// IsPublic returns whether the project is public or not
func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) {
project, err := p.Get(projectIDOrName)
if err != nil {
return false, err
return nil, err
}
if project == nil {
return false, nil
if id > 0 {
return dao.GetProjectByID(id)
}
return project.Public == 1, nil
}
// GetPublic returns all public projects
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
t := true
return p.GetAll(&models.ProjectQueryParam{
Public: &t,
})
return dao.GetProjectByName(name)
}
// Create ...
func (p *ProjectManager) Create(project *models.Project) (int64, error) {
func (d *driver) Create(project *models.Project) (int64, error) {
if project == nil {
return 0, fmt.Errorf("project is nil")
}
@ -128,10 +106,13 @@ func (p *ProjectManager) Create(project *models.Project) (int64, error) {
}
// Delete ...
func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
id, ok := projectIDOrName.(int64)
if !ok {
project, err := p.Get(projectIDOrName)
func (d *driver) Delete(projectIDOrName interface{}) error {
id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
if err != nil {
return err
}
if len(name) > 0 {
project, err := dao.GetProjectByName(name)
if err != nil {
return err
}
@ -142,27 +123,31 @@ func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
}
// Update ...
func (p *ProjectManager) Update(projectIDOrName interface{},
func (d *driver) Update(projectIDOrName interface{},
project *models.Project) error {
id, ok := projectIDOrName.(int64)
if !ok {
pro, err := p.Get(projectIDOrName)
if err != nil {
return err
}
id = pro.ProjectID
// nil implement
return nil
}
// TODO remove base
// List returns a project list according to the query parameters
func (d *driver) List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (
*models.ProjectQueryResult, error) {
total, err := dao.GetTotalOfProjects(query, base...)
if err != nil {
return nil, err
}
return dao.ToggleProjectPublicity(id, project.Public)
projects, err := dao.GetProjects(query, base...)
if err != nil {
return nil, err
}
return &models.ProjectQueryResult{
Total: total,
Projects: projects,
}, nil
}
// GetAll returns a project list according to the query parameters
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
[]*models.Project, error) {
return dao.GetProjects(query, base...)
}
// GetTotal returns the total count according to the query parameters
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
int64, error) {
return dao.GetTotalOfProjects(query, base...)
func (d *driver) EnableExternalMetaMgr() bool {
return true
}

View File

@ -71,7 +71,7 @@ func TestMain(m *testing.M) {
}
func TestGet(t *testing.T) {
pm := &ProjectManager{}
pm := &driver{}
// project name
project, err := pm.Get("library")
@ -95,45 +95,8 @@ func TestGet(t *testing.T) {
assert.NotNil(t, err)
}
func TestExist(t *testing.T) {
pm := &ProjectManager{}
// exist project
exist, err := pm.Exist("library")
assert.Nil(t, err)
assert.True(t, exist)
// non-exist project
exist, err = pm.Exist("non-exist-project")
assert.Nil(t, err)
assert.False(t, exist)
}
func TestIsPublic(t *testing.T) {
pms := &ProjectManager{}
// public project
public, err := pms.IsPublic("library")
assert.Nil(t, err)
assert.True(t, public)
// non exist project
public, err = pms.IsPublic("non_exist_project")
assert.Nil(t, err)
assert.False(t, public)
}
func TestGetPublic(t *testing.T) {
pm := &ProjectManager{}
projects, err := pm.GetPublic()
assert.Nil(t, err)
assert.NotEqual(t, 0, len(projects))
for _, project := range projects {
assert.Equal(t, 1, project.Public)
}
}
func TestCreateAndDelete(t *testing.T) {
pm := &ProjectManager{}
pm := &driver{}
// nil project
_, err := pm.Create(nil)
@ -183,63 +146,12 @@ func TestCreateAndDelete(t *testing.T) {
}
func TestUpdate(t *testing.T) {
pm := &ProjectManager{}
id, err := pm.Create(&models.Project{
Name: "test",
OwnerID: 1,
})
assert.Nil(t, err)
defer pm.Delete(id)
project, err := pm.Get(id)
assert.Nil(t, err)
assert.Equal(t, 0, project.Public)
project.Public = 1
assert.Nil(t, pm.Update(id, project))
project, err = pm.Get(id)
assert.Nil(t, err)
assert.Equal(t, 1, project.Public)
pm := &driver{}
assert.Nil(t, pm.Update(1, nil))
}
func TestGetTotal(t *testing.T) {
pm := &ProjectManager{}
id, err := pm.Create(&models.Project{
Name: "get_total_test",
OwnerID: 1,
Public: 1,
})
assert.Nil(t, err)
defer pm.Delete(id)
// get by name
total, err := pm.GetTotal(&models.ProjectQueryParam{
Name: "get_total_test",
})
assert.Nil(t, err)
assert.Equal(t, int64(1), total)
// get by owner
total, err = pm.GetTotal(&models.ProjectQueryParam{
Owner: "admin",
})
assert.Nil(t, err)
assert.NotEqual(t, 0, total)
// get by public
value := true
total, err = pm.GetTotal(&models.ProjectQueryParam{
Public: &value,
})
assert.Nil(t, err)
assert.NotEqual(t, 0, total)
}
func TestGetAll(t *testing.T) {
pm := &ProjectManager{}
func TestList(t *testing.T) {
pm := &driver{}
id, err := pm.Create(&models.Project{
Name: "get_all_test",
@ -250,19 +162,19 @@ func TestGetAll(t *testing.T) {
defer pm.Delete(id)
// get by name
projects, err := pm.GetAll(&models.ProjectQueryParam{
result, err := pm.List(&models.ProjectQueryParam{
Name: "get_all_test",
})
assert.Nil(t, err)
assert.Equal(t, id, projects[0].ProjectID)
assert.Equal(t, id, result.Projects[0].ProjectID)
// get by owner
projects, err = pm.GetAll(&models.ProjectQueryParam{
result, err = pm.List(&models.ProjectQueryParam{
Owner: "admin",
})
assert.Nil(t, err)
exist := false
for _, project := range projects {
for _, project := range result.Projects {
if project.ProjectID == id {
exist = true
break
@ -272,12 +184,12 @@ func TestGetAll(t *testing.T) {
// get by public
value := true
projects, err = pm.GetAll(&models.ProjectQueryParam{
result, err = pm.List(&models.ProjectQueryParam{
Public: &value,
})
assert.Nil(t, err)
exist = false
for _, project := range projects {
for _, project := range result.Projects {
if project.ProjectID == id {
exist = true
break

View File

@ -15,22 +15,158 @@
package promgr
import (
"fmt"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr/metamgr"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
)
// ProMgr is the project mamager which abstracts the operations related
// ProjectManager is the project mamager which abstracts the operations related
// to projects
type ProMgr interface {
type ProjectManager interface {
Get(projectIDOrName interface{}) (*models.Project, error)
IsPublic(projectIDOrName interface{}) (bool, error)
Exist(projectIDOrName interface{}) (bool, error)
// get all public project
GetPublic() ([]*models.Project, error)
Create(*models.Project) (int64, error)
Delete(projectIDOrName interface{}) error
Update(projectIDOrName interface{}, project *models.Project) error
// GetAll returns a project list according to the query parameters
GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error)
// GetTotal returns the total count according to the query parameters
GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error)
// TODO remove base
List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
IsPublic(projectIDOrName interface{}) (bool, error)
Exists(projectIDOrName interface{}) (bool, error)
// get all public project
GetPublic() ([]*models.Project, error)
}
type defaultProjectManager struct {
pmsDriver pmsdriver.PMSDriver
metaMgrEnabled bool // if metaMgrEnabled is enabled, metaMgr will be used to CURD metadata
metaMgr metamgr.ProjectMetadataManaegr
}
// NewDefaultProjectManager returns an instance of defaultProjectManager,
// if metaMgrEnabled is true, a project metadata manager will be created
// and used to CURD metadata
func NewDefaultProjectManager(driver pmsdriver.PMSDriver, metaMgrEnabled bool) ProjectManager {
mgr := &defaultProjectManager{
pmsDriver: driver,
metaMgrEnabled: metaMgrEnabled,
}
if metaMgrEnabled {
mgr.metaMgr = metamgr.NewDefaultProjectMetadataManager()
}
return mgr
}
func (d *defaultProjectManager) Get(projectIDOrName interface{}) (*models.Project, error) {
project, err := d.pmsDriver.Get(projectIDOrName)
if err != nil {
return nil, err
}
if project != nil && d.metaMgrEnabled {
meta, err := d.metaMgr.Get(project.ProjectID)
if err != nil {
return nil, err
}
if len(project.Metadata) == 0 {
project.Metadata = make(map[string]interface{})
}
for k, v := range meta {
project.Metadata[k] = v
}
}
return project, nil
}
func (d *defaultProjectManager) Create(project *models.Project) (int64, error) {
id, err := d.pmsDriver.Create(project)
if err != nil {
return 0, err
}
if len(project.Metadata) > 0 && d.metaMgrEnabled {
if err = d.metaMgr.Add(id, project.Metadata); err != nil {
log.Errorf("failed to add metadata for project %s: %v", project.Name, err)
}
}
return id, nil
}
func (d *defaultProjectManager) Delete(projectIDOrName interface{}) error {
project, err := d.Get(projectIDOrName)
if err != nil {
return err
}
if project == nil {
return nil
}
if project.Metadata != nil && d.metaMgrEnabled {
if err = d.metaMgr.Delete(project.ProjectID); err != nil {
return err
}
}
return d.pmsDriver.Delete(project.ProjectID)
}
func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *models.Project) error {
if len(project.Metadata) > 0 && d.metaMgrEnabled {
pro, err := d.Get(projectIDOrName)
if err != nil {
return err
}
if pro == nil {
return fmt.Errorf("project %v not found", projectIDOrName)
}
if err = d.metaMgr.Update(pro.ProjectID, project.Metadata); err != nil {
return err
}
}
return d.pmsDriver.Update(projectIDOrName, project)
}
// TODO remove base
func (d *defaultProjectManager) List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) {
result, err := d.pmsDriver.List(query, base...)
if err != nil {
return nil, err
}
if d.metaMgrEnabled {
for _, project := range result.Projects {
meta, err := d.metaMgr.Get(project.ProjectID)
if err != nil {
return nil, err
}
project.Metadata = meta
}
}
return result, nil
}
func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) {
project, err := d.Get(projectIDOrName)
if err != nil {
return false, err
}
if project == nil {
return false, nil
}
return project.Public == 1, nil
}
func (d *defaultProjectManager) Exists(projectIDOrName interface{}) (bool, error) {
project, err := d.Get(projectIDOrName)
return project != nil, err
}
func (d *defaultProjectManager) GetPublic() ([]*models.Project, error) {
value := true
result, err := d.List(&models.ProjectQueryParam{
Public: &value,
})
if err != nil {
return nil, err
}
return result.Projects, nil
}

View File

@ -0,0 +1,120 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 promgr
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
)
type fakePMSDriver struct {
project *models.Project
}
func newFakePMSDriver() pmsdriver.PMSDriver {
return &fakePMSDriver{
project: &models.Project{
ProjectID: 1,
Name: "library",
Public: 1,
},
}
}
func (f *fakePMSDriver) Get(projectIDOrName interface{}) (*models.Project, error) {
return f.project, nil
}
func (f *fakePMSDriver) Create(*models.Project) (int64, error) {
return 1, nil
}
func (f *fakePMSDriver) Delete(projectIDOrName interface{}) error {
return nil
}
func (f *fakePMSDriver) Update(projectIDOrName interface{}, project *models.Project) error {
return nil
}
func (f *fakePMSDriver) List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) {
return &models.ProjectQueryResult{
Total: 1,
Projects: []*models.Project{f.project},
}, nil
}
var (
proMgr = NewDefaultProjectManager(newFakePMSDriver(), false)
)
func TestGet(t *testing.T) {
project, err := proMgr.Get(1)
require.Nil(t, err)
assert.Equal(t, int64(1), project.ProjectID)
}
func TestCreate(t *testing.T) {
id, err := proMgr.Create(&models.Project{
Name: "library",
OwnerID: 1,
})
require.Nil(t, err)
assert.Equal(t, int64(1), id)
}
func TestDelete(t *testing.T) {
assert.Nil(t, proMgr.Delete(1))
}
func TestUpdate(t *testing.T) {
assert.Nil(t, proMgr.Update(1,
&models.Project{
Metadata: map[string]interface{}{
models.ProMetaPublic: true,
},
}))
}
func TestList(t *testing.T) {
result, err := proMgr.List(nil)
require.Nil(t, err)
assert.Equal(t, int64(1), result.Total)
assert.Equal(t, int64(1), result.Projects[0].ProjectID)
}
func TestIsPublic(t *testing.T) {
public, err := proMgr.IsPublic(1)
require.Nil(t, err)
assert.True(t, public)
}
func TestExist(t *testing.T) {
exist, err := proMgr.Exists(1)
require.Nil(t, err)
assert.True(t, exist)
}
func TestGetPublic(t *testing.T) {
projects, err := proMgr.GetPublic()
require.Nil(t, err)
assert.Equal(t, 1, len(projects))
assert.Equal(t, 1, projects[0].Public)
}

View File

@ -2,14 +2,14 @@ package proxy
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
//"github.com/stretchr/testify/require"
"github.com/vmware/harbor/src/adminserver/client"
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models"
notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
utilstest "github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
//"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
"net/http"
"net/http/httptest"
@ -128,6 +128,8 @@ func TestEnvPolicyChecker(t *testing.T) {
assert.Equal(sev, models.SevNone)
}
// TODO uncheck after admiral pms driver is implemented
/*
func TestPMSPolicyChecker(t *testing.T) {
var defaultConfigAdmiral = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint,
@ -147,7 +149,6 @@ func TestPMSPolicyChecker(t *testing.T) {
if err := config.Init(); err != nil {
panic(err)
}
pm := admiral.NewProjectManager(http.DefaultClient,
admiralEndpoint, &admiral.RawTokenReader{
Token: "token",
@ -175,7 +176,7 @@ func TestPMSPolicyChecker(t *testing.T) {
assert.False(t, projectVulnerableEnabled)
assert.Equal(t, projectVulnerableSeverity, models.SevLow)
}
*/
func TestMatchNotaryDigest(t *testing.T) {
assert := assert.New(t)
//The data from common/utils/notary/helper_test.go

View File

@ -88,7 +88,7 @@ func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
}
type pmsPolicyChecker struct {
pm promgr.ProMgr
pm promgr.ProjectManager
}
func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool {
@ -109,7 +109,7 @@ func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
}
// newPMSPolicyChecker returns an instance of an pmsPolicyChecker
func newPMSPolicyChecker(pm promgr.ProMgr) policyChecker {
func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker {
return &pmsPolicyChecker{
pm: pm,
}

View File

@ -81,7 +81,7 @@ func GetResourceActions(scopes []string) []*token.ResourceActions {
//filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions.
func filterAccess(access []*token.ResourceActions, ctx security.Context,
pm promgr.ProMgr, filters map[string]accessFilter) error {
pm promgr.ProjectManager, filters map[string]accessFilter) error {
var err error
for _, a := range access {
f, ok := filters[a.Type]

View File

@ -127,13 +127,13 @@ func parseImg(s string) (*image, error) {
// An accessFilter will filter access based on userinfo
type accessFilter interface {
filter(ctx security.Context, pm promgr.ProMgr, a *token.ResourceActions) error
filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error
}
type registryFilter struct {
}
func (reg registryFilter) filter(ctx security.Context, pm promgr.ProMgr,
func (reg registryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
a *token.ResourceActions) error {
//Do not filter if the request is to access registry catalog
if a.Name != "catalog" {
@ -151,7 +151,7 @@ type repositoryFilter struct {
parser imageParser
}
func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProMgr,
func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
a *token.ResourceActions) error {
//clear action list to assign to new acess element after perm check.
img, err := rep.parser.parse(a.Name)
@ -161,7 +161,7 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProMgr,
project := img.namespace
permission := ""
exist, err := pm.Exist(project)
exist, err := pm.Exists(project)
if err != nil {
return err
}