mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge pull request #5524 from steven-zou/pro_deletable_enable
Enhance the project deletable checking logic to include helm charts checking
This commit is contained in:
commit
bc6d7b69e7
@ -36,6 +36,9 @@ type Controller struct {
|
|||||||
|
|
||||||
//To cover all the manipulation requests
|
//To cover all the manipulation requests
|
||||||
manipulationHandler *ManipulationHandler
|
manipulationHandler *ManipulationHandler
|
||||||
|
|
||||||
|
//To cover the other utility requests
|
||||||
|
utilityHandler *UtilityHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewController is constructor of the chartserver.Controller
|
//NewController is constructor of the chartserver.Controller
|
||||||
@ -86,6 +89,11 @@ func NewController(backendServer *url.URL) (*Controller, error) {
|
|||||||
backendServerAddress: backendServer,
|
backendServerAddress: backendServer,
|
||||||
chartCache: cache,
|
chartCache: cache,
|
||||||
},
|
},
|
||||||
|
utilityHandler: &UtilityHandler{
|
||||||
|
apiClient: client,
|
||||||
|
backendServerAddress: backendServer,
|
||||||
|
chartOperator: operator,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +112,11 @@ func (c *Controller) GetManipulationHandler() *ManipulationHandler {
|
|||||||
return c.manipulationHandler
|
return c.manipulationHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetUtilityHandler returns the reference of UtilityHandler
|
||||||
|
func (c *Controller) GetUtilityHandler() *UtilityHandler {
|
||||||
|
return c.utilityHandler
|
||||||
|
}
|
||||||
|
|
||||||
//What's the cache driver if it is set
|
//What's the cache driver if it is set
|
||||||
func parseCacheDriver() (string, bool) {
|
func parseCacheDriver() (string, bool) {
|
||||||
driver, ok := os.LookupEnv(cacheDriverENVKey)
|
driver, ok := os.LookupEnv(cacheDriverENVKey)
|
||||||
|
37
src/chartserver/utility_handler.go
Normal file
37
src/chartserver/utility_handler.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package chartserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//UtilityHandler provides utility methods
|
||||||
|
type UtilityHandler struct {
|
||||||
|
//Parse and process the chart version to provide required info data
|
||||||
|
chartOperator *ChartOperator
|
||||||
|
|
||||||
|
//HTTP client used to call the realted APIs of the backend chart repositories
|
||||||
|
apiClient *ChartClient
|
||||||
|
|
||||||
|
//Point to the url of the backend server
|
||||||
|
backendServerAddress *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetChartsByNs gets the chart list under the namespace
|
||||||
|
func (uh *UtilityHandler) GetChartsByNs(namespace string) ([]*ChartInfo, error) {
|
||||||
|
if len(strings.TrimSpace(namespace)) == 0 {
|
||||||
|
return nil, errors.New("empty namespace when getting chart list")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/api/%s/charts", namespace)
|
||||||
|
url := fmt.Sprintf("%s%s", uh.backendServerAddress.String(), path)
|
||||||
|
|
||||||
|
content, err := uh.apiClient.GetContent(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uh.chartOperator.GetChartList(content)
|
||||||
|
}
|
44
src/chartserver/utility_handler_test.go
Normal file
44
src/chartserver/utility_handler_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package chartserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//TestGetChartsByNs tests GetChartsByNs method in UtilityHandler
|
||||||
|
func TestGetChartsByNs(t *testing.T) {
|
||||||
|
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/api/repo1/charts":
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
w.Write(chartListContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
w.Write([]byte("not supported"))
|
||||||
|
}))
|
||||||
|
defer mockServer.Close()
|
||||||
|
|
||||||
|
serverURL, err := url.Parse(mockServer.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
theController, err := NewController(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
charts, err := theController.GetUtilityHandler().GetChartsByNs("repo1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(charts) != 2 {
|
||||||
|
t.Fatalf("expect 2 items but got %d", len(charts))
|
||||||
|
}
|
||||||
|
}
|
@ -217,7 +217,7 @@ func (p *ProjectAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := deletable(p.project.ProjectID)
|
result, err := p.deletable(p.project.ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.HandleInternalServerError(fmt.Sprintf(
|
p.HandleInternalServerError(fmt.Sprintf(
|
||||||
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
||||||
@ -258,7 +258,7 @@ func (p *ProjectAPI) Deletable() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := deletable(p.project.ProjectID)
|
result, err := p.deletable(p.project.ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.HandleInternalServerError(fmt.Sprintf(
|
p.HandleInternalServerError(fmt.Sprintf(
|
||||||
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
|
||||||
@ -269,7 +269,7 @@ func (p *ProjectAPI) Deletable() {
|
|||||||
p.ServeJSON()
|
p.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletable(projectID int64) (*deletableResp, error) {
|
func (p *ProjectAPI) deletable(projectID int64) (*deletableResp, error) {
|
||||||
count, err := dao.GetTotalOfRepositories(&models.RepositoryQuery{
|
count, err := dao.GetTotalOfRepositories(&models.RepositoryQuery{
|
||||||
ProjectIDs: []int64{projectID},
|
ProjectIDs: []int64{projectID},
|
||||||
})
|
})
|
||||||
@ -280,7 +280,7 @@ func deletable(projectID int64) (*deletableResp, error) {
|
|||||||
if count > 0 {
|
if count > 0 {
|
||||||
return &deletableResp{
|
return &deletableResp{
|
||||||
Deletable: false,
|
Deletable: false,
|
||||||
Message: "the project contains repositories, can not be deleled",
|
Message: "the project contains repositories, can not be deleted",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,10 +292,25 @@ func deletable(projectID int64) (*deletableResp, error) {
|
|||||||
if len(policies) > 0 {
|
if len(policies) > 0 {
|
||||||
return &deletableResp{
|
return &deletableResp{
|
||||||
Deletable: false,
|
Deletable: false,
|
||||||
Message: "the project contains replication rules, can not be deleled",
|
Message: "the project contains replication rules, can not be deleted",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check helm charts number
|
||||||
|
if config.WithChartMuseum() {
|
||||||
|
charts, err := chartController.GetUtilityHandler().GetChartsByNs(p.project.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(charts) > 0 {
|
||||||
|
return &deletableResp{
|
||||||
|
Deletable: false,
|
||||||
|
Message: "the project contains helm charts, can not be deleted",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &deletableResp{
|
return &deletableResp{
|
||||||
Deletable: true,
|
Deletable: true,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -16,10 +16,14 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/chartserver"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
@ -362,6 +366,13 @@ func TestProjectLogsFilter(t *testing.T) {
|
|||||||
|
|
||||||
func TestDeletable(t *testing.T) {
|
func TestDeletable(t *testing.T) {
|
||||||
apiTest := newHarborAPI()
|
apiTest := newHarborAPI()
|
||||||
|
chServer, oldController, err := mockChartController()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, chServer)
|
||||||
|
defer chServer.Close()
|
||||||
|
defer func() {
|
||||||
|
chartController = oldController
|
||||||
|
}()
|
||||||
|
|
||||||
project := models.Project{
|
project := models.Project{
|
||||||
Name: "project_for_test_deletable",
|
Name: "project_for_test_deletable",
|
||||||
@ -398,3 +409,36 @@ func TestDeletable(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, code)
|
assert.Equal(t, http.StatusOK, code)
|
||||||
assert.False(t, del)
|
assert.False(t, del)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Provides a mock chart controller for deletable test cases
|
||||||
|
func mockChartController() (*httptest.Server, *chartserver.Controller, error) {
|
||||||
|
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/api/project_for_test_deletable/charts":
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
w.Write([]byte("{}"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
w.Write([]byte("not supported"))
|
||||||
|
}))
|
||||||
|
|
||||||
|
var oldController, newController *chartserver.Controller
|
||||||
|
url, err := url.Parse(mockServer.URL)
|
||||||
|
if err == nil {
|
||||||
|
newController, err = chartserver.NewController(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
mockServer.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Override current controller and keep the old one for restoring
|
||||||
|
oldController = chartController
|
||||||
|
chartController = newController
|
||||||
|
|
||||||
|
return mockServer, oldController, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user