From a79bb127b3acbf5ded51687ac13e372883de5ad8 Mon Sep 17 00:00:00 2001
From: Wenkai Yin <yinw@vmware.com>
Date: Wed, 10 Jun 2020 17:14:12 +0800
Subject: [PATCH] Update creating project API to support proxy cache project

Update creating project API to support proxy cache project

Signed-off-by: Wenkai Yin <yinw@vmware.com>
---
 api/v2.0/legacy_swagger.yaml                  |  8 ++++++
 .../postgresql/0040_2.1.0_schema.up.sql       |  1 +
 src/common/dao/project.go                     | 17 +++++++-----
 src/common/models/project.go                  |  9 ++++---
 src/core/api/project.go                       | 26 ++++++++++++++++---
 src/core/api/registry.go                      | 12 +++++++++
 src/core/promgr/pmsdriver/local/local.go      |  1 +
 7 files changed, 62 insertions(+), 12 deletions(-)
 create mode 100644 make/migrations/postgresql/0040_2.1.0_schema.up.sql

diff --git a/api/v2.0/legacy_swagger.yaml b/api/v2.0/legacy_swagger.yaml
index bfb795068..3f9867bd6 100644
--- a/api/v2.0/legacy_swagger.yaml
+++ b/api/v2.0/legacy_swagger.yaml
@@ -3766,6 +3766,10 @@ definitions:
         type: integer
         format: int64
         description: The storage quota of the project.
+      registry_id:
+        type: integer
+        format: int64
+        description: The ID of referenced registry when creating the proxy cache project
   Project:
     type: object
     properties:
@@ -3780,6 +3784,10 @@ definitions:
       name:
         type: string
         description: The name of the project.
+      registry_id:
+        type: integer
+        format: int64
+        description: The ID of referenced registry when the project is a proxy cache project.
       creation_time:
         type: string
         description: The creation time of the project.
