diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 06ea03777..29092018a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -167,6 +167,38 @@ paths: description: User need to log in first. '500': 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: summary: Delete project by projectID description: | @@ -193,39 +225,6 @@ paths: description: 'Project contains policies, can not be deleted.' '500': 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': get: summary: Get access logs accompany with a relevant project. @@ -2016,10 +2015,6 @@ definitions: owner_name: type: string description: The owner name of the project. - public: - type: integer - format: int - description: The public status of the project. Togglable: type: boolean description: >- @@ -2031,6 +2026,18 @@ definitions: repo_count: type: integer 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: type: boolean description: >- diff --git a/make/common/db/registry.sql b/make/common/db/registry.sql index 3ab00b920..952b5b827 100644 --- a/make/common/db/registry.sql +++ b/make/common/db/registry.sql @@ -73,14 +73,13 @@ create table project ( creation_time timestamp, update_time timestamp, deleted tinyint (1) DEFAULT 0 NOT NULL, - public tinyint (1) DEFAULT 0 NOT NULL, primary key (project_id), FOREIGN KEY (owner_id) REFERENCES user(user_id), UNIQUE (name) ); -insert into project (owner_id, name, creation_time, update_time, public) values -(1, 'library', NOW(), NOW(), 1); +insert into project (owner_id, name, creation_time, update_time) values +(1, 'library', NOW(), NOW()); create table project_member ( project_id int NOT NULL, diff --git a/make/common/db/registry_sqlite.sql b/make/common/db/registry_sqlite.sql index 45688c82f..967473dab 100644 --- a/make/common/db/registry_sqlite.sql +++ b/make/common/db/registry_sqlite.sql @@ -71,13 +71,12 @@ create table project ( creation_time timestamp, update_time timestamp, deleted tinyint (1) DEFAULT 0 NOT NULL, - public tinyint (1) DEFAULT 0 NOT NULL, FOREIGN KEY (owner_id) REFERENCES user(user_id), UNIQUE (name) ); -insert into project (owner_id, name, creation_time, update_time, public) values -(1, 'library', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1); +insert into project (owner_id, name, creation_time, update_time) values +(1, 'library', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); create table project_member ( project_id int NOT NULL, diff --git a/src/common/dao/dao_test.go b/src/common/dao/dao_test.go index f9ca6f445..de7636722 100644 --- a/src/common/dao/dao_test.go +++ b/src/common/dao/dao_test.go @@ -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) { 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) { r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID) 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) { total, err := GetTotalOfProjects(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) { err := AddProjectMember(currentProject.ProjectID, 1, models.DEVELOPER) if err != nil { diff --git a/src/common/dao/pro_meta.go b/src/common/dao/pro_meta.go index 417bb236e..5ccf7b885 100644 --- a/src/common/dao/pro_meta.go +++ b/src/common/dao/pro_meta.go @@ -91,3 +91,12 @@ func paramPlaceholder(n int) string { } 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 +} diff --git a/src/common/dao/pro_meta_test.go b/src/common/dao/pro_meta_test.go index 66638a4ac..8eba25a88 100644 --- a/src/common/dao/pro_meta_test.go +++ b/src/common/dao/pro_meta_test.go @@ -64,6 +64,12 @@ func TestProMetaDaoMethods(t *testing.T) { assert.Equal(t, value1, m[name1].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 newValue1 := "new_value1" meta1.Value = newValue1 diff --git a/src/common/dao/project.go b/src/common/dao/project.go index 6680f5e0a..7f1f139e0 100644 --- a/src/common/dao/project.go +++ b/src/common/dao/project.go @@ -30,13 +30,13 @@ import ( func AddProject(project models.Project) (int64, error) { 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 { return 0, err } 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 { return 0, err } @@ -50,49 +50,11 @@ func AddProject(project models.Project) (int64, error) { 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 ... func GetProjectByID(id int64) (*models.Project, error) { 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 = ?` queryParam := make([]interface{}, 1) queryParam = append(queryParam, id) @@ -127,94 +89,18 @@ func GetProjectByName(name string) (*models.Project, error) { 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 // according to the query conditions -func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { - - var ( - owner string - name string - public *bool - member string - role int - ) - +func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) { + var pagination *models.Pagination if query != nil { - owner = query.Owner - name = query.Name - public = query.Public - if query.Member != nil { - member = query.Member.Name - role = query.Member.Role - } + pagination = query.Pagination + query.Pagination = nil + } + sql, params := projectQueryConditions(query) + if query != nil { + query.Pagination = pagination } - - sql, params := projectQueryConditions(owner, name, public, member, role, base...) sql = `select count(*) ` + sql @@ -224,86 +110,39 @@ func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BasePro } // 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 ( - 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, + sql = `select distinct p.project_id, p.name, p.owner_id, 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 _, err := GetOrmer().Raw(sql, params).QueryRows(&projects) return projects, err } -func projectQueryConditions(owner, name string, public *bool, member string, - role int, base ...*models.BaseProjectCollection) (string, []interface{}) { +func projectQueryConditions(query *models.ProjectQueryParam) (string, []interface{}) { params := []interface{}{} - // the base project collections: - // 1. all projects - // 2. public projects - // 3. public projects and projects which the user is a member of - collection := `project ` - 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 project as p` + + if query == nil { + sql += ` where p.deleted=0 order by p.name` + return sql, params } - 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 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 on p.project_id = pm.project_id join user u2 @@ -311,33 +150,24 @@ func projectQueryConditions(owner, name string, public *bool, member string, } sql += ` where p.deleted=0` - if len(owner) != 0 { + if len(query.Owner) != 0 { 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 ?` - params = append(params, "%"+escape(name)+"%") + params = append(params, "%"+escape(query.Name)+"%") } - if public != nil { - sql += ` and p.public = ?` - if *public { - params = append(params, 1) - } else { - params = append(params, 0) - } - } - - if len(member) != 0 { + if query.Member != nil && len(query.Member.Name) != 0 { 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 = ?` roleID := 0 - switch role { + switch query.Member.Role { case common.RoleProjectAdmin: roleID = 1 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` + 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 } diff --git a/src/common/dao/repository_test.go b/src/common/dao/repository_test.go index aefbce0e3..78379d85e 100644 --- a/src/common/dao/repository_test.go +++ b/src/common/dao/repository_test.go @@ -103,7 +103,6 @@ func TestGetTopRepos(t *testing.T) { project1 := models.Project{ OwnerID: 1, Name: "project1", - Public: 0, } project1.ProjectID, err = AddProject(project1) require.NoError(err) @@ -112,7 +111,6 @@ func TestGetTopRepos(t *testing.T) { project2 := models.Project{ OwnerID: 1, Name: "project2", - Public: 0, } project2.ProjectID, err = AddProject(project2) require.NoError(err) @@ -232,7 +230,6 @@ func TestGetAllRepositories(t *testing.T) { project1 := models.Project{ OwnerID: 1, Name: "projectRepo", - Public: 0, } var err2 error project1.ProjectID, err2 = AddProject(project1) diff --git a/src/common/models/pro_meta.go b/src/common/models/pro_meta.go index 8fba7d20b..d3854d0bd 100644 --- a/src/common/models/pro_meta.go +++ b/src/common/models/pro_meta.go @@ -18,13 +18,17 @@ import ( "time" ) -// keys of project metadata +// keys of project metadata and severity values const ( ProMetaPublic = "public" ProMetaEnableContentTrust = "enable_content_trust" - ProMetaPreventVul = "prevent_vul" + ProMetaPreventVul = "prevent_vul" //prevent vulnerable images from being pulled ProMetaSeverity = "severity" ProMetaAutoScan = "auto_scan" + SeverityNone = "negligible" + SeverityLow = "low" + SeverityMedium = "medium" + SeverityHigh = "high" ) // ProjectMetadata holds the metadata of a project. diff --git a/src/common/models/project.go b/src/common/models/project.go index 0943149e1..1228a191b 100644 --- a/src/common/models/project.go +++ b/src/common/models/project.go @@ -15,31 +15,91 @@ package models import ( + "strings" "time" ) // Project holds the details of a project. -// TODO remove useless attrs type Project struct { - ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` - OwnerID int `orm:"column(owner_id)" json:"owner_id"` - Name string `orm:"column(name)" json:"name"` - CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` - UpdateTime time.Time `orm:"update_time" json:"update_time"` - Deleted int `orm:"column(deleted)" json:"deleted"` - CreationTimeStr string `orm:"-" json:"creation_time_str"` - OwnerName string `orm:"-" json:"owner_name"` - Togglable bool `orm:"-"` - Role int `orm:"-" json:"current_user_role_id"` - RepoCount int `orm:"-" json:"repo_count"` - Metadata map[string]string `orm:"-" json:"metadata"` + ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"` + OwnerID int `orm:"column(owner_id)" json:"owner_id"` + Name string `orm:"column(name)" json:"name"` + CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` + UpdateTime time.Time `orm:"update_time" json:"update_time"` + Deleted int `orm:"column(deleted)" json:"deleted"` + OwnerName string `orm:"-" json:"owner_name"` + Togglable bool `orm:"-" json:"togglable"` + Role int `orm:"-" json:"current_user_role_id"` + RepoCount int `orm:"-" json:"repo_count"` + Metadata map[string]string `orm:"-" json:"metadata"` +} - // TODO remove - Public int `orm:"column(public)" json:"public"` - EnableContentTrust bool `orm:"-" json:"enable_content_trust"` - PreventVulnerableImagesFromRunning bool `orm:"-" json:"prevent_vulnerable_images_from_running"` - PreventVulnerableImagesFromRunningSeverity string `orm:"-" json:"prevent_vulnerable_images_from_running_severity"` - AutomaticallyScanImagesOnPush bool `orm:"-" json:"automatically_scan_images_on_push"` +// GetMetadata ... +func (p *Project) GetMetadata(key string) (string, bool) { + if len(p.Metadata) == 0 { + return "", false + } + 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 @@ -79,6 +139,7 @@ type ProjectQueryParam struct { Public *bool // the project is public or not, can be ture, false and nil Member *MemberQuery // the member of project Pagination *Pagination // pagination information + ProjectIDs []int64 // project ID list } // MemberQuery fitler by member's username and role @@ -103,12 +164,9 @@ type BaseProjectCollection struct { // ProjectRequest holds informations that need for creating project API type ProjectRequest struct { - Name string `json:"project_name"` - Public int `json:"public"` - EnableContentTrust bool `json:"enable_content_trust"` - 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"` + Name string `json:"project_name"` + Public *int `json:"public"` //deprecated, reserved for project creation in replication + Metadata map[string]string `json:"metadata"` } // ProjectQueryResult ... diff --git a/src/common/utils/clair/utils.go b/src/common/utils/clair/utils.go index cf3552994..4e1690e2f 100644 --- a/src/common/utils/clair/utils.go +++ b/src/common/utils/clair/utils.go @@ -30,13 +30,13 @@ import ( func ParseClairSev(clairSev string) models.Severity { sev := strings.ToLower(clairSev) switch sev { - case "negligible": + case models.SeverityNone: return models.SevNone - case "low": + case models.SeverityLow: return models.SevLow - case "medium": + case models.SeverityMedium: return models.SevMedium - case "high": + case models.SeverityHigh: return models.SevHigh default: return models.SevUnknown diff --git a/src/jobservice/replication/transfer.go b/src/jobservice/replication/transfer.go index 5fae137aa..d9e76e0ff 100644 --- a/src/jobservice/replication/transfer.go +++ b/src/jobservice/replication/transfer.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strconv" "strings" "github.com/docker/distribution" @@ -258,13 +259,23 @@ func getProject(name string) (*models.Project, error) { } func (c *Checker) createProject(project *models.Project) error { - pro := &models.ProjectRequest{ - Name: project.Name, - Public: project.Public, - EnableContentTrust: project.EnableContentTrust, - PreventVulnerableImagesFromRunning: project.PreventVulnerableImagesFromRunning, - PreventVulnerableImagesFromRunningSeverity: project.PreventVulnerableImagesFromRunningSeverity, - AutomaticallyScanImagesOnPush: project.AutomaticallyScanImagesOnPush, + // only replicate the public property of project + pro := struct { + models.ProjectRequest + Public int `json:"public"` + }{ + ProjectRequest: models.ProjectRequest{ + Name: project.Name, + Metadata: map[string]string{ + models.ProMetaPublic: strconv.FormatBool(project.IsPublic()), + }, + }, + } + + // 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) diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 088b0144f..9b082db85 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -93,12 +93,11 @@ func init() { beego.Router("/api/search/", &SearchAPI{}) 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", &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/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]+)/_deletable", &ProjectAPI{}, "get:Deletable") 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. -func (a testapi) ToggleProjectPublicity(prjUsr usrInfo, projectID string, ispublic int32) (int, error) { - // create path and map variables - path := "/api/projects/" + projectID + "/publicity/" - _sling := sling.New().Put(a.basePath) - - _sling = _sling.Path(path) - - type QueryParams struct { - Public int32 `json:"public,omitempty"` - } - - _sling = _sling.BodyJSON(&QueryParams{Public: ispublic}) +func (a testapi) ProjectsPut(prjUsr usrInfo, projectID string, + project *models.Project) (int, error) { + path := "/api/projects/" + projectID + _sling := sling.New().Put(a.basePath).Path(path).BodyJSON(project) httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) return httpStatusCode, err diff --git a/src/ui/api/log_test.go b/src/ui/api/log_test.go index cfef52579..c9f57db65 100644 --- a/src/ui/api/log_test.go +++ b/src/ui/api/log_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/tests/apitests/apilib" ) @@ -38,7 +39,7 @@ func TestLogGet(t *testing.T) { fmt.Println("add the project first.") project := apilib.ProjectReq{ ProjectName: "project_for_test_log", - Public: 1, + Metadata: map[string]string{models.ProMetaPublic: "true"}, } reply, err := apiTest.ProjectsPost(*testUser, project) diff --git a/src/ui/api/project.go b/src/ui/api/project.go index a9920d4f9..3a78cd2b8 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -18,6 +18,7 @@ import ( "fmt" "net/http" "regexp" + "strings" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" @@ -120,14 +121,23 @@ func (p *ProjectAPI) Post() { 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{ - Name: pro.Name, - Public: pro.Public, - OwnerName: p.SecurityCtx.GetUsername(), - EnableContentTrust: pro.EnableContentTrust, - PreventVulnerableImagesFromRunning: pro.PreventVulnerableImagesFromRunning, - PreventVulnerableImagesFromRunningSeverity: pro.PreventVulnerableImagesFromRunningSeverity, - AutomaticallyScanImagesOnPush: pro.AutomaticallyScanImagesOnPush, + Name: pro.Name, + OwnerName: p.SecurityCtx.GetUsername(), + Metadata: pro.Metadata, }) if err != nil { if err == errutil.ErrDupProject { @@ -178,7 +188,7 @@ func (p *ProjectAPI) Head() { // Get ... func (p *ProjectAPI) Get() { - if p.project.Public == 0 { + if !p.project.IsPublic() { if !p.SecurityCtx.IsAuthenticated() { p.HandleUnauthorized() return @@ -311,21 +321,52 @@ func (p *ProjectAPI) List() { query.Public = &pub } - // base project collection from which filter is done - base := &models.BaseProjectCollection{} - if !p.SecurityCtx.IsAuthenticated() { - // not login, only get public projects - base.Public = true - } else { - if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) { - // login, but not system admin, get public projects and - // projects that the user is member of - base.Member = p.SecurityCtx.GetUsername() - base.Public = true + // standalone, filter projects according to the privilleges of the user first + if !config.WithAdmiral() { + var projects []*models.Project + if !p.SecurityCtx.IsAuthenticated() { + // not login, only get public projects + 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 { + if !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) { + projects = []*models.Project{} + // login, but not system admin or solution user, get public projects and + // projects that the user is member of + pros, err := p.ProjectMgr.GetPublic() + 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 { p.ParseAndHandleError("failed to list projects", err) return @@ -358,8 +399,8 @@ func (p *ProjectAPI) List() { p.ServeJSON() } -// ToggleProjectPublic ... -func (p *ProjectAPI) ToggleProjectPublic() { +// Put ... +func (p *ProjectAPI) Put() { if !p.SecurityCtx.IsAuthenticated() { p.HandleUnauthorized() return @@ -372,16 +413,10 @@ func (p *ProjectAPI) ToggleProjectPublic() { var req *models.ProjectRequest 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, &models.Project{ - Metadata: map[string]string{ - models.ProMetaPublic: strconv.Itoa(req.Public), - }, + Metadata: req.Metadata, }); err != nil { p.ParseAndHandleError(fmt.Sprintf("failed to update project %d", p.project.ProjectID), err) @@ -464,5 +499,39 @@ func validateProjectReq(req *models.ProjectRequest) error { if !legal { 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 } diff --git a/src/ui/api/project_test.go b/src/ui/api/project_test.go index bd66005ed..49f3e0d87 100644 --- a/src/ui/api/project_test.go +++ b/src/ui/api/project_test.go @@ -31,7 +31,7 @@ var addProject *apilib.ProjectReq var addPID int func InitAddPro() { - addProject = &apilib.ProjectReq{"add_project", 1} + addProject = &apilib.ProjectReq{"add_project", map[string]string{models.ProMetaPublic: "true"}} } 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 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 { t.Error("Error while creat project", err.Error()) t.Log(err) @@ -112,7 +112,7 @@ func TestListProjects(t *testing.T) { assert.Nil(err) assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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 addPID = int(result[0].ProjectId) @@ -130,7 +130,7 @@ func TestListProjects(t *testing.T) { } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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") } @@ -155,7 +155,7 @@ func TestListProjects(t *testing.T) { } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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") } id := strconv.Itoa(CommonGetUserID()) @@ -187,7 +187,7 @@ func TestProGetByID(t *testing.T) { } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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") } @@ -273,48 +273,37 @@ func TestProHead(t *testing.T) { 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") assert := assert.New(t) apiTest := newHarborAPI() - //-------------------case1: Response Code=200------------------------------// + project := &models.Project{ + Metadata: map[string]string{ + models.ProMetaPublic: "true", + }, + } + fmt.Println("case 1: respose code:200") - httpStatusCode, err := apiTest.ToggleProjectPublicity(*admin, "1", 1) - if err != nil { - t.Error("Error while search project by proId", err.Error()) - t.Log(err) - } else { - assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") - } - //-------------------case2: Response Code=401 User need to log in first. ------------------------------// + code, err := apiTest.ProjectsPut(*admin, "1", project) + require.Nil(t, err) + assert.Equal(int(200), code) + fmt.Println("case 2: respose code:401, User need to log in first.") - httpStatusCode, err = apiTest.ToggleProjectPublicity(*unknownUsr, "1", 1) - if err != nil { - t.Error("Error while search project by proId", err.Error()) - t.Log(err) - } else { - assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401") - } - //-------------------case3: Response Code=400 Invalid project id------------------------------// + code, err = apiTest.ProjectsPut(*unknownUsr, "1", project) + require.Nil(t, err) + assert.Equal(int(401), code) + fmt.Println("case 3: respose code:400, Invalid project id") - httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "cc", 1) - if err != nil { - t.Error("Error while search project by proId", err.Error()) - t.Log(err) - } else { - assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400") - } - //-------------------case4: Response Code=404 Not found the project------------------------------// + code, err = apiTest.ProjectsPut(*admin, "cc", project) + require.Nil(t, err) + assert.Equal(int(400), code) + fmt.Println("case 4: respose code:404, Not found the project") - httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "1234", 1) - if err != nil { - t.Error("Error while search project by proId", err.Error()) - t.Log(err) - } else { - assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") - } + code, err = apiTest.ProjectsPut(*admin, "1234", project) + require.Nil(t, err) + assert.Equal(int(404), code) fmt.Printf("\n") } diff --git a/src/ui/api/search.go b/src/ui/api/search.go index 630c32b74..ded814f1c 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -147,7 +147,7 @@ func filterRepositories(projects []*models.Project, keyword string) ( entry["repository_name"] = r.Name entry["project_name"] = projects[j].Name entry["project_id"] = projects[j].ProjectID - entry["project_public"] = projects[j].Public + entry["project_public"] = projects[j].IsPublic() entry["pull_count"] = r.PullCount tags, err := getTags(r.Name) diff --git a/src/ui/api/search_test.go b/src/ui/api/search_test.go index 463db3883..54056f258 100644 --- a/src/ui/api/search_test.go +++ b/src/ui/api/search_test.go @@ -37,7 +37,7 @@ func TestSearch(t *testing.T) { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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(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--------// @@ -49,7 +49,7 @@ func TestSearch(t *testing.T) { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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(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--------// @@ -61,7 +61,7 @@ func TestSearch(t *testing.T) { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") 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(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)") } } diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 9b0651902..0c162aef9 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -15,7 +15,7 @@ package config import ( - //"crypto/tls" + "crypto/tls" "encoding/json" "fmt" "net/http" @@ -108,38 +108,32 @@ func initSecretStore() { func initProjectManager() { var driver pmsdriver.PMSDriver if WithAdmiral() { - // TODO add support for admiral - /* - // integration with admiral - log.Info("initializing the project manager based on PMS...") - // TODO read ca/cert file and pass it to the TLS config - AdmiralClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, + // integration with admiral + log.Info("initializing the project manager based on PMS...") + // TODO read ca/cert file and pass it to the TLS config + AdmiralClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, }, - } + }, + } - path := os.Getenv("SERVICE_TOKEN_FILE_PATH") - if len(path) == 0 { - path = defaultTokenFilePath - } - log.Infof("service token file path: %s", path) - TokenReader = &admiral.FileTokenReader{ - Path: path, - } - GlobalProjectMgr = admiral.NewProjectManager(AdmiralClient, - AdmiralEndpoint(), TokenReader) - */ - GlobalProjectMgr = nil + path := os.Getenv("SERVICE_TOKEN_FILE_PATH") + if len(path) == 0 { + path = defaultTokenFilePath + } + log.Infof("service token file path: %s", path) + TokenReader = &admiral.FileTokenReader{ + Path: path, + } + driver = admiral.NewDriver(AdmiralClient, AdmiralEndpoint(), TokenReader) } else { // standalone log.Info("initializing the project manager based on local database...") driver = local.NewDriver() - // TODO move the statement out of the else block when admiral driver is completed - GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true) } + GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true) } diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 3f3d14d9f..c99e001f1 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -33,7 +33,7 @@ import ( "github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/promgr" - //"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" ) type key string @@ -264,15 +264,13 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool { return false } - /* - log.Debug("creating PMS project manager...") - pm := admiral.NewProjectManager(config.AdmiralClient, - config.AdmiralEndpoint(), &admiral.RawTokenReader{ - Token: token, - }) - */ - // TODO create the DefaultProjectManager with the real admiral PMSDriver - pm := promgr.NewDefaultProjectManager(nil, false) + log.Debug("creating PMS project manager...") + driver := admiral.NewDriver(config.AdmiralClient, + config.AdmiralEndpoint(), &admiral.RawTokenReader{ + Token: token, + }) + + pm := promgr.NewDefaultProjectManager(driver, false) log.Debug("creating admiral security context...") securCtx := admr.NewSecurityContext(authContext, pm) @@ -291,13 +289,10 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { var pm promgr.ProjectManager if config.WithAdmiral() { // integration with admiral - /* - log.Debug("creating PMS project manager...") - pm = admiral.NewProjectManager(config.AdmiralClient, - config.AdmiralEndpoint(), nil) - */ - // TODO create the DefaultProjectManager with the real admiral PMSDriver - pm = promgr.NewDefaultProjectManager(nil, false) + log.Debug("creating PMS project manager...") + driver := admiral.NewDriver(config.AdmiralClient, + config.AdmiralEndpoint(), nil) + pm = promgr.NewDefaultProjectManager(driver, false) log.Debug("creating admiral security context...") securCtx = admr.NewSecurityContext(nil, pm) } else { diff --git a/src/ui/promgr/metamgr/metamgr.go b/src/ui/promgr/metamgr/metamgr.go index f5ddbcc24..bfd54aa7c 100644 --- a/src/ui/promgr/metamgr/metamgr.go +++ b/src/ui/promgr/metamgr/metamgr.go @@ -15,15 +15,13 @@ package metamgr import ( - "strconv" - "github.com/vmware/harbor/src/common/dao" "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 -type ProjectMetadataManaegr interface { +type ProjectMetadataManager interface { // Add metadatas for project specified by projectID Add(projectID int64, meta map[string]string) error // 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 // absent, get all 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 ... -func NewDefaultProjectMetadataManager() ProjectMetadataManaegr { - return &defaultProjectMetadataManaegr{} +func NewDefaultProjectMetadataManager() ProjectMetadataManager { + 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 { proMeta := &models.ProjectMetadata{ ProjectID: projectID, @@ -57,11 +57,11 @@ func (d *defaultProjectMetadataManaegr) Add(projectID int64, meta map[string]str 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...) } -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 { if err := dao.UpdateProjectMetadata(&models.ProjectMetadata{ 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 } -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...) if err != nil { return nil, nil @@ -98,3 +88,14 @@ func (d *defaultProjectMetadataManaegr) Get(projectID int64, meta ...string) (ma 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 +} diff --git a/src/ui/promgr/metamgr/metamgr_test.go b/src/ui/promgr/metamgr/metamgr_test.go index f5cb2acfa..a197a79a9 100644 --- a/src/ui/promgr/metamgr/metamgr_test.go +++ b/src/ui/promgr/metamgr/metamgr_test.go @@ -54,6 +54,12 @@ func TestMetaMgrMethods(t *testing.T) { assert.Equal(t, 1, len(m)) 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 require.Nil(t, mgr.Update(1, map[string]string{ key: newValue, diff --git a/src/ui/promgr/pmsdriver/admiral/admiral.go b/src/ui/promgr/pmsdriver/admiral/admiral.go index 05fc6fce3..f41e9adaf 100644 --- a/src/ui/promgr/pmsdriver/admiral/admiral.go +++ b/src/ui/promgr/pmsdriver/admiral/admiral.go @@ -30,13 +30,12 @@ import ( "github.com/vmware/harbor/src/common/utils" er "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" + "github.com/vmware/harbor/src/ui/promgr/pmsdriver" ) const dupProjectPattern = `Project name '\w+' is already used` -// ProjectManager implements projectmanager.ProjecdtManager interface -// base on project management service -type ProjectManager struct { +type driver struct { client *http.Client endpoint string tokenReader TokenReader @@ -57,10 +56,10 @@ type project struct { Guests []*user `json:"viewers"` } -// NewProjectManager returns an instance of ProjectManager -func NewProjectManager(client *http.Client, endpoint string, - tokenReader TokenReader) *ProjectManager { - return &ProjectManager{ +// NewDriver returns an instance of driver +func NewDriver(client *http.Client, endpoint string, + tokenReader TokenReader) pmsdriver.PMSDriver { + return &driver{ client: client, endpoint: strings.TrimRight(endpoint, "/"), tokenReader: tokenReader, @@ -68,8 +67,8 @@ func NewProjectManager(client *http.Client, endpoint string, } // Get ... -func (p *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, error) { - project, err := p.get(projectIDOrName) +func (d *driver) Get(projectIDOrName interface{}) (*models.Project, error) { + project, err := d.get(projectIDOrName) if err != nil { 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 -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 len(p.getToken()) != 0 { - project, err := p.getFromMy(projectIDOrName) + if len(d.getToken()) != 0 { + project, err := d.getFromMy(projectIDOrName) if err != nil { return nil, err } @@ -90,18 +89,18 @@ func (p *ProjectManager) get(projectIDOrName interface{}) (*project, error) { } // 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 // which the user is a member of -func (p *ProjectManager) getFromMy(projectIDOrName interface{}) (*project, error) { - return p.getAdmiralProject(projectIDOrName, false) +func (d *driver) getFromMy(projectIDOrName interface{}) (*project, error) { + return d.getAdmiralProject(projectIDOrName, false) } // call GET /projects?public=true&$filter=xxx eq xxx -func (p *ProjectManager) getFromPublic(projectIDOrName interface{}) (*project, error) { - project, err := p.getAdmiralProject(projectIDOrName, true) +func (d *driver) getFromPublic(projectIDOrName interface{}) (*project, error) { + project, err := d.getAdmiralProject(projectIDOrName, true) if project != nil { // the projects returned by GET /projects?public=true&xxx have no // "public" property, populate it here @@ -110,7 +109,7 @@ func (p *ProjectManager) getFromPublic(projectIDOrName interface{}) (*project, e 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{} id, name, err := utils.ParseProjectIDOrName(projectIDOrName) @@ -126,7 +125,7 @@ func (p *ProjectManager) getAdmiralProject(projectIDOrName interface{}, public b m["public"] = "true" } - projects, err := p.filter(m) + projects, err := d.filter(m) if err != nil { return nil, err } @@ -145,7 +144,7 @@ func (p *ProjectManager) getAdmiralProject(projectIDOrName interface{}, public b 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 := "" for k, v := range m { if len(query) == 0 { @@ -165,7 +164,7 @@ func (p *ProjectManager) filter(m map[string]string) ([]*project, error) { } path := "/projects" + query - data, err := p.send(http.MethodGet, path, nil) + data, err := d.send(http.MethodGet, path, nil) if err != nil { return nil, err } @@ -201,7 +200,7 @@ func convert(p *project) (*models.Project, error) { Name: p.Name, } if p.Public { - project.Public = 1 + project.SetMetadata(models.ProMetaPublic, "true") } value := p.CustomProperties["__projectIndex"] @@ -221,7 +220,7 @@ func convert(p *project) (*models.Project, error) { if err != nil { 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"] @@ -230,12 +229,12 @@ func convert(p *project) (*models.Project, error) { if err != nil { 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"] if len(value) != 0 { - project.PreventVulnerableImagesFromRunningSeverity = value + project.SetMetadata(models.ProMetaSeverity, value) } value = p.CustomProperties["__automaticallyScanImagesOnPush"] @@ -244,93 +243,14 @@ func convert(p *project) (*models.Project, error) { if err != nil { 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 } -// IsPublic ... -func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { - 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) +func (d *driver) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) { + pro, err := d.get(projectIDOrName) if err != nil { return "", err } @@ -342,32 +262,24 @@ func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (str return pro.ID, nil } -// GetPublic ... -func (p *ProjectManager) GetPublic() ([]*models.Project, error) { - t := true - return p.GetAll(&models.ProjectQueryParam{ - Public: &t, - }) -} - // Create ... -func (p *ProjectManager) Create(pro *models.Project) (int64, error) { +func (d *driver) Create(pro *models.Project) (int64, error) { proj := &project{ CustomProperties: make(map[string]string), } proj.Name = pro.Name - proj.Public = pro.Public == 1 - proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.EnableContentTrust) - proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.PreventVulnerableImagesFromRunning) - proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.PreventVulnerableImagesFromRunningSeverity - proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutomaticallyScanImagesOnPush) + proj.Public = pro.IsPublic() + proj.CustomProperties["__enableContentTrust"] = strconv.FormatBool(pro.ContentTrustEnabled()) + proj.CustomProperties["__preventVulnerableImagesFromRunning"] = strconv.FormatBool(pro.VulPrevented()) + proj.CustomProperties["__preventVulnerableImagesFromRunningSeverity"] = pro.Severity() + proj.CustomProperties["__automaticallyScanImagesOnPush"] = strconv.FormatBool(pro.AutoScan()) data, err := json.Marshal(proj) if err != nil { 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 { // when creating a project with a duplicate name in Admiral, a 500 error // with a specific message will be returned for now. @@ -413,23 +325,23 @@ func (p *ProjectManager) Create(pro *models.Project) (int64, error) { } // Delete ... -func (p *ProjectManager) Delete(projectIDOrName interface{}) error { - id, err := p.getIDbyHarborIDOrName(projectIDOrName) +func (d *driver) Delete(projectIDOrName interface{}) error { + id, err := d.getIDbyHarborIDOrName(projectIDOrName) if err != nil { 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 } // 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") } -// GetAll ... -func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) { +// List ... +func (d *driver) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) { m := map[string]string{} if query != nil { 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 { return nil, err } @@ -454,27 +366,24 @@ func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models list = append(list, project) } - return list, nil + return &models.ProjectQueryResult{ + Total: int64(len(list)), + Projects: list, + }, nil } -// GetTotal ... -func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { - 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) +func (d *driver) send(method, path string, body io.Reader) ([]byte, error) { + req, err := http.NewRequest(method, d.endpoint+path, body) if err != nil { 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() req.URL.RawQuery = req.URL.Query().Encode() - resp, err := p.client.Do(req) + resp, err := d.client.Do(req) if err != nil { log.Debugf("\"%s %s\" failed", req.Method, url) return nil, err @@ -497,12 +406,12 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro return b, nil } -func (p *ProjectManager) getToken() string { - if p.tokenReader == nil { +func (d *driver) getToken() string { + if d.tokenReader == nil { return "" } - token, err := p.tokenReader.ReadToken() + token, err := d.tokenReader.ReadToken() if err != nil { token = "" log.Errorf("failed to read token: %v", err) diff --git a/src/ui/promgr/pmsdriver/admiral/admiral_test.go b/src/ui/promgr/pmsdriver/admiral/admiral_test.go index e57ae139c..22efaccfb 100644 --- a/src/ui/promgr/pmsdriver/admiral/admiral_test.go +++ b/src/ui/promgr/pmsdriver/admiral/admiral_test.go @@ -101,12 +101,12 @@ func TestConvert(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, pro) 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.True(t, pro.EnableContentTrust) - assert.True(t, pro.PreventVulnerableImagesFromRunning) - assert.Equal(t, "medium", pro.PreventVulnerableImagesFromRunningSeverity) - assert.True(t, pro.AutomaticallyScanImagesOnPush) + assert.True(t, pro.ContentTrustEnabled()) + assert.True(t, pro.VulPrevented()) + assert.Equal(t, "medium", pro.Severity()) + assert.True(t, pro.AutoScan()) } func TestParse(t *testing.T) { @@ -182,233 +182,130 @@ func TestParse(t *testing.T) { } func TestGet(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) + d := NewDriver(client, endpoint, tokenReader) name := "project_for_test_get" - id, err := pm.Create(&models.Project{ + id, err := d.Create(&models.Project{ Name: name, }) require.Nil(t, err) defer delete(t, id) // get by invalid input type - _, err = pm.Get([]string{}) + _, err = d.Get([]string{}) assert.NotNil(t, err) // get by invalid ID - project, err := pm.Get(int64(0)) + project, err := d.Get(int64(0)) assert.Nil(t, err) assert.Nil(t, project) // get by invalid name - project, err = pm.Get("invalid_name") + project, err = d.Get("invalid_name") assert.Nil(t, err) assert.Nil(t, project) // get by valid ID - project, err = pm.Get(id) + project, err = d.Get(id) assert.Nil(t, err) assert.Equal(t, id, project.ProjectID) // get by valid name - project, err = pm.Get(name) + project, err = d.Get(name) assert.Nil(t, err) 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) { - pm := NewProjectManager(client, endpoint, tokenReader) + d := NewDriver(client, endpoint, tokenReader) name := "project_for_test_create" - id, err := pm.Create(&models.Project{ - Name: name, - Public: 1, - EnableContentTrust: true, - PreventVulnerableImagesFromRunning: true, - PreventVulnerableImagesFromRunningSeverity: "medium", - AutomaticallyScanImagesOnPush: true, + id, err := d.Create(&models.Project{ + Name: name, + Metadata: map[string]string{ + models.ProMetaPublic: "true", + models.ProMetaEnableContentTrust: "true", + models.ProMetaPreventVul: "true", + models.ProMetaSeverity: "medium", + models.ProMetaAutoScan: "true", + }, }) require.Nil(t, err) defer delete(t, id) - project, err := pm.Get(id) + project, err := d.Get(id) assert.Nil(t, err) assert.Equal(t, name, project.Name) - assert.Equal(t, 1, project.Public) - assert.True(t, project.EnableContentTrust) - assert.True(t, project.PreventVulnerableImagesFromRunning) - assert.Equal(t, "medium", project.PreventVulnerableImagesFromRunningSeverity) - assert.True(t, project.AutomaticallyScanImagesOnPush) + assert.True(t, project.IsPublic()) + assert.True(t, project.ContentTrustEnabled()) + assert.True(t, project.VulPrevented()) + assert.Equal(t, "medium", project.Severity()) + assert.True(t, project.AutoScan()) // duplicate project name - _, err = pm.Create(&models.Project{ + _, err = d.Create(&models.Project{ Name: name, }) assert.Equal(t, errutil.ErrDupProject, err) } func TestDelete(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) + d := NewDriver(client, endpoint, tokenReader) // non-exist project - err := pm.Delete(int64(0)) + err := d.Delete(int64(0)) assert.NotNil(t, err) // delete by ID name := "project_for_pm_based_on_pms_id" - id, err := pm.Create(&models.Project{ + id, err := d.Create(&models.Project{ Name: name, }) require.Nil(t, err) - err = pm.Delete(id) + err = d.Delete(id) assert.Nil(t, err) // delete by name name = "project_for_pm_based_on_pms_name" - id, err = pm.Create(&models.Project{ + id, err = d.Create(&models.Project{ Name: name, }) require.Nil(t, err) - err = pm.Delete(name) + err = d.Delete(name) assert.Nil(t, err) } func TestUpdate(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) - err := pm.Update(nil, nil) + d := NewDriver(client, endpoint, tokenReader) + err := d.Update(nil, nil) assert.NotNil(t, err) } -func TestGetAll(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) +func TestList(t *testing.T) { + d := NewDriver(client, endpoint, tokenReader) name1 := "project_for_test_get_all_01" - id1, err := pm.Create(&models.Project{ + id1, err := d.Create(&models.Project{ Name: name1, }) require.Nil(t, err) defer delete(t, id1) name2 := "project_for_test_get_all_02" - id2, err := pm.Create(&models.Project{ - Name: name2, - Public: 1, + id2, err := d.Create(&models.Project{ + Name: name2, + Metadata: map[string]string{ + models.ProMetaPublic: "true", + }, }) require.Nil(t, err) defer delete(t, id2) // no filter - projects, err := pm.GetAll(nil) + result, err := d.List(nil) require.Nil(t, err) found1 := false found2 := false - for _, project := range projects { + for _, project := range result.Projects { if project.ProjectID == id1 { found1 = true } @@ -420,12 +317,12 @@ func TestGetAll(t *testing.T) { assert.True(t, found2) // filter by name - projects, err = pm.GetAll(&models.ProjectQueryParam{ + result, err = d.List(&models.ProjectQueryParam{ Name: name1, }) require.Nil(t, err) found1 = false - for _, project := range projects { + for _, project := range result.Projects { if project.ProjectID == id1 { found1 = true break @@ -435,12 +332,12 @@ func TestGetAll(t *testing.T) { // filter by public value := true - projects, err = pm.GetAll(&models.ProjectQueryParam{ + result, err = d.List(&models.ProjectQueryParam{ Public: &value, }) require.Nil(t, err) found2 = false - for _, project := range projects { + for _, project := range result.Projects { if project.ProjectID == id2 { found2 = true break @@ -449,27 +346,9 @@ func TestGetAll(t *testing.T) { 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) { - pm := NewProjectManager(client, endpoint, tokenReader) - if err := pm.Delete(id); err != nil { + d := NewDriver(client, endpoint, tokenReader) + if err := d.Delete(id); err != nil { t.Logf("failed to delete project %d: %v", id, err) } } diff --git a/src/ui/promgr/pmsdriver/driver.go b/src/ui/promgr/pmsdriver/driver.go index def00fc54..d5b2ae757 100644 --- a/src/ui/promgr/pmsdriver/driver.go +++ b/src/ui/promgr/pmsdriver/driver.go @@ -30,7 +30,5 @@ type PMSDriver interface { // Update the properties of a project Update(projectIDOrName interface{}, project *models.Project) error // List lists projects according to the query conditions - // TODO remove base - List(query *models.ProjectQueryParam, - base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) + List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) } diff --git a/src/ui/promgr/pmsdriver/local/local.go b/src/ui/promgr/pmsdriver/local/local.go index 804913600..9004f9ae3 100644 --- a/src/ui/promgr/pmsdriver/local/local.go +++ b/src/ui/promgr/pmsdriver/local/local.go @@ -82,7 +82,6 @@ func (d *driver) Create(project *models.Project) (int64, error) { t := time.Now() pro := &models.Project{ Name: project.Name, - Public: project.Public, OwnerID: project.OwnerID, CreationTime: t, UpdateTime: t, @@ -129,16 +128,15 @@ func (d *driver) Update(projectIDOrName interface{}, return nil } -// TODO remove base // List returns a project list according to the query parameters -func (d *driver) List(query *models.ProjectQueryParam, - base ...*models.BaseProjectCollection) ( +func (d *driver) List(query *models.ProjectQueryParam) ( *models.ProjectQueryResult, error) { - total, err := dao.GetTotalOfProjects(query, base...) + total, err := dao.GetTotalOfProjects(query) if err != nil { return nil, err } - projects, err := dao.GetProjects(query, base...) + + projects, err := dao.GetProjects(query) if err != nil { return nil, err } diff --git a/src/ui/promgr/pmsdriver/local/local_test.go b/src/ui/promgr/pmsdriver/local/local_test.go index 8a88b0f8f..a739a7582 100644 --- a/src/ui/promgr/pmsdriver/local/local_test.go +++ b/src/ui/promgr/pmsdriver/local/local_test.go @@ -156,7 +156,9 @@ func TestList(t *testing.T) { id, err := pm.Create(&models.Project{ Name: "get_all_test", OwnerID: 1, - Public: 1, + Metadata: map[string]string{ + models.ProMetaPublic: "true", + }, }) assert.Nil(t, err) defer pm.Delete(id) diff --git a/src/ui/promgr/promgr.go b/src/ui/promgr/promgr.go index 05cd9b8a6..bfa28ca15 100644 --- a/src/ui/promgr/promgr.go +++ b/src/ui/promgr/promgr.go @@ -16,6 +16,7 @@ package promgr import ( "fmt" + "strconv" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" @@ -30,9 +31,7 @@ type ProjectManager interface { Create(*models.Project) (int64, error) Delete(projectIDOrName interface{}) error Update(projectIDOrName interface{}, project *models.Project) error - // TODO remove base - List(query *models.ProjectQueryParam, - base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) + List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) IsPublic(projectIDOrName interface{}) (bool, error) Exists(projectIDOrName interface{}) (bool, error) // get all public project @@ -42,7 +41,7 @@ type ProjectManager interface { type defaultProjectManager struct { pmsDriver pmsdriver.PMSDriver 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, @@ -117,7 +116,25 @@ func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *mod if pro == nil { 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 } } @@ -125,13 +142,32 @@ func (d *defaultProjectManager) Update(projectIDOrName interface{}, project *mod return d.pmsDriver.Update(projectIDOrName, project) } -// TODO remove base -func (d *defaultProjectManager) List(query *models.ProjectQueryParam, - base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) { - result, err := d.pmsDriver.List(query, base...) +func (d *defaultProjectManager) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) { + // query by public/private property with ProjectMetadataManager first + if d.metaMgrEnabled && query != nil && query.Public != nil { + projectIDs, err := d.filterByPublic(*query.Public) + if err != nil { + 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 { for _, project := range result.Projects { meta, err := d.metaMgr.Get(project.ProjectID) @@ -144,6 +180,35 @@ func (d *defaultProjectManager) List(query *models.ProjectQueryParam, 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) { project, err := d.Get(projectIDOrName) if err != nil { @@ -152,7 +217,7 @@ func (d *defaultProjectManager) IsPublic(projectIDOrName interface{}) (bool, err if project == nil { return false, nil } - return project.Public == 1, nil + return project.IsPublic(), nil } func (d *defaultProjectManager) Exists(projectIDOrName interface{}) (bool, error) { diff --git a/src/ui/promgr/promgr_test.go b/src/ui/promgr/promgr_test.go index ffc83aec7..e646217d6 100644 --- a/src/ui/promgr/promgr_test.go +++ b/src/ui/promgr/promgr_test.go @@ -32,7 +32,9 @@ func newFakePMSDriver() pmsdriver.PMSDriver { project: &models.Project{ ProjectID: 1, 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 } -func (f *fakePMSDriver) List(query *models.ProjectQueryParam, - base ...*models.BaseProjectCollection) (*models.ProjectQueryResult, error) { +func (f *fakePMSDriver) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) { return &models.ProjectQueryResult{ Total: 1, Projects: []*models.Project{f.project}, @@ -116,5 +117,5 @@ func TestGetPublic(t *testing.T) { projects, err := proMgr.GetPublic() require.Nil(t, err) assert.Equal(t, 1, len(projects)) - assert.Equal(t, 1, projects[0].Public) + assert.True(t, projects[0].IsPublic()) } diff --git a/src/ui/proxy/interceptor_test.go b/src/ui/proxy/interceptor_test.go index 65fdf1ebe..dda580cfa 100644 --- a/src/ui/proxy/interceptor_test.go +++ b/src/ui/proxy/interceptor_test.go @@ -2,14 +2,14 @@ package proxy import ( "github.com/stretchr/testify/assert" - //"github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" "github.com/vmware/harbor/src/adminserver/client" "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" notarytest "github.com/vmware/harbor/src/common/utils/notary/test" utilstest "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/ui/config" - //"github.com/vmware/harbor/src/ui/promgr/pmsdriver/admiral" "net/http" "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) { var defaultConfigAdmiral = map[string]interface{}{ common.ExtEndpoint: "https://" + endpoint, common.WithNotary: true, common.CfgExpiration: 5, - common.AdmiralEndpoint: admiralEndpoint, 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) if err != nil { @@ -149,34 +135,38 @@ func TestPMSPolicyChecker(t *testing.T) { if err := config.Init(); err != nil { panic(err) } - pm := admiral.NewProjectManager(http.DefaultClient, - admiralEndpoint, &admiral.RawTokenReader{ - Token: "token", - }) + database, err := config.Database() + if err != nil { + panic(err) + } + if err := dao.InitDatabase(database); err != nil { + panic(err) + } + name := "project_for_test_get_sev_low" - id, err := pm.Create(&models.Project{ - Name: name, - EnableContentTrust: true, - PreventVulnerableImagesFromRunning: false, - PreventVulnerableImagesFromRunningSeverity: "low", + id, err := config.GlobalProjectMgr.Create(&models.Project{ + Name: name, + OwnerID: 1, + Metadata: map[string]string{ + models.ProMetaEnableContentTrust: "true", + models.ProMetaPreventVul: "true", + models.ProMetaSeverity: "low", + }, }) require.Nil(t, err) 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) } }(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") assert.True(t, contentTrustFlag) projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy("project_for_test_get_sev_low") - assert.False(t, projectVulnerableEnabled) + assert.True(t, projectVulnerableEnabled) assert.Equal(t, projectVulnerableSeverity, models.SevLow) } -*/ + func TestMatchNotaryDigest(t *testing.T) { assert := assert.New(t) //The data from common/utils/notary/helper_test.go diff --git a/src/ui/proxy/interceptors.go b/src/ui/proxy/interceptors.go index a8ebcb4fc..3284815a9 100644 --- a/src/ui/proxy/interceptors.go +++ b/src/ui/proxy/interceptors.go @@ -16,7 +16,6 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "regexp" "strconv" "strings" @@ -38,9 +37,6 @@ var rec *httptest.ResponseRecorder // NotaryEndpoint , exported for testing. 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 func MatchPullManifest(req *http.Request) (bool, string, string) { //TODO: add user agent check. @@ -77,16 +73,6 @@ type policyChecker interface { 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 { 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) return true } - return project.EnableContentTrust + return project.ContentTrustEnabled() } func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) { 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) 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 @@ -116,10 +102,7 @@ func newPMSPolicyChecker(pm promgr.ProjectManager) policyChecker { } func getPolicyChecker() policyChecker { - if config.WithAdmiral() { - return newPMSPolicyChecker(config.GlobalProjectMgr) - } - return EnvChecker + return newPMSPolicyChecker(config.GlobalProjectMgr) } type imageInfo struct { diff --git a/src/ui/router.go b/src/ui/router.go index 22f329ca2..51eca5438 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -70,7 +70,6 @@ func initRouters() { beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{}) beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head") 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", &api.UserAPI{}, "get:List;post:Post") diff --git a/src/ui/service/notifications/registry/handler.go b/src/ui/service/notifications/registry/handler.go index 12a19d365..4e532242c 100644 --- a/src/ui/service/notifications/registry/handler.go +++ b/src/ui/service/notifications/registry/handler.go @@ -16,7 +16,6 @@ package registry import ( "encoding/json" - "os" "regexp" "strings" "time" @@ -170,10 +169,8 @@ func autoScanEnabled(project *models.Project) bool { log.Debugf("Auto Scan disabled because Harbor is not deployed with Clair") return false } - if config.WithAdmiral() { - return project.AutomaticallyScanImagesOnPush - } - return os.Getenv("ENABLE_HARBOR_SCAN_ON_PUSH") == "1" + + return project.AutoScan() } // Render returns nil as it won't render any template. diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html index abf057aa8..23053f2fb 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.html +++ b/src/ui_ng/src/app/project/create-project/create-project.component.html @@ -23,7 +23,7 @@