From 6e3c9e29df29e571bbb203975bbfd19facda377c Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Thu, 15 Apr 2021 17:27:58 +0800 Subject: [PATCH] Api refactor label (#14650) * Refactor labl api move to the new program model Signed-off-by: wang yan * continue resolve review comments Signed-off-by: Wang Yan --- api/v2.0/legacy_swagger.yaml | 167 -------- api/v2.0/swagger.yaml | 167 ++++++++ src/chartserver/chart_operator.go | 6 +- src/common/dao/label.go | 111 ----- src/common/dao/label_test.go | 120 ------ src/common/dao/resource_label.go | 7 +- src/common/dao/resource_label_test.go | 8 +- src/common/models/base.go | 1 - src/common/models/label.go | 49 --- src/common/models/label_test.go | 27 +- src/common/models/repo.go | 11 - src/controller/artifact/controller_test.go | 13 +- src/controller/artifact/model.go | 4 +- src/controller/p2p/preheat/enforcer.go | 3 +- src/controller/p2p/preheat/enforcer_test.go | 5 +- src/core/api/chart_label.go | 3 +- src/core/api/chart_label_test.go | 26 +- src/core/api/chart_repository.go | 5 +- src/core/api/harborapi_test.go | 2 - src/core/api/label.go | 310 -------------- src/core/api/label_resource.go | 10 +- src/core/api/label_test.go | 435 -------------------- src/core/label/manager.go | 39 +- src/pkg/label/{ => dao}/dao.go | 83 +++- src/pkg/label/{ => dao}/dao_test.go | 16 +- src/pkg/label/manager.go | 48 ++- src/pkg/label/manager_test.go | 160 +++---- src/pkg/label/model.go | 31 -- src/pkg/label/model/model.go | 80 ++++ src/pkg/label/model/model_test.go | 87 ++++ src/server/v2.0/handler/handler.go | 1 + src/server/v2.0/handler/label.go | 189 +++++++++ src/server/v2.0/handler/model/label.go | 6 +- src/server/v2.0/route/legacy.go | 2 - src/testing/pkg/label/dao/dao.go | 213 ++++++++++ src/testing/pkg/label/manager.go | 239 ++++++++--- src/testing/pkg/pkg.go | 2 + tests/apitests/python/library/base.py | 3 +- tests/apitests/python/library/label.py | 15 +- 39 files changed, 1227 insertions(+), 1477 deletions(-) delete mode 100644 src/common/dao/label.go delete mode 100644 src/common/dao/label_test.go delete mode 100644 src/core/api/label.go delete mode 100644 src/core/api/label_test.go rename src/pkg/label/{ => dao}/dao.go (63%) rename src/pkg/label/{ => dao}/dao_test.go (91%) delete mode 100644 src/pkg/label/model.go create mode 100644 src/pkg/label/model/model.go create mode 100644 src/pkg/label/model/model_test.go create mode 100644 src/server/v2.0/handler/label.go create mode 100644 src/testing/pkg/label/dao/dao.go diff --git a/api/v2.0/legacy_swagger.yaml b/api/v2.0/legacy_swagger.yaml index dacb880e8..7276d2281 100644 --- a/api/v2.0/legacy_swagger.yaml +++ b/api/v2.0/legacy_swagger.yaml @@ -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. diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 99f7ee83c..4a4134bf1 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -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: diff --git a/src/chartserver/chart_operator.go b/src/chartserver/chart_operator.go index 65d403cfe..c4f2ba09a 100644 --- a/src/chartserver/chart_operator.go +++ b/src/chartserver/chart_operator.go @@ -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 diff --git a/src/common/dao/label.go b/src/common/dao/label.go deleted file mode 100644 index a2c46f5bf..000000000 --- a/src/common/dao/label.go +++ /dev/null @@ -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 -} diff --git a/src/common/dao/label_test.go b/src/common/dao/label_test.go deleted file mode 100644 index 69a15c0d7..000000000 --- a/src/common/dao/label_test.go +++ /dev/null @@ -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) -} diff --git a/src/common/dao/resource_label.go b/src/common/dao/resource_label.go index ef10ee990..75549f333 100644 --- a/src/common/dao/resource_label.go +++ b/src/common/dao/resource_label.go @@ -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 } diff --git a/src/common/dao/resource_label_test.go b/src/common/dao/resource_label_test.go index ddc1b03fb..c9ece0448 100644 --- a/src/common/dao/resource_label_test.go +++ b/src/common/dao/resource_label_test.go @@ -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 diff --git a/src/common/models/base.go b/src/common/models/base.go index 603ffc311..ee106cdb1 100644 --- a/src/common/models/base.go +++ b/src/common/models/base.go @@ -25,7 +25,6 @@ func init() { new(Role), new(RepoRecord), new(ProjectMetadata), - new(Label), new(ResourceLabel), new(JobLog), new(OIDCUser), diff --git a/src/common/models/label.go b/src/common/models/label.go index 5be0c4eb5..783db9cc0 100644 --- a/src/common/models/label.go +++ b/src/common/models/label.go @@ -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)"` diff --git a/src/common/models/label_test.go b/src/common/models/label_test.go index 5fdb51d56..b191a73a4 100644 --- a/src/common/models/label_test.go +++ b/src/common/models/label_test.go @@ -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) + } } } diff --git a/src/common/models/repo.go b/src/common/models/repo.go index 1e6528a44..adf18924b 100644 --- a/src/common/models/repo.go +++ b/src/common/models/repo.go @@ -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"` diff --git a/src/controller/artifact/controller_test.go b/src/controller/artifact/controller_test.go index f2ddd785d..f0ebb62fb 100644 --- a/src/controller/artifact/controller_test.go +++ b/src/controller/artifact/controller_test.go @@ -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 = ®istry.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) } diff --git a/src/controller/artifact/model.go b/src/controller/artifact/model.go index 27cb13b24..63aa721e2 100644 --- a/src/controller/artifact/model.go +++ b/src/controller/artifact/model.go @@ -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 diff --git a/src/controller/p2p/preheat/enforcer.go b/src/controller/p2p/preheat/enforcer.go index dff603fd4..fb5f8677c 100644 --- a/src/controller/p2p/preheat/enforcer.go +++ b/src/controller/p2p/preheat/enforcer.go @@ -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) diff --git a/src/controller/p2p/preheat/enforcer_test.go b/src/controller/p2p/preheat/enforcer_test.go index a0d33e418..f00ac04b5 100644 --- a/src/controller/p2p/preheat/enforcer_test.go +++ b/src/controller/p2p/preheat/enforcer_test.go @@ -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", }, { diff --git a/src/core/api/chart_label.go b/src/core/api/chart_label.go index de33497c5..f7790bb48 100644 --- a/src/core/api/chart_label.go +++ b/src/core/api/chart_label.go @@ -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 diff --git a/src/core/api/chart_label_test.go b/src/core/api/chart_label_test.go index 7caa96747..63ff40227 100644 --- a/src/core/api/chart_label_test.go +++ b/src/core/api/chart_label_test.go @@ -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) } diff --git a/src/core/api/chart_repository.go b/src/core/api/chart_repository.go index 245c1373d..35a4b064d 100755 --- a/src/core/api/chart_repository.go +++ b/src/core/api/chart_repository.go @@ -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 { diff --git a/src/core/api/harborapi_test.go b/src/core/api/harborapi_test.go index 4f031bad6..9670c5294 100644 --- a/src/core/api/harborapi_test.go +++ b/src/core/api/harborapi_test.go @@ -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{} diff --git a/src/core/api/label.go b/src/core/api/label.go deleted file mode 100644 index 1fcd035cd..000000000 --- a/src/core/api/label.go +++ /dev/null @@ -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 - } -} diff --git a/src/core/api/label_resource.go b/src/core/api/label_resource.go index 807b11029..c63f46ad8 100644 --- a/src/core/api/label_resource.go +++ b/src/core/api/label_resource.go @@ -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 diff --git a/src/core/api/label_test.go b/src/core/api/label_test.go deleted file mode 100644 index c0c43b519..000000000 --- a/src/core/api/label_test.go +++ /dev/null @@ -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...) -} diff --git a/src/core/label/manager.go b/src/core/label/manager.go index 7ac5a4d3c..6a3d02e1b 100644 --- a/src/core/label/manager.go +++ b/src/core/label/manager.go @@ -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") } diff --git a/src/pkg/label/dao.go b/src/pkg/label/dao/dao.go similarity index 63% rename from src/pkg/label/dao.go rename to src/pkg/label/dao/dao.go index 172b5de97..a9151d37e 100644 --- a/src/pkg/label/dao.go +++ b/src/pkg/label/dao/dao.go @@ -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 } diff --git a/src/pkg/label/dao_test.go b/src/pkg/label/dao/dao_test.go similarity index 91% rename from src/pkg/label/dao_test.go rename to src/pkg/label/dao/dao_test.go index fa0343731..0df550015 100644 --- a/src/pkg/label/dao_test.go +++ b/src/pkg/label/dao/dao_test.go @@ -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, }) diff --git a/src/pkg/label/manager.go b/src/pkg/label/manager.go index 0992792f8..7cd4508c5 100644 --- a/src/pkg/label/manager.go +++ b/src/pkg/label/manager.go @@ -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, diff --git a/src/pkg/label/manager_test.go b/src/pkg/label/manager_test.go index 11636578e..f4cf820af 100644 --- a/src/pkg/label/manager_test.go +++ b/src/pkg/label/manager_test.go @@ -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) { diff --git a/src/pkg/label/model.go b/src/pkg/label/model.go deleted file mode 100644 index c0b97bd1b..000000000 --- a/src/pkg/label/model.go +++ /dev/null @@ -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" -} diff --git a/src/pkg/label/model/model.go b/src/pkg/label/model/model.go new file mode 100644 index 000000000..18be0aa55 --- /dev/null +++ b/src/pkg/label/model/model.go @@ -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" +} diff --git a/src/pkg/label/model/model_test.go b/src/pkg/label/model/model_test.go new file mode 100644 index 000000000..90eda9ac1 --- /dev/null +++ b/src/pkg/label/model/model_test.go @@ -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()) + } + } +} diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index 4bedd2f0c..0de493148 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -49,6 +49,7 @@ func New() http.Handler { SysteminfoAPI: newSystemInfoAPI(), PingAPI: newPingAPI(), LdapAPI: newLdapAPI(), + LabelAPI: newLabelAPI(), GCAPI: newGCAPI(), QuotaAPI: newQuotaAPI(), RetentionAPI: newRetentionAPI(), diff --git a/src/server/v2.0/handler/label.go b/src/server/v2.0/handler/label.go new file mode 100644 index 000000000..b78c90ca6 --- /dev/null +++ b/src/server/v2.0/handler/label.go @@ -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) +} diff --git a/src/server/v2.0/handler/model/label.go b/src/server/v2.0/handler/model/label.go index d48cd2076..48a61d847 100644 --- a/src/server/v2.0/handler/model/label.go +++ b/src/server/v2.0/handler/model/label.go @@ -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} } diff --git a/src/server/v2.0/route/legacy.go b/src/server/v2.0/route/legacy.go index c2f566d53..5548684d7 100755 --- a/src/server/v2.0/route/legacy.go +++ b/src/server/v2.0/route/legacy.go @@ -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() { diff --git a/src/testing/pkg/label/dao/dao.go b/src/testing/pkg/label/dao/dao.go new file mode 100644 index 000000000..c40cded2a --- /dev/null +++ b/src/testing/pkg/label/dao/dao.go @@ -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 +} diff --git a/src/testing/pkg/label/manager.go b/src/testing/pkg/label/manager.go index 7949b8c9e..69b34d90d 100644 --- a/src/testing/pkg/label/manager.go +++ b/src/testing/pkg/label/manager.go @@ -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 } diff --git a/src/testing/pkg/pkg.go b/src/testing/pkg/pkg.go index 2a8a4fbd4..1c4d97149 100644 --- a/src/testing/pkg/pkg.go +++ b/src/testing/pkg/pkg.go @@ -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 diff --git a/tests/apitests/python/library/base.py b/tests/apitests/python/library/base.py index b1a971b9e..55366a94e 100644 --- a/tests/apitests/python/library/base.py +++ b/tests/apitests/python/library/base.py @@ -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)), diff --git a/tests/apitests/python/library/label.py b/tests/apitests/python/library/label.py index f34344066..528ed3941 100644 --- a/tests/apitests/python/library/label.py +++ b/tests/apitests/python/library/label.py @@ -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)) \ No newline at end of file + return client.delete_label_with_http_info(int(label_id)) \ No newline at end of file