mirror of
https://github.com/goharbor/harbor.git
synced 2025-04-09 05:26:35 +02:00
Add API to check whether a project can be deleted or not
This commit is contained in:
parent
8612a9ac84
commit
9d7ad6de68
@ -25,6 +25,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"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/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic")
|
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]+)/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/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get;post:Post;delete:Delete;put:Put")
|
||||||
beego.Router("/api/repositories", &RepositoryAPI{})
|
beego.Router("/api/repositories", &RepositoryAPI{})
|
||||||
beego.Router("/api/statistics", &StatisticAPI{})
|
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)
|
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---------------------------------------//
|
//-------------------------Member Test---------------------------------------//
|
||||||
|
|
||||||
//Return relevant role members of projectID
|
//Return relevant role members of projectID
|
||||||
|
@ -30,6 +30,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type deletableResp struct {
|
||||||
|
Deletable bool `json:"deletable"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
|
||||||
type ProjectAPI struct {
|
type ProjectAPI struct {
|
||||||
BaseController
|
BaseController
|
||||||
@ -202,22 +207,14 @@ func (p *ProjectAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contains, err := projectContainsRepo(p.project.Name)
|
result, err := deletable(p.project.ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to check whether project %s contains any repository: %v", p.project.Name, err)
|
p.HandleInternalServerError(fmt.Sprintf(
|
||||||
p.CustomAbort(http.StatusInternalServerError, "")
|
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if contains {
|
if !result.Deletable {
|
||||||
p.CustomAbort(http.StatusPreconditionFailed, "project contains repositores, can not be deleted")
|
p.CustomAbort(http.StatusPreconditionFailed, result.Message)
|
||||||
}
|
|
||||||
|
|
||||||
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 err = p.ProjectMgr.Delete(p.project.ProjectID); err != nil {
|
if err = p.ProjectMgr.Delete(p.project.ProjectID); err != nil {
|
||||||
@ -239,22 +236,57 @@ func (p *ProjectAPI) Delete() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func projectContainsRepo(name string) (bool, error) {
|
// Deletable ...
|
||||||
repositories, err := getReposByProject(name)
|
func (p *ProjectAPI) Deletable() {
|
||||||
if err != nil {
|
if !p.SecurityCtx.IsAuthenticated() {
|
||||||
return false, err
|
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) {
|
func deletable(projectID int64) (*deletableResp, error) {
|
||||||
policies, err := dao.GetRepPolicyByProject(id)
|
count, err := dao.GetTotalOfRepositoriesByProject([]int64{projectID}, "")
|
||||||
if err != nil {
|
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 ...
|
// List ...
|
||||||
|
@ -15,11 +15,15 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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"
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -361,3 +365,42 @@ func TestProjectLogsFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
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)
|
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) {
|
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
||||||
tags, err := client.ListTag()
|
tags, err := client.ListTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,6 +85,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/search", &api.SearchAPI{})
|
beego.Router("/api/search", &api.SearchAPI{})
|
||||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post")
|
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]+)/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/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
|
||||||
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
||||||
beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll")
|
beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll")
|
||||||
|
Loading…
Reference in New Issue
Block a user