diff --git a/make/migrations/postgresql/0040_2.1.0_schema.up.sql b/make/migrations/postgresql/0040_2.1.0_schema.up.sql
new file mode 100644
index 000000000..78977a40f
--- /dev/null
+++ b/make/migrations/postgresql/0040_2.1.0_schema.up.sql
@@ -0,0 +1 @@
+ALTER TABLE project ADD COLUMN IF NOT EXISTS registry_id int;
diff --git a/src/common/dao/project.go b/src/common/dao/project.go
index 18a34ef1b..464137058 100644
--- a/src/common/dao/project.go
+++ b/src/common/dao/project.go
@@ -27,11 +27,11 @@ import (
 func AddProject(project models.Project) (int64, error) {
 	o := GetOrmer()
 
-	sql := "insert into project (owner_id, name, creation_time, update_time, deleted) values (?, ?, ?, ?, ?) RETURNING project_id"
+	sql := "insert into project (owner_id, name, registry_id, creation_time, update_time, deleted) values (?, ?, ?, ?, ?, ?) RETURNING project_id"
 	var projectID int64
 	now := time.Now()
 
-	err := o.Raw(sql, project.OwnerID, project.Name, now, now, project.Deleted).QueryRow(&projectID)
+	err := o.Raw(sql, project.OwnerID, project.Name, project.RegistryID, now, now, project.Deleted).QueryRow(&projectID)
 	if err != nil {
 		return 0, err
 	}
@@ -78,7 +78,7 @@ func addProjectMember(member models.Member) (int, error) {
 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
+	sql := `select p.project_id, p.name, p.registry_id, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
 		from project p left join harbor_user u on p.owner_id = u.user_id where p.deleted = false and p.project_id = ?`
 	queryParam := make([]interface{}, 1)
 	queryParam = append(queryParam, id)
@@ -142,7 +142,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
 // GetProjects returns a project list according to the query conditions
 func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
 	sqlStr, queryParam := projectQueryConditions(query)
-	sqlStr = `select distinct p.project_id, p.name, p.owner_id,
+	sqlStr = `select distinct p.project_id, p.name, p.registry_id, p.owner_id,
 		p.creation_time, p.update_time ` + sqlStr + ` order by p.name`
 	sqlStr, queryParam = CreatePagination(query, sqlStr, queryParam)
 
@@ -157,12 +157,12 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
 // and the user is in the group which is a group member of this project.
 func GetGroupProjects(groupIDs []int, query *models.ProjectQueryParam) ([]*models.Project, error) {
 	sql, params := projectQueryConditions(query)
-	sql = `select distinct p.project_id, p.name, p.owner_id,
+	sql = `select distinct p.project_id, p.name, p.registry_id, p.owner_id,
 				p.creation_time, p.update_time ` + sql
 	groupIDCondition := JoinNumberConditions(groupIDs)
 	if len(groupIDs) > 0 {
 		sql = fmt.Sprintf(
-			`%s union select distinct p.project_id, p.name, p.owner_id, p.creation_time, p.update_time
+			`%s union select distinct p.project_id, p.name, p.registry_id, p.owner_id, p.creation_time, p.update_time
 		     from project p
 		     left join project_member pm on p.project_id = pm.project_id
 		     left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
@@ -237,6 +237,11 @@ func projectQueryConditions(query *models.ProjectQueryParam) (string, []interfac
 		params = append(params, "%"+Escape(query.Name)+"%")
 	}
 
+	if query.RegistryID > 0 {
+		sql += ` and p.registry_id = ?`
+		params = append(params, query.RegistryID)
+	}
+
 	if query.Member != nil && len(query.Member.Name) != 0 {
 		sql += ` and u2.username=?`
 		params = append(params, query.Member.Name)
diff --git a/src/common/models/project.go b/src/common/models/project.go
index 7afa92437..dbb6bcc41 100644
--- a/src/common/models/project.go
+++ b/src/common/models/project.go
@@ -45,6 +45,7 @@ type Project struct {
 	ChartCount   uint64            `orm:"-" json:"chart_count"`
 	Metadata     map[string]string `orm:"-" json:"metadata"`
 	CVEWhitelist CVEWhitelist      `orm:"-" json:"cve_whitelist"`
+	RegistryID   int64             `orm:"column(registry_id)" json:"registry_id"`
 }
 
 // GetMetadata ...
@@ -136,9 +137,10 @@ func isTrue(value string) bool {
 // List projects which user1 is member of: query := &QueryParam{Member:&Member{Name:"user1"}}
 // List projects which user1 is the project admin : query := &QueryParam{Member:&Member{Name:"user1",Role:1}}
 type ProjectQueryParam struct {
-	Name       string       // the name of project
-	Owner      string       // the username of project owner
-	Public     *bool        // the project is public or not, can be ture, false and nil
+	Name       string // the name of project
+	Owner      string // the username of project owner
+	Public     *bool  // the project is public or not, can be ture, false and nil
+	RegistryID int64
 	Member     *MemberQuery // the member of project
 	Pagination *Pagination  // pagination information
 	ProjectIDs []int64      // project ID list
@@ -178,6 +180,7 @@ type ProjectRequest struct {
 	CVEWhitelist CVEWhitelist      `json:"cve_whitelist"`
 
 	StorageLimit *int64 `json:"storage_limit,omitempty"`
+	RegistryID   int64  `json:"registry_id"`
 }
 
 // ProjectQueryResult ...
diff --git a/src/core/api/project.go b/src/core/api/project.go
index 87c385404..b1df3d2c3 100644
--- a/src/core/api/project.go
+++ b/src/core/api/project.go
@@ -17,6 +17,7 @@ package api
 import (
 	"context"
 	"fmt"
+	"github.com/goharbor/harbor/src/replication"
 	"net/http"
 	"regexp"
 	"strconv"
@@ -126,6 +127,24 @@ func (p *ProjectAPI) Post() {
 		return
 	}
 
+	// trying to create a proxy cache project
+	if pro.RegistryID > 0 {
+		// only system admin can create the proxy cache project
+		if !p.SecurityCtx.IsSysAdmin() {
+			p.SendForbiddenError(errors.New("Only system admin can create proxy cache project"))
+			return
+		}
+		registry, err := replication.RegistryMgr.Get(pro.RegistryID)
+		if err != nil {
+			p.SendInternalServerError(fmt.Errorf("failed to get the registry %d: %v", pro.RegistryID, err))
+			return
+		}
+		if registry == nil {
+			p.SendNotFoundError(fmt.Errorf("registry %d not found", pro.RegistryID))
+			return
+		}
+	}
+
 	var hardLimits types.ResourceList
 	if config.QuotaPerProjectEnable() {
 		setting, err := config.QuotaSetting()
@@ -187,9 +206,10 @@ func (p *ProjectAPI) Post() {
 		owner = user.Username
 	}
 	projectID, err := p.ProjectMgr.Create(&models.Project{
-		Name:      pro.Name,
-		OwnerName: owner,
-		Metadata:  pro.Metadata,
+		Name:       pro.Name,
+		OwnerName:  owner,
+		Metadata:   pro.Metadata,
+		RegistryID: pro.RegistryID,
 	})
 	if err != nil {
 		if err == errutil.ErrDupProject {
diff --git a/src/core/api/registry.go b/src/core/api/registry.go
index e55da244a..b3416ad13 100644
--- a/src/core/api/registry.go
+++ b/src/core/api/registry.go
@@ -7,6 +7,7 @@ import (
 	"strconv"
 
 	common_http "github.com/goharbor/harbor/src/common/http"
+	common_models "github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/common/utils"
 	"github.com/goharbor/harbor/src/core/api/models"
 	"github.com/goharbor/harbor/src/lib/log"
@@ -375,6 +376,17 @@ func (t *RegistryAPI) Delete() {
 		return
 	}
 
+	// check whether the registry is referenced by any proxy cache projects
+	result, err := t.ProjectMgr.List(&common_models.ProjectQueryParam{RegistryID: id})
+	if err != nil {
+		t.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
+		return
+	}
+	if result != nil && result.Total > 0 {
+		t.SendPreconditionFailedError(fmt.Errorf("Can't delete registry %d,  %d proxy cache projects referennce it", id, result.Total))
+		return
+	}
+
 	if err := t.manager.Remove(id); err != nil {
 		msg := fmt.Sprintf("Delete registry %d error: %v", id, err)
 		log.Error(msg)
diff --git a/src/core/promgr/pmsdriver/local/local.go b/src/core/promgr/pmsdriver/local/local.go
index d29121942..755649ad0 100644
--- a/src/core/promgr/pmsdriver/local/local.go
+++ b/src/core/promgr/pmsdriver/local/local.go
@@ -83,6 +83,7 @@ func (d *driver) Create(project *models.Project) (int64, error) {
 	pro := &models.Project{
 		Name:         project.Name,
 		OwnerID:      project.OwnerID,
+		RegistryID:   project.RegistryID,
 		CreationTime: t,
 		UpdateTime:   t,
 	}