Apply project level policies to standalone Harbor

The following features are only enabled in integration mode, this commit moves
these to standalone Harbor:
 - Content trust policy: only signed images can be pulled
 - Vulnerability policy: only images whose severity is below the threshold can be pulled
 - Automatic scan policy: automatic scan pushed images
This commit is contained in:
Wenkai Yin 2017-09-29 16:37:43 +08:00
parent a1b8969793
commit 66b2d0d3f3
43 changed files with 690 additions and 953 deletions

View File

@ -167,6 +167,38 @@ paths:
description: User need to log in first. description: User need to log in first.
'500': '500':
description: Internal errors. description: Internal errors.
put:
summary: Update properties for a selected project.
description: |
This endpoint is aimed to update the properties of a project.
parameters:
- name: project_id
in: path
type: integer
format: int64
required: true
description: Selected project ID.
- name: project
in: body
required: true
schema:
$ref: '#/definitions/Project'
description: Updates of project.
tags:
- Products
responses:
'200':
description: Updated project properties successfully.
'400':
description: Illegal format of provided ID value.
'401':
description: User need to log in first.
'403':
description: User does not have permission to the project.
'404':
description: Project ID does not exist.
'500':
description: Unexpected internal errors.
delete: delete:
summary: Delete project by projectID summary: Delete project by projectID
description: | description: |
@ -193,39 +225,6 @@ paths:
description: 'Project contains policies, can not be deleted.' description: 'Project contains policies, can not be deleted.'
'500': '500':
description: Internal errors. description: Internal errors.
'/projects/{project_id}/publicity':
put:
summary: Update properties for a selected project.
description: |
This endpoint is aimed to toggle a project publicity status.
parameters:
- name: project_id
in: path
type: integer
format: int64
required: true
description: Selected project ID.
- name: project
in: body
required: true
schema:
$ref: '#/definitions/Project'
description: Updates of project.
tags:
- Products
responses:
'200':
description: Updated project publicity status successfully.
'400':
description: Illegal format of provided ID value.
'401':
description: User need to log in first.
'403':
description: User does not have permission to the project.
'404':
description: Project ID does not exist.
'500':
description: Unexpected internal errors.
'/projects/{project_id}/logs': '/projects/{project_id}/logs':
get: get:
summary: Get access logs accompany with a relevant project. summary: Get access logs accompany with a relevant project.
@ -2016,10 +2015,6 @@ definitions:
owner_name: owner_name:
type: string type: string
description: The owner name of the project. description: The owner name of the project.
public:
type: integer
format: int
description: The public status of the project.
Togglable: Togglable:
type: boolean type: boolean
description: >- description: >-
@ -2031,6 +2026,18 @@ definitions:
repo_count: repo_count:
type: integer type: integer
description: The number of the repositories under this project. description: The number of the repositories under this project.
metadata:
type: object
description: The metadata of the project.
items:
$ref: '#/definitions/ProjectMetadata'
ProjectMetadata:
type: object
properties:
public:
type: integer
format: int
description: The public status of the project.
enable_content_trust: enable_content_trust:
type: boolean type: boolean
description: >- description: >-

View File

