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:
Daniel Jiang 2017-07-27 10:29:03 +03:00 committed by GitHub
commit 8117e9ee79
5 changed files with 125 additions and 45 deletions

View File

@ -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

View File

@ -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 ...

View File

@ -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)
}

View File

@ -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 {

View File

@ -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")