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, }