@ -73,14 +73,13 @@ create table project (
creation_time timestamp, creation_time timestamp,
update_time timestamp, update_time timestamp,
deleted tinyint (1) DEFAULT 0 NOT NULL, deleted tinyint (1) DEFAULT 0 NOT NULL,
public tinyint (1) DEFAULT 0 NOT NULL,
primary key (project_id), primary key (project_id),
FOREIGN KEY (owner_id) REFERENCES user(user_id), FOREIGN KEY (owner_id) REFERENCES user(user_id),
UNIQUE (name) UNIQUE (name)
); );
insert into project (owner_id, name, creation_time, update_time, public) values insert into project (owner_id, name, creation_time, update_time) values
(1, 'library', NOW(), NOW(), 1); (1, 'library', NOW(), NOW());
create table project_member ( create table project_member (
project_id int NOT NULL, project_id int NOT NULL,

View File

@ -71,13 +71,12 @@ create table project (
creation_time timestamp, creation_time timestamp,
update_time timestamp, update_time timestamp,
deleted tinyint (1) DEFAULT 0 NOT NULL, deleted tinyint (1) DEFAULT 0 NOT NULL,
public tinyint (1) DEFAULT 0 NOT NULL,
FOREIGN KEY (owner_id) REFERENCES user(user_id), FOREIGN KEY (owner_id) REFERENCES user(user_id),
UNIQUE (name) UNIQUE (name)
); );
insert into project (owner_id, name, creation_time, update_time, public) values insert into project (owner_id, name, creation_time, update_time) values
(1, 'library', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1); (1, 'library', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
create table project_member ( create table project_member (
project_id int NOT NULL, project_id int NOT NULL,

View File

@ -420,19 +420,6 @@ func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
} }
} }
func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
projects, err := GetHasReadPermProjects(currentUser.Username)
if err != nil {
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
}
if len(projects) != 1 {
t.Errorf("Expected only one project in DB, but actual: %d", len(projects))
}
if projects[0].Name != "library" {
t.Errorf("There name of the project does not match, expected: %s, actual: %s", "library", projects[0].Name)
}
}
func TestAddProject(t *testing.T) { func TestAddProject(t *testing.T) {
project := models.Project{ project := models.Project{
@ -657,43 +644,6 @@ func TestGetUserByProject(t *testing.T) {
} }
func TestToggleProjectPublicity(t *testing.T) {
err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
currentProject, err = GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != publicityOn {
t.Errorf("project, id: %d, its publicity is not on", currentProject.ProjectID)
}
err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
currentProject, err = GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != publicityOff {
t.Errorf("project, id: %d, its publicity is not off", currentProject.ProjectID)
}
}
/*
func TestIsProjectPublic(t *testing.T) {
if isPublic := IsProjectPublic(projectName); isPublic {
t.Errorf("project, id: %d, its publicity is not false after turning off", currentProject.ProjectID)
}
}
*/
func TestGetUserProjectRoles(t *testing.T) { func TestGetUserProjectRoles(t *testing.T) {
r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID) r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID)
if err != nil { if err != nil {
@ -710,17 +660,6 @@ func TestGetUserProjectRoles(t *testing.T) {
} }
} }
/*
func TestProjectPermission(t *testing.T) {
roleCode, err := GetPermission(currentUser.Username, currentProject.Name)
if err != nil {
t.Errorf("Error occurred in GetPermission: %v", err)
}
if roleCode != "MDRWS" {
t.Errorf("The expected role code is MDRWS,but actual: %s", roleCode)
}
}
*/
func TestGetTotalOfProjects(t *testing.T) { func TestGetTotalOfProjects(t *testing.T) {
total, err := GetTotalOfProjects(nil) total, err := GetTotalOfProjects(nil)
if err != nil { if err != nil {
@ -745,22 +684,6 @@ func TestGetProjects(t *testing.T) {
} }
} }
func TestGetPublicProjects(t *testing.T) {
value := true
projects, err := GetProjects(&models.ProjectQueryParam{
Public: &value,
})
if err != nil {
t.Errorf("Error occurred in getProjects: %v", err)
}
if len(projects) != 1 {
t.Errorf("Expected length of projects is 1, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[0].Name != "library" {
t.Errorf("Expected project name in the list: %s, actual: %s", "library", projects[0].Name)
}
}
func TestAddProjectMember(t *testing.T) { func TestAddProjectMember(t *testing.T) {
err := AddProjectMember(currentProject.ProjectID, 1, models.DEVELOPER) err := AddProjectMember(currentProject.ProjectID, 1, models.DEVELOPER)
if err != nil { if err != nil {

View File

@ -91,3 +91,12 @@ func paramPlaceholder(n int) string {
} }
return strings.Join(placeholders, ",") return strings.Join(placeholders, ",")
} }
// ListProjectMetadata ...
func ListProjectMetadata(name, value string) ([]*models.ProjectMetadata, error) {
sql := `select * from project_metadata
where name = ? and value = ? and deleted = 0`
metadatas := []*models.ProjectMetadata{}
_, err := GetOrmer().Raw(sql, name, value).QueryRows(&metadatas)
return metadatas, err
}

View File

@ -64,6 +64,12 @@ func TestProMetaDaoMethods(t *testing.T) {
assert.Equal(t, value1, m[name1].Value) assert.Equal(t, value1, m[name1].Value)
assert.Equal(t, value2, m[name2].Value) assert.Equal(t, value2, m[name2].Value)
// test list
metas, err = ListProjectMetadata(name1, value1)
require.Nil(t, err)
assert.Equal(t, 1, len(metas))
assert.Equal(t, int64(1), metas[0].ProjectID)
// test update // test update
newValue1 := "new_value1" newValue1 := "new_value1"
meta1.Value = newValue1 meta1.Value = newValue1

View File

@ -30,13 +30,13 @@ import (
func AddProject(project models.Project) (int64, error) { func AddProject(project models.Project) (int64, error) {
o := GetOrmer() o := GetOrmer()
p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare() p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted) values (?, ?, ?, ?, ?)").Prepare()
if err != nil { if err != nil {
return 0, err return 0, err
} }
now := time.Now() now := time.Now()
r, err := p.Exec(project.OwnerID, project.Name, now, now, project.Deleted, project.Public) r, err := p.Exec(project.OwnerID, project.Name, now, now, project.Deleted)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -50,49 +50,11 @@ func AddProject(project models.Project) (int64, error) {
return projectID, err return projectID, err
} }
/*
// IsProjectPublic ...
func IsProjectPublic(projectName string) bool {
project, err := GetProjectByName(projectName)
if err != nil {
log.Errorf("Error occurred in GetProjectByName: %v", err)
return false
}
if project == nil {
return false
}
return project.Public == 1
}
//ProjectExists returns whether the project exists according to its name of ID.
func ProjectExists(nameOrID interface{}) (bool, error) {
o := GetOrmer()
type dummy struct{}
sql := `select project_id from project where deleted = 0 and `
switch nameOrID.(type) {
case int64:
sql += `project_id = ?`
case string:
sql += `name = ?`
default:
return false, fmt.Errorf("Invalid nameOrId: %v", nameOrID)
}
var d []dummy
num, err := o.Raw(sql, nameOrID).QueryRows(&d)
if err != nil {
return false, err
}
return num > 0, nil
}
*/
// GetProjectByID ... // GetProjectByID ...
func GetProjectByID(id int64) (*models.Project, error) { func GetProjectByID(id int64) (*models.Project, error) {
o := GetOrmer() o := GetOrmer()
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time, p.public sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
from project p left join user u on p.owner_id = u.user_id where p.deleted = 0 and p.project_id = ?` from project p left join user u on p.owner_id = u.user_id where p.deleted = 0 and p.project_id = ?`
queryParam := make([]interface{}, 1) queryParam := make([]interface{}, 1)
queryParam = append(queryParam, id) queryParam = append(queryParam, id)
@ -127,95 +89,19 @@ func GetProjectByName(name string) (*models.Project, error) {
return &p[0], nil return &p[0], nil
} }
/*
// GetPermission gets roles that the user has according to the project.
func GetPermission(username, projectName string) (string, error) {
o := GetOrmer()
sql := `select r.role_code from role as r
inner join project_member as pm on r.role_id = pm.role
inner join user as u on u.user_id = pm.user_id
inner join project p on p.project_id = pm.project_id
where u.username = ? and p.name = ? and u.deleted = 0 and p.deleted = 0`
var r []models.Role
n, err := o.Raw(sql, username, projectName).QueryRows(&r)
if err != nil {
return "", err
}
if n == 0 {
return "", nil
}
return r[0].RoleCode, nil
}
*/
// ToggleProjectPublicity toggles the publicity of the project.
func ToggleProjectPublicity(projectID int64, publicity int) error {
o := GetOrmer()
sql := "update project set public = ? where project_id = ?"
_, err := o.Raw(sql, publicity, projectID).Exec()
return err
}
// GetHasReadPermProjects returns a project list,
// which satisfies the following conditions:
// 1. the project is not deleted
// 2. the prject is public or the user is a member of the project
func GetHasReadPermProjects(username string) ([]*models.Project, error) {
user, err := GetUser(models.User{
Username: username,
})
if err != nil {
return nil, err
}
o := GetOrmer()
sql :=
`select distinct p.project_id, p.name, p.public,
p.owner_id, p.creation_time, p.update_time
from project p
left join project_member pm
on p.project_id = pm.project_id
where (pm.user_id = ? or p.public = 1)
and p.deleted = 0 `
var projects []*models.Project
if _, err := o.Raw(sql, user.UserID).QueryRows(&projects); err != nil {
return nil, err
}
return projects, nil
}
// GetTotalOfProjects returns the total count of projects // GetTotalOfProjects returns the total count of projects
// according to the query conditions // according to the query conditions
func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
var pagination *models.Pagination
var (
owner string
name string
public *bool
member string
role int
)
if query != nil { if query != nil {
owner = query.Owner pagination = query.Pagination
name = query.Name query.Pagination = nil
public = query.Public
if query.Member != nil {
member = query.Member.Name
role = query.Member.Role
} }
sql, params := projectQueryConditions(query)
if query != nil {
query.Pagination = pagination
} }
sql, params := projectQueryConditions(owner, name, public, member, role, base...)
sql = `select count(*) ` + sql sql = `select count(*) ` + sql
var total int64 var total int64
@ -224,86 +110,39 @@ func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BasePro
} }
// GetProjects returns a project list according to the query conditions // GetProjects returns a project list according to the query conditions
func GetProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) { func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
sql, params := projectQueryConditions(query)
var ( sql = `select distinct p.project_id, p.name, p.owner_id,
owner string
name string
public *bool
member string
role int
page int64
size int64
)
if query != nil {
owner = query.Owner
name = query.Name
public = query.Public
if query.Member != nil {
member = query.Member.Name
role = query.Member.Role
}
if query.Pagination != nil {
page = query.Pagination.Page
size = query.Pagination.Size
}
}
sql, params := projectQueryConditions(owner, name, public, member, role, base...)
sql = `select distinct p.project_id, p.name, p.public, p.owner_id,
p.creation_time, p.update_time ` + sql p.creation_time, p.update_time ` + sql
if size > 0 {
sql += ` limit ?`
params = append(params, size)
if page > 0 {
sql += ` offset ?`
params = append(params, (page-1)*size)
}
}
var projects []*models.Project var projects []*models.Project
_, err := GetOrmer().Raw(sql, params).QueryRows(&projects) _, err := GetOrmer().Raw(sql, params).QueryRows(&projects)
return projects, err return projects, err
} }
func projectQueryConditions(owner, name string, public *bool, member string, func projectQueryConditions(query *models.ProjectQueryParam) (string, []interface{}) {
role int, base ...*models.BaseProjectCollection) (string, []interface{}) {
params := []interface{}{} params := []interface{}{}
// the base project collections: sql := ` from project as p`
// 1. all projects
// 2. public projects if query == nil {
// 3. public projects and projects which the user is a member of sql += ` where p.deleted=0 order by p.name`
collection := `project ` return sql, params
if len(base) != 0 && base[0] != nil {
if len(base[0].Member) == 0 && base[0].Public {
collection = `(select * from project pr
where pr.public=1) `
}
if len(base[0].Member) > 0 && base[0].Public {
collection = `(select pr.project_id, pr.owner_id, pr.name, pr.
creation_time, pr.update_time, pr.deleted, pr.public
from project pr
join project_member prm
on pr.project_id = prm.project_id
join user ur
on prm.user_id=ur.user_id
where ur.username=? or pr.public=1 )`
params = append(params, base[0].Member)
}
} }
sql := ` from ` + collection + ` as p` // if query.ProjectIDs is not nil but has no element, the query will returns no rows
if query.ProjectIDs != nil && len(query.ProjectIDs) == 0 {
sql += ` where 1 = 0`
return sql, params
}
if len(owner) != 0 { if len(query.Owner) != 0 {
sql += ` join user u1 sql += ` join user u1
on p.owner_id = u1.user_id` on p.owner_id = u1.user_id`
} }
if len(member) != 0 { if query.Member != nil && len(query.Member.Name) != 0 {
sql += ` join project_member pm sql += ` join project_member pm
on p.project_id = pm.project_id on p.project_id = pm.project_id
join user u2 join user u2
@ -311,33 +150,24 @@ func projectQueryConditions(owner, name string, public *bool, member string,
} }
sql += ` where p.deleted=0` sql += ` where p.deleted=0`
if len(owner) != 0 { if len(query.Owner) != 0 {
sql += ` and u1.username=?` sql += ` and u1.username=?`
params = append(params, owner) params = append(params, query.Owner)
} }
if len(name) != 0 { if len(query.Name) != 0 {
sql += ` and p.name like ?` sql += ` and p.name like ?`
params = append(params, "%"+escape(name)+"%") params = append(params, "%"+escape(query.Name)+"%")
} }
if public != nil { if query.Member != nil && len(query.Member.Name) != 0 {
sql += ` and p.public = ?`
if *public {
params = append(params, 1)
} else {
params = append(params, 0)
}
}
if len(member) != 0 {
sql += ` and u2.username=?` sql += ` and u2.username=?`
params = append(params, member) params = append(params, query.Member.Name)
if role > 0 { if query.Member.Role > 0 {
sql += ` and pm.role = ?` sql += ` and pm.role = ?`
roleID := 0 roleID := 0
switch role { switch query.Member.Role {
case common.RoleProjectAdmin: case common.RoleProjectAdmin:
roleID = 1 roleID = 1
case common.RoleDeveloper: case common.RoleDeveloper:
@ -350,8 +180,24 @@ func projectQueryConditions(owner, name string, public *bool, member string,
} }
} }
if len(query.ProjectIDs) > 0 {
sql += fmt.Sprintf(` and p.project_id in ( %s )`,
paramPlaceholder(len(query.ProjectIDs)))
params = append(params, query.ProjectIDs)
}
sql += ` order by p.name` sql += ` order by p.name`
if query.Pagination != nil && query.Pagination.Size > 0 {
sql += ` limit ?`
params = append(params, query.Pagination.Size)
if query.Pagination.Page > 0 {
sql += ` offset ?`
params = append(params, (query.Pagination.Page-1)*query.Pagination.Size)
}
}
return sql, params return sql, params
} }

View File

@ -103,7 +103,6 @@ func TestGetTopRepos(t *testing.T) {
project1 := models.Project{ project1 := models.Project{
OwnerID: 1, OwnerID: 1,
Name: "project1", Name: "project1",
Public: 0,
} }
project1.ProjectID, err = AddProject(project1) project1.ProjectID, err = AddProject(project1)
require.NoError(err) require.NoError(err)
@ -112,7 +111,6 @@ func TestGetTopRepos(t *testing.T) {
project2 := models.Project{ project2 := models.Project{
OwnerID: 1, OwnerID: 1,
Name: "project2", Name: "project2",
Public: 0,
} }
project2.ProjectID, err = AddProject(project2) project2.ProjectID, err = AddProject(project2)
require.NoError(err) require.NoError(err)
@ -232,7 +230,6 @@ func TestGetAllRepositories(t *testing.T) {
project1 := models.Project{ project1 := models.Project{
OwnerID: 1, OwnerID: 1,
Name: "projectRepo", Name: "projectRepo",
Public: 0,
} }
var err2 error var err2 error
project1.ProjectID, err2 = AddProject(project1) project1.ProjectID, err2 = AddProject(project1)

View File

@ -18,13 +18,17 @@ import (
"time" "time"
) )
// keys of project metadata // keys of project metadata and severity values
const ( const (
ProMetaPublic = "public" ProMetaPublic = "public"
ProMetaEnableContentTrust = "enable_content_trust" ProMetaEnableContentTrust = "enable_content_trust"
ProMetaPreventVul = "prevent_vul" ProMetaPreventVul = "prevent_vul" //prevent vulnerable images from being pulled
ProMetaSeverity = "severity" ProMetaSeverity = "severity"
ProMetaAutoScan = "auto_scan" ProMetaAutoScan = "auto_scan"
SeverityNone = "negligible"
SeverityLow = "low"
SeverityMedium = "medium"
SeverityHigh = "high"
) )
// ProjectMetadata holds the metadata of a project. // ProjectMetadata holds the metadata of a project.

View File

@ -15,11 +15,11 @@
package models package models
import ( import (
"strings"
"time" "time"
) )
// Project holds the details of a project. // Project holds the details of a project.
// TODO remove useless attrs
type Project struct { type Project struct {
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
OwnerID int `orm:"column(owner_id)" json:"owner_id"` OwnerID int `orm:"column(owner_id)" json:"owner_id"`
@ -27,19 +27,79 @@ type Project struct {
CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"`
UpdateTime time.Time `orm:"update_time" json:"update_time"` UpdateTime time.Time `orm:"update_time" json:"update_time"`
Deleted int `orm:"column(deleted)" json:"deleted"` Deleted int `orm:"column(deleted)" json:"deleted"`
CreationTimeStr string `orm:"-" json:"creation_time_str"`
OwnerName string `orm:"-" json:"owner_name"` OwnerName string `orm:"-" json:"owner_name"`
Togglable bool `orm:"-"` Togglable bool `orm:"-" json:"togglable"`
Role int `orm:"-" json:"current_user_role_id"` Role int `orm:"-" json:"current_user_role_id"`
RepoCount int `orm:"-" json:"repo_count"` RepoCount int `orm:"-" json:"repo_count"`
Metadata map[string]string `orm:"-" json:"metadata"` Metadata map[string]string `orm:"-" json:"metadata"`
}
// TODO remove // GetMetadata ...
Public int `orm:"column(public)" json:"public"` func (p *Project) GetMetadata(key string) (string, bool) {
EnableContentTrust bool `orm:"-" json:"enable_content_trust"` if len(p.Metadata) == 0 {
PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` return "", false
PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` }
AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` value, exist := p.Metadata[key]
return value, exist
}
// SetMetadata ...
func (p *Project) SetMetadata(key, value string) {
if p.Metadata == nil {
p.Metadata = map[string]string{}
}
p.Metadata[key] = value
}
// IsPublic ...
func (p *Project) IsPublic() bool {
public, exist := p.GetMetadata(ProMetaPublic)
if !exist {
return false
}
return isTrue(public)
}
// ContentTrustEnabled ...
func (p *Project) ContentTrustEnabled() bool {
enabled, exist := p.GetMetadata(ProMetaEnableContentTrust)
if !exist {
return false
}
return isTrue(enabled)
}
// VulPrevented ...
func (p *Project) VulPrevented() bool {
prevent, exist := p.GetMetadata(ProMetaPreventVul)
if !exist {
return false
}
return isTrue(prevent)
}
// Severity ...
func (p *Project) Severity() string {
severity, exist := p.GetMetadata(ProMetaSeverity)
if !exist {
return ""
}
return severity
}
// AutoScan ...
func (p *Project) AutoScan() bool {
auto, exist := p.GetMetadata(ProMetaAutoScan)
if !exist {
return false
}
return isTrue(auto)
}
func isTrue(value string) bool {
return strings.ToLower(value) == "true" ||
strings.ToLower(value) == "1"
} }
// ProjectSorter holds an array of projects // ProjectSorter holds an array of projects
@ -79,6 +139,7 @@ type ProjectQueryParam struct {
Public *bool // the project is public or not, can be ture, false and nil Public *bool // the project is public or not, can be ture, false and nil
Member *MemberQuery // the member of project Member *MemberQuery // the member of project
Pagination *Pagination // pagination information Pagination *Pagination // pagination information
ProjectIDs []int64 // project ID list
} }
// MemberQuery fitler by member's username and role // MemberQuery fitler by member's username and role
@ -104,11 +165,8 @@ type BaseProjectCollection struct {
// ProjectRequest holds informations that need for creating project API // ProjectRequest holds informations that need for creating project API
type ProjectRequest struct { type ProjectRequest struct {
Name string `json:"project_name"` Name string `json:"project_name"`
Public int `json:"public"` Public *int `json:"public"` //deprecated, reserved for project creation in replication
EnableContentTrust bool `json:"enable_content_trust"` Metadata map[string]string `json:"metadata"`
PreventVulnerableImagesFromRunning bool `json:"prevent_vulnerable_images_from_running"`
PreventVulnerableImagesFromRunningSeverity string `json:"prevent_vulnerable_images_from_running_severity"`
AutomaticallyScanImagesOnPush bool `json:"automatically_scan_images_on_push"`
} }
// ProjectQueryResult ... // ProjectQueryResult ...

View File

@ -30,13 +30,13 @@ import (
func ParseClairSev(clairSev string) models.Severity { func ParseClairSev(clairSev string) models.Severity {
sev := strings.ToLower(clairSev) sev := strings.ToLower(clairSev)
switch sev { switch sev {
case "negligible": case models.SeverityNone:
return models.SevNone return models.SevNone
case "low": case models.SeverityLow:
return models.SevLow return models.SevLow
case "medium": case models.SeverityMedium:
return models.SevMedium return models.SevMedium
case "high": case models.SeverityHigh:
return models.SevHigh return models.SevHigh
default: default:
return models.SevUnknown return models.SevUnknown

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/docker/distribution" "github.com/docker/distribution"
@ -258,13 +259,23 @@ func getProject(name string) (*models.Project, error) {
} }
func (c *Checker) createProject(project *models.Project) error { func (c *Checker) createProject(project *models.Project) error {
pro := &models.ProjectRequest{ // only replicate the public property of project
pro := struct {
models.ProjectRequest
Public int `json:"public"`
}{
ProjectRequest: models.ProjectRequest{
Name: project.Name, Name: project.Name,
Public: project.Public, Metadata: map[string]string{
EnableContentTrust: project.EnableContentTrust, models.ProMetaPublic: strconv.FormatBool(project.IsPublic()),
PreventVulnerableImagesFromRunning: project.PreventVulnerableImagesFromRunning, },
PreventVulnerableImagesFromRunningSeverity: project.PreventVulnerableImagesFromRunningSeverity, },
AutomaticallyScanImagesOnPush: project.AutomaticallyScanImagesOnPush, }
// put "public" property in both metadata and public field to keep compatibility
// with old version API(<=1.2.0)
if project.IsPublic() {
pro.Public = 1
} }
data, err := json.Marshal(pro) data, err := json.Marshal(pro)

View File

@ -93,12 +93,11 @@ func init() {
beego.Router("/api/search/", &SearchAPI{}) beego.Router("/api/search/", &SearchAPI{})
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head") beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get") beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get;put:Put")
beego.Router("/api/users/:id", &UserAPI{}, "get:Get") beego.Router("/api/users/:id", &UserAPI{}, "get:Get")
beego.Router("/api/users", &UserAPI{}, "get:List;post:Post;delete:Delete;put:Put") beego.Router("/api/users", &UserAPI{}, "get:List;post:Post;delete:Delete;put:Put")
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword") beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
beego.Router("/api/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole") beego.Router("/api/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole")
beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic")
beego.Router("/api/projects/:id([0-9]+)/logs", &ProjectAPI{}, "get:Logs") beego.Router("/api/projects/:id([0-9]+)/logs", &ProjectAPI{}, "get:Logs")
beego.Router("/api/projects/:id([0-9]+)/_deletable", &ProjectAPI{}, "get:Deletable") beego.Router("/api/projects/:id([0-9]+)/_deletable", &ProjectAPI{}, "get:Deletable")
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get;post:Post;delete:Delete;put:Put") beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get;post:Post;delete:Delete;put:Put")
@ -359,18 +358,10 @@ func (a testapi) ProjectsGet(query *apilib.ProjectQuery, authInfo ...usrInfo) (i
} }
//Update properties for a selected project. //Update properties for a selected project.
func (a testapi) ToggleProjectPublicity(prjUsr usrInfo, projectID string, ispublic int32) (int, error) { func (a testapi) ProjectsPut(prjUsr usrInfo, projectID string,
// create path and map variables project *models.Project) (int, error) {
path := "/api/projects/" + projectID + "/publicity/" path := "/api/projects/" + projectID
_sling := sling.New().Put(a.basePath) _sling := sling.New().Put(a.basePath).Path(path).BodyJSON(project)
_sling = _sling.Path(path)
type QueryParams struct {
Public int32 `json:"public,omitempty"`
}
_sling = _sling.BodyJSON(&QueryParams{Public: ispublic})
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr)
return httpStatusCode, err return httpStatusCode, err

View File

@ -19,6 +19,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/tests/apitests/apilib" "github.com/vmware/harbor/tests/apitests/apilib"
) )
@ -38,7 +39,7 @@ func TestLogGet(t *testing.T) {
fmt.Println("add the project first.") fmt.Println("add the project first.")
project := apilib.ProjectReq{ project := apilib.ProjectReq{
ProjectName: "project_for_test_log", ProjectName: "project_for_test_log",
Public: 1, Metadata: map[string]string{models.ProMetaPublic: "true"},
} }
reply, err := apiTest.ProjectsPost(*testUser, project) reply, err := apiTest.ProjectsPost(*testUser, project)

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
"strings"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
@ -120,14 +121,23 @@ func (p *ProjectAPI) Post() {
return return
} }
if pro.Metadata == nil {
pro.Metadata = map[string]string{}
}
// accept the "public" property to make replication work well with old versions(<=1.2.0)
if pro.Public != nil && len(pro.Metadata[models.ProMetaPublic]) == 0 {
pro.Metadata[models.ProMetaPublic] = strconv.FormatBool(*pro.Public == 1)
}
// populate public metadata as false if it isn't set
if _, ok := pro.Metadata[models.ProMetaPublic]; !ok {
pro.Metadata[models.ProMetaPublic] = strconv.FormatBool(false)
}
projectID, err := p.ProjectMgr.Create(&models.Project{ projectID, err := p.ProjectMgr.Create(&models.Project{
Name: pro.Name, Name: pro.Name,
Public: pro.Public,
OwnerName: p.SecurityCtx.GetUsername(), OwnerName: p.SecurityCtx.GetUsername(),
EnableContentTrust: pro.EnableContentTrust, Metadata: pro.Metadata,
PreventVulnerableImagesFromRunning: pro.PreventVulnerableImagesFromRunning,
PreventVulnerableImagesFromRunningSeverity: pro.PreventVulnerableImagesFromRunningSeverity,
AutomaticallyScanImagesOnPush: pro.AutomaticallyScanImagesOnPush,
}) })
if err != nil { if err != nil {
if err == errutil.ErrDupProject { if err == errutil.ErrDupProject {
@ -178,7 +188,7 @@ func (p *ProjectAPI) Head() {
// Get ... // Get ...
func (p *ProjectAPI) Get() { func (p *ProjectAPI) Get() {
if p.project.Public == 0 { if !p.project.IsPublic() {
if !p.SecurityCtx.IsAuthenticated() { if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized() p.HandleUnauthorized()
return return
@ -311,21 +321,52 @@ func (p *ProjectAPI) List() {
query.Public = &pub query.Public = &pub
} }
// base project collection from which filter is done // standalone, filter projects according to the privilleges of the user first
base := &models.BaseProjectCollection{} if !config.WithAdmiral() {
var projects []*models.Project
if !p.SecurityCtx.IsAuthenticated() { if !p.SecurityCtx.IsAuthenticated() {
// not login, only get public projects // not login, only get public projects
base.Public = true pros, err := p.ProjectMgr.GetPublic()
if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to get public projects: %v", err))
return
}
projects = []*models.Project{}
projects = append(projects, pros...)
} else { } else {
if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) { if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
// login, but not system admin, get public projects and projects = []*models.Project{}
// login, but not system admin or solution user, get public projects and
// projects that the user is member of // projects that the user is member of
base.Member = p.SecurityCtx.GetUsername() pros, err := p.ProjectMgr.GetPublic()
base.Public = true if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to get public projects: %v", err))
return
}
projects = append(projects, pros...)
mps, err := p.ProjectMgr.List(&models.ProjectQueryParam{
Member: &models.MemberQuery{
Name: p.SecurityCtx.GetUsername(),
},
})
if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to list projects: %v", err))
return
}
projects = append(projects, mps.Projects...)
}
}
if projects != nil {
projectIDs := []int64{}
for _, project := range projects {
projectIDs = append(projectIDs, project.ProjectID)
}
query.ProjectIDs = projectIDs
} }
} }
result, err := p.ProjectMgr.List(query, base) result, err := p.ProjectMgr.List(query)
if err != nil { if err != nil {
p.ParseAndHandleError("failed to list projects", err) p.ParseAndHandleError("failed to list projects", err)
return return
@ -358,8 +399,8 @@ func (p *ProjectAPI) List() {
p.ServeJSON() p.ServeJSON()
} }
// ToggleProjectPublic ... // Put ...
func (p *ProjectAPI) ToggleProjectPublic() { func (p *ProjectAPI) Put() {
if !p.SecurityCtx.IsAuthenticated() { if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized() p.HandleUnauthorized()
return return
@ -372,16 +413,10 @@ func (p *ProjectAPI) ToggleProjectPublic() {
var req *models.ProjectRequest var req *models.ProjectRequest
p.DecodeJSONReq(&req) p.DecodeJSONReq(&req)
if req.Public != 0 && req.Public != 1 {
p.HandleBadRequest("public should be 0 or 1")
return
}
if err := p.ProjectMgr.Update(p.project.ProjectID, if err := p.ProjectMgr.Update(p.project.ProjectID,
&models.Project{ &models.Project{
Metadata: map[string]string{ Metadata: req.Metadata,
models.ProMetaPublic: strconv.Itoa(req.Public),
},
}); err != nil { }); err != nil {
p.ParseAndHandleError(fmt.Sprintf("failed to update project %d", p.ParseAndHandleError(fmt.Sprintf("failed to update project %d",
p.project.ProjectID), err) p.project.ProjectID), err)
@ -464,5 +499,39 @@ func validateProjectReq(req *models.ProjectRequest) error {
if !legal { if !legal {
return fmt.Errorf("project name is not in lower case or contains illegal characters") return fmt.Errorf("project name is not in lower case or contains illegal characters")
} }
if req.Metadata != nil {
metas := req.Metadata
req.Metadata = map[string]string{}
boolMetas := []string{
models.ProMetaPublic,
models.ProMetaEnableContentTrust,
models.ProMetaPreventVul,
models.ProMetaAutoScan}
for _, boolMeta := range boolMetas {
value, exist := metas[boolMeta]
if exist {
b, err := strconv.ParseBool(value)
if err != nil {
log.Errorf("failed to parse %s to bool: %v", value, err)
b = false
}
req.Metadata[boolMeta] = strconv.FormatBool(b)
}
}
value, exist := metas[models.ProMetaSeverity]
if exist {
switch strings.ToLower(value) {
case models.SeverityHigh, models.SeverityMedium, models.SeverityLow, models.SeverityNone:
req.Metadata[models.ProMetaSeverity] = strings.ToLower(value)
default:
return fmt.Errorf("invalid severity %s", value)
}
}
}
return nil return nil
} }

View File

@ -31,7 +31,7 @@ var addProject *apilib.ProjectReq
var addPID int var addPID int
func InitAddPro() { func InitAddPro() {
addProject = &apilib.ProjectReq{"add_project", 1} addProject = &apilib.ProjectReq{"add_project", map[string]string{models.ProMetaPublic: "true"}}
} }
func TestAddProject(t *testing.T) { func TestAddProject(t *testing.T) {
@ -82,7 +82,7 @@ func TestAddProject(t *testing.T) {
//case 4: reponse code = 400 : Project name is illegal in length //case 4: reponse code = 400 : Project name is illegal in length
fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ") fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ")
result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", 1}) result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", map[string]string{models.ProMetaPublic: "true"}})
if err != nil { if err != nil {
t.Error("Error while creat project", err.Error()) t.Error("Error while creat project", err.Error())
t.Log(err) t.Log(err)
@ -112,7 +112,7 @@ func TestListProjects(t *testing.T) {
assert.Nil(err) assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong") assert.Equal("true", result[0].Metadata[models.ProMetaPublic], "Public is wrong")
//find add projectID //find add projectID
addPID = int(result[0].ProjectId) addPID = int(result[0].ProjectId)
@ -130,7 +130,7 @@ func TestListProjects(t *testing.T) {
} else { } else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong") assert.Equal("true", result[0].Metadata[models.ProMetaPublic], "Public is wrong")
assert.Equal(int32(1), result[0].CurrentUserRoleId, "User project role is wrong") assert.Equal(int32(1), result[0].CurrentUserRoleId, "User project role is wrong")
} }
@ -155,7 +155,7 @@ func TestListProjects(t *testing.T) {
} else { } else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong") assert.Equal("true", result[0].Metadata[models.ProMetaPublic], "Public is wrong")
assert.Equal(int32(2), result[0].CurrentUserRoleId, "User project role is wrong") assert.Equal(int32(2), result[0].CurrentUserRoleId, "User project role is wrong")
} }
id := strconv.Itoa(CommonGetUserID()) id := strconv.Itoa(CommonGetUserID())
@ -187,7 +187,7 @@ func TestProGetByID(t *testing.T) {
} else { } else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong") assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong")
assert.Equal(int32(1), result.Public, "Public is wrong") assert.Equal("true", result.Metadata[models.ProMetaPublic], "Public is wrong")
} }
fmt.Printf("\n") fmt.Printf("\n")
} }
@ -273,48 +273,37 @@ func TestProHead(t *testing.T) {
fmt.Printf("\n") fmt.Printf("\n")
} }
func TestToggleProjectPublicity(t *testing.T) { func TestPut(t *testing.T) {
fmt.Println("\nTest for Project PUT API: Update properties for a selected project") fmt.Println("\nTest for Project PUT API: Update properties for a selected project")
assert := assert.New(t) assert := assert.New(t)
apiTest := newHarborAPI() apiTest := newHarborAPI()
//-------------------case1: Response Code=200------------------------------// project := &models.Project{
Metadata: map[string]string{
models.ProMetaPublic: "true",
},
}
fmt.Println("case 1: respose code:200") fmt.Println("case 1: respose code:200")
httpStatusCode, err := apiTest.ToggleProjectPublicity(*admin, "1", 1) code, err := apiTest.ProjectsPut(*admin, "1", project)
if err != nil { require.Nil(t, err)
t.Error("Error while search project by proId", err.Error()) assert.Equal(int(200), code)
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
}
//-------------------case2: Response Code=401 User need to log in first. ------------------------------//
fmt.Println("case 2: respose code:401, User need to log in first.") fmt.Println("case 2: respose code:401, User need to log in first.")
httpStatusCode, err = apiTest.ToggleProjectPublicity(*unknownUsr, "1", 1) code, err = apiTest.ProjectsPut(*unknownUsr, "1", project)
if err != nil { require.Nil(t, err)
t.Error("Error while search project by proId", err.Error()) assert.Equal(int(401), code)
t.Log(err)
} else {
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
}
//-------------------case3: Response Code=400 Invalid project id------------------------------//
fmt.Println("case 3: respose code:400, Invalid project id") fmt.Println("case 3: respose code:400, Invalid project id")
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "cc", 1) code, err = apiTest.ProjectsPut(*admin, "cc", project)
if err != nil { require.Nil(t, err)
t.Error("Error while search project by proId", err.Error()) assert.Equal(int(400), code)
t.Log(err)
} else {
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400")
}
//-------------------case4: Response Code=404 Not found the project------------------------------//
fmt.Println("case 4: respose code:404, Not found the project") fmt.Println("case 4: respose code:404, Not found the project")
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "1234", 1) code, err = apiTest.ProjectsPut(*admin, "1234", project)
if err != nil { require.Nil(t, err)
t.Error("Error while search project by proId", err.Error()) assert.Equal(int(404), code)
t.Log(err)
} else {
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
}
fmt.Printf("\n") fmt.Printf("\n")
} }

View File

@ -147,7 +147,7 @@ func filterRepositories(projects []*models.Project, keyword string) (
entry["repository_name"] = r.Name entry["repository_name"] = r.Name
entry["project_name"] = projects[j].Name entry["project_name"] = projects[j].Name
entry["project_id"] = projects[j].ProjectID entry["project_id"] = projects[j].ProjectID
entry["project_public"] = projects[j].Public entry["project_public"] = projects[j].IsPublic()
entry["pull_count"] = r.PullCount entry["pull_count"] = r.PullCount
tags, err := getTags(r.Name) tags, err := getTags(r.Name)

View File

@ -37,7 +37,7 @@ func TestSearch(t *testing.T) {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(int64(1), result.Projects[0].ProjectID, "Project id should be equal") assert.Equal(int64(1), result.Projects[0].ProjectID, "Project id should be equal")
assert.Equal("library", result.Projects[0].Name, "Project name should be library") assert.Equal("library", result.Projects[0].Name, "Project name should be library")
assert.Equal(1, result.Projects[0].Public, "Project public status should be 1 (true)") assert.True(result.Projects[0].IsPublic(), "Project public status should be 1 (true)")
} }
//--------case 2 : Response Code = 200, sysAdmin and search repo--------// //--------case 2 : Response Code = 200, sysAdmin and search repo--------//
@ -49,7 +49,7 @@ func TestSearch(t *testing.T) {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library") assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library")
assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker") assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker")
assert.Equal(int32(1), result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)") assert.True(result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)")
} }
//--------case 3 : Response Code = 200, normal user and search repo--------// //--------case 3 : Response Code = 200, normal user and search repo--------//
@ -61,7 +61,7 @@ func TestSearch(t *testing.T) {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library") assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library")
assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker") assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker")
assert.Equal(int32(1), result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)") assert.True(result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)")
} }
} }

View File

@ -15,7 +15,7 @@
package config package config
import ( import (
//"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -108,8 +108,6 @@ func initSecretStore() {
func initProjectManager() { func initProjectManager() {
var driver pmsdriver.PMSDriver var driver pmsdriver.PMSDriver
if WithAdmiral() { if WithAdmiral() {
// TODO add support for admiral
/*
// integration with admiral // integration with admiral
log.Info("initializing the project manager based on PMS...") log.Info("initializing the project manager based on PMS...")
// TODO read ca/cert file and pass it to the TLS config // TODO read ca/cert file and pass it to the TLS config
@ -129,17 +127,13 @@ func initProjectManager() {
TokenReader = &admiral.FileTokenReader{ TokenReader = &admiral.FileTokenReader{
Path: path, Path: path,
} }
GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient, driver = admiral.NewDriver(AdmiralClient, AdmiralEndpoint(), TokenReader)
AdmiralEndpoint(), TokenReader)
*/
GlobalProjectMgr = nil
} else { } else {
// standalone // standalone
log.Info("initializing the project manager based on local database...") log.Info("initializing the project manager based on local database...")
driver = local.NewDriver() driver = local.NewDriver()
// TODO move the statement out of the else block when admiral driver is completed
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
} }
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
} }

View File

@ -33,7 +33,7 @@ import (
"github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/promgr" "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 type key string
@ -264,15 +264,13 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
return false return false
} }
/*
log.Debug("creating PMS project manager...") log.Debug("creating PMS project manager...")
pm := admiral.NewProjectManager(config.AdmiralClient, driver := admiral.NewDriver(config.AdmiralClient,
config.AdmiralEndpoint(), &admiral.RawTokenReader{ config.AdmiralEndpoint(), &admiral.RawTokenReader{
Token: token, Token: token,
}) })
*/
// TODO create the DefaultProjectManager with the real admiral PMSDriver pm := promgr.NewDefaultProjectManager(driver, false)
pm := promgr.NewDefaultProjectManager(nil, false)
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx := admr.NewSecurityContext(authContext, pm) securCtx := admr.NewSecurityContext(authContext, pm)
@ -291,13 +289,10 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
var pm promgr.ProjectManager var pm promgr.ProjectManager
if config.WithAdmiral() { if config.WithAdmiral() {
// integration with admiral // integration with admiral
/*
log.Debug("creating PMS project manager...") log.Debug("creating PMS project manager...")
pm = admiral.NewProjectManager(config.AdmiralClient, driver := admiral.NewDriver(config.AdmiralClient,
config.AdmiralEndpoint(), nil) config.AdmiralEndpoint(), nil)
*/ pm = promgr.NewDefaultProjectManager(driver, false)
// TODO create the DefaultProjectManager with the real admiral PMSDriver
pm = promgr.NewDefaultProjectManager(nil, false)
log.Debug("creating admiral security context...") log.Debug("creating admiral security context...")
securCtx = admr.NewSecurityContext(nil, pm) securCtx = admr.NewSecurityContext(nil, pm)
} else { } else {

View File

@ -15,15 +15,13 @@
package metamgr package metamgr
import ( import (
"strconv"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
) )
// ProjectMetadataManaegr defines the operations that a project metadata manager should // ProjectMetadataManager defines the operations that a project metadata manager should
// implement // implement
type ProjectMetadataManaegr interface { type ProjectMetadataManager interface {
// Add metadatas for project specified by projectID // Add metadatas for project specified by projectID
Add(projectID int64, meta map[string]string) error Add(projectID int64, meta map[string]string) error
// Delete metadatas whose keys are specified in parameter meta, if it // Delete metadatas whose keys are specified in parameter meta, if it
@ -34,16 +32,18 @@ type ProjectMetadataManaegr interface {
// Get metadatas whose keys are specified in parameter meta, if it is // Get metadatas whose keys are specified in parameter meta, if it is
// absent, get all // absent, get all
Get(projectID int64, meta ...string) (map[string]string, error) Get(projectID int64, meta ...string) (map[string]string, error)
// List metadata according to the name and value
List(name, value string) ([]*models.ProjectMetadata, error)
} }
type defaultProjectMetadataManaegr struct{} type defaultProjectMetadataManager struct{}
// NewDefaultProjectMetadataManager ... // NewDefaultProjectMetadataManager ...
func NewDefaultProjectMetadataManager() ProjectMetadataManaegr { func NewDefaultProjectMetadataManager() ProjectMetadataManager {
return &defaultProjectMetadataManaegr{} return &defaultProjectMetadataManager{}
} }
func (d *defaultProjectMetadataManaegr) Add(projectID int64, meta map[string]string) error { func (d *defaultProjectMetadataManager) Add(projectID int64, meta map[string]string) error {
for k, v := range meta { for k, v := range meta {
proMeta := &models.ProjectMetadata{ proMeta := &models.ProjectMetadata{
ProjectID: projectID, ProjectID: projectID,
@ -57,11 +57,11 @@ func (d *defaultProjectMetadataManaegr) Add(projectID int64, meta map[string]str
return nil return nil
} }
func (d *defaultProjectMetadataManaegr) Delete(projectID int64, meta ...string) error { func (d *defaultProjectMetadataManager) Delete(projectID int64, meta ...string) error {
return dao.DeleteProjectMetadata(projectID, meta...) return dao.DeleteProjectMetadata(projectID, meta...)
} }
func (d *defaultProjectMetadataManaegr) Update(projectID int64, meta map[string]string) error { func (d *defaultProjectMetadataManager) Update(projectID int64, meta map[string]string) error {
for k, v := range meta { for k, v := range meta {
if err := dao.UpdateProjectMetadata(&models.ProjectMetadata{ if err := dao.UpdateProjectMetadata(&models.ProjectMetadata{
ProjectID: projectID, ProjectID: projectID,
@ -72,20 +72,10 @@ func (d *defaultProjectMetadataManaegr) Update(projectID int64, meta map[string]
} }
} }
// TODO remove the logic
public, ok := meta[models.ProMetaPublic]
if ok {
i, err := strconv.Atoi(public)
if err != nil {
return err
}
return dao.ToggleProjectPublicity(projectID, i)
}
return nil return nil
} }
func (d *defaultProjectMetadataManaegr) Get(projectID int64, meta ...string) (map[string]string, error) { func (d *defaultProjectMetadataManager) Get(projectID int64, meta ...string) (map[string]string, error) {
proMetas, err := dao.GetProjectMetadata(projectID, meta...) proMetas, err := dao.GetProjectMetadata(projectID, meta...)
if err != nil { if err != nil {
return nil, nil return nil, nil
@ -98,3 +88,14 @@ func (d *defaultProjectMetadataManaegr) Get(projectID int64, meta ...string) (ma
return m, nil return m, nil
} }
func (d *defaultProjectMetadataManager) List(name, value string) ([]*models.ProjectMetadata, error) {
metas := []*models.ProjectMetadata{}
mds, err := dao.ListProjectMetadata(name, value)
if err != nil {
return nil, err
}
metas = append(metas, mds...)
return metas, nil
}

View File

@ -54,6 +54,12 @@ func TestMetaMgrMethods(t *testing.T) {
assert.Equal(t, 1, len(m)) assert.Equal(t, 1, len(m))
assert.Equal(t, value, m[key]) assert.Equal(t, value, m[key])
// test list
metas, err := mgr.List(key, value)
require.Nil(t, err)
assert.Equal(t, 1, len(metas))
assert.Equal(t, int64(1), metas[0].ProjectID)
// test update // test update
require.Nil(t, mgr.Update(1, map[string]string{ require.Nil(t, mgr.Update(1, map[string]string{
key: newValue, key: newValue,

View File

@ -30,13 +30,12 @@ import (
"github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils"
er "github.com/vmware/harbor/src/common/utils/error" er "github.com/vmware/harbor/src/common/utils/error"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
) )
const dupProjectPattern = `Project name '\w+' is already used` const dupProjectPattern = `Project name '\w+' is already used`
// ProjectManager implements projectmanager.ProjecdtManager interface type driver struct {
// base on project management service
type ProjectManager struct {
client *http.Client client *http.Client
endpoint string endpoint string
tokenReader TokenReader tokenReader TokenReader
@ -57,10 +56,10 @@ type project struct {
Guests []*user `json:"viewers"` Guests []*user `json:"viewers"`
} }
// NewProjectManager returns an instance of ProjectManager // NewDriver returns an instance of driver
func NewProjectManager(client *http.Client, endpoint string, func NewDriver(client *http.Client, endpoint string,
tokenReader TokenReader) *ProjectManager { tokenReader TokenReader) pmsdriver.PMSDriver {
return &ProjectManager{ return &driver{
client: client, client: client,
endpoint: strings.TrimRight(endpoint, "/"), endpoint: strings.TrimRight(endpoint, "/"),
tokenReader: tokenReader, tokenReader: tokenReader,
@ -68,8 +67,8 @@ func NewProjectManager(client *http.Client, endpoint string,
} }
// Get ... // Get ...
func (p *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, error) { func (d *driver) Get(projectIDOrName interface{}) (*models.Project, error) {
project, err := p.get(projectIDOrName) project, err := d.get(projectIDOrName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -77,10 +76,10 @@ func (p *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, erro
} }
// get Admiral project with Harbor project ID or name // get Admiral project with Harbor project ID or name
func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) { func (d *driver) get(projectIDOrName interface{}) (*project, error) {
// if token is provided, search project from my projects list first // if token is provided, search project from my projects list first
if len(p.getToken()) != 0 { if len(d.getToken()) != 0 {
project, err := p.getFromMy(projectIDOrName) project, err := d.getFromMy(projectIDOrName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,18 +89,18 @@ func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) {
} }
// try to get project from public projects list // try to get project from public projects list
return p.getFromPublic(projectIDOrName) return d.getFromPublic(projectIDOrName)
} }
// call GET /projects?$filter=xxx eq xxx, the API can only filter projects // call GET /projects?$filter=xxx eq xxx, the API can only filter projects
// which the user is a member of // which the user is a member of
func (p *ProjectManager) getFromMy(projectIDOrName interface{}) (*project, error) { func (d *driver) getFromMy(projectIDOrName interface{}) (*project, error) {
return p.getAdmiralProject(projectIDOrName, false) return d.getAdmiralProject(projectIDOrName, false)
} }
// call GET /projects?public=true&$filter=xxx eq xxx // call GET /projects?public=true&$filter=xxx eq xxx
func (p *ProjectManager) getFromPublic(projectIDOrName interface{}) (*project, error) { func (d *driver) getFromPublic(projectIDOrName interface{}) (*project, error) {
project, err := p.getAdmiralProject(projectIDOrName, true) project, err := d.getAdmiralProject(projectIDOrName, true)
if project != nil { if project != nil {
// the projects returned by GET /projects?public=true&xxx have no // the projects returned by GET /projects?public=true&xxx have no
// "public" property, populate it here // "public" property, populate it here
@ -110,7 +109,7 @@ func (p *ProjectManager) getFromPublic(projectIDOrName interface{}) (*project, e
return project, err return project, err
} }
func (p *ProjectManager) getAdmiralProject(projectIDOrName interface{}, public bool) (*project, error) { func (d *driver) getAdmiralProject(projectIDOrName interface{}, public bool) (*project, error) {
m := map[string]string{} m := map[string]string{}
id, name, err := utils.ParseProjectIDOrName(projectIDOrName) id, name, err := utils.ParseProjectIDOrName(projectIDOrName)
@ -126,7 +125,7 @@ func (p *ProjectManager) getAdmiralProject(projectIDOrName interface{}, public b
m["public"] = "true" m["public"] = "true"
} }
projects, err := p.filter(m) projects, err := d.filter(m)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -145,7 +144,7 @@ func (p *ProjectManager) getAdmiralProject(projectIDOrName interface{}, public b
return projects[0], nil return projects[0], nil
} }
func (p *ProjectManager) filter(m map[string]string) ([]*project, error) { func (d *driver) filter(m map[string]string) ([]*project, error) {
query := "" query := ""
for k, v := range m { for k, v := range m {
if len(query) == 0 { if len(query) == 0 {
@ -165,7 +164,7 @@ func (p *ProjectManager) filter(m map[string]string) ([]*project, error) {
} }
path := "/projects" + query path := "/projects" + query
data, err := p.send(http.MethodGet, path, nil) data, err := d.send(http.MethodGet, path, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,7 +200,7 @@ func convert(p *project) (*models.Project, error) {
Name: p.Name, Name: p.Name,
} }
if p.Public { if p.Public {
project.Public = 1 project.SetMetadata(models.ProMetaPublic, "true")
} }
value := p.CustomProperties["__projectIndex"] value := p.CustomProperties["__projectIndex"]
@ -221,7 +220,7 @@ func convert(p *project) (*models.Project, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse __enableContentTrust %s to bool: %v", value, err) return nil, fmt.Errorf("failed to parse __enableContentTrust %s to bool: %v", value, err)
} }
project.EnableContentTrust = enable project.SetMetadata(models.ProMetaEnableContentTrust, strconv.FormatBool(enable))
} }
value = p.CustomProperties["__preventVulnerableImagesFromRunning"] value = p.CustomProperties["__preventVulnerableImagesFromRunning"]
@ -230,12 +229,12 @@ func convert(p *project) (*models.Project, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse __preventVulnerableImagesFromRunning %s to bool: %v", value, err) return nil, fmt.Errorf("failed to parse __preventVulnerableImagesFromRunning %s to bool: %v", value, err)
} }
project.PreventVulnerableImagesFromRunning = prevent project.SetMetadata(models.ProMetaPreventVul, strconv.FormatBool(prevent))
} }
value = p.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] value = p.CustomProperties["__preventVulnerableImagesFromRunningSeverity"]
if len(value) != 0 { if len(value) != 0 {
project.PreventVulnerableImagesFromRunningSeverity = value project.SetMetadata(models.ProMetaSeverity, value)
} }
value = p.CustomProperties["__automaticallyScanImagesOnPush"] value = p.CustomProperties["__automaticallyScanImagesOnPush"]
@ -244,93 +243,14 @@ func convert(p *project) (*models.Project, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse __automaticallyScanImagesOnPush %s to bool: %v", value, err) return nil, fmt.Errorf("failed to parse __automaticallyScanImagesOnPush %s to bool: %v", value, err)
} }
project.AutomaticallyScanImagesOnPush = scan project.SetMetadata(models.ProMetaAutoScan, strconv.FormatBool(scan))
} }
return project, nil return project, nil
} }
// IsPublic ... func (d *driver) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) {
func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { pro, err := d.get(projectIDOrName)
project, err := p.get(projectIDOrName)
if err != nil {
return false, err
}
if project == nil {
return false, nil
}
return project.Public, nil
}
// Exist ...
func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) {
project, err := p.get(projectIDOrName)
if err != nil {
return false, err
}
return project != nil, nil
}
/*
// GetRoles gets roles that the user has to the project
// This method is used in GET /projects API.
// Jobservice calls GET /projects API to get information of source
// project when trying to replicate the project. There is no auth
// context in this use case, so the method is needed.
func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) ([]int, error) {
if len(username) == 0 || projectIDOrName == nil {
return nil, nil
}
id, err := p.getIDbyHarborIDOrName(projectIDOrName)
if err != nil {
return nil, err
}
// get expanded project which contains role info by GET /projects/id?expand=true
path := fmt.Sprintf("/projects/%s?expand=true", id)
data, err := p.send(http.MethodGet, path, nil)
if err != nil {
return nil, err
}
pro := &project{}
if err = json.Unmarshal(data, pro); err != nil {
return nil, err
}
roles := []int{}
for _, user := range pro.Administrators {
if user.Email == username {
roles = append(roles, common.RoleProjectAdmin)
break
}
}
for _, user := range pro.Developers {
if user.Email == username {
roles = append(roles, common.RoleDeveloper)
break
}
}
for _, user := range pro.Guests {
if user.Email == username {
roles = append(roles, common.RoleGuest)
break
}
}
return roles, nil
}
*/
func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) {
pro, err := p.get(projectIDOrName)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -342,32 +262,24 @@ func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (str
return pro.ID, nil return pro.ID, nil
} }
// GetPublic ...
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
t := true
return p.GetAll(&models.ProjectQueryParam{
Public: &t,
})
}
// Create ... // Create ...
func (p *ProjectManager) Create(pro *models.Project) (int64, error) { func (d *driver) Create(pro *models.Project) (int64, error) {
proj := &project{ proj := &project{
CustomProperties: make(map[string]string), CustomProperties: make(map[string]string),
} }
proj.Name = pro.Name proj.Name = pro.Name
proj.Public = pro.Public == 1 proj.Public = pro.IsPublic()
proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.EnableContentTrust) proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.ContentTrustEnabled())
proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.PreventVulnerableImagesFromRunning) proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.VulPrevented())
proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.PreventVulnerableImagesFromRunningSeverity proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.Severity()
proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutomaticallyScanImagesOnPush) proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutoScan())
data, err := json.Marshal(proj) data, err := json.Marshal(proj)
if err != nil { if err != nil {
return 0, err return 0, err
} }
b, err := p.send(http.MethodPost, "/projects", bytes.NewBuffer(data)) b, err := d.send(http.MethodPost, "/projects", bytes.NewBuffer(data))
if err != nil { if err != nil {
// when creating a project with a duplicate name in Admiral, a 500 error // when creating a project with a duplicate name in Admiral, a 500 error
// with a specific message will be returned for now. // with a specific message will be returned for now.
@ -413,23 +325,23 @@ func (p *ProjectManager) Create(pro *models.Project) (int64, error) {
} }
// Delete ... // Delete ...
func (p *ProjectManager) Delete(projectIDOrName interface{}) error { func (d *driver) Delete(projectIDOrName interface{}) error {
id, err := p.getIDbyHarborIDOrName(projectIDOrName) id, err := d.getIDbyHarborIDOrName(projectIDOrName)
if err != nil { if err != nil {
return err return err
} }
_, err = p.send(http.MethodDelete, fmt.Sprintf("/projects/%s", id), nil) _, err = d.send(http.MethodDelete, fmt.Sprintf("/projects/%s", id), nil)
return err return err
} }
// Update ... // Update ...
func (p *ProjectManager) Update(projectIDOrName interface{}, project *models.Project) error { func (d *driver) Update(projectIDOrName interface{}, project *models.Project) error {
return errors.New("project update is unsupported") return errors.New("project update is unsupported")
} }
// GetAll ... // List ...
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) { func (d *driver) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) {
m := map[string]string{} m := map[string]string{}
if query != nil { if query != nil {
if len(query.Name) > 0 { if len(query.Name) > 0 {
@ -440,7 +352,7 @@ func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models
} }
} }
projects, err := p.filter(m) projects, err := d.filter(m)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -454,27 +366,24 @@ func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models
list = append(list, project) list = append(list, project)
} }
return list, nil return &models.ProjectQueryResult{
Total: int64(len(list)),
Projects: list,
}, nil
} }
// GetTotal ... func (d *driver) send(method, path string, body io.Reader) ([]byte, error) {
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { req, err := http.NewRequest(method, d.endpoint+path, body)
projects, err := p.GetAll(query)
return int64(len(projects)), err
}
func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, error) {
req, err := http.NewRequest(method, p.endpoint+path, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Add("x-xenon-auth-token", p.getToken()) req.Header.Add("x-xenon-auth-token", d.getToken())
url := req.URL.String() url := req.URL.String()
req.URL.RawQuery = req.URL.Query().Encode() req.URL.RawQuery = req.URL.Query().Encode()
resp, err := p.client.Do(req) resp, err := d.client.Do(req)
if err != nil { if err != nil {
log.Debugf("\"%s %s\" failed", req.Method, url) log.Debugf("\"%s %s\" failed", req.Method, url)
return nil, err return nil, err
@ -497,12 +406,12 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro
return b, nil return b, nil
} }
func (p *ProjectManager) getToken() string { func (d *driver) getToken() string {
if p.tokenReader == nil { if d.tokenReader == nil {
return "" return ""
} }
token, err := p.tokenReader.ReadToken() token, err := d.tokenReader.ReadToken()
if err != nil { if err != nil {
token = "" token = ""
log.Errorf("failed to read token: %v", err) log.Errorf("failed to read token: %v", err)

View File

@ -101,12 +101,12 @@ func TestConvert(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, pro) assert.NotNil(t, pro)
assert.Equal(t, "test", pro.Name) assert.Equal(t, "test", pro.Name)
assert.Equal(t, 1, pro.Public) assert.True(t, pro.IsPublic())
assert.Equal(t, int64(1), pro.ProjectID) assert.Equal(t, int64(1), pro.ProjectID)
assert.True(t, pro.EnableContentTrust) assert.True(t, pro.ContentTrustEnabled())
assert.True(t, pro.PreventVulnerableImagesFromRunning) assert.True(t, pro.VulPrevented())
assert.Equal(t, "medium", pro.PreventVulnerableImagesFromRunningSeverity) assert.Equal(t, "medium", pro.Severity())
assert.True(t, pro.AutomaticallyScanImagesOnPush) assert.True(t, pro.AutoScan())
} }
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
@ -182,233 +182,130 @@ func TestParse(t *testing.T) {
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
name := "project_for_test_get" name := "project_for_test_get"
id, err := pm.Create(&models.Project{ id, err := d.Create(&models.Project{
Name: name, Name: name,
}) })
require.Nil(t, err) require.Nil(t, err)
defer delete(t, id) defer delete(t, id)
// get by invalid input type // get by invalid input type
_, err = pm.Get([]string{}) _, err = d.Get([]string{})
assert.NotNil(t, err) assert.NotNil(t, err)
// get by invalid ID // get by invalid ID
project, err := pm.Get(int64(0)) project, err := d.Get(int64(0))
assert.Nil(t, err) assert.Nil(t, err)
assert.Nil(t, project) assert.Nil(t, project)
// get by invalid name // get by invalid name
project, err = pm.Get("invalid_name") project, err = d.Get("invalid_name")
assert.Nil(t, err) assert.Nil(t, err)
assert.Nil(t, project) assert.Nil(t, project)
// get by valid ID // get by valid ID
project, err = pm.Get(id) project, err = d.Get(id)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, id, project.ProjectID) assert.Equal(t, id, project.ProjectID)
// get by valid name // get by valid name
project, err = pm.Get(name) project, err = d.Get(name)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, id, project.ProjectID) assert.Equal(t, id, project.ProjectID)
} }
func TestIsPublic(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader)
// invalid input type
public, err := pm.IsPublic([]string{})
assert.NotNil(t, err)
assert.False(t, public)
// non-exist project
public, err = pm.IsPublic(int64(2))
assert.Nil(t, err)
assert.False(t, public)
// public project
name := "project_for_pm_based_on_pms_public"
id, err := pm.Create(&models.Project{
Name: name,
Public: 1,
})
require.Nil(t, err)
defer delete(t, id)
public, err = pm.IsPublic(id)
assert.Nil(t, err)
assert.True(t, public)
public, err = pm.IsPublic(name)
assert.Nil(t, err)
assert.True(t, public)
// private project
name = "project_for_pm_based_on_pms_private"
id, err = pm.Create(&models.Project{
Name: name,
Public: 0,
})
require.Nil(t, err)
defer delete(t, id)
public, err = pm.IsPublic(id)
assert.Nil(t, err)
assert.False(t, public)
public, err = pm.IsPublic(name)
assert.Nil(t, err)
assert.False(t, public)
}
func TestExist(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader)
// invalid input type
exist, err := pm.Exist([]string{})
assert.NotNil(t, err)
assert.False(t, exist)
// non-exist project
exist, err = pm.Exist(int64(2))
assert.Nil(t, err)
assert.False(t, exist)
// exist project
name := "project_for_test_exist"
id, err := pm.Create(&models.Project{
Name: name,
})
require.Nil(t, err)
defer delete(t, id)
exist, err = pm.Exist(id)
assert.Nil(t, err)
assert.True(t, exist)
exist, err = pm.Exist(name)
assert.Nil(t, err)
assert.True(t, exist)
}
func TestGetPublic(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader)
projects, err := pm.GetPublic()
assert.Nil(t, nil)
size := len(projects)
name := "project_for_test_get_public"
id, err := pm.Create(&models.Project{
Name: name,
Public: 1,
})
require.Nil(t, err)
defer delete(t, id)
projects, err = pm.GetPublic()
assert.Nil(t, nil)
assert.Equal(t, size+1, len(projects))
found := false
for _, project := range projects {
if project.ProjectID == id {
found = true
break
}
}
assert.True(t, found)
}
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
name := "project_for_test_create" name := "project_for_test_create"
id, err := pm.Create(&models.Project{ id, err := d.Create(&models.Project{
Name: name, Name: name,
Public: 1, Metadata: map[string]string{
EnableContentTrust: true, models.ProMetaPublic: "true",
PreventVulnerableImagesFromRunning: true, models.ProMetaEnableContentTrust: "true",
PreventVulnerableImagesFromRunningSeverity: "medium", models.ProMetaPreventVul: "true",
AutomaticallyScanImagesOnPush: true, models.ProMetaSeverity: "medium",
models.ProMetaAutoScan: "true",
},
}) })
require.Nil(t, err) require.Nil(t, err)
defer delete(t, id) defer delete(t, id)
project, err := pm.Get(id) project, err := d.Get(id)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, name, project.Name) assert.Equal(t, name, project.Name)
assert.Equal(t, 1, project.Public) assert.True(t, project.IsPublic())
assert.True(t, project.EnableContentTrust) assert.True(t, project.ContentTrustEnabled())
assert.True(t, project.PreventVulnerableImagesFromRunning) assert.True(t, project.VulPrevented())
assert.Equal(t, "medium", project.PreventVulnerableImagesFromRunningSeverity) assert.Equal(t, "medium", project.Severity())
assert.True(t, project.AutomaticallyScanImagesOnPush) assert.True(t, project.AutoScan())
// duplicate project name // duplicate project name
_, err = pm.Create(&models.Project{ _, err = d.Create(&models.Project{
Name: name, Name: name,
}) })
assert.Equal(t, errutil.ErrDupProject, err) assert.Equal(t, errutil.ErrDupProject, err)
} }
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
// non-exist project // non-exist project
err := pm.Delete(int64(0)) err := d.Delete(int64(0))
assert.NotNil(t, err) assert.NotNil(t, err)
// delete by ID // delete by ID
name := "project_for_pm_based_on_pms_id" name := "project_for_pm_based_on_pms_id"
id, err := pm.Create(&models.Project{ id, err := d.Create(&models.Project{
Name: name, Name: name,
}) })
require.Nil(t, err) require.Nil(t, err)
err = pm.Delete(id) err = d.Delete(id)
assert.Nil(t, err) assert.Nil(t, err)
// delete by name // delete by name
name = "project_for_pm_based_on_pms_name" name = "project_for_pm_based_on_pms_name"
id, err = pm.Create(&models.Project{ id, err = d.Create(&models.Project{
Name: name, Name: name,
}) })
require.Nil(t, err) require.Nil(t, err)
err = pm.Delete(name) err = d.Delete(name)
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
err := pm.Update(nil, nil) err := d.Update(nil, nil)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestGetAll(t *testing.T) { func TestList(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
name1 := "project_for_test_get_all_01" name1 := "project_for_test_get_all_01"
id1, err := pm.Create(&models.Project{ id1, err := d.Create(&models.Project{
Name: name1, Name: name1,
}) })
require.Nil(t, err) require.Nil(t, err)
defer delete(t, id1) defer delete(t, id1)
name2 := "project_for_test_get_all_02" name2 := "project_for_test_get_all_02"
id2, err := pm.Create(&models.Project{ id2, err := d.Create(&models.Project{
Name: name2, Name: name2,
Public: 1, Metadata: map[string]string{
models.ProMetaPublic: "true",
},
}) })
require.Nil(t, err) require.Nil(t, err)
defer delete(t, id2) defer delete(t, id2)
// no filter // no filter
projects, err := pm.GetAll(nil) result, err := d.List(nil)
require.Nil(t, err) require.Nil(t, err)
found1 := false found1 := false
found2 := false found2 := false
for _, project := range projects { for _, project := range result.Projects {
if project.ProjectID == id1 { if project.ProjectID == id1 {
found1 = true found1 = true
} }
@ -420,12 +317,12 @@ func TestGetAll(t *testing.T) {
assert.True(t, found2) assert.True(t, found2)
// filter by name // filter by name
projects, err = pm.GetAll(&models.ProjectQueryParam{ result, err = d.List(&models.ProjectQueryParam{
Name: name1, Name: name1,
}) })
require.Nil(t, err) require.Nil(t, err)
found1 = false found1 = false
for _, project := range projects { for _, project := range result.Projects {
if project.ProjectID == id1 { if project.ProjectID == id1 {
found1 = true found1 = true
break break
@ -435,12 +332,12 @@ func TestGetAll(t *testing.T) {
// filter by public // filter by public
value := true value := true
projects, err = pm.GetAll(&models.ProjectQueryParam{ result, err = d.List(&models.ProjectQueryParam{
Public: &value, Public: &value,
}) })
require.Nil(t, err) require.Nil(t, err)
found2 = false found2 = false
for _, project := range projects { for _, project := range result.Projects {
if project.ProjectID == id2 { if project.ProjectID == id2 {
found2 = true found2 = true
break break
@ -449,27 +346,9 @@ func TestGetAll(t *testing.T) {
assert.True(t, found2) assert.True(t, found2)
} }
func TestGetTotal(t *testing.T) {
pm := NewProjectManager(client, endpoint, tokenReader)
total1, err := pm.GetTotal(nil)
require.Nil(t, err)
name := "project_for_test_get_total"
id, err := pm.Create(&models.Project{
Name: name,
})
require.Nil(t, err)
defer delete(t, id)
total2, err := pm.GetTotal(nil)
require.Nil(t, err)
assert.Equal(t, total1+1, total2)
}
func delete(t *testing.T, id int64) { func delete(t *testing.T, id int64) {
pm := NewProjectManager(client, endpoint, tokenReader) d := NewDriver(client, endpoint, tokenReader)
if err := pm.Delete(id); err != nil { if err := d.Delete(id); err != nil {
t.Logf("failed to delete project %d: %v", id, err) t.Logf("failed to delete project %d: %v", id, err)
} }
} }

View File

@ -30,7 +30,5 @@ type PMSDriver interface {
// Update the properties of a project // Update the properties of a project
Update(projectIDOrName interface{}, project *models.Project) error Update(projectIDOrName interface{}, project *models.Project) error
// List lists projects according to the query conditions // List lists projects according to the query conditions
// TODO remove base List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error)
List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
} }

View File

@ -82,7 +82,6 @@ func (d *driver) Create(project *models.Project) (int64, error) {
t := time.Now() t := time.Now()
pro := &models.Project{ pro := &models.Project{
Name: project.Name, Name: project.Name,
Public: project.Public,
OwnerID: project.OwnerID, OwnerID: project.OwnerID,
CreationTime: t, CreationTime: t,
UpdateTime: t, UpdateTime: t,
@ -129,16 +128,15 @@ func (d *driver) Update(projectIDOrName interface{},
return nil return nil
} }
// TODO remove base
// List returns a project list according to the query parameters // List returns a project list according to the query parameters
func (d *driver) List(query *models.ProjectQueryParam, func (d *driver) List(query *models.ProjectQueryParam) (
base ...*models.BaseProjectCollection) (
*models.ProjectQueryResult, error) { *models.ProjectQueryResult, error) {
total, err := dao.GetTotalOfProjects(query, base...) total, err := dao.GetTotalOfProjects(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
projects, err := dao.GetProjects(query, base...)
projects, err := dao.GetProjects(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -156,7 +156,9 @@ func TestList(t *testing.T) {
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
Name: "get_all_test", Name: "get_all_test",
OwnerID: 1, OwnerID: 1,
Public: 1, Metadata: map[string]string{
models.ProMetaPublic: "true",
},
}) })
assert.Nil(t, err) assert.Nil(t, err)
defer pm.Delete(id) defer pm.Delete(id)

View File

@ -16,6 +16,7 @@ package promgr
import ( import (
"fmt" "fmt"
"strconv"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
@ -30,9 +31,7 @@ type ProjectManager interface {
Create(*models.Project) (int64, error) Create(*models.Project) (int64, error)
Delete(projectIDOrName interface{}) error Delete(projectIDOrName interface{}) error
Update(projectIDOrName interface{}, project *models.Project) error Update(projectIDOrName interface{}, project *models.Project) error
// TODO remove base List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error)
List(query *models.ProjectQueryParam,
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error)
IsPublic(projectIDOrName interface{}) (bool, error) IsPublic(projectIDOrName interface{}) (bool, error)
Exists(projectIDOrName interface{}) (bool, error) Exists(projectIDOrName interface{}) (bool, error)
// get all public project // get all public project
@ -42,7 +41,7 @@ type ProjectManager interface {
type defaultProjectManager struct { type defaultProjectManager struct {
pmsDriver pmsdriver.PMSDriver pmsDriver pmsdriver.PMSDriver
metaMgrEnabled bool // if metaMgrEnabled is enabled, metaMgr will be used to CURD metadata metaMgrEnabled bool // if metaMgrEnabled is enabled, metaMgr will be used to CURD metadata
metaMgr metamgr.ProjectMetadataManaegr metaMgr metamgr.ProjectMetadataManager
} }
// NewDefaultProjectManager returns an instance of defaultProjectManager, // NewDefaultProjectManager returns an instance of defaultProjectManager,
@ -117,7 +116,25 @@ func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *mod
if pro == nil { if pro == nil {
return fmt.Errorf("project %v not found", projectIDOrName) return fmt.Errorf("project %v not found", projectIDOrName)
} }
if err = d.metaMgr.Update(pro.ProjectID, project.Metadata); err != nil {
// TODO transaction?
metaNeedUpdated := map[string]string{}
metaNeedCreated := map[string]string{}
if pro.Metadata == nil {
pro.Metadata = map[string]string{}
}
for key, value := range project.Metadata {
_, exist := pro.Metadata[key]
if exist {
metaNeedUpdated[key] = value
} else {
metaNeedCreated[key] = value
}
}
if err = d.metaMgr.Add(pro.ProjectID, metaNeedCreated); err != nil {
return err
}
if err = d.metaMgr.Update(pro.ProjectID, metaNeedUpdated); err != nil {
return err return err
} }
} }
@ -125,13 +142,32 @@ func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *mod
return d.pmsDriver.Update(projectIDOrName, project) return d.pmsDriver.Update(projectIDOrName, project)
} }
// TODO remove base func (d *defaultProjectManager) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) {
func (d *defaultProjectManager) List(query *models.ProjectQueryParam, // query by public/private property with ProjectMetadataManager first
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) { if d.metaMgrEnabled && query != nil && query.Public != nil {
result, err := d.pmsDriver.List(query, base...) projectIDs, err := d.filterByPublic(*query.Public)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(projectIDs) == 0 {
return &models.ProjectQueryResult{}, nil
}
if query.ProjectIDs == nil {
query.ProjectIDs = projectIDs
} else {
query.ProjectIDs = findInBoth(query.ProjectIDs, projectIDs)
}
}
// query by other properties
result, err := d.pmsDriver.List(query)
if err != nil {
return nil, err
}
// populate metadata
if d.metaMgrEnabled { if d.metaMgrEnabled {
for _, project := range result.Projects { for _, project := range result.Projects {
meta, err := d.metaMgr.Get(project.ProjectID) meta, err := d.metaMgr.Get(project.ProjectID)
@ -144,6 +180,35 @@ func (d *defaultProjectManager) List(query *models.ProjectQueryParam,
return result, nil return result, nil
} }
func (d *defaultProjectManager) filterByPublic(public bool) ([]int64, error) {
metas, err := d.metaMgr.List(models.ProMetaPublic, strconv.FormatBool(public))
if err != nil {
return nil, err
}
projectIDs := []int64{}
for _, meta := range metas {
projectIDs = append(projectIDs, meta.ProjectID)
}
return projectIDs, nil
}
func findInBoth(ids1 []int64, ids2 []int64) []int64 {
m := map[int64]struct{}{}
for _, id := range ids1 {
m[id] = struct{}{}
}
ids := []int64{}
for _, id := range ids2 {
if _, exist := m[id]; exist {
ids = append(ids, id)
}
}
return ids
}
func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) {
project, err := d.Get(projectIDOrName) project, err := d.Get(projectIDOrName)
if err != nil { if err != nil {
@ -152,7 +217,7 @@ func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, err
if project == nil { if project == nil {
return false, nil return false, nil
} }
return project.Public == 1, nil return project.IsPublic(), nil
} }
func (d *defaultProjectManager) Exists(projectIDOrName interface{}) (bool, error) { func (d *defaultProjectManager) Exists(projectIDOrName interface{}) (bool, error) {

View File

@ -32,7 +32,9 @@ func newFakePMSDriver() pmsdriver.PMSDriver {
project: &models.Project{ project: &models.Project{
ProjectID: 1, ProjectID: 1,
Name: "library", Name: "library",
Public: 1, Metadata: map[string]string{
models.ProMetaPublic: "true",
},
}, },
} }
} }
@ -53,8 +55,7 @@ func (f *fakePMSDriver) Update(projectIDOrName interface{}, project *models.Proj
return nil return nil
} }
func (f *fakePMSDriver) List(query *models.ProjectQueryParam, func (f *fakePMSDriver) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) {
base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) {
return &models.ProjectQueryResult{ return &models.ProjectQueryResult{
Total: 1, Total: 1,
Projects: []*models.Project{f.project}, Projects: []*models.Project{f.project},
@ -116,5 +117,5 @@ func TestGetPublic(t *testing.T) {
projects, err := proMgr.GetPublic() projects, err := proMgr.GetPublic()
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, 1, len(projects)) assert.Equal(t, 1, len(projects))
assert.Equal(t, 1, projects[0].Public) assert.True(t, projects[0].IsPublic())
} }

View File

@ -2,14 +2,14 @@ package proxy
import ( import (
"github.com/stretchr/testify/assert" "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/adminserver/client"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
notarytest "github.com/vmware/harbor/src/common/utils/notary/test" notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
utilstest "github.com/vmware/harbor/src/common/utils/test" utilstest "github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
//"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -110,33 +110,19 @@ func TestMatchListRepos(t *testing.T) {
} }
func TestEnvPolicyChecker(t *testing.T) {
assert := assert.New(t)
if err := os.Setenv("PROJECT_CONTENT_TRUST", "1"); err != nil {
t.Fatalf("Failed to set env variable: %v", err)
}
if err2 := os.Setenv("PROJECT_VULNERABLE", "1"); err2 != nil {
t.Fatalf("Failed to set env variable: %v", err2)
}
if err3 := os.Setenv("PROJECT_SEVERITY", "negligible"); err3 != nil {
t.Fatalf("Failed to set env variable: %v", err3)
}
contentTrustFlag := getPolicyChecker().contentTrustEnabled("whatever")
vulFlag, sev := getPolicyChecker().vulnerablePolicy("whatever")
assert.True(contentTrustFlag)
assert.True(vulFlag)
assert.Equal(sev, models.SevNone)
}
// TODO uncheck after admiral pms driver is implemented
/*
func TestPMSPolicyChecker(t *testing.T) { func TestPMSPolicyChecker(t *testing.T) {
var defaultConfigAdmiral = map[string]interface{}{ var defaultConfigAdmiral = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint, common.ExtEndpoint: "https://" + endpoint,
common.WithNotary: true, common.WithNotary: true,
common.CfgExpiration: 5, common.CfgExpiration: 5,
common.AdmiralEndpoint: admiralEndpoint,
common.TokenExpiration: 30, common.TokenExpiration: 30,
common.DatabaseType: "mysql",
common.MySQLHost: "127.0.0.1",
common.MySQLPort: 3306,
common.MySQLUsername: "root",
common.MySQLPassword: "root123",
common.MySQLDatabase: "registry",
common.SQLiteFile: "/tmp/registry.db",
} }
adminServer, err := utilstest.NewAdminserver(defaultConfigAdmiral) adminServer, err := utilstest.NewAdminserver(defaultConfigAdmiral)
if err != nil { if err != nil {
@ -149,34 +135,38 @@ func TestPMSPolicyChecker(t *testing.T) {
if err := config.Init(); err != nil { if err := config.Init(); err != nil {
panic(err) panic(err)
} }
pm := admiral.NewProjectManager(http.DefaultClient, database, err := config.Database()
admiralEndpoint, &admiral.RawTokenReader{ if err != nil {
Token: "token", panic(err)
}) }
if err := dao.InitDatabase(database); err != nil {
panic(err)
}
name := "project_for_test_get_sev_low" name := "project_for_test_get_sev_low"
id, err := pm.Create(&models.Project{ id, err := config.GlobalProjectMgr.Create(&models.Project{
Name: name, Name: name,
EnableContentTrust: true, OwnerID: 1,
PreventVulnerableImagesFromRunning: false, Metadata: map[string]string{
PreventVulnerableImagesFromRunningSeverity: "low", models.ProMetaEnableContentTrust: "true",
models.ProMetaPreventVul: "true",
models.ProMetaSeverity: "low",
},
}) })
require.Nil(t, err) require.Nil(t, err)
defer func(id int64) { defer func(id int64) {
if err := pm.Delete(id); err != nil { if err := config.GlobalProjectMgr.Delete(id); err != nil {
t.Logf("failed to delete project %d: %v", id, err) t.Logf("failed to delete project %d: %v", id, err)
} }
}(id) }(id)
project, err := pm.Get(id)
assert.Nil(t, err)
assert.Equal(t, id, project.ProjectID)
contentTrustFlag := getPolicyChecker().contentTrustEnabled("project_for_test_get_sev_low") contentTrustFlag := getPolicyChecker().contentTrustEnabled("project_for_test_get_sev_low")
assert.True(t, contentTrustFlag) assert.True(t, contentTrustFlag)
projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy("project_for_test_get_sev_low") projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy("project_for_test_get_sev_low")
assert.False(t, projectVulnerableEnabled) assert.True(t, projectVulnerableEnabled)
assert.Equal(t, projectVulnerableSeverity, models.SevLow) assert.Equal(t, projectVulnerableSeverity, models.SevLow)
} }
*/
func TestMatchNotaryDigest(t *testing.T) { func TestMatchNotaryDigest(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
//The data from common/utils/notary/helper_test.go //The data from common/utils/notary/helper_test.go

View File

@ -16,7 +16,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -38,9 +37,6 @@ var rec *httptest.ResponseRecorder
// NotaryEndpoint , exported for testing. // NotaryEndpoint , exported for testing.
var NotaryEndpoint = config.InternalNotaryEndpoint() var NotaryEndpoint = config.InternalNotaryEndpoint()
// EnvChecker is the instance of envPolicyChecker
var EnvChecker = envPolicyChecker{}
// MatchPullManifest checks if the request looks like a request to pull manifest. If it is returns the image and tag/sha256 digest as 2nd and 3rd return values // MatchPullManifest checks if the request looks like a request to pull manifest. If it is returns the image and tag/sha256 digest as 2nd and 3rd return values
func MatchPullManifest(req *http.Request) (bool, string, string) { func MatchPullManifest(req *http.Request) (bool, string, string) {
//TODO: add user agent check. //TODO: add user agent check.
@ -77,16 +73,6 @@ type policyChecker interface {
vulnerablePolicy(name string) (bool, models.Severity) vulnerablePolicy(name string) (bool, models.Severity)
} }
//For testing
type envPolicyChecker struct{}
func (ec envPolicyChecker) contentTrustEnabled(name string) bool {
return os.Getenv("PROJECT_CONTENT_TRUST") == "1"
}
func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) {
return os.Getenv("PROJECT_VULNERABLE") == "1", clair.ParseClairSev(os.Getenv("PROJECT_SEVERITY"))
}
type pmsPolicyChecker struct { type pmsPolicyChecker struct {
pm promgr.ProjectManager pm promgr.ProjectManager
} }
@ -97,7 +83,7 @@ func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool {
log.Errorf("Unexpected error when getting the project, error: %v", err) log.Errorf("Unexpected error when getting the project, error: %v", err)
return true return true
} }
return project.EnableContentTrust return project.ContentTrustEnabled()
} }
func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) { func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) {
project, err := pc.pm.Get(name) project, err := pc.pm.Get(name)
@ -105,7 +91,7 @@ func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity)
log.Errorf("Unexpected error when getting the project, error: %v", err) log.Errorf("Unexpected error when getting the project, error: %v", err)
return true, models.SevUnknown return true, models.SevUnknown
} }
return project.PreventVulnerableImagesFromRunning, clair.ParseClairSev(project.PreventVulnerableImagesFromRunningSeverity) return project.VulPrevented(), clair.ParseClairSev(project.Severity())
} }
// newPMSPolicyChecker returns an instance of an pmsPolicyChecker // newPMSPolicyChecker returns an instance of an pmsPolicyChecker
@ -116,11 +102,8 @@ func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker {
} }
func getPolicyChecker() policyChecker { func getPolicyChecker() policyChecker {
if config.WithAdmiral() {
return newPMSPolicyChecker(config.GlobalProjectMgr) return newPMSPolicyChecker(config.GlobalProjectMgr)
} }
return EnvChecker
}
type imageInfo struct { type imageInfo struct {
repository string repository string

View File

@ -70,7 +70,6 @@ func initRouters() {
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{}) beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head") beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head")
beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{}) beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
beego.Router("/api/projects/:id([0-9]+)/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put") beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put")
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post") beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")

View File

@ -16,7 +16,6 @@ package registry
import ( import (
"encoding/json" "encoding/json"
"os"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -170,10 +169,8 @@ func autoScanEnabled(project *models.Project) bool {
log.Debugf("Auto Scan disabled because Harbor is not deployed with Clair") log.Debugf("Auto Scan disabled because Harbor is not deployed with Clair")
return false return false
} }
if config.WithAdmiral() {
return project.AutomaticallyScanImagesOnPush return project.AutoScan()
}
return os.Getenv("ENABLE_HARBOR_SCAN_ON_PUSH") == "1"
} }
// Render returns nil as it won't render any template. // Render returns nil as it won't render any template.

View File

@ -23,7 +23,7 @@
<div class="form-group" style="padding-left: 135px;"> <div class="form-group" style="padding-left: 135px;">
<label class="col-md-4 form-group-label-override">{{'PROJECT.ACCESS_LEVEL' | translate}}</label> <label class="col-md-4 form-group-label-override">{{'PROJECT.ACCESS_LEVEL' | translate}}</label>
<div class="checkbox-inline"> <div class="checkbox-inline">
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public"> <input type="checkbox" id="create_project_public" [(ngModel)]="project.metadata.public" name="public">
<label for="create_project_public"></label> <label for="create_project_public"></label>
<span class="access-level-label">{{ accessLevelDisplayText | translate}}</span> <span class="access-level-label">{{ accessLevelDisplayText | translate}}</span>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-md tooltip-bottom-right" style="top:-8px; left:-8px;"> <a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-md tooltip-bottom-right" style="top:-8px; left:-8px;">

View File

@ -73,7 +73,7 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
private messageHandlerService: MessageHandlerService) { } private messageHandlerService: MessageHandlerService) { }
public get accessLevelDisplayText(): string { public get accessLevelDisplayText(): string {
return this.project.public ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE'; return this.project.metadata.public ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE';
} }
ngOnInit(): void { ngOnInit(): void {
@ -115,7 +115,7 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr
this.isSubmitOnGoing=true; this.isSubmitOnGoing=true;
this.projectService this.projectService
.createProject(this.project.name, this.project.public ? 1 : 0) .createProject(this.project.name, this.project.metadata)
.subscribe( .subscribe(
status => { status => {
this.isSubmitOnGoing=false; this.isSubmitOnGoing=false;

View File

@ -7,11 +7,11 @@
<clr-dg-row *ngFor="let p of projects"> <clr-dg-row *ngFor="let p of projects">
<clr-dg-action-overflow [hidden]="!(p.current_user_role_id === 1 || isSystemAdmin)"> <clr-dg-action-overflow [hidden]="!(p.current_user_role_id === 1 || isSystemAdmin)">
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button> <button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
<button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button> <button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.metadata.public === 'false' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button>
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button> <button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell> <clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
<clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell> <clr-dg-cell>{{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell> <clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell> <clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell> <clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>

View File

@ -173,15 +173,15 @@ export class ListProjectComponent implements OnDestroy {
toggleProject(p: Project) { toggleProject(p: Project) {
if (p) { if (p) {
p.public === 0 ? p.public = 1 : p.public = 0; p.metadata.public === 'true' ? p.metadata.public = 'false' : p.metadata.public = 'true';
this.proService this.proService
.toggleProjectPublic(p.project_id, p.public) .toggleProjectPublic(p.project_id, p.metadata.public)
.subscribe( .subscribe(
response => { response => {
this.msgHandler.showSuccess('PROJECT.TOGGLED_SUCCESS'); this.msgHandler.showSuccess('PROJECT.TOGGLED_SUCCESS');
let pp: Project = this.projects.find((item: Project) => item.project_id === p.project_id); let pp: Project = this.projects.find((item: Project) => item.project_id === p.project_id);
if (pp) { if (pp) {
pp.public = p.public; pp.metadata.public = p.metadata.public;
this.statisticHandler.refresh(); this.statisticHandler.refresh();
} }
}, },

View File

@ -58,18 +58,20 @@ export class ProjectService {
.catch(error=>Observable.throw(error)); .catch(error=>Observable.throw(error));
} }
createProject(name: string, isPublic: number): Observable<any> { createProject(name: string, metadata: any): Observable<any> {
return this.http return this.http
.post(`/api/projects`, .post(`/api/projects`,
JSON.stringify({'project_name': name, 'public': isPublic}) JSON.stringify({'project_name': name, 'metadata': {
public: metadata.public ? 'true' : 'false',
}})
, this.options) , this.options)
.map(response=>response.status) .map(response=>response.status)
.catch(error=>Observable.throw(error)); .catch(error=>Observable.throw(error));
} }
toggleProjectPublic(projectId: number, isPublic: number): Observable<any> { toggleProjectPublic(projectId: number, isPublic: string): Observable<any> {
return this.http return this.http
.put(`/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options) .put(`/api/projects/${projectId}`, { 'metadata': {'public': isPublic} }, this.options)
.map(response => response.status) .map(response => response.status)
.catch(error => Observable.throw(error)); .catch(error => Observable.throw(error));
} }

View File

@ -22,7 +22,7 @@
"deleted": 0, "deleted": 0,
"owner_name": "", "owner_name": "",
"public": 1, "public": 1,
"Togglable": true, "togglable": true,
"update_time": "2017-02-10T07:57:56Z", "update_time": "2017-02-10T07:57:56Z",
"current_user_role_id": 1, "current_user_role_id": 1,
"repo_count": 0 "repo_count": 0
@ -37,12 +37,22 @@ export class Project {
creation_time_str: string; creation_time_str: string;
deleted: number; deleted: number;
owner_name: string; owner_name: string;
public: number; togglable: boolean;
Togglable: boolean;
update_time: Date; update_time: Date;
current_user_role_id: number; current_user_role_id: number;
repo_count: number; repo_count: number;
has_project_admin_role: boolean; has_project_admin_role: boolean;
is_member: boolean; is_member: boolean;
role_name: string; role_name: string;
metadata: {
public: string | boolean;
enable_content_trust: string | boolean;
prevent_vul: string | boolean;
severity: string;
auto_scan: string | boolean;
};
constructor () {
this.metadata = <any>{};
this.metadata.public = false;
}
} }

View File

@ -45,11 +45,11 @@ type Project struct {
// The owner name of the project. // The owner name of the project.
OwnerName string `json:"owner_name,omitempty"` OwnerName string `json:"owner_name,omitempty"`
// The public status of the project. // The metadata of the project.
Public int32 `json:"public,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
// Correspond to the UI about whether the project's publicity is updatable (for UI) // Correspond to the UI about whether the project's publicity is updatable (for UI)
Togglable bool `json:"Togglable,omitempty"` Togglable bool `json:"togglable,omitempty"`
// The role ID of the current user who triggered the API (for UI) // The role ID of the current user who triggered the API (for UI)
CurrentUserRoleId int32 `json:"current_user_role_id,omitempty"` CurrentUserRoleId int32 `json:"current_user_role_id,omitempty"`

View File

@ -23,10 +23,8 @@
package apilib package apilib
type ProjectReq struct { type ProjectReq struct {
// The name of the project. // The name of the project.
ProjectName string `json:"project_name,omitempty"` ProjectName string `json:"project_name,omitempty"`
// The metadata of the project.
// The public status of the project. Metadata map[string]string `json:"metadata,omitempty"`
Public int32 `json:"public,omitempty"`
} }

View File

@ -30,8 +30,8 @@ type SearchRepository struct {
// The name of the project that the repository belongs to // The name of the project that the repository belongs to
ProjectName string `json:"project_name,omitempty"` ProjectName string `json:"project_name,omitempty"`
// The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not) // The flag to indicate the publicity of the project that the repository belongs to
ProjectPublic int32 `json:"project_public,omitempty"` ProjectPublic bool `json:"project_public,omitempty"`
// The name of the repository // The name of the repository
RepositoryName string `json:"repository_name,omitempty"` RepositoryName string `json:"repository_name,omitempty"`

View File

@ -54,3 +54,4 @@ Changelog for harbor database schema
- create table `project_metadata` - create table `project_metadata`
- insert data into table `project_metadata` - insert data into table `project_metadata`
- delete column `public` from table `project`