mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Add API to support marking labels to charts
- add related chart label API entries - extract label related functionalities to a separate manager interface - add a base controller for label related actions - add related UT cases Signed-off-by: Steven Zou <szou@vmware.com>
This commit is contained in:
parent
abf67c8de0
commit
7b8fe27c22
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
helm_repo "k8s.io/helm/pkg/repo"
|
||||
)
|
||||
|
||||
@ -131,6 +132,28 @@ func (uh *UtilityHandler) DeleteChart(namespace, chartName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetChartVersion returns the summary of the specified chart version.
|
||||
func (uh *UtilityHandler) GetChartVersion(namespace, name, version string) (*helm_repo.ChartVersion, error) {
|
||||
if len(namespace) == 0 || len(name) == 0 || len(version) == 0 {
|
||||
return nil, errors.New("bad arguments to get chart version summary")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/%s/charts/%s/%s", namespace, name, version)
|
||||
url := fmt.Sprintf("%s%s", uh.backendServerAddress.String(), path)
|
||||
|
||||
content, err := uh.apiClient.GetContent(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chartVersion := &helm_repo.ChartVersion{}
|
||||
if err := yaml.Unmarshal(content, chartVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chartVersion, nil
|
||||
}
|
||||
|
||||
// deleteChartVersion deletes the specified chart version
|
||||
func (uh *UtilityHandler) deleteChartVersion(namespace, chartName, version string) error {
|
||||
path := fmt.Sprintf("/api/%s/charts/%s/%s", namespace, chartName, version)
|
||||
|
@ -80,6 +80,82 @@ func TestDeleteChart(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test the GetChartVersion in utility handler
|
||||
func TestGetChartVersionSummary(t *testing.T) {
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.RequestURI {
|
||||
case "/api/repo1/charts/harbor/0.2.0":
|
||||
if r.Method == http.MethodGet {
|
||||
w.Write([]byte(chartVersionOfHarbor020))
|
||||
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)
|
||||
}
|
||||
|
||||
chartV, err := theController.GetUtilityHandler().GetChartVersion("repo1", "harbor", "0.2.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if chartV.GetName() != "harbor" {
|
||||
t.Fatalf("expect chart name 'harbor' but got '%s'", chartV.GetName())
|
||||
}
|
||||
|
||||
if chartV.GetVersion() != "0.2.0" {
|
||||
t.Fatalf("expect chart version '0.2.0' but got '%s'", chartV.GetVersion())
|
||||
}
|
||||
}
|
||||
|
||||
var chartVersionOfHarbor020 = `
|
||||
{
|
||||
"name": "harbor",
|
||||
"home": "https://github.com/vmware/harbor",
|
||||
"sources": [
|
||||
"https://github.com/vmware/harbor/tree/master/contrib/helm/harbor"
|
||||
],
|
||||
"version": "0.2.0",
|
||||
"description": "An Enterprise-class Docker Registry by VMware",
|
||||
"keywords": [
|
||||
"vmware",
|
||||
"docker",
|
||||
"registry",
|
||||
"harbor"
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Jesse Hu",
|
||||
"email": "huh@vmware.com"
|
||||
},
|
||||
{
|
||||
"name": "paulczar",
|
||||
"email": "username.taken@gmail.com"
|
||||
}
|
||||
],
|
||||
"engine": "gotpl",
|
||||
"icon": "https://raw.githubusercontent.com/vmware/harbor/master/docs/img/harbor_logo.png",
|
||||
"appVersion": "1.5.0",
|
||||
"urls": [
|
||||
"charts/harbor-0.2.0.tgz"
|
||||
],
|
||||
"created": "2018-08-29T10:26:21.141611102Z",
|
||||
"digest": "fc8aae8dade9f0dfca12e9f1085081c49843d30a063a3fa7eb42497e3ceb277c"
|
||||
}
|
||||
`
|
||||
|
||||
var chartVersionsOfHarbor = `
|
||||
[
|
||||
{
|
||||
|
@ -37,6 +37,7 @@ const (
|
||||
ResourceTypeProject = "p"
|
||||
ResourceTypeRepository = "r"
|
||||
ResourceTypeImage = "i"
|
||||
ResourceTypeChart = "c"
|
||||
|
||||
ExtEndpoint = "ext_endpoint"
|
||||
AUTHMode = "auth_mode"
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/ui/config"
|
||||
"github.com/goharbor/harbor/src/ui/filter"
|
||||
"github.com/goharbor/harbor/src/ui/promgr"
|
||||
"github.com/goharbor/harbor/src/ui/utils"
|
||||
)
|
||||
|
||||
// BaseController ...
|
||||
@ -60,6 +61,43 @@ func (b *BaseController) Prepare() {
|
||||
b.ProjectMgr = pm
|
||||
}
|
||||
|
||||
// RenderFormatedError renders errors with well formted style `{"error": "This is an error"}`
|
||||
func (b *BaseController) RenderFormatedError(code int, err error) {
|
||||
formatedErr := utils.WrapError(err)
|
||||
log.Errorf("%s %s failed with error: %s", b.Ctx.Request.Method, b.Ctx.Request.URL.String(), formatedErr.Error())
|
||||
b.RenderError(code, formatedErr.Error())
|
||||
}
|
||||
|
||||
// SendUnAuthorizedError sends unauthorized error to the client.
|
||||
func (b *BaseController) SendUnAuthorizedError(err error) {
|
||||
b.RenderFormatedError(http.StatusUnauthorized, err)
|
||||
}
|
||||
|
||||
// SendConflictError sends conflict error to the client.
|
||||
func (b *BaseController) SendConflictError(err error) {
|
||||
b.RenderFormatedError(http.StatusConflict, err)
|
||||
}
|
||||
|
||||
// SendNotFoundError sends not found error to the client.
|
||||
func (b *BaseController) SendNotFoundError(err error) {
|
||||
b.RenderFormatedError(http.StatusNotFound, err)
|
||||
}
|
||||
|
||||
// SendBadRequestError sends bad request error to the client.
|
||||
func (b *BaseController) SendBadRequestError(err error) {
|
||||
b.RenderFormatedError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
// SendInternalServerError sends internal server error to the client.
|
||||
func (b *BaseController) SendInternalServerError(err error) {
|
||||
b.RenderFormatedError(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
// SendForbiddenError sends forbidden error to the client.
|
||||
func (b *BaseController) SendForbiddenError(err error) {
|
||||
b.RenderFormatedError(http.StatusForbidden, err)
|
||||
}
|
||||
|
||||
// Init related objects/configurations for the API controllers
|
||||
func Init() error {
|
||||
// If chart repository is not enabled then directly return
|
||||
|
103
src/ui/api/chart_label.go
Normal file
103
src/ui/api/chart_label.go
Normal file
@ -0,0 +1,103 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
)
|
||||
|
||||
const (
|
||||
versionParam = ":version"
|
||||
idParam = ":id"
|
||||
)
|
||||
|
||||
// ChartLabelAPI handles the requests of marking/removing lables to/from charts.
|
||||
type ChartLabelAPI struct {
|
||||
LabelResourceAPI
|
||||
project *models.Project
|
||||
chartFullName string
|
||||
}
|
||||
|
||||
// Prepare required material for follow-up actions.
|
||||
func (cla *ChartLabelAPI) Prepare() {
|
||||
// Super
|
||||
cla.LabelResourceAPI.Prepare()
|
||||
|
||||
// Check authorization
|
||||
if !cla.SecurityCtx.IsAuthenticated() {
|
||||
cla.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
project := cla.GetStringFromPath(namespaceParam)
|
||||
|
||||
// Project should be a valid existing one
|
||||
existingProject, err := cla.ProjectMgr.Get(project)
|
||||
if err != nil {
|
||||
cla.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
if existingProject == nil {
|
||||
cla.SendNotFoundError(fmt.Errorf("project '%s' not found", project))
|
||||
return
|
||||
}
|
||||
cla.project = existingProject
|
||||
|
||||
// Check permission
|
||||
if !cla.checkPermissions(project) {
|
||||
cla.SendForbiddenError(errors.New(cla.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
|
||||
// Check the existence of target chart
|
||||
chartName := cla.GetStringFromPath(nameParam)
|
||||
version := cla.GetStringFromPath(versionParam)
|
||||
|
||||
if _, err = chartController.GetUtilityHandler().GetChartVersion(project, chartName, version); err != nil {
|
||||
cla.SendNotFoundError(err)
|
||||
return
|
||||
}
|
||||
cla.chartFullName = fmt.Sprintf("%s/%s:%s", project, chartName, version)
|
||||
}
|
||||
|
||||
// MarkLabel handles the request of marking label to chart.
|
||||
func (cla *ChartLabelAPI) MarkLabel() {
|
||||
l := &models.Label{}
|
||||
cla.DecodeJSONReq(l)
|
||||
|
||||
label, ok := cla.validate(l.ID, cla.project.ProjectID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
label2Res := &models.ResourceLabel{
|
||||
LabelID: label.ID,
|
||||
ResourceType: common.ResourceTypeChart,
|
||||
ResourceName: cla.chartFullName,
|
||||
}
|
||||
|
||||
cla.markLabelToResource(label2Res)
|
||||
}
|
||||
|
||||
// RemoveLabel handles the request of removing label from chart.
|
||||
func (cla *ChartLabelAPI) RemoveLabel() {
|
||||
lID, err := cla.GetInt64FromPath(idParam)
|
||||
if err != nil {
|
||||
cla.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
label, ok := cla.exists(lID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
cla.removeLabelFromResource(common.ResourceTypeChart, cla.chartFullName, label.ID)
|
||||
}
|
||||
|
||||
// GetLabels gets labels for the specified chart version.
|
||||
func (cla *ChartLabelAPI) GetLabels() {
|
||||
cla.getLabelsOfResource(common.ResourceTypeChart, cla.chartFullName)
|
||||
}
|
220
src/ui/api/chart_label_test.go
Normal file
220
src/ui/api/chart_label_test.go
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/chartserver"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
resourceLabelAPIPath = "/api/chartrepo/library/charts/harbor/0.2.0/labels"
|
||||
resourceLabelAPIPathWithFakeProject = "/api/chartrepo/not-exist/charts/harbor/0.2.0/labels"
|
||||
resourceLabelAPIPathWithFakeChart = "/api/chartrepo/library/charts/not-exist/0.2.0/labels"
|
||||
cProLibraryLabelID int64
|
||||
mockChartServer *httptest.Server
|
||||
oldChartController *chartserver.Controller
|
||||
)
|
||||
|
||||
func TestToStartMockChartService(t *testing.T) {
|
||||
var err error
|
||||
mockChartServer, oldChartController, err = mockChartController()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start the mock chart service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddToChart(t *testing.T) {
|
||||
cSysLevelLabelID, err := dao.AddLabel(&models.Label{
|
||||
Name: "c_sys_level_label",
|
||||
Level: common.LabelLevelSystem,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer dao.DeleteLabel(cSysLevelLabelID)
|
||||
|
||||
cProTestLabelID, err := dao.AddLabel(&models.Label{
|
||||
Name: "c_pro_test_label",
|
||||
Level: common.LabelLevelUser,
|
||||
Scope: common.LabelScopeProject,
|
||||
ProjectID: 100,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer dao.DeleteLabel(cProTestLabelID)
|
||||
|
||||
cProLibraryLabelID, err = dao.AddLabel(&models.Label{
|
||||
Name: "c_pro_library_label",
|
||||
Level: common.LabelLevelUser,
|
||||
Scope: common.LabelScopeProject,
|
||||
ProjectID: 1,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projGuest,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 500 project doesn't exist
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPathWithFakeProject,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 404 chart doesn't exist
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPathWithFakeChart,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 404 label doesn't exist
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
bodyJSON: struct {
|
||||
ID int64
|
||||
}{
|
||||
ID: 1000,
|
||||
},
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 400 system level label
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
bodyJSON: struct {
|
||||
ID int64
|
||||
}{
|
||||
ID: cSysLevelLabelID,
|
||||
},
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400 try to add the label of project1 to the image under project2
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
bodyJSON: struct {
|
||||
ID int64
|
||||
}{
|
||||
ID: cProTestLabelID,
|
||||
},
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodPost,
|
||||
credential: projDeveloper,
|
||||
bodyJSON: struct {
|
||||
ID int64
|
||||
}{
|
||||
ID: cProLibraryLabelID,
|
||||
},
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestGetOfChart(t *testing.T) {
|
||||
labels := []*models.Label{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodGet,
|
||||
credential: projDeveloper,
|
||||
}, &labels)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 1, len(labels))
|
||||
assert.Equal(t, cProLibraryLabelID, labels[0].ID)
|
||||
}
|
||||
|
||||
func TestRemoveFromChart(t *testing.T) {
|
||||
runCodeCheckingCases(t, &codeCheckingCase{
|
||||
request: &testingRequest{
|
||||
url: fmt.Sprintf("%s/%d", resourceLabelAPIPath, cProLibraryLabelID),
|
||||
method: http.MethodDelete,
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
})
|
||||
|
||||
labels := []*models.Label{}
|
||||
err := handleAndParse(&testingRequest{
|
||||
url: resourceLabelAPIPath,
|
||||
method: http.MethodGet,
|
||||
credential: projDeveloper,
|
||||
}, &labels)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 0, len(labels))
|
||||
}
|
||||
|
||||
func TestToStopMockChartService(t *testing.T) {
|
||||
if mockChartServer != nil {
|
||||
mockChartServer.Close()
|
||||
}
|
||||
|
||||
if oldChartController != nil {
|
||||
chartController = oldChartController
|
||||
}
|
||||
|
||||
dao.DeleteLabel(cProLibraryLabelID)
|
||||
}
|
@ -170,6 +170,11 @@ func init() {
|
||||
beego.Router("/api/system/gc/:id", &GCAPI{}, "get:GetGC")
|
||||
beego.Router("/api/system/gc/:id([0-9]+)/log", &GCAPI{}, "get:GetLog")
|
||||
beego.Router("/api/system/gc/schedule", &GCAPI{}, "get:Get;put:Put;post:Post")
|
||||
// Labels for chart
|
||||
chartLabelAPIType := &ChartLabelAPI{}
|
||||
beego.Router("/api/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel")
|
||||
beego.Router("/api/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel")
|
||||
|
||||
_ = updateInitPassword(1, "Harbor12345")
|
||||
|
||||
if err := core.Init(); err != nil {
|
||||
|
104
src/ui/api/label_resource.go
Normal file
104
src/ui/api/label_resource.go
Normal file
@ -0,0 +1,104 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/ui/label"
|
||||
)
|
||||
|
||||
// LabelResourceAPI provides the related basic functions to handle marking labels to resources
|
||||
type LabelResourceAPI struct {
|
||||
BaseController
|
||||
labelManager label.Manager
|
||||
}
|
||||
|
||||
// Prepare resources for follow-up actions.
|
||||
func (lra *LabelResourceAPI) Prepare() {
|
||||
lra.BaseController.Prepare()
|
||||
|
||||
// Create label manager
|
||||
lra.labelManager = &label.BaseManager{}
|
||||
}
|
||||
|
||||
func (lra *LabelResourceAPI) checkPermissions(project string) bool {
|
||||
if lra.Ctx.Request.Method == http.MethodPost ||
|
||||
lra.Ctx.Request.Method == http.MethodDelete {
|
||||
if lra.SecurityCtx.HasWritePerm(project) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if lra.Ctx.Request.Method == http.MethodGet {
|
||||
if lra.SecurityCtx.HasReadPerm(project) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (lra *LabelResourceAPI) getLabelsOfResource(rType string, rIDOrName interface{}) {
|
||||
labels, err := lra.labelManager.GetLabelsOfResource(rType, rIDOrName)
|
||||
if err != nil {
|
||||
lra.handleErrors(err)
|
||||
return
|
||||
}
|
||||
|
||||
lra.Data["json"] = labels
|
||||
lra.ServeJSON()
|
||||
}
|
||||
|
||||
func (lra *LabelResourceAPI) markLabelToResource(rl *models.ResourceLabel) {
|
||||
labelID, err := lra.labelManager.MarkLabelToResource(rl)
|
||||
if err != nil {
|
||||
lra.handleErrors(err)
|
||||
return
|
||||
}
|
||||
|
||||
// return the ID of label and return status code 200 rather than 201 as the label is not created
|
||||
lra.Redirect(http.StatusOK, strconv.FormatInt(labelID, 10))
|
||||
}
|
||||
|
||||
func (lra *LabelResourceAPI) removeLabelFromResource(rType string, rIDOrName interface{}, labelID int64) {
|
||||
if err := lra.labelManager.RemoveLabelFromResource(rType, rIDOrName, labelID); err != nil {
|
||||
lra.handleErrors(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// eat the error of validate method of label manager
|
||||
func (lra *LabelResourceAPI) validate(labelID, projectID int64) (*models.Label, bool) {
|
||||
label, err := lra.labelManager.Validate(labelID, projectID)
|
||||
if err != nil {
|
||||
lra.handleErrors(err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return label, true
|
||||
}
|
||||
|
||||
// eat the error of exists method of label manager
|
||||
func (lra *LabelResourceAPI) exists(labelID int64) (*models.Label, bool) {
|
||||
label, err := lra.labelManager.Exists(labelID)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return label, true
|
||||
}
|
||||
|
||||
// Handle different kinds of errors.
|
||||
func (lra *LabelResourceAPI) handleErrors(err error) {
|
||||
switch err.(type) {
|
||||
case *label.ErrLabelBadRequest:
|
||||
lra.SendBadRequestError(err)
|
||||
case *label.ErrLabelConflict:
|
||||
lra.SendConflictError(err)
|
||||
case *label.ErrLabelNotFound:
|
||||
lra.SendNotFoundError(err)
|
||||
default:
|
||||
lra.SendInternalServerError(err)
|
||||
}
|
||||
}
|
@ -419,6 +419,11 @@ func mockChartController() (*httptest.Server, *chartserver.Controller, error) {
|
||||
w.Write([]byte("{}"))
|
||||
return
|
||||
}
|
||||
case "/api/library/charts/harbor/0.2.0":
|
||||
if r.Method == http.MethodGet {
|
||||
w.Write([]byte(chartVersionOfHarbor020))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
@ -442,3 +447,39 @@ func mockChartController() (*httptest.Server, *chartserver.Controller, error) {
|
||||
|
||||
return mockServer, oldController, nil
|
||||
}
|
||||
|
||||
var chartVersionOfHarbor020 = `
|
||||
{
|
||||
"name": "harbor",
|
||||
"home": "https://github.com/vmware/harbor",
|
||||
"sources": [
|
||||
"https://github.com/vmware/harbor/tree/master/contrib/helm/harbor"
|
||||
],
|
||||
"version": "0.2.0",
|
||||
"description": "An Enterprise-class Docker Registry by VMware",
|
||||
"keywords": [
|
||||
"vmware",
|
||||
"docker",
|
||||
"registry",
|
||||
"harbor"
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Jesse Hu",
|
||||
"email": "huh@vmware.com"
|
||||
},
|
||||
{
|
||||
"name": "paulczar",
|
||||
"email": "username.taken@gmail.com"
|
||||
}
|
||||
],
|
||||
"engine": "gotpl",
|
||||
"icon": "https://raw.githubusercontent.com/vmware/harbor/master/docs/img/harbor_logo.png",
|
||||
"appVersion": "1.5.0",
|
||||
"urls": [
|
||||
"charts/harbor-0.2.0.tgz"
|
||||
],
|
||||
"created": "2018-08-29T10:26:21.141611102Z",
|
||||
"digest": "fc8aae8dade9f0dfca12e9f1085081c49843d30a063a3fa7eb42497e3ceb277c"
|
||||
}
|
||||
`
|
||||
|
@ -15,9 +15,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
@ -28,7 +28,7 @@ import (
|
||||
|
||||
// RepositoryLabelAPI handles requests for adding/removing label to/from repositories and images
|
||||
type RepositoryLabelAPI struct {
|
||||
BaseController
|
||||
LabelResourceAPI
|
||||
repository *models.RepoRecord
|
||||
tag string
|
||||
label *models.Label
|
||||
@ -36,27 +36,29 @@ type RepositoryLabelAPI struct {
|
||||
|
||||
// Prepare ...
|
||||
func (r *RepositoryLabelAPI) Prepare() {
|
||||
r.BaseController.Prepare()
|
||||
// Super
|
||||
r.LabelResourceAPI.Prepare()
|
||||
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.HandleUnauthorized()
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
repository := r.GetString(":splat")
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
if !r.SecurityCtx.HasWritePerm(project) {
|
||||
r.HandleForbidden(r.SecurityCtx.GetUsername())
|
||||
if !r.checkPermissions(project) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := dao.GetRepositoryByName(repository)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get repository %s: %v",
|
||||
repository, err))
|
||||
r.SendInternalServerError(fmt.Errorf("failed to get repository %s: %v", repository, err))
|
||||
return
|
||||
}
|
||||
|
||||
if repo == nil {
|
||||
r.HandleNotFound(fmt.Sprintf("repository %s not found", repository))
|
||||
r.SendNotFoundError(fmt.Errorf("repository %s not found", repository))
|
||||
return
|
||||
}
|
||||
r.repository = repo
|
||||
@ -65,49 +67,30 @@ func (r *RepositoryLabelAPI) Prepare() {
|
||||
if len(tag) > 0 {
|
||||
exist, err := imageExist(r.SecurityCtx.GetUsername(), repository, tag)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of image %s:%s: %v",
|
||||
repository, tag, err))
|
||||
r.SendInternalServerError(fmt.Errorf("failed to check the existence of image %s:%s: %v", repository, tag, err))
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
r.HandleNotFound(fmt.Sprintf("image %s:%s not found", repository, tag))
|
||||
r.SendNotFoundError(fmt.Errorf("image %s:%s not found", repository, tag))
|
||||
return
|
||||
}
|
||||
r.tag = tag
|
||||
}
|
||||
|
||||
if r.Ctx.Request.Method == http.MethodPost {
|
||||
p, err := r.ProjectMgr.Get(project)
|
||||
if err != nil {
|
||||
r.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
l := &models.Label{}
|
||||
r.DecodeJSONReq(l)
|
||||
|
||||
label, err := dao.GetLabel(l.ID)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", l.ID, err))
|
||||
label, ok := r.validate(l.ID, p.ProjectID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if label == nil {
|
||||
r.HandleNotFound(fmt.Sprintf("label %d not found", l.ID))
|
||||
return
|
||||
}
|
||||
|
||||
if label.Level != common.LabelLevelUser {
|
||||
r.HandleBadRequest("only user level labels can be used")
|
||||
return
|
||||
}
|
||||
|
||||
if label.Scope == common.LabelScopeProject {
|
||||
p, err := r.ProjectMgr.Get(project)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get project %s: %v", project, err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.ProjectID != label.ProjectID {
|
||||
r.HandleBadRequest("can not add labels which don't belong to the project to the resources under the project")
|
||||
return
|
||||
}
|
||||
}
|
||||
r.label = label
|
||||
|
||||
return
|
||||
@ -116,27 +99,22 @@ func (r *RepositoryLabelAPI) Prepare() {
|
||||
if r.Ctx.Request.Method == http.MethodDelete {
|
||||
labelID, err := r.GetInt64FromPath(":id")
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get ID parameter from path: %v", err))
|
||||
r.SendInternalServerError(fmt.Errorf("failed to get ID parameter from path: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
label, err := dao.GetLabel(labelID)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", labelID, err))
|
||||
label, ok := r.exists(labelID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if label == nil {
|
||||
r.HandleNotFound(fmt.Sprintf("label %d not found", labelID))
|
||||
return
|
||||
}
|
||||
r.label = label
|
||||
}
|
||||
}
|
||||
|
||||
// GetOfImage returns labels of an image
|
||||
func (r *RepositoryLabelAPI) GetOfImage() {
|
||||
r.getLabels(common.ResourceTypeImage, fmt.Sprintf("%s:%s", r.repository.Name, r.tag))
|
||||
r.getLabelsOfResource(common.ResourceTypeImage, fmt.Sprintf("%s:%s", r.repository.Name, r.tag))
|
||||
}
|
||||
|
||||
// AddToImage adds the label to an image
|
||||
@ -146,18 +124,18 @@ func (r *RepositoryLabelAPI) AddToImage() {
|
||||
ResourceType: common.ResourceTypeImage,
|
||||
ResourceName: fmt.Sprintf("%s:%s", r.repository.Name, r.tag),
|
||||
}
|
||||
r.addLabel(rl)
|
||||
r.markLabelToResource(rl)
|
||||
}
|
||||
|
||||
// RemoveFromImage removes the label from an image
|
||||
func (r *RepositoryLabelAPI) RemoveFromImage() {
|
||||
r.removeLabel(common.ResourceTypeImage,
|
||||
r.removeLabelFromResource(common.ResourceTypeImage,
|
||||
fmt.Sprintf("%s:%s", r.repository.Name, r.tag), r.label.ID)
|
||||
}
|
||||
|
||||
// GetOfRepository returns labels of a repository
|
||||
func (r *RepositoryLabelAPI) GetOfRepository() {
|
||||
r.getLabels(common.ResourceTypeRepository, r.repository.RepositoryID)
|
||||
r.getLabelsOfResource(common.ResourceTypeRepository, r.repository.RepositoryID)
|
||||
}
|
||||
|
||||
// AddToRepository adds the label to a repository
|
||||
@ -167,71 +145,12 @@ func (r *RepositoryLabelAPI) AddToRepository() {
|
||||
ResourceType: common.ResourceTypeRepository,
|
||||
ResourceID: r.repository.RepositoryID,
|
||||
}
|
||||
r.addLabel(rl)
|
||||
r.markLabelToResource(rl)
|
||||
}
|
||||
|
||||
// RemoveFromRepository removes the label from a repository
|
||||
func (r *RepositoryLabelAPI) RemoveFromRepository() {
|
||||
r.removeLabel(common.ResourceTypeRepository, r.repository.RepositoryID, r.label.ID)
|
||||
}
|
||||
|
||||
func (r *RepositoryLabelAPI) getLabels(rType string, rIDOrName interface{}) {
|
||||
labels, err := dao.GetLabelsOfResource(rType, rIDOrName)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to get labels of resource %s %v: %v",
|
||||
rType, rIDOrName, err))
|
||||
return
|
||||
}
|
||||
r.Data["json"] = labels
|
||||
r.ServeJSON()
|
||||
}
|
||||
|
||||
func (r *RepositoryLabelAPI) addLabel(rl *models.ResourceLabel) {
|
||||
var rIDOrName interface{}
|
||||
if rl.ResourceID != 0 {
|
||||
rIDOrName = rl.ResourceID
|
||||
} else {
|
||||
rIDOrName = rl.ResourceName
|
||||
}
|
||||
rlabel, err := dao.GetResourceLabel(rl.ResourceType, rIDOrName, rl.LabelID)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of label %d for resource %s %v: %v",
|
||||
rl.LabelID, rl.ResourceType, rIDOrName, err))
|
||||
return
|
||||
}
|
||||
|
||||
if rlabel != nil {
|
||||
r.HandleConflict()
|
||||
return
|
||||
}
|
||||
if _, err := dao.AddResourceLabel(rl); err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to add label %d to resource %s %v: %v",
|
||||
rl.LabelID, rl.ResourceType, rIDOrName, err))
|
||||
return
|
||||
}
|
||||
|
||||
// return the ID of label and return status code 200 rather than 201 as the label is not created
|
||||
r.Redirect(http.StatusOK, strconv.FormatInt(rl.LabelID, 10))
|
||||
}
|
||||
|
||||
func (r *RepositoryLabelAPI) removeLabel(rType string, rIDOrName interface{}, labelID int64) {
|
||||
rl, err := dao.GetResourceLabel(rType, rIDOrName, labelID)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to check the existence of label %d for resource %s %v: %v",
|
||||
labelID, rType, rIDOrName, err))
|
||||
return
|
||||
}
|
||||
|
||||
if rl == nil {
|
||||
r.HandleNotFound(fmt.Sprintf("label %d of resource %s %s not found",
|
||||
labelID, rType, rIDOrName))
|
||||
return
|
||||
}
|
||||
if err = dao.DeleteResourceLabel(rl.ID); err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to delete resource label record %d: %v",
|
||||
rl.ID, err))
|
||||
return
|
||||
}
|
||||
r.removeLabelFromResource(common.ResourceTypeRepository, r.repository.RepositoryID, r.label.ID)
|
||||
}
|
||||
|
||||
func imageExist(username, repository, tag string) (bool, error) {
|
||||
|
@ -250,4 +250,6 @@ func TestRemoveFromRepository(t *testing.T) {
|
||||
}, &labels)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 0, len(labels))
|
||||
|
||||
dao.DeleteLabel(proLibraryLabelID)
|
||||
}
|
||||
|
76
src/ui/label/errors.go
Normal file
76
src/ui/label/errors.go
Normal file
@ -0,0 +1,76 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrLabelBase contains the basic required info for building the final errors.
|
||||
type ErrLabelBase struct {
|
||||
LabelID int64
|
||||
ResourceType string
|
||||
ResourceIDOrName interface{}
|
||||
}
|
||||
|
||||
// ErrLabelNotFound defines the error of not found label on the resource
|
||||
// or the specified label is not found.
|
||||
type ErrLabelNotFound struct {
|
||||
ErrLabelBase
|
||||
}
|
||||
|
||||
// ErrLabelConflict defines the error of label conflicts on the resource.
|
||||
type ErrLabelConflict struct {
|
||||
ErrLabelBase
|
||||
}
|
||||
|
||||
// ErrLabelBadRequest defines the error of bad request to the resource.
|
||||
type ErrLabelBadRequest struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// NewErrLabelNotFound builds an error with ErrLabelNotFound type
|
||||
func NewErrLabelNotFound(labelID int64, resourceType string, resourceIDOrName interface{}) *ErrLabelNotFound {
|
||||
return &ErrLabelNotFound{
|
||||
ErrLabelBase{
|
||||
LabelID: labelID,
|
||||
ResourceType: resourceType,
|
||||
ResourceIDOrName: resourceIDOrName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error message of ErrLabelNotFound.
|
||||
func (nf *ErrLabelNotFound) Error() string {
|
||||
if len(nf.ResourceType) > 0 && nf.ResourceIDOrName != nil {
|
||||
return fmt.Sprintf("not found: label '%d' on %s '%v'", nf.LabelID, nf.ResourceType, nf.ResourceIDOrName)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("not found: label '%d'", nf.LabelID)
|
||||
}
|
||||
|
||||
// NewErrLabelConflict builds an error with NewErrLabelConflict type.
|
||||
func NewErrLabelConflict(labelID int64, resourceType string, resourceIDOrName interface{}) *ErrLabelConflict {
|
||||
return &ErrLabelConflict{
|
||||
ErrLabelBase{
|
||||
LabelID: labelID,
|
||||
ResourceType: resourceType,
|
||||
ResourceIDOrName: resourceIDOrName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error message of ErrLabelConflict.
|
||||
func (cl *ErrLabelConflict) Error() string {
|
||||
return fmt.Sprintf("conflict: %s '%v' is already marked with label '%d'", cl.ResourceType, cl.ResourceIDOrName, cl.LabelID)
|
||||
}
|
||||
|
||||
// NewErrLabelBadRequest builds an error with ErrLabelBadRequest type.
|
||||
func NewErrLabelBadRequest(message string) *ErrLabelBadRequest {
|
||||
return &ErrLabelBadRequest{
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error message of ErrLabelBadRequest.
|
||||
func (br *ErrLabelBadRequest) Error() string {
|
||||
return br.Message
|
||||
}
|
33
src/ui/label/errors_test.go
Normal file
33
src/ui/label/errors_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test cases for kinds of error definitions.
|
||||
func TestErrorFormats(t *testing.T) {
|
||||
br := NewErrLabelBadRequest("bad requests")
|
||||
if !checkErrorFormat(br, "bad requests") {
|
||||
t.Fatalf("expect an error with ErrLabelBadRequest kind but got incorrect format '%v'", br)
|
||||
}
|
||||
|
||||
cf := NewErrLabelConflict(1, "c", "repo1/mychart:1.0.0")
|
||||
if !checkErrorFormat(cf, fmt.Sprintf("conflict: %s '%v' is already marked with label '%d'", "c", "repo1/mychart:1.0.0", 1)) {
|
||||
t.Fatalf("expect an error with ErrLabelConflict kind but got incorrect format '%v'", cf)
|
||||
}
|
||||
|
||||
nf := NewErrLabelNotFound(1, "c", "repo1/mychart:1.0.0")
|
||||
if !checkErrorFormat(nf, fmt.Sprintf("not found: label '%d' on %s '%v'", 1, "c", "repo1/mychart:1.0.0")) {
|
||||
t.Fatalf("expect an error with ErrLabelNotFound kind but got incorrect format '%v'", nf)
|
||||
}
|
||||
|
||||
nf2 := NewErrLabelNotFound(1, "", "")
|
||||
if !checkErrorFormat(nf2, fmt.Sprintf("not found: label '%d'", 1)) {
|
||||
t.Fatalf("expect an error with ErrLabelNotFound kind but got incorrect format %v", nf2)
|
||||
}
|
||||
}
|
||||
|
||||
func checkErrorFormat(err error, msg string) bool {
|
||||
return err.Error() == msg
|
||||
}
|
145
src/ui/label/manager.go
Normal file
145
src/ui/label/manager.go
Normal file
@ -0,0 +1,145 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
)
|
||||
|
||||
// Manager defines the related operations for label management
|
||||
type Manager interface {
|
||||
// Mark label to the resource.
|
||||
//
|
||||
// If succeed, the relationship ID will be returned.
|
||||
// Otherwise, an non-nil error will be returned.
|
||||
MarkLabelToResource(label *models.ResourceLabel) (int64, error)
|
||||
|
||||
// Remove the label from the resource.
|
||||
// Resource type and ID(/name) should be provided to identify the relationship.
|
||||
//
|
||||
// An non-nil error will be got if meet any issues or nil error returned.
|
||||
RemoveLabelFromResource(resourceType string, resourceIDOrName interface{}, labelID int64) error
|
||||
|
||||
// Get labels for the specified resource.
|
||||
// Resource is identified by the resource type and ID(/name).
|
||||
//
|
||||
// If succeed, a label list is returned.
|
||||
// Otherwise, a non-nil error will be returned.
|
||||
GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*models.Label, error)
|
||||
|
||||
// Check the existence of the specified label.
|
||||
//
|
||||
// If label existing, a non-nil label object is returned and nil error is set.
|
||||
// A non-nil error will be set if any issues met while checking or label is not found.
|
||||
Exists(labelID int64) (*models.Label, error)
|
||||
|
||||
// Validate if the scope of the input label is correct.
|
||||
// If the scope is project level, the projectID is required then.
|
||||
//
|
||||
// If everything is ok, an validated label reference will be returned.
|
||||
// Otherwise, a non-nil error is returned.
|
||||
Validate(labelID int64, projectID int64) (*models.Label, error)
|
||||
}
|
||||
|
||||
// BaseManager is the default implementation of the Manager interface.
|
||||
type BaseManager struct{}
|
||||
|
||||
// MarkLabelToResource is the implementation of same method in Manager interface.
|
||||
func (bm *BaseManager) MarkLabelToResource(label *models.ResourceLabel) (int64, error) {
|
||||
if label == nil {
|
||||
return -1, errors.New("nil label object")
|
||||
}
|
||||
|
||||
// Use ID or name of resource. ID first.
|
||||
var rIDOrName interface{}
|
||||
if label.ResourceID != 0 {
|
||||
rIDOrName = label.ResourceID
|
||||
} else {
|
||||
rIDOrName = label.ResourceName
|
||||
}
|
||||
|
||||
rlabel, err := dao.GetResourceLabel(label.ResourceType, rIDOrName, label.LabelID)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("failed to check the existence of label %d for resource %s %v: %v", label.LabelID, label.ResourceType, rIDOrName, err)
|
||||
}
|
||||
|
||||
if rlabel != nil {
|
||||
return -1, NewErrLabelConflict(label.LabelID, label.ResourceType, rIDOrName)
|
||||
}
|
||||
|
||||
if _, err := dao.AddResourceLabel(label); err != nil {
|
||||
return -1, fmt.Errorf("failed to add label %d to resource %s %v: %v", label.LabelID, label.ResourceType, rIDOrName, err)
|
||||
}
|
||||
|
||||
// return the ID of label
|
||||
return label.LabelID, nil
|
||||
}
|
||||
|
||||
// RemoveLabelFromResource is the implementation of same method in Manager interface.
|
||||
func (bm *BaseManager) RemoveLabelFromResource(resourceType string, resourceIDOrName interface{}, labelID int64) error {
|
||||
rl, err := dao.GetResourceLabel(resourceType, resourceIDOrName, labelID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check the existence of label %d for resource %s %v: %v", labelID, resourceType, resourceIDOrName, err)
|
||||
}
|
||||
|
||||
if rl == nil {
|
||||
return NewErrLabelNotFound(labelID, resourceType, resourceIDOrName)
|
||||
}
|
||||
|
||||
if err = dao.DeleteResourceLabel(rl.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete resource label record %d: %v", rl.ID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLabelsOfResource is the implementation of same method in Manager interface.
|
||||
func (bm *BaseManager) GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*models.Label, error) {
|
||||
labels, err := dao.GetLabelsOfResource(resourceType, resourceIDOrName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get labels of resource %s %v: %v", resourceType, resourceIDOrName, err)
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// Exists is the implementation of same method in Manager interface.
|
||||
func (bm *BaseManager) Exists(labelID int64) (*models.Label, error) {
|
||||
label, err := dao.GetLabel(labelID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get label %d: %v", labelID, err)
|
||||
}
|
||||
|
||||
if label == nil {
|
||||
return nil, NewErrLabelNotFound(labelID, "", nil)
|
||||
}
|
||||
|
||||
return label, nil
|
||||
}
|
||||
|
||||
// Validate is the implementation of same method in Manager interface.
|
||||
func (bm *BaseManager) Validate(labelID int64, projectID int64) (*models.Label, error) {
|
||||
label, err := dao.GetLabel(labelID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get label %d: %v", labelID, err)
|
||||
}
|
||||
|
||||
if label == nil {
|
||||
return nil, NewErrLabelNotFound(labelID, "", nil)
|
||||
}
|
||||
|
||||
if label.Level != common.LabelLevelUser {
|
||||
return nil, NewErrLabelBadRequest("only user level labels can be used")
|
||||
}
|
||||
|
||||
if label.Scope == common.LabelScopeProject {
|
||||
if projectID != label.ProjectID {
|
||||
return nil, NewErrLabelBadRequest("can not add labels which don't belong to the project to the resources under the project")
|
||||
}
|
||||
}
|
||||
|
||||
return label, nil
|
||||
}
|
@ -148,6 +148,11 @@ func initRouters() {
|
||||
beego.Router("/chartrepo/:repo/index.yaml", chartRepositoryAPIType, "get:GetIndexByRepo")
|
||||
beego.Router("/chartrepo/index.yaml", chartRepositoryAPIType, "get:GetIndex")
|
||||
beego.Router("/chartrepo/:repo/charts/:filename", chartRepositoryAPIType, "get:DownloadChart")
|
||||
|
||||
// Labels for chart
|
||||
chartLabelAPIType := &api.ChartLabelAPI{}
|
||||
beego.Router("/api/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel")
|
||||
beego.Router("/api/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel")
|
||||
}
|
||||
|
||||
// Error pages
|
||||
|
27
src/ui/utils/error.go
Normal file
27
src/ui/utils/error.go
Normal file
@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// WrapErrorMessage wraps the error msg to the well formated error message `{ "error": "The error message" }`
|
||||
func WrapErrorMessage(msg string) string {
|
||||
errBody := make(map[string]string, 1)
|
||||
errBody["error"] = msg
|
||||
data, err := json.Marshal(&errBody)
|
||||
if err != nil {
|
||||
return msg
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// WrapError wraps the error to the well formated error `{ "error": "The error message" }`
|
||||
func WrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(WrapErrorMessage(err.Error()))
|
||||
}
|
33
src/ui/utils/error_test.go
Normal file
33
src/ui/utils/error_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test case for error wrapping function.
|
||||
func TestWrapError(t *testing.T) {
|
||||
if WrapError(nil) != nil {
|
||||
t.Fatal("expect nil error but got a non-nil one")
|
||||
}
|
||||
|
||||
err := errors.New("mock error")
|
||||
formatedErr := WrapError(err)
|
||||
if formatedErr == nil {
|
||||
t.Fatal("expect non-nil error but got nil")
|
||||
}
|
||||
|
||||
jsonErr := formatedErr.Error()
|
||||
structuredErr := make(map[string]string, 1)
|
||||
if e := json.Unmarshal([]byte(jsonErr), &structuredErr); e != nil {
|
||||
t.Fatal("expect nil error but got a non-nil one when doing error converting")
|
||||
}
|
||||
if msg, ok := structuredErr["error"]; !ok {
|
||||
t.Fatal("expect an 'error' filed but missing")
|
||||
} else {
|
||||
if msg != "mock error" {
|
||||
t.Fatalf("expect error message '%s' but got '%s'", "mock error", msg)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user