2021-02-25 09:19:55 +01:00
|
|
|
// Copyright Project Harbor Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-03-17 04:58:43 +01:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-15 18:09:06 +02:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2020-03-17 04:58:43 +01:00
|
|
|
"github.com/go-openapi/runtime/middleware"
|
2020-07-30 11:29:25 +02:00
|
|
|
"github.com/go-openapi/strfmt"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/common"
|
2020-03-17 04:58:43 +01:00
|
|
|
"github.com/goharbor/harbor/src/common/rbac"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/common/security"
|
|
|
|
"github.com/goharbor/harbor/src/common/security/local"
|
2021-08-17 10:35:36 +02:00
|
|
|
robotSec "github.com/goharbor/harbor/src/common/security/robot"
|
2020-08-31 11:02:15 +02:00
|
|
|
"github.com/goharbor/harbor/src/controller/p2p/preheat"
|
2020-03-24 13:45:45 +01:00
|
|
|
"github.com/goharbor/harbor/src/controller/project"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/controller/quota"
|
2021-03-31 09:49:23 +02:00
|
|
|
"github.com/goharbor/harbor/src/controller/registry"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/controller/repository"
|
2021-02-25 09:19:55 +01:00
|
|
|
"github.com/goharbor/harbor/src/controller/retention"
|
2021-03-03 05:23:36 +01:00
|
|
|
"github.com/goharbor/harbor/src/controller/scanner"
|
2021-08-24 03:34:02 +02:00
|
|
|
"github.com/goharbor/harbor/src/controller/user"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/core/api"
|
|
|
|
"github.com/goharbor/harbor/src/lib"
|
2021-05-14 05:27:23 +02:00
|
|
|
"github.com/goharbor/harbor/src/lib/config"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/lib/errors"
|
|
|
|
"github.com/goharbor/harbor/src/lib/log"
|
|
|
|
"github.com/goharbor/harbor/src/lib/orm"
|
|
|
|
"github.com/goharbor/harbor/src/lib/q"
|
2020-03-17 04:58:43 +01:00
|
|
|
"github.com/goharbor/harbor/src/pkg/audit"
|
2021-05-14 05:27:23 +02:00
|
|
|
"github.com/goharbor/harbor/src/pkg/member"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/pkg/project/metadata"
|
2021-08-17 10:35:36 +02:00
|
|
|
pkgModels "github.com/goharbor/harbor/src/pkg/project/models"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/pkg/quota/types"
|
|
|
|
"github.com/goharbor/harbor/src/pkg/retention/policy"
|
2021-01-04 03:24:31 +01:00
|
|
|
"github.com/goharbor/harbor/src/pkg/robot"
|
2021-08-24 03:34:02 +02:00
|
|
|
userModels "github.com/goharbor/harbor/src/pkg/user/models"
|
2020-08-15 18:09:06 +02:00
|
|
|
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
2020-03-17 04:58:43 +01:00
|
|
|
"github.com/goharbor/harbor/src/server/v2.0/models"
|
|
|
|
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/project"
|
|
|
|
)
|
|
|
|
|
2020-08-15 18:09:06 +02:00
|
|
|
// for the proxy cache type project, we will create a 7 days retention policy for it by default
|
|
|
|
const defaultDaysToRetentionForProxyCacheProject = 7
|
|
|
|
|
2020-03-17 04:58:43 +01:00
|
|
|
func newProjectAPI() *projectAPI {
|
|
|
|
return &projectAPI{
|
2020-08-15 18:09:06 +02:00
|
|
|
auditMgr: audit.Mgr,
|
|
|
|
metadataMgr: metadata.Mgr,
|
2021-08-24 03:34:02 +02:00
|
|
|
userCtl: user.Ctl,
|
2020-08-15 18:09:06 +02:00
|
|
|
repositoryCtl: repository.Ctl,
|
|
|
|
projectCtl: project.Ctl,
|
2021-04-02 08:22:18 +02:00
|
|
|
memberMgr: member.Mgr,
|
2020-08-15 18:09:06 +02:00
|
|
|
quotaCtl: quota.Ctl,
|
2021-01-04 03:24:31 +01:00
|
|
|
robotMgr: robot.Mgr,
|
2020-08-31 11:02:15 +02:00
|
|
|
preheatCtl: preheat.Ctl,
|
2021-01-08 01:09:34 +01:00
|
|
|
retentionCtl: retention.Ctl,
|
2021-03-03 05:23:36 +01:00
|
|
|
scannerCtl: scanner.DefaultController,
|
2020-03-17 04:58:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type projectAPI struct {
|
|
|
|
BaseAPI
|
2020-08-15 18:09:06 +02:00
|
|
|
auditMgr audit.Manager
|
|
|
|
metadataMgr metadata.Manager
|
2021-08-24 03:34:02 +02:00
|
|
|
userCtl user.Controller
|
2020-08-15 18:09:06 +02:00
|
|
|
repositoryCtl repository.Controller
|
|
|
|
projectCtl project.Controller
|
2021-04-02 08:22:18 +02:00
|
|
|
memberMgr member.Manager
|
2020-08-15 18:09:06 +02:00
|
|
|
quotaCtl quota.Controller
|
2021-01-04 03:24:31 +01:00
|
|
|
robotMgr robot.Manager
|
2020-08-31 11:02:15 +02:00
|
|
|
preheatCtl preheat.Controller
|
2021-01-08 01:09:34 +01:00
|
|
|
retentionCtl retention.Controller
|
2021-03-03 05:23:36 +01:00
|
|
|
scannerCtl scanner.Controller
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateProjectParams) middleware.Responder {
|
|
|
|
if err := a.RequireAuthenticated(ctx); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2021-03-11 13:25:51 +01:00
|
|
|
onlyAdmin, err := config.OnlyAdminCreateProject(ctx)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, fmt.Errorf("failed to determine whether only admin can create projects: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
secCtx, _ := security.FromContext(ctx)
|
2021-08-24 03:34:02 +02:00
|
|
|
if r, ok := secCtx.(*robotSec.SecurityContext); ok && !r.User().IsSysLevel() {
|
|
|
|
log.Errorf("Only system level robot can create project")
|
|
|
|
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system level robot can create project"))
|
|
|
|
}
|
2021-01-07 08:45:04 +01:00
|
|
|
if onlyAdmin && !(a.isSysAdmin(ctx, rbac.ActionCreate) || secCtx.IsSolutionUser()) {
|
2020-08-15 18:09:06 +02:00
|
|
|
log.Errorf("Only sys admin can create project")
|
|
|
|
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create project"))
|
|
|
|
}
|
|
|
|
|
|
|
|
req := params.Project
|
|
|
|
|
2021-01-07 08:45:04 +01:00
|
|
|
if req.RegistryID != nil && !a.isSysAdmin(ctx, rbac.ActionCreate) {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create proxy cache project"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// populate storage limit
|
2021-03-11 13:25:51 +01:00
|
|
|
if config.QuotaPerProjectEnable(ctx) {
|
2020-08-15 18:09:06 +02:00
|
|
|
// the security context is not sys admin, set the StorageLimit the global StoragePerProject
|
2021-01-07 08:45:04 +01:00
|
|
|
if req.StorageLimit == nil || *req.StorageLimit == 0 || !a.isSysAdmin(ctx, rbac.ActionCreate) {
|
2021-03-11 13:25:51 +01:00
|
|
|
setting, err := config.QuotaSetting(ctx)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to get quota setting: %v", err)
|
|
|
|
return a.SendError(ctx, fmt.Errorf("failed to get quota setting: %v", err))
|
|
|
|
}
|
|
|
|
defaultStorageLimit := setting.StoragePerProject
|
|
|
|
req.StorageLimit = &defaultStorageLimit
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// ignore storage limit when quota per project disabled
|
|
|
|
req.StorageLimit = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.Metadata == nil {
|
|
|
|
req.Metadata = &models.ProjectMetadata{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// accept the "public" property to make replication work well with old versions(<=1.2.0)
|
|
|
|
if req.Public != nil && req.Metadata.Public == "" {
|
|
|
|
req.Metadata.Public = strconv.FormatBool(*req.Public)
|
|
|
|
}
|
|
|
|
|
|
|
|
// populate public metadata as false if it isn't set
|
|
|
|
if req.Metadata.Public == "" {
|
|
|
|
req.Metadata.Public = strconv.FormatBool(false)
|
|
|
|
}
|
|
|
|
|
2020-09-02 17:23:27 +02:00
|
|
|
// ignore enable_content_trust metadata for proxy cache project
|
|
|
|
// see https://github.com/goharbor/harbor/issues/12940 to get more info
|
|
|
|
if req.RegistryID != nil {
|
|
|
|
req.Metadata.EnableContentTrust = nil
|
|
|
|
}
|
|
|
|
|
2020-08-15 18:09:06 +02:00
|
|
|
// validate the RegistryID and StorageLimit in the body of the request
|
|
|
|
if err := a.validateProjectReq(ctx, req); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ownerID int
|
2021-08-24 03:34:02 +02:00
|
|
|
// TODO: revise the ownerID in project model.
|
2020-08-15 18:09:06 +02:00
|
|
|
// set the owner as the system admin when the API being called by replication
|
|
|
|
// it's a solution to workaround the restriction of project creation API:
|
|
|
|
// only normal users can create projects
|
2021-08-24 03:34:02 +02:00
|
|
|
// Remove the assumption of user id 1 is the system admin. And use the minimum system admin id as the owner ID,
|
|
|
|
// in most case, it's 1
|
|
|
|
if _, ok := secCtx.(*robotSec.SecurityContext); ok || secCtx.IsSolutionUser() {
|
|
|
|
q := &q.Query{
|
|
|
|
Keywords: map[string]interface{}{
|
|
|
|
"sysadmin_flag": true,
|
|
|
|
},
|
|
|
|
Sorts: []*q.Sort{
|
|
|
|
q.NewSort("user_id", false),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
admins, err := a.userCtl.List(ctx, q, userModels.WithDefaultAdmin())
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
if len(admins) == 0 {
|
|
|
|
return a.SendError(ctx, errors.New(nil).WithMessage("cannot create project as no system admin found"))
|
|
|
|
}
|
|
|
|
ownerID = admins[0].UserID
|
2020-08-15 18:09:06 +02:00
|
|
|
} else {
|
|
|
|
ownerName := secCtx.GetUsername()
|
2021-08-24 03:34:02 +02:00
|
|
|
user, err := a.userCtl.GetByName(ctx, ownerName)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
ownerID = user.UserID
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &project.Project{
|
|
|
|
Name: req.ProjectName,
|
|
|
|
OwnerID: ownerID,
|
|
|
|
RegistryID: lib.Int64Value(req.RegistryID),
|
|
|
|
}
|
|
|
|
lib.JSONCopy(&p.Metadata, req.Metadata)
|
|
|
|
|
|
|
|
projectID, err := a.projectCtl.Create(ctx, p)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StorageLimit is provided in the request body and it's valid,
|
|
|
|
// create the quota for the project
|
|
|
|
if req.StorageLimit != nil {
|
|
|
|
referenceID := quota.ReferenceID(projectID)
|
|
|
|
hardLimits := types.ResourceList{types.ResourceStorage: *req.StorageLimit}
|
|
|
|
if _, err := a.quotaCtl.Create(ctx, quota.ProjectReference, referenceID, hardLimits); err != nil {
|
|
|
|
return a.SendError(ctx, fmt.Errorf("failed to create quota for project: %v", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegistryID is provided in the request body and it's valid,
|
|
|
|
// create a default retention policy for proxy project
|
|
|
|
if req.RegistryID != nil {
|
|
|
|
plc := policy.WithNDaysSinceLastPull(projectID, defaultDaysToRetentionForProxyCacheProject)
|
2021-01-08 01:09:34 +01:00
|
|
|
retentionID, err := a.retentionCtl.CreateRetention(ctx, plc)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
md := map[string]string{"retention_id": strconv.FormatInt(retentionID, 10)}
|
|
|
|
if err := a.metadataMgr.Add(ctx, projectID, md); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
var location string
|
|
|
|
if lib.BoolValue(params.XResourceNameInLocation) {
|
|
|
|
location = fmt.Sprintf("%s/%s", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), req.ProjectName)
|
|
|
|
} else {
|
|
|
|
location = fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), projectID)
|
|
|
|
}
|
|
|
|
|
2020-08-15 18:09:06 +02:00
|
|
|
return operation.NewCreateProjectCreated().WithLocation(location)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) DeleteProject(ctx context.Context, params operation.DeleteProjectParams) middleware.Responder {
|
2020-12-14 08:48:52 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
p, result, err := a.deletable(ctx, projectNameOrID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !result.Deletable {
|
|
|
|
return a.SendError(ctx, errors.PreconditionFailedError(errors.New(result.Message)))
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
if err := a.projectCtl.Delete(ctx, p.ProjectID); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-08-18 04:38:45 +02:00
|
|
|
// remove the robot associated with the project
|
2020-12-14 08:48:52 +01:00
|
|
|
if err := a.robotMgr.DeleteByProjectID(ctx, p.ProjectID); err != nil {
|
2020-08-18 04:38:45 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
referenceID := quota.ReferenceID(p.ProjectID)
|
2020-08-15 18:09:06 +02:00
|
|
|
q, err := a.quotaCtl.GetByRef(ctx, quota.ProjectReference, referenceID)
|
|
|
|
if err != nil {
|
2020-12-14 08:48:52 +01:00
|
|
|
log.Warningf("failed to get quota for project %s, error: %v", projectNameOrID, err)
|
2020-08-15 18:09:06 +02:00
|
|
|
} else {
|
|
|
|
if err := a.quotaCtl.Delete(ctx, q.ID); err != nil {
|
|
|
|
return a.SendError(ctx, fmt.Errorf("failed to delete quota for project: %v", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 11:02:15 +02:00
|
|
|
// preheat policies under the project should be deleted after deleting the project
|
2020-12-14 08:48:52 +01:00
|
|
|
if err = a.preheatCtl.DeletePoliciesOfProject(ctx, p.ProjectID); err != nil {
|
2020-08-31 11:02:15 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-08-15 18:09:06 +02:00
|
|
|
return operation.NewDeleteProjectOK()
|
2020-03-17 04:58:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) GetLogs(ctx context.Context, params operation.GetLogsParams) middleware.Responder {
|
2020-03-19 07:56:37 +01:00
|
|
|
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourceLog); err != nil {
|
2020-03-17 04:58:43 +01:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
2020-08-15 18:09:06 +02:00
|
|
|
pro, err := a.projectCtl.GetByName(ctx, params.ProjectName)
|
2020-03-17 04:58:43 +01:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
2021-03-03 09:31:02 +01:00
|
|
|
query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
2020-03-17 04:58:43 +01:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
query.Keywords["ProjectID"] = pro.ProjectID
|
|
|
|
|
|
|
|
total, err := a.auditMgr.Count(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
logs, err := a.auditMgr.List(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var auditLogs []*models.AuditLog
|
|
|
|
for _, log := range logs {
|
|
|
|
auditLogs = append(auditLogs, &models.AuditLog{
|
|
|
|
ID: log.ID,
|
|
|
|
Resource: log.Resource,
|
|
|
|
ResourceType: log.ResourceType,
|
|
|
|
Username: log.Username,
|
|
|
|
Operation: log.Operation,
|
2020-07-30 11:29:25 +02:00
|
|
|
OpTime: strfmt.DateTime(log.OpTime),
|
2020-03-17 04:58:43 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return operation.NewGetLogsOK().
|
|
|
|
WithXTotalCount(total).
|
|
|
|
WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
|
|
|
WithPayload(auditLogs)
|
|
|
|
}
|
2020-08-15 18:09:06 +02:00
|
|
|
|
|
|
|
func (a *projectAPI) GetProject(ctx context.Context, params operation.GetProjectParams) middleware.Responder {
|
2020-12-14 08:48:52 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
p, err := a.getProject(ctx, projectNameOrID, project.WithCVEAllowlist(), project.WithOwner())
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewGetProjectOK().WithPayload(model.NewProject(p).ToSwagger())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) GetProjectDeletable(ctx context.Context, params operation.GetProjectDeletableParams) middleware.Responder {
|
2020-12-14 08:48:52 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
_, result, err := a.deletable(ctx, projectNameOrID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewGetProjectDeletableOK().WithPayload(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) GetProjectSummary(ctx context.Context, params operation.GetProjectSummaryParams) middleware.Responder {
|
2020-12-14 08:48:52 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
p, err := a.getProject(ctx, projectNameOrID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
summary := &models.ProjectSummary{
|
|
|
|
ChartCount: int64(p.ChartCount),
|
|
|
|
RepoCount: p.RepoCount,
|
|
|
|
}
|
|
|
|
|
|
|
|
var fetchSummaries []func(context.Context, *project.Project, *models.ProjectSummary)
|
|
|
|
|
|
|
|
if hasPerm := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionRead, rbac.ResourceQuota); hasPerm {
|
|
|
|
fetchSummaries = append(fetchSummaries, getProjectQuotaSummary)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasPerm := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionList, rbac.ResourceMember); hasPerm {
|
2021-04-02 08:22:18 +02:00
|
|
|
fetchSummaries = append(fetchSummaries, a.getProjectMemberSummary)
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
|
2020-09-02 17:23:27 +02:00
|
|
|
if p.IsProxy() {
|
2020-08-15 18:09:06 +02:00
|
|
|
fetchSummaries = append(fetchSummaries, getProjectRegistrySummary)
|
|
|
|
}
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, fn := range fetchSummaries {
|
|
|
|
fn := fn
|
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
fn(ctx, p, summary)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
return operation.NewGetProjectSummaryOK().WithPayload(summary)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) HeadProject(ctx context.Context, params operation.HeadProjectParams) middleware.Responder {
|
|
|
|
if err := a.RequireAuthenticated(ctx); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := a.projectCtl.GetByName(ctx, params.ProjectName); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewHeadProjectOK()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) ListProjects(ctx context.Context, params operation.ListProjectsParams) middleware.Responder {
|
2021-03-12 08:04:08 +01:00
|
|
|
query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
2020-08-15 18:09:06 +02:00
|
|
|
|
|
|
|
if name := lib.StringValue(params.Name); name != "" {
|
|
|
|
query.Keywords["name"] = &q.FuzzyMatchValue{Value: name}
|
|
|
|
}
|
|
|
|
if owner := lib.StringValue(params.Owner); owner != "" {
|
|
|
|
query.Keywords["owner"] = owner
|
|
|
|
}
|
|
|
|
if params.Public != nil {
|
|
|
|
query.Keywords["public"] = lib.BoolValue(params.Public)
|
|
|
|
}
|
|
|
|
|
|
|
|
secCtx, ok := security.FromContext(ctx)
|
|
|
|
if ok && secCtx.IsAuthenticated() {
|
2021-01-07 08:45:04 +01:00
|
|
|
if !a.isSysAdmin(ctx, rbac.ActionList) && !secCtx.IsSolutionUser() {
|
2020-08-15 18:09:06 +02:00
|
|
|
// authenticated but not system admin or solution user,
|
|
|
|
// return public projects and projects that the user is member of
|
|
|
|
if l, ok := secCtx.(*local.SecurityContext); ok {
|
|
|
|
currentUser := l.User()
|
|
|
|
member := &project.MemberQuery{
|
2020-09-07 08:35:26 +02:00
|
|
|
UserID: currentUser.UserID,
|
2020-08-15 18:09:06 +02:00
|
|
|
GroupIDs: currentUser.GroupIDs,
|
|
|
|
}
|
|
|
|
|
|
|
|
// not filter by public or filter by the public with true,
|
|
|
|
// so also return public projects for the member
|
|
|
|
if public, ok := query.Keywords["public"]; !ok || lib.ToBool(public) {
|
|
|
|
member.WithPublic = true
|
|
|
|
}
|
|
|
|
|
|
|
|
query.Keywords["member"] = member
|
2021-08-17 10:35:36 +02:00
|
|
|
} else if r, ok := secCtx.(*robotSec.SecurityContext); ok {
|
|
|
|
// for the system level robot that covers all the project, see it as the system admin.
|
|
|
|
var coverAll bool
|
|
|
|
var names []string
|
|
|
|
for _, p := range r.User().Permissions {
|
2021-08-24 03:34:02 +02:00
|
|
|
if p.IsCoverAll() {
|
2021-08-17 10:35:36 +02:00
|
|
|
coverAll = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
names = append(names, p.Namespace)
|
|
|
|
}
|
|
|
|
if !coverAll {
|
|
|
|
namesQuery := &pkgModels.NamesQuery{
|
|
|
|
Names: names,
|
|
|
|
}
|
|
|
|
if public, ok := query.Keywords["public"]; !ok || lib.ToBool(public) {
|
|
|
|
namesQuery.WithPublic = true
|
|
|
|
}
|
|
|
|
query.Keywords["names"] = namesQuery
|
|
|
|
}
|
2020-08-15 18:09:06 +02:00
|
|
|
} else {
|
|
|
|
// can't get the user info, force to return public projects
|
|
|
|
query.Keywords["public"] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if params.Public != nil && !*params.Public {
|
|
|
|
// anonymous want to query private projects return empty projects directly
|
|
|
|
return operation.NewListProjectsOK().WithXTotalCount(0).WithPayload([]*models.Project{})
|
|
|
|
}
|
|
|
|
// force to return public projects for anonymous
|
|
|
|
query.Keywords["public"] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
total, err := a.projectCtl.Count(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if total == 0 {
|
|
|
|
// no projects found for the query return directly
|
|
|
|
return operation.NewListProjectsOK().WithXTotalCount(0).WithPayload([]*models.Project{})
|
|
|
|
}
|
|
|
|
|
2020-11-30 09:13:07 +01:00
|
|
|
projects, err := a.projectCtl.List(ctx, query, project.Detail(lib.BoolValue(params.WithDetail)), project.WithCVEAllowlist(), project.WithOwner())
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, p := range projects {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(p *project.Project) {
|
|
|
|
defer wg.Done()
|
|
|
|
// due to the issue https://github.com/lib/pq/issues/81 of lib/pg or postgres,
|
|
|
|
// simultaneous queries in transaction may failed, so clone a ctx with new ormer here
|
|
|
|
if err := a.populateProperties(orm.Clone(ctx), p); err != nil {
|
|
|
|
log.G(ctx).Errorf("failed to populate propertites for project %s, error: %v", p.Name, err)
|
|
|
|
}
|
|
|
|
}(p)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
var payload []*models.Project
|
|
|
|
for _, p := range projects {
|
|
|
|
payload = append(payload, model.NewProject(p).ToSwagger())
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewListProjectsOK().
|
|
|
|
WithXTotalCount(total).
|
|
|
|
WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
|
|
|
WithPayload(payload)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) UpdateProject(ctx context.Context, params operation.UpdateProjectParams) middleware.Responder {
|
2020-12-14 08:48:52 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate); err != nil {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
p, err := a.projectCtl.Get(ctx, projectNameOrID, project.Metadata(false))
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if params.Project.CVEAllowlist != nil {
|
|
|
|
if params.Project.CVEAllowlist.ProjectID == 0 {
|
|
|
|
// project_id in cve_allowlist not provided or provided as 0, let it to be the id of the project which will be updating
|
2020-12-14 08:48:52 +01:00
|
|
|
params.Project.CVEAllowlist.ProjectID = p.ProjectID
|
|
|
|
} else if params.Project.CVEAllowlist.ProjectID != p.ProjectID {
|
2020-08-15 18:09:06 +02:00
|
|
|
return a.SendError(ctx, errors.BadRequestError(nil).
|
2020-12-14 08:48:52 +01:00
|
|
|
WithMessage("project_id in cve_allowlist must be %d but it's %d", p.ProjectID, params.Project.CVEAllowlist.ProjectID))
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := lib.JSONCopy(&p.CVEAllowlist, params.Project.CVEAllowlist); err != nil {
|
|
|
|
return a.SendError(ctx, errors.UnknownError(nil).WithMessage("failed to process cve_allowlist, error: %v", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-02 17:23:27 +02:00
|
|
|
// ignore enable_content_trust metadata for proxy cache project
|
|
|
|
// see https://github.com/goharbor/harbor/issues/12940 to get more info
|
|
|
|
if params.Project.Metadata != nil && p.IsProxy() {
|
|
|
|
params.Project.Metadata.EnableContentTrust = nil
|
|
|
|
}
|
2020-08-15 18:09:06 +02:00
|
|
|
lib.JSONCopy(&p.Metadata, params.Project.Metadata)
|
|
|
|
|
|
|
|
if err := a.projectCtl.Update(ctx, p); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewUpdateProjectOK()
|
|
|
|
}
|
|
|
|
|
2021-03-03 05:23:36 +01:00
|
|
|
func (a *projectAPI) GetScannerOfProject(ctx context.Context, params operation.GetScannerOfProjectParams) middleware.Responder {
|
2021-05-14 05:27:23 +02:00
|
|
|
if err := a.RequireAuthenticated(ctx); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2021-03-03 05:23:36 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceScanner); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := a.projectCtl.Get(ctx, projectNameOrID, project.Metadata(false))
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
scanner, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(scanner).ToSwagger(ctx))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) ListScannerCandidatesOfProject(ctx context.Context, params operation.ListScannerCandidatesOfProjectParams) middleware.Responder {
|
2021-05-14 05:27:23 +02:00
|
|
|
if err := a.RequireAuthenticated(ctx); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2021-03-03 05:23:36 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceScanner); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2021-03-03 09:31:02 +01:00
|
|
|
query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
2021-03-03 05:23:36 +01:00
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
total, err := a.scannerCtl.GetTotalOfRegistrations(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
scanners, err := a.scannerCtl.ListRegistrations(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
payload := make([]*models.ScannerRegistration, len(scanners))
|
|
|
|
for i, scanner := range scanners {
|
|
|
|
payload[i] = model.NewScannerRegistration(scanner).ToSwagger(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewListScannerCandidatesOfProjectOK().
|
|
|
|
WithXTotalCount(total).
|
|
|
|
WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
|
|
|
WithPayload(payload)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) SetScannerOfProject(ctx context.Context, params operation.SetScannerOfProjectParams) middleware.Responder {
|
2021-05-14 05:27:23 +02:00
|
|
|
if err := a.RequireAuthenticated(ctx); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
2021-03-03 05:23:36 +01:00
|
|
|
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
|
|
|
if err := a.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceScanner); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := a.projectCtl.Get(ctx, projectNameOrID, project.Metadata(false))
|
|
|
|
if err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.scannerCtl.SetRegistrationByProject(ctx, p.ProjectID, *params.Payload.UUID); err != nil {
|
|
|
|
return a.SendError(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return operation.NewSetScannerOfProjectOK()
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
func (a *projectAPI) deletable(ctx context.Context, projectNameOrID interface{}) (*project.Project, *models.ProjectDeletable, error) {
|
|
|
|
p, err := a.getProject(ctx, projectNameOrID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
2020-12-14 08:48:52 +01:00
|
|
|
return nil, nil, err
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result := &models.ProjectDeletable{Deletable: true}
|
2020-12-14 08:48:52 +01:00
|
|
|
if p.RepoCount > 0 {
|
2020-08-15 18:09:06 +02:00
|
|
|
result.Deletable = false
|
|
|
|
result.Message = "the project contains repositories, can not be deleted"
|
2020-12-14 08:48:52 +01:00
|
|
|
} else if p.ChartCount > 0 {
|
2020-08-15 18:09:06 +02:00
|
|
|
result.Deletable = false
|
|
|
|
result.Message = "the project contains helm charts, can not be deleted"
|
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
return p, result, nil
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
|
2020-12-14 08:48:52 +01:00
|
|
|
func (a *projectAPI) getProject(ctx context.Context, projectNameOrID interface{}, options ...project.Option) (*project.Project, error) {
|
|
|
|
p, err := a.projectCtl.Get(ctx, projectNameOrID, options...)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.populateProperties(ctx, p); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) validateProjectReq(ctx context.Context, req *models.ProjectReq) error {
|
|
|
|
if req.RegistryID != nil {
|
|
|
|
if *req.RegistryID <= 0 {
|
|
|
|
return errors.BadRequestError(fmt.Errorf("%d is invalid value of registry_id, it should be geater than 0", *req.RegistryID))
|
|
|
|
}
|
|
|
|
|
2021-03-31 09:49:23 +02:00
|
|
|
registry, err := registry.Ctl.Get(ctx, *req.RegistryID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get the registry %d: %v", *req.RegistryID, err)
|
|
|
|
}
|
|
|
|
permitted := false
|
|
|
|
for _, t := range config.GetPermittedRegistryTypesForProxyCache() {
|
|
|
|
if string(registry.Type) == t {
|
|
|
|
permitted = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !permitted {
|
|
|
|
return errors.BadRequestError(fmt.Errorf("unsupported registry type %s", string(registry.Type)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.StorageLimit != nil {
|
|
|
|
hardLimits := types.ResourceList{types.ResourceStorage: *req.StorageLimit}
|
|
|
|
if err := quota.Validate(ctx, quota.ProjectReference, hardLimits); err != nil {
|
|
|
|
return errors.BadRequestError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *projectAPI) populateProperties(ctx context.Context, p *project.Project) error {
|
|
|
|
if secCtx, ok := security.FromContext(ctx); ok {
|
|
|
|
if sc, ok := secCtx.(*local.SecurityContext); ok {
|
2021-03-12 08:04:08 +01:00
|
|
|
roles, err := a.projectCtl.ListRoles(ctx, p.ProjectID, sc.User())
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
p.RoleList = roles
|
|
|
|
p.Role = highestRole(roles)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
total, err := a.repositoryCtl.Count(ctx, q.New(q.KeyWords{"project_id": p.ProjectID}))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
p.RepoCount = total
|
|
|
|
|
|
|
|
// Populate chart count property
|
|
|
|
if config.WithChartMuseum() {
|
|
|
|
count, err := api.GetChartController().GetCountOfCharts([]string{p.Name})
|
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrap(err, fmt.Sprintf("get chart count of project %d failed", p.ProjectID))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
p.ChartCount = count
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-07 08:45:04 +01:00
|
|
|
func (a *projectAPI) isSysAdmin(ctx context.Context, action rbac.Action) bool {
|
|
|
|
if err := a.RequireSystemAccess(ctx, action, rbac.ResourceProject); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-08-15 18:09:06 +02:00
|
|
|
func getProjectQuotaSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
2021-03-11 13:25:51 +01:00
|
|
|
if !config.QuotaPerProjectEnable(ctx) {
|
2020-08-15 18:09:06 +02:00
|
|
|
log.Debug("Quota per project disabled")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
q, err := quota.Ctl.GetByRef(ctx, quota.ProjectReference, quota.ReferenceID(p.ProjectID))
|
|
|
|
if err != nil {
|
|
|
|
log.Warningf("failed to get quota for project: %d", p.ProjectID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
summary.Quota = &models.ProjectSummaryQuota{}
|
|
|
|
if hard, err := q.GetHard(); err == nil {
|
2021-11-04 16:39:36 +01:00
|
|
|
summary.Quota.Hard = model.NewResourceList(hard).ToSwagger()
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
if used, err := q.GetUsed(); err == nil {
|
2021-11-04 16:39:36 +01:00
|
|
|
summary.Quota.Used = model.NewResourceList(used).ToSwagger()
|
2020-08-15 18:09:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 08:22:18 +02:00
|
|
|
func (a *projectAPI) getProjectMemberSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
2020-08-15 18:09:06 +02:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
for _, e := range []struct {
|
|
|
|
role int
|
|
|
|
count *int64
|
|
|
|
}{
|
|
|
|
{common.RoleProjectAdmin, &summary.ProjectAdminCount},
|
|
|
|
{common.RoleMaintainer, &summary.MaintainerCount},
|
|
|
|
{common.RoleDeveloper, &summary.DeveloperCount},
|
|
|
|
{common.RoleGuest, &summary.GuestCount},
|
|
|
|
{common.RoleLimitedGuest, &summary.LimitedGuestCount},
|
|
|
|
} {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(role int, count *int64) {
|
|
|
|
defer wg.Done()
|
2021-04-02 08:22:18 +02:00
|
|
|
total, err := a.memberMgr.GetTotalOfProjectMembers(orm.Clone(ctx), p.ProjectID, nil, role)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Warningf("failed to get total of project members of role %d", role)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-02 08:22:18 +02:00
|
|
|
*count = int64(total)
|
2020-08-15 18:09:06 +02:00
|
|
|
}(e.role, e.count)
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getProjectRegistrySummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
|
|
|
if p.RegistryID <= 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-31 09:49:23 +02:00
|
|
|
registry, err := registry.Ctl.Get(ctx, p.RegistryID)
|
2020-08-15 18:09:06 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Warningf("failed to get registry %d: %v", p.RegistryID, err)
|
|
|
|
} else if registry != nil {
|
|
|
|
registry.Credential = nil
|
|
|
|
lib.JSONCopy(&summary.Registry, registry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the highest role in the role list.
|
|
|
|
// This func should be removed once we deprecate the "current_user_role_id" in project API
|
|
|
|
// A user can have multiple roles and they may not have a strict ranking relationship
|
|
|
|
func highestRole(roles []int) int {
|
|
|
|
if roles == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
rolePower := map[int]int{
|
|
|
|
common.RoleProjectAdmin: 50,
|
|
|
|
common.RoleMaintainer: 40,
|
|
|
|
common.RoleDeveloper: 30,
|
|
|
|
common.RoleGuest: 20,
|
|
|
|
common.RoleLimitedGuest: 10,
|
|
|
|
}
|
|
|
|
var highest, highestPower int
|
|
|
|
for _, role := range roles {
|
|
|
|
if p, ok := rolePower[role]; ok && p > highestPower {
|
|
|
|
highest = role
|
|
|
|
highestPower = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return highest
|
|
|
|
}
|