Api refactor label (#14650)

* Refactor labl api

move to the new program model

Signed-off-by: wang yan <wangyan@vmware.com>

* continue resolve review comments

Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2021-04-15 17:27:58 +08:00 committed by GitHub
parent 45663e002d
commit 6e3c9e29df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1227 additions and 1477 deletions

View File

@ -180,173 +180,6 @@ paths:
description: User need to log in first.
'500':
description: Unexpected internal errors.
/labels:
get:
summary: List labels according to the query strings.
description: |
This endpoint let user list labels by name, scope and project_id
parameters:
- name: name
in: query
type: string
required: false
description: The label name.
- name: scope
in: query
type: string
required: true
description: The label scope. Valid values are g and p. g for global labels and p for project labels.
- name: project_id
in: query
type: integer
format: int64
required: false
description: 'Relevant project ID, required when scope is p.'
- name: page
in: query
type: integer
format: int32
required: false
description: The page number.
- name: page_size
in: query
type: integer
format: int32
required: false
description: The size of per page.
tags:
- Products
responses:
'200':
description: Get successfully.
schema:
type: array
items:
$ref: '#/definitions/Label'
headers:
X-Total-Count:
description: The total count of available items
type: integer
Link:
description: Link to previous page and next page
type: string
'400':
description: Invalid parameters.
'401':
description: User need to log in first.
'500':
description: Unexpected internal errors.
post:
summary: Post creates a label
description: |
This endpoint let user creates a label.
parameters:
- name: label
in: body
description: The json object of label.
required: true
schema:
$ref: '#/definitions/Label'
tags:
- Products
responses:
'201':
description: Create successfully.
headers:
Location:
type: string
description: The URL of the created resource
'400':
description: Invalid parameters.
'401':
description: User need to log in first.
'409':
description: Label with the same name and same scope already exists.
'415':
$ref: '#/responses/UnsupportedMediaType'
'500':
description: Unexpected internal errors.
'/labels/{id}':
get:
summary: Get the label specified by ID.
description: |
This endpoint let user get the label by specific ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Label ID
tags:
- Products
responses:
'200':
description: Get successfully.
schema:
$ref: '#/definitions/Label'
'401':
description: User need to log in first.
'404':
description: The resource does not exist.
'500':
description: Unexpected internal errors.
put:
summary: Update the label properties.
description: |
This endpoint let user update label properties.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Label ID
- name: label
in: body
description: The updated label json object.
required: true
schema:
$ref: '#/definitions/Label'
tags:
- Products
responses:
'200':
description: Update successfully.
'400':
description: Invalid parameters.
'401':
description: User need to log in first.
'404':
description: The resource does not exist.
'409':
description: The label with the same name already exists.
'500':
description: Unexpected internal errors.
delete:
summary: Delete the label specified by ID.
description: |
Delete the label specified by ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Label ID
tags:
- Products
responses:
'200':
description: Delete successfully.
'400':
description: Invalid parameters.
'401':
description: User need to log in first.
'404':
description: The resource does not exist.
'500':
description: Unexpected internal errors.
/email/ping:
post:
summary: Test connection and authentication with email server.

View File

@ -4815,6 +4815,158 @@ paths:
description: The auth mode of the system is not "oidc_auth", or the user is not onboarded via OIDC AuthN.
'500':
$ref: '#/responses/500'
/labels:
get:
summary: List labels according to the query strings.
description: |
This endpoint let user list labels by name, scope and project_id
tags:
- label
operationId: ListLabels
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/query'
- $ref: '#/parameters/sort'
- $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize'
- name: name
in: query
type: string
required: false
description: The label name.
- name: scope
in: query
type: string
required: false
description: The label scope. Valid values are g and p. g for global labels and p for project labels.
- name: project_id
in: query
type: integer
format: int64
required: false
description: Relevant project ID, required when scope is p.
responses:
'200':
description: Get successfully.
schema:
type: array
items:
$ref: '#/definitions/Label'
headers:
X-Total-Count:
description: The total count of available items
type: integer
Link:
description: Link to previous page and next page
type: string
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'500':
$ref: '#/responses/500'
post:
summary: Post creates a label
description: |
This endpoint let user creates a label.
tags:
- label
operationId: CreateLabel
parameters:
- name: label
in: body
description: The json object of label.
required: true
schema:
$ref: '#/definitions/Label'
responses:
'201':
description: Create successfully.
headers:
Location:
type: string
description: The URL of the created resource
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'409':
$ref: '#/responses/409'
'415':
$ref: '#/responses/415'
'500':
$ref: '#/responses/500'
'/labels/{label_id}':
get:
summary: Get the label specified by ID.
description: |
This endpoint let user get the label by specific ID.
tags:
- label
operationId: GetLabelByID
parameters:
- $ref: '#/parameters/labelId'
responses:
'200':
description: Get successfully.
schema:
$ref: '#/definitions/Label'
'401':
$ref: '#/responses/401'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
put:
summary: Update the label properties.
description: |
This endpoint let user update label properties.
tags:
- label
operationId: UpdateLabel
parameters:
- $ref: '#/parameters/labelId'
- name: label
in: body
description: The updated label json object.
required: true
schema:
$ref: '#/definitions/Label'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'404':
$ref: '#/responses/404'
'409':
$ref: '#/responses/409'
'500':
$ref: '#/responses/500'
delete:
summary: Delete the label specified by ID.
description: |
Delete the label specified by ID.
tags:
- label
operationId: DeleteLabel
parameters:
- $ref: '#/parameters/labelId'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
parameters:
query:
name: q
@ -4947,6 +5099,13 @@ parameters:
required: true
type: integer
format: int64
labelId:
name: label_id
in: path
description: Label ID
required: true
type: integer
format: int64
webhookPolicyId:
name: webhook_policy_id
in: path
@ -5040,6 +5199,14 @@ responses:
type: string
schema:
$ref: '#/definitions/Errors'
'415':
description: Unsupported MediaType
headers:
X-Request-Id:
description: The ID of the corresponding request for the response
type: string
schema:
$ref: '#/definitions/Errors'
'500':
description: Internal server error
headers:

View File

@ -10,8 +10,8 @@ import (
"time"
"github.com/Masterminds/semver"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/goharbor/harbor/src/common/models"
hlog "github.com/goharbor/harbor/src/lib/log"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
@ -27,7 +27,7 @@ const (
// ChartVersion extends the helm ChartVersion with additional labels
type ChartVersion struct {
helm_repo.ChartVersion
Labels []*models.Label `json:"labels"`
Labels []*model.Label `json:"labels"`
}
// ChartVersions is an array of extended ChartVersion
@ -40,7 +40,7 @@ type ChartVersionDetails struct {
Values map[string]interface{} `json:"values"`
Files map[string]string `json:"files"`
Security *SecurityReport `json:"security"`
Labels []*models.Label `json:"labels"`
Labels []*model.Label `json:"labels"`
}
// SecurityReport keeps the info related with security

View File

@ -1,111 +0,0 @@
// Copyright Project Harbor Authors
//
// 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 dao
import (
"fmt"
"time"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/models"
libOrm "github.com/goharbor/harbor/src/lib/orm"
)
// AddLabel creates a label
func AddLabel(label *models.Label) (int64, error) {
now := time.Now()
label.CreationTime = now
label.UpdateTime = now
return GetOrmer().Insert(label)
}
// GetLabel specified by ID
func GetLabel(id int64) (*models.Label, error) {
label := &models.Label{
ID: id,
}
if err := GetOrmer().Read(label); err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
return label, nil
}
// GetTotalOfLabels returns the total count of labels
func GetTotalOfLabels(query *models.LabelQuery) (int64, error) {
qs := getLabelQuerySetter(query)
return qs.Count()
}
// ListLabels list labels according to the query conditions
func ListLabels(query *models.LabelQuery) ([]*models.Label, error) {
qs := getLabelQuerySetter(query)
if query.Size > 0 {
qs = qs.Limit(query.Size)
if query.Page > 0 {
qs = qs.Offset((query.Page - 1) * query.Size)
}
}
qs = qs.OrderBy("-CreationTime")
labels := []*models.Label{}
_, err := qs.All(&labels)
return labels, err
}
func getLabelQuerySetter(query *models.LabelQuery) orm.QuerySeter {
qs := GetOrmer().QueryTable(&models.Label{})
if len(query.Name) > 0 {
if query.FuzzyMatchName {
qs = qs.Filter("Name__icontains", libOrm.Escape(query.Name))
} else {
qs = qs.Filter("Name", query.Name)
}
}
if len(query.Level) > 0 {
qs = qs.Filter("Level", query.Level)
}
if len(query.Scope) > 0 {
qs = qs.Filter("Scope", query.Scope)
}
if query.ProjectID != 0 {
qs = qs.Filter("ProjectID", query.ProjectID)
}
qs = qs.Filter("Deleted", false)
return qs
}
// UpdateLabel ...
func UpdateLabel(label *models.Label) error {
label.UpdateTime = time.Now()
_, err := GetOrmer().Update(label)
return err
}
// DeleteLabel ...
func DeleteLabel(id int64) error {
label, err := GetLabel(id)
if err != nil {
return err
}
label.Name = fmt.Sprintf("%s#%d", label.Name, label.ID)
label.UpdateTime = time.Now()
label.Deleted = true
_, err = GetOrmer().Update(label, "Name", "UpdateTime", "Deleted")
return err
}

View File

@ -1,120 +0,0 @@
// Copyright Project Harbor Authors
//
// 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 dao
import (
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMethodsOfLabel(t *testing.T) {
labelName := "test"
label := &models.Label{
Name: labelName,
Level: common.LabelLevelUser,
Scope: common.LabelScopeProject,
ProjectID: 1,
}
// add
id, err := AddLabel(label)
require.Nil(t, err)
label.ID = id
// add a label which has the same name to another project
projectID, err := AddProject(models.Project{
OwnerID: 1,
Name: "project_for_label_test",
})
require.Nil(t, err)
defer GetOrmer().QueryTable(&models.Project{}).
Filter("project_id", projectID).Delete()
id2, err := AddLabel(&models.Label{
Name: labelName,
Level: common.LabelLevelUser,
Scope: common.LabelScopeProject,
ProjectID: projectID,
})
require.Nil(t, err)
defer DeleteLabel(id2)
// get
l, err := GetLabel(id)
require.Nil(t, err)
assert.Equal(t, label.ID, l.ID)
assert.Equal(t, label.Name, l.Name)
assert.Equal(t, label.Scope, l.Scope)
assert.Equal(t, label.ProjectID, l.ProjectID)
// get total count
total, err := GetTotalOfLabels(&models.LabelQuery{
Scope: common.LabelScopeProject,
ProjectID: 1,
})
require.Nil(t, err)
assert.Equal(t, int64(1), total)
// list: exact match
labels, err := ListLabels(&models.LabelQuery{
Scope: common.LabelScopeProject,
ProjectID: 1,
Name: label.Name,
})
require.Nil(t, err)
assert.Equal(t, 1, len(labels))
// list: fuzzy match
labels, err = ListLabels(&models.LabelQuery{
Scope: common.LabelScopeProject,
ProjectID: 1,
Name: label.Name[:1],
FuzzyMatchName: true,
})
require.Nil(t, err)
assert.Equal(t, 1, len(labels))
// list: not exist
labels, err = ListLabels(&models.LabelQuery{
Scope: common.LabelScopeProject,
ProjectID: 1,
Name: label.Name[:1],
})
require.Nil(t, err)
assert.Equal(t, 0, len(labels))
// update
newName := "dev"
label.Name = newName
err = UpdateLabel(label)
require.Nil(t, err)
l, err = GetLabel(id)
require.Nil(t, err)
assert.Equal(t, newName, l.Name)
// delete
err = DeleteLabel(id)
require.Nil(t, err)
l, err = GetLabel(id)
require.Nil(t, err)
assert.True(t, l.Deleted)
}

View File

@ -15,10 +15,11 @@
package dao
import (
"github.com/goharbor/harbor/src/common/models"
"time"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/label/model"
)
// AddResourceLabel add a label to a resource
@ -60,7 +61,7 @@ func GetResourceLabel(rType string, rIDOrName interface{}, labelID int64) (*mode
// GetLabelsOfResource returns the label list of the resource
// Get the labels by ResourceID if rIDOrName is int, or get the labels by ResourceName
func GetLabelsOfResource(rType string, rIDOrName interface{}) ([]*models.Label, error) {
func GetLabelsOfResource(rType string, rIDOrName interface{}) ([]*model.Label, error) {
sql := `select l.id, l.name, l.description, l.color, l.scope, l.project_id, l.creation_time, l.update_time
from harbor_resource_label rl
join harbor_label l on rl.label_id=l.id
@ -71,7 +72,7 @@ func GetLabelsOfResource(rType string, rIDOrName interface{}) ([]*models.Label,
sql += ` rl.resource_name = ?`
}
labels := []*models.Label{}
labels := []*model.Label{}
_, err := GetOrmer().Raw(sql, rType, rIDOrName).QueryRows(&labels)
return labels, err
}

View File

@ -15,22 +15,26 @@
package dao
import (
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/label/dao"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMethodsOfResourceLabel(t *testing.T) {
labelID, err := AddLabel(&models.Label{
labelDao := dao.New()
labelID, err := labelDao.Create(orm.Context(), &model.Label{
Name: "test_label",
Level: common.LabelLevelUser,
Scope: common.LabelScopeGlobal,
})
require.Nil(t, err)
defer DeleteLabel(labelID)
defer labelDao.Delete(orm.Context(), labelID)
var resourceID int64 = 1
resourceType := common.ResourceTypeRepository

View File

@ -25,7 +25,6 @@ func init() {
new(Role),
new(RepoRecord),
new(ProjectMetadata),
new(Label),
new(ResourceLabel),
new(JobLog),
new(OIDCUser),

View File

@ -15,58 +15,9 @@
package models
import (
"fmt"
"time"
"github.com/astaxie/beego/validation"
"github.com/goharbor/harbor/src/common"
)
// Label holds information used for a label
type Label struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Name string `orm:"column(name)" json:"name"`
Description string `orm:"column(description)" json:"description"`
Color string `orm:"column(color)" json:"color"`
Level string `orm:"column(level)" json:"-"`
Scope string `orm:"column(scope)" json:"scope"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
Deleted bool `orm:"column(deleted)" json:"deleted"`
}
// TableName ...
func (l *Label) TableName() string {
return "harbor_label"
}
// LabelQuery : query parameters for labels
type LabelQuery struct {
Name string
FuzzyMatchName bool // the property is used to determine the query for lable name is fuzzy matching or exaxt matching
Level string
Scope string
ProjectID int64
Pagination
}
// Valid ...
func (l *Label) Valid(v *validation.Validation) {
if len(l.Name) == 0 {
v.SetError("name", "cannot be empty")
}
if len(l.Name) > 128 {
v.SetError("name", "max length is 128")
}
if l.Scope != common.LabelScopeGlobal && l.Scope != common.LabelScopeProject {
v.SetError("scope", fmt.Sprintf("invalid: %s", l.Scope))
} else if l.Scope == common.LabelScopeProject && l.ProjectID <= 0 {
v.SetError("project_id", fmt.Sprintf("invalid: %d", l.ProjectID))
}
}
// ResourceLabel records the relationship between resource and label
type ResourceLabel struct {
ID int64 `orm:"pk;auto;column(id)"`

View File

@ -17,51 +17,51 @@ package models
import (
"testing"
"github.com/astaxie/beego/validation"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/stretchr/testify/assert"
)
func TestValidOfLabel(t *testing.T) {
cases := []struct {
label *Label
label *model.Label
hasError bool
}{
{
label: &Label{
label: &model.Label{
Name: "",
},
hasError: true,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "",
},
hasError: true,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "invalid_scope",
},
hasError: true,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "g",
},
hasError: false,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "p",
},
hasError: true,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "p",
ProjectID: -1,
@ -69,7 +69,7 @@ func TestValidOfLabel(t *testing.T) {
hasError: true,
},
{
label: &Label{
label: &model.Label{
Name: "test",
Scope: "p",
ProjectID: 1,
@ -79,8 +79,11 @@ func TestValidOfLabel(t *testing.T) {
}
for _, c := range cases {
v := &validation.Validation{}
c.label.Valid(v)
assert.Equal(t, c.hasError, v.HasErrors())
err := c.label.Valid()
if c.hasError {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
}
}

View File

@ -20,7 +20,6 @@ import (
"time"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/pkg/signature/notary/model"
"github.com/lib/pq"
"github.com/theupdateframework/notary/tuf/data"
)
@ -72,16 +71,6 @@ type RepositoryQuery struct {
Sorting
}
// TagResp holds the information of one image tag
type TagResp struct {
TagDetail
Signature *model.Target `json:"signature"`
ScanOverview map[string]interface{} `json:"scan_overview,omitempty"`
Labels []*Label `json:"labels"`
PushTime time.Time `json:"push_time"`
PullTime time.Time `json:"pull_time"`
}
// TagDetail ...
type TagDetail struct {
Digest string `json:"digest"`

View File

@ -31,6 +31,7 @@ import (
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/label/model"
model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag"
tagtesting "github.com/goharbor/harbor/src/testing/controller/tag"
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
@ -64,7 +65,7 @@ type controllerTestSuite struct {
artrashMgr *artrashtesting.FakeManager
blobMgr *blob.Manager
tagCtl *tagtesting.FakeController
labelMgr *label.FakeManager
labelMgr *label.Manager
abstractor *fakeAbstractor
immutableMtr *immutable.FakeMatcher
regCli *registry.FakeClient
@ -76,7 +77,7 @@ func (c *controllerTestSuite) SetupTest() {
c.artrashMgr = &artrashtesting.FakeManager{}
c.blobMgr = &blob.Manager{}
c.tagCtl = &tagtesting.FakeController{}
c.labelMgr = &label.FakeManager{}
c.labelMgr = &label.Manager{}
c.abstractor = &fakeAbstractor{}
c.immutableMtr = &immutable.FakeMatcher{}
c.regCli = &registry.FakeClient{}
@ -118,11 +119,11 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
}
c.tagCtl.On("List").Return([]*tag.Tag{tg}, nil)
ctx := lib.WithAPIVersion(nil, "2.0")
lb := &models.Label{
lb := &model.Label{
ID: 1,
Name: "label",
}
c.labelMgr.On("ListByArtifact").Return([]*models.Label{
c.labelMgr.On("ListByArtifact", mock.Anything, mock.Anything).Return([]*model.Label{
lb,
}, nil)
artifact := c.ctl.assembleArtifact(ctx, art, option)
@ -537,13 +538,13 @@ func (c *controllerTestSuite) TestGetAddition() {
}
func (c *controllerTestSuite) TestAddTo() {
c.labelMgr.On("AddTo").Return(nil)
c.labelMgr.On("AddTo", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := c.ctl.AddLabel(context.Background(), 1, 1)
c.Require().Nil(err)
}
func (c *controllerTestSuite) TestRemoveFrom() {
c.labelMgr.On("RemoveFrom").Return(nil)
c.labelMgr.On("RemoveFrom", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := c.ctl.RemoveLabel(nil, 1, 1)
c.Require().Nil(err)
}

View File

@ -16,11 +16,11 @@ package artifact
import (
"fmt"
cmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/controller/tag"
"github.com/goharbor/harbor/src/lib/encode/repository"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/label/model"
)
// Artifact is the overall view of artifact
@ -28,7 +28,7 @@ type Artifact struct {
artifact.Artifact
Tags []*tag.Tag `json:"tags"` // the list of tags that attached to the artifact
AdditionLinks map[string]*AdditionLink `json:"addition_links"` // the resource link for build history(image), values.yaml(chart), dependency(chart), etc
Labels []*cmodels.Label `json:"labels"`
Labels []*model.Label `json:"labels"`
}
// SetAdditionLink set a addition link

View File

@ -33,6 +33,7 @@ import (
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/lib/selector"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/goharbor/harbor/src/pkg/p2p/preheat"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/instance"
pol "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
@ -570,7 +571,7 @@ func pureRepository(ns, r string) string {
}
// getLabels gets label texts from the label objects
func getLabels(labels []*models.Label) []string {
func getLabels(labels []*model.Label) []string {
lt := make([]string, 0)
for _, l := range labels {
lt = append(lt, l.Name)

View File

@ -28,6 +28,7 @@ import (
"github.com/goharbor/harbor/src/lib/selector"
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
ar "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/label/model"
po "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
pr "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider"
@ -265,7 +266,7 @@ func mockArtifacts() []*car.Artifact {
Signed: false,
},
},
Labels: []*models.Label{
Labels: []*model.Label{
{
Name: "approved",
}, {
@ -293,7 +294,7 @@ func mockArtifacts() []*car.Artifact {
Signed: true,
},
},
Labels: []*models.Label{
Labels: []*model.Label{
{
Name: "approved",
}, {

View File

@ -7,6 +7,7 @@ import (
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/pkg/label/model"
)
const (
@ -63,7 +64,7 @@ func (cla *ChartLabelAPI) MarkLabel() {
return
}
l := &models.Label{}
l := &model.Label{}
if err := cla.DecodeJSONReq(l); err != nil {
cla.SendBadRequestError(err)
return

View File

@ -16,6 +16,7 @@ package api
import (
"fmt"
"github.com/goharbor/harbor/src/lib/orm"
"net/http"
"net/http/httptest"
"testing"
@ -23,8 +24,8 @@ import (
"github.com/goharbor/harbor/src/chartserver"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/api"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
pkg_dao "github.com/goharbor/harbor/src/pkg/label/dao"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -36,6 +37,7 @@ var (
cProLibraryLabelID int64
mockChartServer *httptest.Server
oldChartController *chartserver.Controller
labelDao pkg_dao.DAO
)
func TestToStartMockChartService(t *testing.T) {
@ -44,26 +46,28 @@ func TestToStartMockChartService(t *testing.T) {
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{
labelDao = pkg_dao.New()
cSysLevelLabelID, err := labelDao.Create(orm.Context(), &model.Label{
Name: "c_sys_level_label",
Level: common.LabelLevelSystem,
})
require.Nil(t, err)
defer dao.DeleteLabel(cSysLevelLabelID)
defer labelDao.Delete(orm.Context(), cSysLevelLabelID)
cProTestLabelID, err := dao.AddLabel(&models.Label{
cProTestLabelID, err := labelDao.Create(orm.Context(), &model.Label{
Name: "c_pro_test_label",
Level: common.LabelLevelUser,
Scope: common.LabelScopeProject,
ProjectID: 100,
})
require.Nil(t, err)
defer dao.DeleteLabel(cProTestLabelID)
defer labelDao.Delete(orm.Context(), cProTestLabelID)
cProLibraryLabelID, err = dao.AddLabel(&models.Label{
cProLibraryLabelID, err = labelDao.Create(orm.Context(), &model.Label{
Name: "c_pro_library_label",
Level: common.LabelLevelUser,
Scope: common.LabelScopeProject,
@ -177,7 +181,7 @@ func TestAddToChart(t *testing.T) {
}
func TestGetOfChart(t *testing.T) {
labels := []*models.Label{}
labels := []*model.Label{}
err := handleAndParse(&testingRequest{
url: resourceLabelAPIPath,
method: http.MethodGet,
@ -198,7 +202,7 @@ func TestRemoveFromChart(t *testing.T) {
code: http.StatusOK,
})
labels := []*models.Label{}
labels := []*model.Label{}
err := handleAndParse(&testingRequest{
url: resourceLabelAPIPath,
method: http.MethodGet,
@ -216,6 +220,6 @@ func TestToStopMockChartService(t *testing.T) {
if oldChartController != nil {
chartController = oldChartController
}
dao.DeleteLabel(cProLibraryLabelID)
labelDao = pkg_dao.New()
labelDao.Delete(orm.Context(), cProLibraryLabelID)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/core/label"
hlog "github.com/goharbor/harbor/src/lib/log"
pkg_label "github.com/goharbor/harbor/src/pkg/label"
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/reg/model"
"github.com/goharbor/harbor/src/server/middleware/orm"
@ -99,7 +100,9 @@ func (cra *ChartRepositoryAPI) Prepare() {
}
// Init label manager
cra.labelManager = &label.BaseManager{}
cra.labelManager = &label.BaseManager{
LabelMgr: pkg_label.Mgr,
}
}
func (cra *ChartRepositoryAPI) requireAccess(action rbac.Action, subresource ...rbac.Resource) bool {

View File

@ -98,8 +98,6 @@ func init() {
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete")
beego.Router("/api/statistics", &StatisticAPI{})
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete")
// Charts are controlled under projects
chartRepositoryAPIType := &ChartRepositoryAPI{}

View File

@ -1,310 +0,0 @@
// Copyright 2018 Project Harbor Authors
//
// 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 (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"strconv"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/label"
)
// LabelAPI handles requests for label management
type LabelAPI struct {
label *models.Label
BaseController
}
// Prepare ...
func (l *LabelAPI) Prepare() {
l.BaseController.Prepare()
method := l.Ctx.Request.Method
if method == http.MethodGet {
return
}
// POST, PUT, DELETE need login first
if !l.SecurityCtx.IsAuthenticated() {
l.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
if method == http.MethodPut || method == http.MethodDelete {
id, err := l.GetInt64FromPath(":id")
if err != nil || id <= 0 {
l.SendBadRequestError(errors.New("invalid label ID"))
return
}
label, err := dao.GetLabel(id)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to get label %d: %v", id, err))
return
}
if label == nil || label.Deleted {
l.SendNotFoundError(fmt.Errorf("label %d not found", id))
return
}
l.label = label
}
}
func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subresources ...rbac.Resource) bool {
var hasPermission bool
switch label.Scope {
case common.LabelScopeGlobal:
resource := system.NewNamespace().Resource(rbac.ResourceLabel)
hasPermission = l.SecurityCtx.Can(l.Context(), action, resource)
case common.LabelScopeProject:
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceLabel)
}
hasPermission, _ = l.HasProjectPermission(label.ProjectID, action, subresources...)
}
if !hasPermission {
if !l.SecurityCtx.IsAuthenticated() {
l.SendUnAuthorizedError(errors.New("UnAuthorized"))
} else {
l.SendForbiddenError(errors.New(l.SecurityCtx.GetUsername()))
}
return false
}
return true
}
// Post creates a label
func (l *LabelAPI) Post() {
label := &models.Label{}
isValid, err := l.DecodeJSONReqAndValidate(label)
if !isValid {
l.SendBadRequestError(err)
return
}
label.Level = common.LabelLevelUser
switch label.Scope {
case common.LabelScopeGlobal:
label.ProjectID = 0
case common.LabelScopeProject:
exist, err := l.ProjectCtl.Exists(l.Context(), label.ProjectID)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to check the existence of project %d: %v",
label.ProjectID, err))
return
}
if !exist {
l.SendBadRequestError(fmt.Errorf("project %d not found", label.ProjectID))
return
}
}
if !l.requireAccess(label, rbac.ActionCreate) {
return
}
labels, err := dao.ListLabels(&models.LabelQuery{
Name: label.Name,
Level: label.Level,
Scope: label.Scope,
ProjectID: label.ProjectID,
})
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
return
}
if len(labels) > 0 {
l.SendConflictError(errors.New("conflict label"))
return
}
id, err := dao.AddLabel(label)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to create label: %v", err))
return
}
l.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
}
// Get the label specified by ID
func (l *LabelAPI) Get() {
id, err := l.GetInt64FromPath(":id")
if err != nil || id <= 0 {
l.SendBadRequestError(fmt.Errorf("invalid label ID: %s", l.GetStringFromPath(":id")))
return
}
label, err := dao.GetLabel(id)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to get label %d: %v", id, err))
return
}
if label == nil || label.Deleted {
l.SendNotFoundError(fmt.Errorf("label %d not found", id))
return
}
if !l.requireAccess(label, rbac.ActionRead) {
return
}
l.Data["json"] = label
l.ServeJSON()
}
// List labels according to the query strings
func (l *LabelAPI) List() {
query := &models.LabelQuery{
Name: l.GetString("name"),
FuzzyMatchName: true,
Level: common.LabelLevelUser,
}
scope := l.GetString("scope")
if scope != common.LabelScopeGlobal && scope != common.LabelScopeProject {
l.SendBadRequestError(fmt.Errorf("invalid scope: %s", scope))
return
}
query.Scope = scope
if scope == common.LabelScopeProject {
projectIDStr := l.GetString("project_id")
if len(projectIDStr) == 0 {
l.SendBadRequestError(errors.New("project_id is required"))
return
}
projectID, err := strconv.ParseInt(projectIDStr, 10, 64)
if err != nil || projectID <= 0 {
l.SendBadRequestError(fmt.Errorf("invalid project_id: %s", projectIDStr))
return
}
if !l.RequireProjectAccess(projectID, rbac.ActionList, rbac.ResourceLabel) {
return
}
query.ProjectID = projectID
}
total, err := dao.GetTotalOfLabels(query)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to get total count of labels: %v", err))
return
}
query.Page, query.Size, err = l.GetPaginationParams()
if err != nil {
l.SendBadRequestError(err)
return
}
labels, err := dao.ListLabels(query)
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
return
}
l.SetPaginationHeader(total, query.Page, query.Size)
l.Data["json"] = labels
l.ServeJSON()
}
// Put updates the label
func (l *LabelAPI) Put() {
if !l.requireAccess(l.label, rbac.ActionUpdate) {
return
}
label := &models.Label{}
if err := l.DecodeJSONReq(label); err != nil {
l.SendBadRequestError(err)
return
}
oldName := l.label.Name
// only name, description and color can be changed
l.label.Name = label.Name
l.label.Description = label.Description
l.label.Color = label.Color
isValidate, err := l.Validate(l.label)
if !isValidate {
if err != nil {
l.SendBadRequestError(err)
return
}
}
if l.label.Name != oldName {
labels, err := dao.ListLabels(&models.LabelQuery{
Name: l.label.Name,
Level: l.label.Level,
Scope: l.label.Scope,
ProjectID: l.label.ProjectID,
})
if err != nil {
l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
return
}
if len(labels) > 0 {
l.SendConflictError(errors.New("conflict label"))
return
}
}
if err := dao.UpdateLabel(l.label); err != nil {
l.SendInternalServerError(fmt.Errorf("failed to update label %d: %v", l.label.ID, err))
return
}
}
// Delete the label
func (l *LabelAPI) Delete() {
if !l.requireAccess(l.label, rbac.ActionDelete) {
return
}
id := l.label.ID
if err := dao.DeleteResourceLabelByLabel(id); err != nil {
l.SendInternalServerError(fmt.Errorf("failed to delete resource label mappings of label %d: %v", id, err))
return
}
if err := label.Mgr.RemoveFromAllArtifacts(orm.Context(), id); err != nil {
l.SendInternalServerError(fmt.Errorf("failed to remove the label %d from all artifacts: %v", id, err))
return
}
if err := dao.DeleteLabel(id); err != nil {
l.SendInternalServerError(fmt.Errorf("failed to delete label %d: %v", id, err))
return
}
}

View File

@ -1,11 +1,13 @@
package api
import (
pkg_label "github.com/goharbor/harbor/src/pkg/label"
"net/http"
"strconv"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/label"
"github.com/goharbor/harbor/src/pkg/label/model"
)
// LabelResourceAPI provides the related basic functions to handle marking labels to resources
@ -19,7 +21,9 @@ func (lra *LabelResourceAPI) Prepare() {
lra.BaseController.Prepare()
// Create label manager
lra.labelManager = &label.BaseManager{}
lra.labelManager = &label.BaseManager{
LabelMgr: pkg_label.Mgr,
}
}
func (lra *LabelResourceAPI) getLabelsOfResource(rType string, rIDOrName interface{}) {
@ -52,7 +56,7 @@ func (lra *LabelResourceAPI) removeLabelFromResource(rType string, rIDOrName int
}
// eat the error of validate method of label manager
func (lra *LabelResourceAPI) validate(labelID, projectID int64) (*models.Label, bool) {
func (lra *LabelResourceAPI) validate(labelID, projectID int64) (*model.Label, bool) {
label, err := lra.labelManager.Validate(labelID, projectID)
if err != nil {
lra.handleErrors(err)
@ -63,7 +67,7 @@ func (lra *LabelResourceAPI) validate(labelID, projectID int64) (*models.Label,
}
// eat the error of exists method of label manager
func (lra *LabelResourceAPI) exists(labelID int64) (*models.Label, bool) {
func (lra *LabelResourceAPI) exists(labelID int64) (*model.Label, bool) {
label, err := lra.labelManager.Exists(labelID)
if err != nil {
return nil, false

View File

@ -1,435 +0,0 @@
// Copyright 2018 Project Harbor Authors
//
// 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/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
labelAPIBasePath = "/api/labels"
labelID int64
)
func TestLabelAPIPost(t *testing.T) {
postFunc := func(resp *httptest.ResponseRecorder) error {
id, err := parseResourceID(resp)
if err != nil {
return err
}
labelID = id
return nil
}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
},
code: http.StatusUnauthorized,
},
// 400
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{},
credential: nonSysAdmin,
},
code: http.StatusBadRequest,
},
// 403 non-sysadmin try to create global label
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeGlobal,
},
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 403 non-member user try to create project label
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 403 developer try to create project label
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: projDeveloper,
},
code: http.StatusForbidden,
},
// 400 non-exist project
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeProject,
ProjectID: 10000,
},
credential: projAdmin,
},
code: http.StatusBadRequest,
},
// 200
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: projAdmin,
},
code: http.StatusCreated,
postFunc: postFunc,
},
// 409
{
request: &testingRequest{
method: http.MethodPost,
url: labelAPIBasePath,
bodyJSON: &models.Label{
Name: "test",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: projAdmin,
},
code: http.StatusConflict,
},
}
runCodeCheckingCases(t, cases...)
}
func TestLabelAPIGet(t *testing.T) {
cases := []*codeCheckingCase{
// 400
{
request: &testingRequest{
method: http.MethodGet,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
},
code: http.StatusBadRequest,
},
// 404
{
request: &testingRequest{
method: http.MethodGet,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 1000),
},
code: http.StatusNotFound,
},
// 200
{
request: &testingRequest{
method: http.MethodGet,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}
func TestLabelAPIList(t *testing.T) {
cases := []*codeCheckingCase{
// 400 no scope query string
{
request: &testingRequest{
method: http.MethodGet,
url: labelAPIBasePath,
},
code: http.StatusBadRequest,
},
// 400 invalid scope
{
request: &testingRequest{
method: http.MethodGet,
url: labelAPIBasePath,
queryStruct: struct {
Scope string `url:"scope"`
}{
Scope: "invalid_scope",
},
},
code: http.StatusBadRequest,
},
// 400 invalid project_id
{
request: &testingRequest{
method: http.MethodGet,
url: labelAPIBasePath,
queryStruct: struct {
Scope string `url:"scope"`
ProjectID int64 `url:"project_id"`
}{
Scope: "p",
ProjectID: 0,
},
},
code: http.StatusBadRequest,
},
}
runCodeCheckingCases(t, cases...)
// 200
labels := []*models.Label{}
err := handleAndParse(&testingRequest{
method: http.MethodGet,
url: labelAPIBasePath,
queryStruct: struct {
Scope string `url:"scope"`
ProjectID int64 `url:"project_id"`
Name string `url:"name"`
}{
Scope: "p",
ProjectID: 1,
Name: "tes",
},
}, &labels)
require.Nil(t, err)
assert.Equal(t, 1, len(labels))
err = handleAndParse(&testingRequest{
method: http.MethodGet,
url: labelAPIBasePath,
queryStruct: struct {
Scope string `url:"scope"`
ProjectID int64 `url:"project_id"`
Name string `url:"name"`
}{
Scope: "p",
ProjectID: 1,
Name: "dev",
},
}, &labels)
require.Nil(t, err)
assert.Equal(t, 0, len(labels))
}
func TestLabelAPIPut(t *testing.T) {
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
},
code: http.StatusUnauthorized,
},
// 400
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
credential: nonSysAdmin,
},
code: http.StatusBadRequest,
},
// 404
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 10000),
credential: nonSysAdmin,
},
code: http.StatusNotFound,
},
// 403 non-member user
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 403 developer
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: projDeveloper,
},
code: http.StatusForbidden,
},
// 400
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
bodyJSON: &models.Label{
Name: "",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: projAdmin,
},
code: http.StatusBadRequest,
},
// 200
{
request: &testingRequest{
method: http.MethodPut,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
bodyJSON: &models.Label{
Name: "product",
Scope: common.LabelScopeProject,
ProjectID: 1,
},
credential: projAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
label := &models.Label{}
err := handleAndParse(&testingRequest{
method: http.MethodGet,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
}, label)
require.Nil(t, err)
assert.Equal(t, "product", label.Name)
}
func TestLabelAPIDelete(t *testing.T) {
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
},
code: http.StatusUnauthorized,
},
// 400
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 0),
credential: nonSysAdmin,
},
code: http.StatusBadRequest,
},
// 404
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, 10000),
credential: nonSysAdmin,
},
code: http.StatusNotFound,
},
// 403 non-member user
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 403 developer
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: projDeveloper,
},
code: http.StatusForbidden,
},
// 200
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: projAdmin,
},
code: http.StatusOK,
},
// 404
{
request: &testingRequest{
method: http.MethodDelete,
url: fmt.Sprintf("%s/%d", labelAPIBasePath, labelID),
credential: projAdmin,
},
code: http.StatusNotFound,
},
}
runCodeCheckingCases(t, cases...)
}

View File

@ -1,12 +1,15 @@
package label
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/label"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/label/model"
)
// Manager defines the related operations for label management
@ -28,24 +31,26 @@ type Manager interface {
//
// If succeed, a label list is returned.
// Otherwise, a non-nil error will be returned.
GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*models.Label, error)
GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*model.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)
Exists(labelID int64) (*model.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)
Validate(labelID int64, projectID int64) (*model.Label, error)
}
// BaseManager is the default implementation of the Manager interface.
type BaseManager struct{}
type BaseManager struct {
LabelMgr label.Manager
}
// MarkLabelToResource is the implementation of same method in Manager interface.
func (bm *BaseManager) MarkLabelToResource(label *models.ResourceLabel) (int64, error) {
@ -97,7 +102,7 @@ func (bm *BaseManager) RemoveLabelFromResource(resourceType string, resourceIDOr
}
// GetLabelsOfResource is the implementation of same method in Manager interface.
func (bm *BaseManager) GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*models.Label, error) {
func (bm *BaseManager) GetLabelsOfResource(resourceType string, resourceIDOrName interface{}) ([]*model.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)
@ -107,30 +112,28 @@ func (bm *BaseManager) GetLabelsOfResource(resourceType string, resourceIDOrName
}
// Exists is the implementation of same method in Manager interface.
func (bm *BaseManager) Exists(labelID int64) (*models.Label, error) {
label, err := dao.GetLabel(labelID)
func (bm *BaseManager) Exists(labelID int64) (*model.Label, error) {
label, err := bm.LabelMgr.Get(orm.Context(), labelID)
if err != nil {
if errors.IsErr(err, errors.NotFoundCode) {
return nil, NewErrLabelNotFound(labelID, "", 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)
func (bm *BaseManager) Validate(labelID int64, projectID int64) (*model.Label, error) {
label, err := bm.LabelMgr.Get(orm.Context(), labelID)
if err != nil {
if errors.IsErr(err, errors.NotFoundCode) {
return nil, NewErrLabelNotFound(labelID, "", 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")
}

View File

@ -12,52 +12,55 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package label
package dao
import (
"context"
beego_orm "github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/label/model"
"time"
)
func init() {
beego_orm.RegisterModel(&Reference{})
}
// DAO is the data access object interface for label
type DAO interface {
// Get the specified label
Get(ctx context.Context, id int64) (label *models.Label, err error)
Get(ctx context.Context, id int64) (label *model.Label, err error)
// Create the label
Create(ctx context.Context, label *models.Label) (id int64, err error)
Create(ctx context.Context, label *model.Label) (id int64, err error)
// Count returns the total count of Labels according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// Update the label
Update(ctx context.Context, label *model.Label) error
// Delete the label
Delete(ctx context.Context, id int64) (err error)
// List ...
List(ctx context.Context, query *q.Query) ([]*model.Label, error)
// List labels that added to the artifact specified by the ID
ListByArtifact(ctx context.Context, artifactID int64) (labels []*models.Label, err error)
ListByArtifact(ctx context.Context, artifactID int64) (labels []*model.Label, err error)
// Create label reference
CreateReference(ctx context.Context, reference *Reference) (id int64, err error)
CreateReference(ctx context.Context, reference *model.Reference) (id int64, err error)
// Delete the label reference specified by ID
DeleteReference(ctx context.Context, id int64) (err error)
// Delete label references specified by query
DeleteReferences(ctx context.Context, query *q.Query) (n int64, err error)
}
// NewDAO creates an instance of the default DAO
func NewDAO() DAO {
// New creates an instance of the default DAO
func New() DAO {
return &defaultDAO{}
}
type defaultDAO struct{}
func (d *defaultDAO) Get(ctx context.Context, id int64) (*models.Label, error) {
func (d *defaultDAO) Get(ctx context.Context, id int64) (*model.Label, error) {
ormer, err := orm.FromContext(ctx)
if err != nil {
return nil, err
}
label := &models.Label{
label := &model.Label{
ID: id,
}
if err = ormer.Read(label); err != nil {
@ -69,7 +72,7 @@ func (d *defaultDAO) Get(ctx context.Context, id int64) (*models.Label, error) {
return label, nil
}
func (d *defaultDAO) Create(ctx context.Context, label *models.Label) (int64, error) {
func (d *defaultDAO) Create(ctx context.Context, label *model.Label) (int64, error) {
ormer, err := orm.FromContext(ctx)
if err != nil {
return 0, err
@ -83,12 +86,36 @@ func (d *defaultDAO) Create(ctx context.Context, label *models.Label) (int64, er
return id, err
}
func (d *defaultDAO) Count(ctx context.Context, query *q.Query) (int64, error) {
qs, err := orm.QuerySetterForCount(ctx, &model.Label{}, query)
if err != nil {
return 0, err
}
return qs.Count()
}
func (d *defaultDAO) Update(ctx context.Context, label *model.Label) error {
ormer, err := orm.FromContext(ctx)
if err != nil {
return err
}
label.UpdateTime = time.Now()
n, err := ormer.Update(label)
if n == 0 {
if e := orm.AsConflictError(err, "label %s already exists", label.Name); e != nil {
err = e
}
return err
}
return err
}
func (d *defaultDAO) Delete(ctx context.Context, id int64) error {
ormer, err := orm.FromContext(ctx)
if err != nil {
return err
}
n, err := ormer.Delete(&models.Label{
n, err := ormer.Delete(&model.Label{
ID: id,
})
if err != nil {
@ -100,7 +127,19 @@ func (d *defaultDAO) Delete(ctx context.Context, id int64) error {
return nil
}
func (d *defaultDAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*models.Label, error) {
func (d *defaultDAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) {
robots := []*model.Label{}
qs, err := orm.QuerySetter(ctx, &model.Label{}, query)
if err != nil {
return nil, err
}
if _, err = qs.All(&robots); err != nil {
return nil, err
}
return robots, nil
}
func (d *defaultDAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) {
sql := `select label.* from harbor_label label
join label_reference ref on label.id = ref.label_id
where ref.artifact_id = ?`
@ -108,13 +147,13 @@ func (d *defaultDAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*m
if err != nil {
return nil, err
}
labels := []*models.Label{}
labels := []*model.Label{}
if _, err = ormer.Raw(sql, artifactID).QueryRows(&labels); err != nil {
return nil, err
}
return labels, nil
}
func (d *defaultDAO) CreateReference(ctx context.Context, ref *Reference) (int64, error) {
func (d *defaultDAO) CreateReference(ctx context.Context, ref *model.Reference) (int64, error) {
ormer, err := orm.FromContext(ctx)
if err != nil {
return 0, err
@ -137,7 +176,7 @@ func (d *defaultDAO) DeleteReference(ctx context.Context, id int64) error {
if err != nil {
return err
}
n, err := ormer.Delete(&Reference{
n, err := ormer.Delete(&model.Reference{
ID: id,
})
if err != nil {
@ -150,7 +189,7 @@ func (d *defaultDAO) DeleteReference(ctx context.Context, id int64) error {
}
func (d *defaultDAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, error) {
qs, err := orm.QuerySetter(ctx, &Reference{}, query)
qs, err := orm.QuerySetter(ctx, &model.Reference{}, query)
if err != nil {
return 0, err
}

View File

@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package label
package dao
import (
"context"
beegoorm "github.com/astaxie/beego/orm"
common_dao "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
artdao "github.com/goharbor/harbor/src/pkg/artifact/dao"
"github.com/goharbor/harbor/src/pkg/label/model"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite"
"testing"
@ -46,7 +46,7 @@ func (l *labelDaoTestSuite) SetupSuite() {
}
func (l *labelDaoTestSuite) SetupTest() {
id, err := l.dao.Create(l.ctx, &models.Label{
id, err := l.dao.Create(l.ctx, &model.Label{
Name: "label_for_label_dao_test_suite",
Scope: "g",
})
@ -64,7 +64,7 @@ func (l *labelDaoTestSuite) SetupTest() {
l.Require().Nil(err)
l.artID = id
id, err = l.dao.CreateReference(l.ctx, &Reference{
id, err = l.dao.CreateReference(l.ctx, &model.Reference{
LabelID: l.id,
ArtifactID: l.artID,
})
@ -99,7 +99,7 @@ func (l *labelDaoTestSuite) TestCreate() {
// happy pass is covered by SetupTest
// conflict
_, err := l.dao.Create(l.ctx, &models.Label{
_, err := l.dao.Create(l.ctx, &model.Label{
Name: "label_for_label_dao_test_suite",
Scope: "g",
})
@ -127,7 +127,7 @@ func (l *labelDaoTestSuite) TestCreateReference() {
// happy pass is covered by SetupTest
// conflict
_, err := l.dao.CreateReference(l.ctx, &Reference{
_, err := l.dao.CreateReference(l.ctx, &model.Reference{
LabelID: l.id,
ArtifactID: l.artID,
})
@ -135,7 +135,7 @@ func (l *labelDaoTestSuite) TestCreateReference() {
l.True(errors.IsErr(err, errors.ConflictCode))
// violating foreign key constraint: the label that the ref tries to refer doesn't exist
_, err = l.dao.CreateReference(l.ctx, &Reference{
_, err = l.dao.CreateReference(l.ctx, &model.Reference{
LabelID: 1000,
ArtifactID: l.artID,
})
@ -143,7 +143,7 @@ func (l *labelDaoTestSuite) TestCreateReference() {
l.True(errors.IsErr(err, errors.NotFoundCode))
// violating foreign key constraint: the artifact that the ref tries to refer doesn't exist
_, err = l.dao.CreateReference(l.ctx, &Reference{
_, err = l.dao.CreateReference(l.ctx, &model.Reference{
LabelID: l.id,
ArtifactID: 1000,
})

View File

@ -16,9 +16,10 @@ package label
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/label/dao"
"github.com/goharbor/harbor/src/pkg/label/model"
"time"
)
@ -27,10 +28,21 @@ var Mgr = New()
// Manager manages the labels and references between label and resource
type Manager interface {
// Create the label
Create(ctx context.Context, label *model.Label) (id int64, err error)
// Get the label specified by ID
Get(ctx context.Context, id int64) (label *models.Label, err error)
Get(ctx context.Context, id int64) (label *model.Label, err error)
// Count returns the total count of Labels according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// Update the label
Update(ctx context.Context, label *model.Label) error
// Delete the label
Delete(ctx context.Context, id int64) (err error)
// List ...
List(ctx context.Context, query *q.Query) ([]*model.Label, error)
// List labels that added to the artifact specified by the ID
ListByArtifact(ctx context.Context, artifactID int64) (labels []*models.Label, err error)
ListByArtifact(ctx context.Context, artifactID int64) (labels []*model.Label, err error)
// Add label to the artifact specified the ID
AddTo(ctx context.Context, labelID int64, artifactID int64) (err error)
// Remove the label added to the artifact specified by the ID
@ -44,25 +56,45 @@ type Manager interface {
// New creates an instance of the default label manager
func New() Manager {
return &manager{
dao: &defaultDAO{},
dao: dao.New(),
}
}
type manager struct {
dao DAO
dao dao.DAO
}
func (m *manager) Get(ctx context.Context, id int64) (*models.Label, error) {
func (m *manager) Create(ctx context.Context, label *model.Label) (id int64, err error) {
return m.dao.Create(ctx, label)
}
func (m *manager) Get(ctx context.Context, id int64) (*model.Label, error) {
return m.dao.Get(ctx, id)
}
func (m *manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*models.Label, error) {
func (m *manager) Count(ctx context.Context, query *q.Query) (total int64, err error) {
return m.dao.Count(ctx, query)
}
func (m *manager) Update(ctx context.Context, label *model.Label) error {
return m.dao.Update(ctx, label)
}
func (m *manager) Delete(ctx context.Context, id int64) error {
return m.dao.Delete(ctx, id)
}
func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.Label, error) {
return m.dao.List(ctx, query)
}
func (m *manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) {
return m.dao.ListByArtifact(ctx, artifactID)
}
func (m *manager) AddTo(ctx context.Context, labelID int64, artifactID int64) error {
now := time.Now()
_, err := m.dao.CreateReference(ctx, &Reference{
_, err := m.dao.CreateReference(ctx, &model.Reference{
LabelID: labelID,
ArtifactID: artifactID,
CreationTime: now,

View File

@ -16,112 +16,118 @@ package label
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/stretchr/testify/mock"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/label/dao"
"github.com/stretchr/testify/suite"
"testing"
)
type fakeDao struct {
mock.Mock
}
func (f *fakeDao) Get(ctx context.Context, id int64) (*models.Label, error) {
args := f.Called()
var label *models.Label
if args.Get(0) != nil {
label = args.Get(0).(*models.Label)
}
return label, args.Error(1)
}
func (f *fakeDao) Create(ctx context.Context, label *models.Label) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
func (f *fakeDao) Delete(ctx context.Context, id int64) error {
args := f.Called()
return args.Error(0)
}
func (f *fakeDao) ListByArtifact(ctx context.Context, artifactID int64) ([]*models.Label, error) {
args := f.Called()
var labels []*models.Label
if args.Get(0) != nil {
labels = args.Get(0).([]*models.Label)
}
return labels, args.Error(1)
}
func (f *fakeDao) CreateReference(ctx context.Context, reference *Reference) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
func (f *fakeDao) DeleteReference(ctx context.Context, id int64) error {
args := f.Called()
return args.Error(0)
}
func (f *fakeDao) DeleteReferences(ctx context.Context, query *q.Query) (int64, error) {
args := f.Called()
return int64(args.Int(0)), args.Error(1)
}
type managerTestSuite struct {
suite.Suite
mgr *manager
dao *fakeDao
dao *dao.DAO
}
func (m *managerTestSuite) SetupTest() {
m.dao = &fakeDao{}
m.dao = &dao.DAO{}
m.mgr = &manager{
dao: m.dao,
}
}
func (m *managerTestSuite) TestGet() {
m.dao.On("Get").Return(nil, nil)
_, err := m.mgr.Get(nil, 1)
m.Require().Nil(err)
func (m *managerTestSuite) TestCreate() {
m.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
_, err := m.mgr.Create(context.Background(), &model.Label{})
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestListArtifact() {
m.dao.On("ListByArtifact").Return(nil, nil)
_, err := m.mgr.ListByArtifact(nil, 1)
m.Require().Nil(err)
func (m *managerTestSuite) TestCount() {
m.dao.On("Count", mock.Anything, mock.Anything).Return(int64(1), nil)
n, err := m.mgr.Count(context.Background(), nil)
m.Nil(err)
m.Equal(int64(1), n)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestDelete() {
m.dao.On("Delete", mock.Anything, mock.Anything).Return(nil)
err := m.mgr.Delete(context.Background(), 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestGet() {
m.dao.On("Get", mock.Anything, mock.Anything).Return(&model.Label{
ID: 1,
Name: "label",
}, nil)
label, err := m.mgr.Get(context.Background(), 1)
m.Nil(err)
m.Equal("label", label.Name)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestUpdate() {
m.dao.On("Update", mock.Anything, mock.Anything).Return(nil)
err := m.mgr.Update(context.Background(), &model.Label{})
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestListByArtifact() {
m.dao.On("ListByArtifact", mock.Anything, mock.Anything).Return([]*model.Label{
{
ID: 1,
Name: "label",
},
}, nil)
rpers, err := m.mgr.ListByArtifact(context.Background(), 1)
m.Nil(err)
m.Equal(1, len(rpers))
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestList() {
m.dao.On("List", mock.Anything, mock.Anything).Return([]*model.Label{
{
ID: 1,
Name: "label",
},
}, nil)
rpers, err := m.mgr.List(context.Background(), nil)
m.Nil(err)
m.Equal(1, len(rpers))
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestAddTo() {
m.dao.On("CreateReference").Return(1, nil)
err := m.mgr.AddTo(nil, 1, 1)
m.Require().Nil(err)
m.dao.On("CreateReference", mock.Anything, mock.Anything).Return(int64(1), nil)
err := m.mgr.AddTo(context.Background(), 1, 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestRemoveFrom() {
// success
m.dao.On("DeleteReferences").Return(1, nil)
err := m.mgr.RemoveFrom(nil, 1, 1)
m.Require().Nil(err)
// reset mock
m.SetupTest()
// not found
m.dao.On("DeleteReferences").Return(0, nil)
err = m.mgr.RemoveFrom(nil, 1, 1)
m.Require().NotNil(err)
m.True(errors.IsErr(err, errors.NotFoundCode))
m.dao.On("DeleteReferences", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
err := m.mgr.RemoveFrom(context.Background(), 1, 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestRemoveAllFrom() {
m.dao.On("DeleteReferences").Return(2, nil)
err := m.mgr.RemoveAllFrom(nil, 1)
m.Require().Nil(err)
m.dao.On("DeleteReferences", mock.Anything, mock.Anything).Return(int64(1), nil)
err := m.mgr.RemoveAllFrom(context.Background(), 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func (m *managerTestSuite) TestRemoveFromAllArtifacts() {
m.dao.On("DeleteReferences").Return(2, nil)
err := m.mgr.RemoveFromAllArtifacts(nil, 1)
m.Require().Nil(err)
m.dao.On("DeleteReferences", mock.Anything, mock.Anything).Return(int64(1), nil)
err := m.mgr.RemoveFromAllArtifacts(context.Background(), 1)
m.Nil(err)
m.dao.AssertExpectations(m.T())
}
func TestManager(t *testing.T) {

View File

@ -1,31 +0,0 @@
// Copyright Project Harbor Authors
//
// 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 label
import "time"
// Reference is the reference of label and artifact
type Reference struct {
ID int64 `orm:"pk;auto;column(id)"`
LabelID int64 `orm:"column(label_id)"`
ArtifactID int64 `orm:"column(artifact_id)"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add"`
UpdateTime time.Time `orm:"column(update_time);auto_now"`
}
// TableName defines the database table name
func (r *Reference) TableName() string {
return "label_reference"
}

View File

@ -0,0 +1,80 @@
package model
import (
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/lib/errors"
"time"
"github.com/astaxie/beego/orm"
)
func init() {
orm.RegisterModel(&Label{})
orm.RegisterModel(&Reference{})
}
// Label holds information used for a label
type Label struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Name string `orm:"column(name)" json:"name"`
Description string `orm:"column(description)" json:"description"`
Color string `orm:"column(color)" json:"color"`
Level string `orm:"column(level)" json:"-"`
Scope string `orm:"column(scope)" json:"scope"`
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
Deleted bool `orm:"column(deleted)" json:"deleted"`
}
// Valid ...
func (l *Label) Valid() error {
if len(l.Name) == 0 {
return errors.New("cannot be empty").WithCode(errors.BadRequestCode)
}
if len(l.Name) > 128 {
return errors.New("max length is 128").WithCode(errors.BadRequestCode)
}
if l.Scope != common.LabelScopeGlobal && l.Scope != common.LabelScopeProject {
return errors.New(nil).WithMessage("invalid: %s", l.Scope).WithCode(errors.BadRequestCode)
} else if l.Scope == common.LabelScopeProject && l.ProjectID <= 0 {
return errors.New(nil).WithMessage("invalid: %d", l.ProjectID).WithCode(errors.BadRequestCode)
}
return nil
}
// TableName ...
func (l *Label) TableName() string {
return "harbor_label"
}
// Reference is the reference of label and artifact
type Reference struct {
ID int64 `orm:"pk;auto;column(id)"`
LabelID int64 `orm:"column(label_id)"`
ArtifactID int64 `orm:"column(artifact_id)"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add"`
UpdateTime time.Time `orm:"column(update_time);auto_now"`
}
// TableName defines the database table name
func (r *Reference) TableName() string {
return "label_reference"
}
// ResourceLabel records the relationship between resource and label
type ResourceLabel struct {
ID int64 `orm:"pk;auto;column(id)"`
LabelID int64 `orm:"column(label_id)"`
ResourceID int64 `orm:"column(resource_id)"`
ResourceName string `orm:"column(resource_name)"`
ResourceType string `orm:"column(resource_type)"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add"`
UpdateTime time.Time `orm:"column(update_time);auto_now"`
}
// TableName ...
func (r *ResourceLabel) TableName() string {
return "harbor_resource_label"
}

View File

@ -0,0 +1,87 @@
// Copyright Project Harbor Authors
//
// 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 model
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidOfLabel(t *testing.T) {
cases := []struct {
label *Label
hasError bool
}{
{
label: &Label{
Name: "",
},
hasError: true,
},
{
label: &Label{
Name: "test",
Scope: "",
},
hasError: true,
},
{
label: &Label{
Name: "test",
Scope: "invalid_scope",
},
hasError: true,
},
{
label: &Label{
Name: "test",
Scope: "g",
},
hasError: false,
},
{
label: &Label{
Name: "test",
Scope: "p",
},
hasError: true,
},
{
label: &Label{
Name: "test",
Scope: "p",
ProjectID: -1,
},
hasError: true,
},
{
label: &Label{
Name: "test",
Scope: "p",
ProjectID: 1,
},
hasError: false,
},
}
for _, c := range cases {
if c.hasError {
assert.NotNil(t, c.label.Valid())
} else {
assert.Nil(t, c.label.Valid())
}
}
}

View File

@ -49,6 +49,7 @@ func New() http.Handler {
SysteminfoAPI: newSystemInfoAPI(),
PingAPI: newPingAPI(),
LdapAPI: newLdapAPI(),
LabelAPI: newLabelAPI(),
GCAPI: newGCAPI(),
QuotaAPI: newQuotaAPI(),
RetentionAPI: newRetentionAPI(),

View File

@ -0,0 +1,189 @@
package handler
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/label"
pkg_model "github.com/goharbor/harbor/src/pkg/label/model"
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
"github.com/goharbor/harbor/src/server/v2.0/models"
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/label"
"strings"
)
func newLabelAPI() *labelAPI {
return &labelAPI{
labelMgr: label.Mgr,
projectCtl: project.Ctl,
}
}
type labelAPI struct {
BaseAPI
labelMgr label.Manager
projectCtl project.Controller
}
func (lAPI *labelAPI) CreateLabel(ctx context.Context, params operation.CreateLabelParams) middleware.Responder {
label := &pkg_model.Label{}
lib.JSONCopy(label, params.Label)
label.Level = common.LabelLevelUser
if label.Scope == common.LabelScopeGlobal {
label.ProjectID = 0
}
if err := lAPI.requireAccess(ctx, label, rbac.ActionCreate); err != nil {
return lAPI.SendError(ctx, err)
}
id, err := lAPI.labelMgr.Create(ctx, label)
if err != nil {
return lAPI.SendError(ctx, err)
}
location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), id)
return operation.NewCreateLabelCreated().WithLocation(location)
}
func (lAPI *labelAPI) GetLabelByID(ctx context.Context, params operation.GetLabelByIDParams) middleware.Responder {
label, err := lAPI.labelMgr.Get(ctx, params.LabelID)
if err != nil {
return lAPI.SendError(ctx, err)
}
if label == nil || label.Deleted {
return lAPI.SendError(ctx, errors.New(nil).WithMessage("label %d not found", params.LabelID).WithCode(errors.NotFoundCode))
}
if err := lAPI.requireAccess(ctx, label, rbac.ActionRead); err != nil {
return lAPI.SendError(ctx, err)
}
return operation.NewGetLabelByIDOK().WithPayload(model.NewLabel(label).ToSwagger())
}
func (lAPI *labelAPI) ListLabels(ctx context.Context, params operation.ListLabelsParams) middleware.Responder {
query, err := lAPI.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
if err != nil {
return lAPI.SendError(ctx, err)
}
scope := lib.StringValue(params.Scope)
if scope != common.LabelScopeGlobal && scope != common.LabelScopeProject {
return lAPI.SendError(ctx, errors.New(nil).WithMessage("invalid scope: %s", scope).WithCode(errors.BadRequestCode))
}
query.Keywords["Level"] = common.LabelLevelUser
query.Keywords["Scope"] = scope
name := lib.StringValue(params.Name)
if name != "" {
query.Keywords["name"] = &q.FuzzyMatchValue{Value: name}
}
if scope == common.LabelScopeProject {
pid := lib.Int64Value(params.ProjectID)
if pid == 0 {
return lAPI.SendError(ctx, errors.BadRequestError(nil).WithMessage("must with project ID when to query project labels"))
}
if err := lAPI.RequireProjectAccess(ctx, pid, rbac.ActionList, rbac.ResourceLabel); err != nil {
return lAPI.SendError(ctx, err)
}
query.Keywords["ProjectID"] = pid
}
results := make([]*models.Label, 0)
total, err := lAPI.labelMgr.Count(ctx, query)
if err != nil {
return lAPI.SendError(ctx, err)
}
if total > 0 {
labels, err := lAPI.labelMgr.List(ctx, query)
if err != nil {
return lAPI.SendError(ctx, err)
}
for _, l := range labels {
results = append(results, model.NewLabel(l).ToSwagger())
}
}
return operation.NewListLabelsOK().
WithXTotalCount(total).
WithLink(lAPI.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
WithPayload(results)
}
func (lAPI *labelAPI) UpdateLabel(ctx context.Context, params operation.UpdateLabelParams) middleware.Responder {
labelData := &pkg_model.Label{}
lib.JSONCopy(labelData, params.Label)
label, err := lAPI.labelMgr.Get(ctx, params.LabelID)
if err != nil {
return lAPI.SendError(ctx, err)
}
if label == nil || label.Deleted {
return lAPI.SendError(ctx, errors.New(nil).WithMessage("label %d not found", params.LabelID).WithCode(errors.NotFoundCode))
}
if err := lAPI.requireAccess(ctx, label, rbac.ActionUpdate); err != nil {
return lAPI.SendError(ctx, err)
}
label.Name = labelData.Name
label.Description = labelData.Description
label.Color = labelData.Color
if err := label.Valid(); err != nil {
return lAPI.SendError(ctx, err)
}
if err := lAPI.labelMgr.Update(ctx, label); err != nil {
return lAPI.SendError(ctx, err)
}
return operation.NewUpdateLabelOK()
}
func (lAPI *labelAPI) DeleteLabel(ctx context.Context, params operation.DeleteLabelParams) middleware.Responder {
label, err := lAPI.labelMgr.Get(ctx, params.LabelID)
if err != nil {
return lAPI.SendError(ctx, err)
}
if err := lAPI.requireAccess(ctx, label, rbac.ActionDelete); err != nil {
return lAPI.SendError(ctx, err)
}
id := label.ID
// TODO remove this step once chart-museum is removed.
if err := dao.DeleteResourceLabelByLabel(id); err != nil {
return lAPI.SendError(ctx, err)
}
if err := lAPI.labelMgr.RemoveFromAllArtifacts(ctx, id); err != nil {
return lAPI.SendError(ctx, err)
}
if err := lAPI.labelMgr.Delete(ctx, id); err != nil {
return lAPI.SendError(ctx, err)
}
return operation.NewDeleteLabelOK()
}
func (lAPI *labelAPI) requireAccess(ctx context.Context, label *pkg_model.Label, action rbac.Action, subresources ...rbac.Resource) error {
switch label.Scope {
case common.LabelScopeGlobal:
resource := system.NewNamespace().Resource(rbac.ResourceLabel)
return lAPI.RequireSystemAccess(ctx, action, resource)
case common.LabelScopeProject:
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceLabel)
}
return lAPI.RequireProjectAccess(ctx, label.ProjectID, action, subresources...)
}
return errors.New("unsupported label scope").WithCode(errors.BadRequestCode)
}

View File

@ -2,13 +2,13 @@ package model
import (
"github.com/go-openapi/strfmt"
common_models "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/label/model"
"github.com/goharbor/harbor/src/server/v2.0/models"
)
// Label model
type Label struct {
*common_models.Label
*model.Label
}
// ToSwagger converts the label to the swagger model
@ -26,6 +26,6 @@ func (l *Label) ToSwagger() *models.Label {
}
// NewLabel ...
func NewLabel(l *common_models.Label) *Label {
func NewLabel(l *model.Label) *Label {
return &Label{Label: l}
}

View File

@ -27,8 +27,6 @@ func registerLegacyRoutes() {
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
beego.Router("/api/"+version+"/statistics", &api.StatisticAPI{})
beego.Router("/api/"+version+"/labels", &api.LabelAPI{}, "post:Post;get:List")
beego.Router("/api/"+version+"/labels/:id([0-9]+)", &api.LabelAPI{}, "get:Get;put:Put;delete:Delete")
// APIs for chart repository
if config.WithChartMuseum() {

View File

@ -0,0 +1,213 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package dao
import (
context "context"
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/pkg/label/model"
q "github.com/goharbor/harbor/src/lib/q"
)
// DAO is an autogenerated mock type for the DAO type
type DAO struct {
mock.Mock
}
// Count provides a mock function with given fields: ctx, query
func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, label
func (_m *DAO) Create(ctx context.Context, label *model.Label) (int64, error) {
ret := _m.Called(ctx, label)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *model.Label) int64); ok {
r0 = rf(ctx, label)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *model.Label) error); ok {
r1 = rf(ctx, label)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateReference provides a mock function with given fields: ctx, reference
func (_m *DAO) CreateReference(ctx context.Context, reference *model.Reference) (int64, error) {
ret := _m.Called(ctx, reference)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *model.Reference) int64); ok {
r0 = rf(ctx, reference)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *model.Reference) error); ok {
r1 = rf(ctx, reference)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, id
func (_m *DAO) Delete(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteReference provides a mock function with given fields: ctx, id
func (_m *DAO) DeleteReference(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteReferences provides a mock function with given fields: ctx, query
func (_m *DAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: ctx, id
func (_m *DAO) Get(ctx context.Context, id int64) (*model.Label, error) {
ret := _m.Called(ctx, id)
var r0 *model.Label
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Label); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, query
func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) {
ret := _m.Called(ctx, query)
var r0 []*model.Label
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Label); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListByArtifact provides a mock function with given fields: ctx, artifactID
func (_m *DAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) {
ret := _m.Called(ctx, artifactID)
var r0 []*model.Label
if rf, ok := ret.Get(0).(func(context.Context, int64) []*model.Label); ok {
r0 = rf(ctx, artifactID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, artifactID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, label
func (_m *DAO) Update(ctx context.Context, label *model.Label) error {
ret := _m.Called(ctx, label)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok {
r0 = rf(ctx, label)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -1,70 +1,213 @@
// Copyright Project Harbor Authors
//
// 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.
// Code generated by mockery v2.1.0. DO NOT EDIT.
package label
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/mock"
context "context"
mock "github.com/stretchr/testify/mock"
model "github.com/goharbor/harbor/src/pkg/label/model"
q "github.com/goharbor/harbor/src/lib/q"
)
// FakeManager is a fake label manager that implement the src/pkg/label.Manager interface
type FakeManager struct {
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// Get ...
func (f *FakeManager) Get(ctx context.Context, id int64) (*models.Label, error) {
args := f.Called()
var label *models.Label
if args.Get(0) != nil {
label = args.Get(0).(*models.Label)
// AddTo provides a mock function with given fields: ctx, labelID, artifactID
func (_m *Manager) AddTo(ctx context.Context, labelID int64, artifactID int64) error {
ret := _m.Called(ctx, labelID, artifactID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, labelID, artifactID)
} else {
r0 = ret.Error(0)
}
return label, args.Error(1)
return r0
}
// ListByArtifact ...
func (f *FakeManager) ListByArtifact(ctx context.Context, artifactID int64) ([]*models.Label, error) {
args := f.Called()
var labels []*models.Label
if args.Get(0) != nil {
labels = args.Get(0).([]*models.Label)
// Count provides a mock function with given fields: ctx, query
func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) {
ret := _m.Called(ctx, query)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Get(0).(int64)
}
return labels, args.Error(1)
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddTo ...
func (f *FakeManager) AddTo(ctx context.Context, labelID int64, artifactID int64) error {
args := f.Called()
return args.Error(0)
// Create provides a mock function with given fields: ctx, _a1
func (_m *Manager) Create(ctx context.Context, _a1 *model.Label) (int64, error) {
ret := _m.Called(ctx, _a1)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, *model.Label) int64); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *model.Label) error); ok {
r1 = rf(ctx, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveFrom ...
func (f *FakeManager) RemoveFrom(ctx context.Context, labelID int64, artifactID int64) error {
args := f.Called()
return args.Error(0)
// Delete provides a mock function with given fields: ctx, id
func (_m *Manager) Delete(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveAllFrom ...
func (f *FakeManager) RemoveAllFrom(ctx context.Context, artifactID int64) error {
args := f.Called()
return args.Error(0)
// Get provides a mock function with given fields: ctx, id
func (_m *Manager) Get(ctx context.Context, id int64) (*model.Label, error) {
ret := _m.Called(ctx, id)
var r0 *model.Label
if rf, ok := ret.Get(0).(func(context.Context, int64) *model.Label); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveFromAllArtifacts ...
func (f *FakeManager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) error {
args := f.Called()
return args.Error(0)
// List provides a mock function with given fields: ctx, query
func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Label, error) {
ret := _m.Called(ctx, query)
var r0 []*model.Label
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.Label); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListByArtifact provides a mock function with given fields: ctx, artifactID
func (_m *Manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) {
ret := _m.Called(ctx, artifactID)
var r0 []*model.Label
if rf, ok := ret.Get(0).(func(context.Context, int64) []*model.Label); ok {
r0 = rf(ctx, artifactID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Label)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, artifactID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveAllFrom provides a mock function with given fields: ctx, artifactID
func (_m *Manager) RemoveAllFrom(ctx context.Context, artifactID int64) error {
ret := _m.Called(ctx, artifactID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, artifactID)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveFrom provides a mock function with given fields: ctx, labelID, artifactID
func (_m *Manager) RemoveFrom(ctx context.Context, labelID int64, artifactID int64) error {
ret := _m.Called(ctx, labelID, artifactID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, labelID, artifactID)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveFromAllArtifacts provides a mock function with given fields: ctx, labelID
func (_m *Manager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) error {
ret := _m.Called(ctx, labelID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, labelID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: ctx, _a1
func (_m *Manager) Update(ctx context.Context, _a1 *model.Label) error {
ret := _m.Called(ctx, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -47,3 +47,5 @@ package pkg
//go:generate mockery --case snake --dir ../../pkg/reg/adapter --name Adapter --output ./reg/adapter --outpkg adapter
//go:generate mockery --case snake --dir ../../pkg/replication/dao --name DAO --output ./replication/dao --outpkg dao
//go:generate mockery --case snake --dir ../../pkg/replication --name Manager --output ./replication --outpkg manager
//go:generate mockery --case snake --dir ../../pkg/label --name Manager --output ./label --outpkg label
//go:generate mockery --case snake --dir ../../pkg/label/dao --name DAO --output ./label/dao --outpkg dao

View File

@ -31,7 +31,7 @@ def _create_client(server, credential, debug, api_type="products"):
cfg = None
if api_type in ('projectv2', 'artifact', 'repository', 'scanner', 'scan', 'scanall', 'preheat', 'quota',
'replication', 'registry', 'robot', 'gc', 'retention', 'immutable', 'system_cve_allowlist',
'configure', 'user', 'member', 'health'):
'configure', 'user', 'member', 'health', 'label'):
cfg = v2_swagger_client.Configuration()
else:
cfg = swagger_client.Configuration()
@ -72,6 +72,7 @@ def _create_client(server, credential, debug, api_type="products"):
"immutable": v2_swagger_client.ImmutableApi(v2_swagger_client.ApiClient(cfg)),
"system_cve_allowlist": v2_swagger_client.SystemCVEAllowlistApi(v2_swagger_client.ApiClient(cfg)),
"configure": v2_swagger_client.ConfigureApi(v2_swagger_client.ApiClient(cfg)),
"label": v2_swagger_client.LabelApi(v2_swagger_client.ApiClient(cfg)),
"user": v2_swagger_client.UserApi(v2_swagger_client.ApiClient(cfg)),
"member": v2_swagger_client.MemberApi(v2_swagger_client.ApiClient(cfg)),
"health": v2_swagger_client.HealthApi(v2_swagger_client.ApiClient(cfg)),

View File

@ -2,21 +2,24 @@
import sys
import base
import swagger_client
from swagger_client.rest import ApiException
import v2_swagger_client
from v2_swagger_client.rest import ApiException
class Label(base.Base, object):
def __init__(self):
super(Label,self).__init__(api_type = "label")
class Label(base.Base):
def create_label(self, name=None, desc="", color="", scope="g",
project_id=0, expect_status_code = 201, **kwargs):
if name is None:
name = base._random_name("label")
label = swagger_client.Label(name=name,
label = v2_swagger_client.Label(name=name,
description=desc, color=color,
scope=scope, project_id=project_id)
client = self._get_client(**kwargs)
try:
_, status_code, header = client.labels_post_with_http_info(label)
_, status_code, header = client.create_label_with_http_info(label)
except ApiException as e:
base._assert_status_code(expect_status_code, e.status)
else:
@ -26,4 +29,4 @@ class Label(base.Base):
def delete_label(self, label_id, **kwargs):
client = self._get_client(**kwargs)
return client.labels_id_delete_with_http_info(int(label_id))
return client.delete_label_with_http_info(int(label_id))