mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01:00
Merge pull request #2884 from ywk253100/170726_api
Add API to check whether a project can be deleted or not
This commit is contained in:
commit
8117e9ee79
@ -25,6 +25,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
@ -99,6 +100,7 @@ func init() {
|
||||
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")
|
||||
beego.Router("/api/repositories", &RepositoryAPI{})
|
||||
beego.Router("/api/statistics", &StatisticAPI{})
|
||||
@ -384,6 +386,30 @@ func (a testapi) ProjectLogs(prjUsr usrInfo, projectID string, query *apilib.Log
|
||||
return request(_sling, jsonAcceptHeader, prjUsr)
|
||||
}
|
||||
|
||||
// ProjectDeletable check whether a project can be deleted
|
||||
func (a testapi) ProjectDeletable(prjUsr usrInfo, projectID int64) (int, bool, error) {
|
||||
_sling := sling.New().Get(a.basePath).
|
||||
Path("/api/projects/" + strconv.FormatInt(projectID, 10) + "/_deletable")
|
||||
|
||||
code, body, err := request(_sling, jsonAcceptHeader, prjUsr)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
if code != http.StatusOK {
|
||||
return code, false, nil
|
||||
}
|
||||
|
||||
deletable := struct {
|
||||
Deletable bool `json:"deletable"`
|
||||
}{}
|
||||
if err = json.Unmarshal(body, &deletable); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
return code, deletable.Deletable, nil
|
||||
}
|
||||
|
||||
//-------------------------Member Test---------------------------------------//
|
||||
|
||||
//Return relevant role members of projectID
|
||||
|
@ -30,6 +30,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type deletableResp struct {
|
||||
Deletable bool `json:"deletable"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
||||
type ProjectAPI struct {
|
||||
BaseController
|
||||
@ -202,22 +207,14 @@ func (p *ProjectAPI) Delete() {
|
||||
return
|
||||
}
|
||||
|
||||
contains, err := projectContainsRepo(p.project.Name)
|
||||
result, err := deletable(p.project.ProjectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any repository: %v", p.project.Name, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
p.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
||||
return
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains repositores, can not be deleted")
|
||||
}
|
||||
|
||||
contains, err = projectContainsPolicy(p.project.ProjectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check whether project %s contains any policy: %v", p.project.Name, err)
|
||||
p.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
if contains {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains policies, can not be deleted")
|
||||
if !result.Deletable {
|
||||
p.CustomAbort(http.StatusPreconditionFailed, result.Message)
|
||||
}
|
||||
|
||||
if err = p.ProjectMgr.Delete(p.project.ProjectID); err != nil {
|
||||
@ -239,22 +236,57 @@ func (p *ProjectAPI) Delete() {
|
||||
}()
|
||||
}
|
||||
|
||||
func projectContainsRepo(name string) (bool, error) {
|
||||
repositories, err := getReposByProject(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// Deletable ...
|
||||
func (p *ProjectAPI) Deletable() {
|
||||
if !p.SecurityCtx.IsAuthenticated() {
|
||||
p.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
return len(repositories) > 0, nil
|
||||
if !p.SecurityCtx.HasAllPerm(p.project.ProjectID) {
|
||||
p.HandleForbidden(p.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := deletable(p.project.ProjectID)
|
||||
if err != nil {
|
||||
p.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
p.Data["json"] = result
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
func projectContainsPolicy(id int64) (bool, error) {
|
||||
policies, err := dao.GetRepPolicyByProject(id)
|
||||
func deletable(projectID int64) (*deletableResp, error) {
|
||||
count, err := dao.GetTotalOfRepositoriesByProject([]int64{projectID}, "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return len(policies) > 0, nil
|
||||
if count > 0 {
|
||||
return &deletableResp{
|
||||
Deletable: false,
|
||||
Message: "the project contains repositories, can not be deleled",
|
||||
}, nil
|
||||
}
|
||||
|
||||
policies, err := dao.GetRepPolicyByProject(projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(policies) > 0 {
|
||||
return &deletableResp{
|
||||
Deletable: false,
|
||||
Message: "the project contains replication rules, can not be deleled",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &deletableResp{
|
||||
Deletable: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List ...
|
||||
|
@ -15,11 +15,15 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||
)
|
||||
|
||||
@ -361,3 +365,42 @@ func TestProjectLogsFilter(t *testing.T) {
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func TestDeletable(t *testing.T) {
|
||||
apiTest := newHarborAPI()
|
||||
|
||||
project := models.Project{
|
||||
Name: "project_for_test_deletable",
|
||||
OwnerID: 1,
|
||||
}
|
||||
id, err := dao.AddProject(project)
|
||||
require.Nil(t, err)
|
||||
|
||||
// non-exist project
|
||||
code, del, err := apiTest.ProjectDeletable(*admin, 1000)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, code)
|
||||
|
||||
// unauthorized
|
||||
code, del, err = apiTest.ProjectDeletable(*unknownUsr, id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusUnauthorized, code)
|
||||
|
||||
// can be deleted
|
||||
code, del, err = apiTest.ProjectDeletable(*admin, id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, code)
|
||||
assert.True(t, del)
|
||||
|
||||
err = dao.AddRepository(models.RepoRecord{
|
||||
Name: project.Name + "/golang",
|
||||
ProjectID: id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// can not be deleted as contains repository
|
||||
code, del, err = apiTest.ProjectDeletable(*admin, id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, code)
|
||||
assert.False(t, del)
|
||||
}
|
||||
|
@ -416,28 +416,6 @@ func buildReplicationActionURL() string {
|
||||
return fmt.Sprintf("%s/api/jobs/replication/actions", url)
|
||||
}
|
||||
|
||||
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
||||
repositories := []string{}
|
||||
|
||||
repos, err := dao.GetRepositoryByProjectName(name)
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
needMatchKeyword := len(keyword) > 0 && len(keyword[0]) != 0
|
||||
|
||||
for _, repo := range repos {
|
||||
if needMatchKeyword &&
|
||||
!strings.Contains(repo.Name, keyword[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
repositories = append(repositories, repo.Name)
|
||||
}
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
||||
tags, err := client.ListTag()
|
||||
if err != nil {
|
||||
|
@ -85,6 +85,7 @@ func initRouters() {
|
||||
beego.Router("/api/search", &api.SearchAPI{})
|
||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post")
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs", &api.ProjectAPI{}, "get:Logs")
|
||||
beego.Router("/api/projects/:id([0-9]+)/_deletable", &api.ProjectAPI{}, "get:Deletable")
|
||||
beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
|
||||
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
||||
beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll")
|
||||
|
Loading…
Reference in New Issue
Block a user