diff --git a/api/v2.0/legacy_swagger.yaml b/api/v2.0/legacy_swagger.yaml index e5f042961..e9f1d4604 100644 --- a/api/v2.0/legacy_swagger.yaml +++ b/api/v2.0/legacy_swagger.yaml @@ -2069,126 +2069,6 @@ paths: description: User have no permission to list webhook jobs of the project. '500': description: Unexpected internal errors. - '/projects/{project_id}/immutabletagrules': - get: - summary: List all immutable tag rules of current project - description: | - This endpoint returns the immutable tag rules of a project - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: Relevant project ID. - tags: - - Products - responses: - '200': - description: List project immutable tag rules successfully. - schema: - type: array - items: - $ref: '#/definitions/ImmutableRule' - '400': - description: Illegal format of provided ID value. - '401': - description: User need to log in first. - '403': - description: User have no permission to list immutable tag rules of the project. - '500': - description: Unexpected internal errors. - post: - summary: Add an immutable tag rule to current project - description: | - This endpoint add an immutable tag rule to the project - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: Relevant project ID. - - name: ImmutableRule - in: body - required: true - schema: - $ref: '#/definitions/ImmutableRule' - tags: - - Products - responses: - '200': - description: Add the immutable tag rule successfully. - '400': - description: Illegal format of provided ID value. - '401': - description: User need to log in first. - '403': - description: User have no permission to get immutable tag rule of the project. - '500': - description: Internal server errors. - '/projects/{project_id}/immutabletagrules/{id}': - put: - summary: Update the immutable tag rule or enable or disable the rule - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: Relevant project ID. - - name: id - in: path - type: integer - format: int64 - required: true - description: Immutable tag rule ID. - - name: ImmutableRule - in: body - required: true - schema: - $ref: '#/definitions/ImmutableRule' - tags: - - Products - responses: - '200': - description: Update the immutable tag rule successfully. - '400': - description: Illegal format of provided ID value. - '401': - description: User need to log in first. - '403': - description: User have no permission to update the immutable tag rule of the project. - '500': - description: Internal server errors. - delete: - summary: Delete the immutable tag rule. - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: Relevant project ID. - - name: id - in: path - type: integer - format: int64 - required: true - description: Immutable tag rule ID. - tags: - - Products - responses: - '200': - description: Delete the immutable tag rule successfully. - '400': - description: Illegal format of provided ID value. - '401': - description: User need to log in first. - '403': - description: User have no permission to delete immutable tags of the project. - '500': - description: Internal server errors. responses: OK: description: 'Success' @@ -3422,45 +3302,6 @@ definitions: type: string description: Webhook supportted notify type. example: 'http' - ImmutableRule: - type: object - properties: - id: - type: integer - priority: - type: integer - disabled: - type: boolean - action: - type: string - template: - type: string - params: - type: object - additionalProperties: - type: object - tag_selectors: - type: array - items: - $ref: '#/definitions/ImmutableSelector' - scope_selectors: - type: object - additionalProperties: - type: array - items: - $ref: '#/definitions/ImmutableSelector' - - ImmutableSelector: - type: object - properties: - kind: - type: string - decoration: - type: string - pattern: - type: string - extras: - type: string parameters: query: diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index f170f258e..9fc075d82 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1702,6 +1702,120 @@ paths: $ref: '#/responses/404' '500': $ref: '#/responses/500' + '/projects/{project_name_or_id}/immutabletagrules': + get: + summary: List all immutable tag rules of current project + description: | + This endpoint returns the immutable tag rules of a project + tags: + - immutable + operationId: ListImmuRules + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - $ref: '#/parameters/page' + - $ref: '#/parameters/pageSize' + - $ref: '#/parameters/query' + responses: + '200': + description: Success + headers: + X-Total-Count: + description: The total count of immutable tag + type: integer + Link: + description: Link refers to the previous page and next page + type: string + schema: + type: array + items: + $ref: '#/definitions/ImmutableRule' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + post: + summary: Add an immutable tag rule to current project + description: | + This endpoint add an immutable tag rule to the project + tags: + - immutable + operationId: CreateImmuRule + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - name: ImmutableRule + in: body + required: true + schema: + $ref: '#/definitions/ImmutableRule' + responses: + '201': + $ref: '#/responses/201' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' + '/projects/{project_name_or_id}/immutabletagrules/{immutable_rule_id}': + put: + summary: Update the immutable tag rule or enable or disable the rule + tags: + - immutable + operationId: UpdateImmuRule + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - $ref: '#/parameters/immutableRuleId' + - name: ImmutableRule + in: body + required: true + schema: + $ref: '#/definitions/ImmutableRule' + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + delete: + summary: Delete the immutable tag rule. + tags: + - immutable + operationId: DeleteImmuRule + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - $ref: '#/parameters/immutableRuleId' + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' /icons/{digest}: get: summary: Get artifact icon @@ -3251,6 +3365,14 @@ parameters: required: true type: integer format: int64 + immutableRuleId: + name: immutable_rule_id + in: path + description: The ID of the immutable rule + required: true + type: integer + format: int64 + responses: '200': description: Success @@ -5046,3 +5168,42 @@ definitions: import: package: "github.com/goharbor/harbor/src/pkg/scan/rest/v1" alias: v1 + + ImmutableRule: + type: object + properties: + id: + type: integer + priority: + type: integer + disabled: + type: boolean + action: + type: string + template: + type: string + params: + type: object + additionalProperties: + type: object + tag_selectors: + type: array + items: + $ref: '#/definitions/ImmutableSelector' + scope_selectors: + type: object + additionalProperties: + type: array + items: + $ref: '#/definitions/ImmutableSelector' + ImmutableSelector: + type: object + properties: + kind: + type: string + decoration: + type: string + pattern: + type: string + extras: + type: string diff --git a/src/controller/artifact/controller.go b/src/controller/artifact/controller.go index 56cdbb31e..48c95e1ef 100644 --- a/src/controller/artifact/controller.go +++ b/src/controller/artifact/controller.go @@ -39,8 +39,8 @@ import ( "github.com/goharbor/harbor/src/pkg/artifactrash" "github.com/goharbor/harbor/src/pkg/artifactrash/model" "github.com/goharbor/harbor/src/pkg/blob" - "github.com/goharbor/harbor/src/pkg/immutabletag/match" - "github.com/goharbor/harbor/src/pkg/immutabletag/match/rule" + "github.com/goharbor/harbor/src/pkg/immutable/match" + "github.com/goharbor/harbor/src/pkg/immutable/match/rule" "github.com/goharbor/harbor/src/pkg/label" "github.com/goharbor/harbor/src/pkg/notification" "github.com/goharbor/harbor/src/pkg/notifier/event" diff --git a/src/controller/artifact/controller_test.go b/src/controller/artifact/controller_test.go index b3236ec0c..f2ddd785d 100644 --- a/src/controller/artifact/controller_test.go +++ b/src/controller/artifact/controller_test.go @@ -16,6 +16,7 @@ package artifact import ( "context" + "github.com/goharbor/harbor/src/testing/pkg/immutable" "testing" "time" @@ -36,7 +37,6 @@ import ( arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact" artrashtesting "github.com/goharbor/harbor/src/testing/pkg/artifactrash" "github.com/goharbor/harbor/src/testing/pkg/blob" - immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag" "github.com/goharbor/harbor/src/testing/pkg/label" "github.com/goharbor/harbor/src/testing/pkg/registry" repotesting "github.com/goharbor/harbor/src/testing/pkg/repository" @@ -66,7 +66,7 @@ type controllerTestSuite struct { tagCtl *tagtesting.FakeController labelMgr *label.FakeManager abstractor *fakeAbstractor - immutableMtr *immutesting.FakeMatcher + immutableMtr *immutable.FakeMatcher regCli *registry.FakeClient } @@ -78,7 +78,7 @@ func (c *controllerTestSuite) SetupTest() { c.tagCtl = &tagtesting.FakeController{} c.labelMgr = &label.FakeManager{} c.abstractor = &fakeAbstractor{} - c.immutableMtr = &immutesting.FakeMatcher{} + c.immutableMtr = &immutable.FakeMatcher{} c.regCli = ®istry.FakeClient{} c.ctl = &controller{ repoMgr: c.repoMgr, diff --git a/src/controller/immutable/controller.go b/src/controller/immutable/controller.go new file mode 100644 index 000000000..32bcc59af --- /dev/null +++ b/src/controller/immutable/controller.go @@ -0,0 +1,88 @@ +package immutable + +import ( + "context" + "fmt" + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/immutable" + + "github.com/goharbor/harbor/src/pkg/immutable/model" +) + +var ( + // Ctr is a global variable for the default immutable controller implementation + Ctr = NewAPIController(immutable.NewDefaultRuleManager()) +) + +// Controller to handle the requests related with immutable +type Controller interface { + // GetImmutableRule ... + GetImmutableRule(ctx context.Context, id int64) (*model.Metadata, error) + + // CreateImmutableRule ... + CreateImmutableRule(ctx context.Context, m *model.Metadata) (int64, error) + + // DeleteImmutableRule ... + DeleteImmutableRule(ctx context.Context, id int64) error + + // UpdateImmutableRule ... + UpdateImmutableRule(ctx context.Context, projectID int64, m *model.Metadata) error + + // ListImmutableRules ... + ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.Metadata, error) + + // Count count the immutable rules + Count(ctx context.Context, query *q.Query) (int64, error) +} + +// DefaultAPIController ... +type DefaultAPIController struct { + manager immutable.Manager +} + +// GetImmutableRule ... +func (r *DefaultAPIController) GetImmutableRule(ctx context.Context, id int64) (*model.Metadata, error) { + return r.manager.GetImmutableRule(ctx, id) +} + +// DeleteImmutableRule ... +func (r *DefaultAPIController) DeleteImmutableRule(ctx context.Context, id int64) error { + return r.manager.DeleteImmutableRule(ctx, id) +} + +// CreateImmutableRule ... +func (r *DefaultAPIController) CreateImmutableRule(ctx context.Context, m *model.Metadata) (int64, error) { + return r.manager.CreateImmutableRule(ctx, m) +} + +// UpdateImmutableRule ... +func (r *DefaultAPIController) UpdateImmutableRule(ctx context.Context, projectID int64, m *model.Metadata) error { + m0, err := r.manager.GetImmutableRule(ctx, m.ID) + if err != nil { + return err + } + if m0 == nil { + return fmt.Errorf("the immutable tag rule is not found id:%v", m.ID) + } + if m0.Disabled != m.Disabled { + return r.manager.EnableImmutableRule(ctx, m.ID, m.Disabled) + } + return r.manager.UpdateImmutableRule(ctx, projectID, m) +} + +// ListImmutableRules ... +func (r *DefaultAPIController) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.Metadata, error) { + return r.manager.ListImmutableRules(ctx, query) +} + +// Count count the immutable rules +func (r *DefaultAPIController) Count(ctx context.Context, query *q.Query) (int64, error) { + return r.manager.Count(ctx, query) +} + +// NewAPIController ... +func NewAPIController(immutableMgr immutable.Manager) Controller { + return &DefaultAPIController{ + manager: immutableMgr, + } +} diff --git a/src/pkg/immutabletag/controller_test.go b/src/controller/immutable/controller_test.go similarity index 80% rename from src/pkg/immutabletag/controller_test.go rename to src/controller/immutable/controller_test.go index c20aacb7a..7670b2277 100644 --- a/src/pkg/immutabletag/controller_test.go +++ b/src/controller/immutable/controller_test.go @@ -1,19 +1,22 @@ -package immutabletag +package immutable import ( + "github.com/goharbor/harbor/src/lib/orm" + "github.com/goharbor/harbor/src/lib/q" "testing" "github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils/test" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" + "github.com/goharbor/harbor/src/pkg/immutable/model" + htesting "github.com/goharbor/harbor/src/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type ControllerTestSuite struct { - suite.Suite + htesting.Suite ctr Controller t *testing.T assert *assert.Assertions @@ -28,7 +31,7 @@ func (s *ControllerTestSuite) SetupSuite() { s.t = s.T() s.assert = assert.New(s.t) s.require = require.New(s.t) - s.ctr = ImmuCtr + s.ctr = Ctr } func (s *ControllerTestSuite) TestImmutableRule() { @@ -65,7 +68,7 @@ func (s *ControllerTestSuite) TestImmutableRule() { }, }, } - s.ruleID, err = s.ctr.CreateImmutableRule(rule) + s.ruleID, err = s.ctr.CreateImmutableRule(orm.Context(), rule) s.require.Nil(err) update := &model.Metadata{ @@ -92,10 +95,10 @@ func (s *ControllerTestSuite) TestImmutableRule() { }, Disabled: false, } - err = s.ctr.UpdateImmutableRule(projectID, update) + err = s.ctr.UpdateImmutableRule(orm.Context(), projectID, update) s.require.Nil(err) - getRule, err := s.ctr.GetImmutableRule(s.ruleID) + getRule, err := s.ctr.GetImmutableRule(orm.Context(), s.ruleID) s.require.Nil(err) s.require.Equal("postgres", getRule.ScopeSelectors["repository"][0].Pattern) @@ -123,9 +126,9 @@ func (s *ControllerTestSuite) TestImmutableRule() { }, Disabled: true, } - err = s.ctr.UpdateImmutableRule(projectID, update2) + err = s.ctr.UpdateImmutableRule(orm.Context(), projectID, update2) s.require.Nil(err) - getRule, err = s.ctr.GetImmutableRule(s.ruleID) + getRule, err = s.ctr.GetImmutableRule(orm.Context(), s.ruleID) s.require.Nil(err) s.require.True(getRule.Disabled) @@ -151,10 +154,10 @@ func (s *ControllerTestSuite) TestImmutableRule() { }, }, } - s.ruleID, err = s.ctr.CreateImmutableRule(rule2) + s.ruleID, err = s.ctr.CreateImmutableRule(orm.Context(), rule2) s.require.Nil(err) - rules, err := s.ctr.ListImmutableRules(projectID) + rules, err := s.ctr.ListImmutableRules(orm.Context(), q.New(q.KeyWords{"ProjectID": projectID})) s.require.Nil(err) s.require.Equal(2, len(rules)) @@ -162,7 +165,7 @@ func (s *ControllerTestSuite) TestImmutableRule() { // TearDownSuite clears env for test suite func (s *ControllerTestSuite) TearDownSuite() { - err := s.ctr.DeleteImmutableRule(s.ruleID) + err := s.ctr.DeleteImmutableRule(orm.Context(), s.ruleID) require.NoError(s.T(), err, "delete immutable rule") } diff --git a/src/controller/tag/controller.go b/src/controller/tag/controller.go index 0c15fedbe..a878b626e 100644 --- a/src/controller/tag/controller.go +++ b/src/controller/tag/controller.go @@ -23,8 +23,8 @@ import ( "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/lib/selector" "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/pkg/immutabletag/match" - "github.com/goharbor/harbor/src/pkg/immutabletag/match/rule" + "github.com/goharbor/harbor/src/pkg/immutable/match" + "github.com/goharbor/harbor/src/pkg/immutable/match/rule" "github.com/goharbor/harbor/src/pkg/signature" "github.com/goharbor/harbor/src/pkg/tag" model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag" @@ -221,7 +221,7 @@ func (c *controller) populateImmutableStatus(ctx context.Context, tag *Tag) { return } _, repoName := utils.ParseRepository(artifact.RepositoryName) - matched, err := c.immutableMtr.Match(artifact.ProjectID, selector.Candidate{ + matched, err := c.immutableMtr.Match(ctx, artifact.ProjectID, selector.Candidate{ Repository: repoName, Tags: []string{tag.Name}, NamespaceID: artifact.ProjectID, diff --git a/src/controller/tag/controller_test.go b/src/controller/tag/controller_test.go index 9ee9e9611..1dd1b29cb 100644 --- a/src/controller/tag/controller_test.go +++ b/src/controller/tag/controller_test.go @@ -23,7 +23,7 @@ import ( "github.com/goharbor/harbor/src/pkg/tag/model/tag" ormtesting "github.com/goharbor/harbor/src/testing/lib/orm" "github.com/goharbor/harbor/src/testing/pkg/artifact" - immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag" + "github.com/goharbor/harbor/src/testing/pkg/immutable" "github.com/goharbor/harbor/src/testing/pkg/repository" tagtesting "github.com/goharbor/harbor/src/testing/pkg/tag" "github.com/stretchr/testify/suite" @@ -37,14 +37,14 @@ type controllerTestSuite struct { repoMgr *repository.FakeManager artMgr *artifact.FakeManager tagMgr *tagtesting.FakeManager - immutableMtr *immutesting.FakeMatcher + immutableMtr *immutable.FakeMatcher } func (c *controllerTestSuite) SetupTest() { c.repoMgr = &repository.FakeManager{} c.artMgr = &artifact.FakeManager{} c.tagMgr = &tagtesting.FakeManager{} - c.immutableMtr = &immutesting.FakeMatcher{} + c.immutableMtr = &immutable.FakeMatcher{} c.ctl = &controller{ tagMgr: c.tagMgr, artMgr: c.artMgr, diff --git a/src/core/api/harborapi_test.go b/src/core/api/harborapi_test.go index 8e535af18..6863fdb47 100644 --- a/src/core/api/harborapi_test.go +++ b/src/core/api/harborapi_test.go @@ -133,8 +133,6 @@ func init() { beego.Router("/api/projects/:pid([0-9]+)/webhook/policies/test", &NotificationPolicyAPI{}, "post:Test") beego.Router("/api/projects/:pid([0-9]+)/webhook/lasttrigger", &NotificationPolicyAPI{}, "get:ListGroupByEventType") beego.Router("/api/projects/:pid([0-9]+)/webhook/jobs/", &NotificationJobAPI{}, "get:List") - beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules", &ImmutableTagRuleAPI{}, "get:List;post:Post") - beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules/:id([0-9]+)", &ImmutableTagRuleAPI{}) // Charts are controlled under projects chartRepositoryAPIType := &ChartRepositoryAPI{} beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus") diff --git a/src/core/api/immutabletagrule.go b/src/core/api/immutabletagrule.go deleted file mode 100644 index 180dd51d1..000000000 --- a/src/core/api/immutabletagrule.go +++ /dev/null @@ -1,158 +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 api - -import ( - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/goharbor/harbor/src/common/rbac" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/immutabletag" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" -) - -// ImmutableTagRuleAPI ... -type ImmutableTagRuleAPI struct { - BaseController - ctr immutabletag.Controller - projectID int64 - ID int64 -} - -// Prepare validates the user and projectID -func (itr *ImmutableTagRuleAPI) Prepare() { - itr.BaseController.Prepare() - // Check access permissions - if !itr.RequireAuthenticated() { - return - } - - pid, err := itr.GetInt64FromPath(":pid") - if err != nil || pid <= 0 { - text := "invalid project ID: " - if err != nil { - text += err.Error() - } else { - text += fmt.Sprintf("%d", pid) - } - itr.SendError(errors.New(err).WithCode(errors.BadRequestCode)) - return - } - itr.projectID = pid - itr.ctr = immutabletag.ImmuCtr - ruleID, err := itr.GetInt64FromPath(":id") - if err == nil || ruleID > 0 { - itr.ID = ruleID - itRule, err := itr.ctr.GetImmutableRule(itr.ID) - if err != nil { - itr.SendError(err) - return - } - if itRule.ProjectID != itr.projectID { - err := fmt.Errorf("immutable tag rule %v not found", itr.ID) - itr.SendError(errors.New(err).WithCode(errors.NotFoundCode)) - return - } - } - - if strings.EqualFold(itr.Ctx.Request.Method, "get") { - if !itr.requireAccess(rbac.ActionList) { - return - } - } else if strings.EqualFold(itr.Ctx.Request.Method, "put") { - if !itr.requireAccess(rbac.ActionUpdate) { - return - } - } else if strings.EqualFold(itr.Ctx.Request.Method, "post") { - if !itr.requireAccess(rbac.ActionCreate) { - return - } - - } else if strings.EqualFold(itr.Ctx.Request.Method, "delete") { - if !itr.requireAccess(rbac.ActionDelete) { - return - } - } -} - -func (itr *ImmutableTagRuleAPI) requireAccess(action rbac.Action) bool { - return itr.RequireProjectAccess(itr.projectID, action, rbac.ResourceImmutableTag) -} - -// List list all immutable tag rules of current project -func (itr *ImmutableTagRuleAPI) List() { - rules, err := itr.ctr.ListImmutableRules(itr.projectID) - if err != nil { - itr.SendError(err) - return - } - itr.WriteJSONData(rules) -} - -// Post create immutable tag rule -func (itr *ImmutableTagRuleAPI) Post() { - ir := &model.Metadata{} - isValid, err := itr.DecodeJSONReqAndValidate(ir) - if !isValid { - itr.SendError(errors.New(err).WithCode(errors.BadRequestCode)) - return - } - ir.ProjectID = itr.projectID - id, err := itr.ctr.CreateImmutableRule(ir) - if err != nil { - itr.SendError(err) - return - } - itr.Redirect(http.StatusCreated, strconv.FormatInt(id, 10)) -} - -// Delete delete immutable tag rule -func (itr *ImmutableTagRuleAPI) Delete() { - if itr.ID <= 0 { - err := fmt.Errorf("invalid immutable rule id %d", itr.ID) - itr.SendError(errors.New(err).WithCode(errors.BadRequestCode)) - return - } - err := itr.ctr.DeleteImmutableRule(itr.ID) - if err != nil { - itr.SendError(err) - return - } -} - -// Put update an immutable tag rule -func (itr *ImmutableTagRuleAPI) Put() { - ir := &model.Metadata{} - if err := itr.DecodeJSONReq(ir); err != nil { - itr.SendError(errors.New(err).WithCode(errors.BadRequestCode)) - return - } - ir.ID = itr.ID - ir.ProjectID = itr.projectID - - if itr.ID <= 0 { - err := fmt.Errorf("invalid immutable rule id %d", itr.ID) - itr.SendError(errors.New(err).WithCode(errors.BadRequestCode)) - return - } - - if err := itr.ctr.UpdateImmutableRule(itr.projectID, ir); err != nil { - itr.SendError(err) - return - } -} diff --git a/src/core/api/immutabletagrule_test.go b/src/core/api/immutabletagrule_test.go deleted file mode 100644 index c65f85b7a..000000000 --- a/src/core/api/immutabletagrule_test.go +++ /dev/null @@ -1,365 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/goharbor/harbor/src/pkg/immutabletag" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" -) - -func TestImmutableTagRuleAPI_List(t *testing.T) { - - metadata := &model.Metadata{ - ProjectID: 1, - Disabled: false, - TagSelectors: []*model.Selector{ - { - Kind: "doublestar", - Decoration: "matches", - Pattern: "release-[\\d\\.]+", - }, - }, - ScopeSelectors: map[string][]*model.Selector{ - "repository": { - { - Kind: "doublestar", - Decoration: "matches", - Pattern: ".+", - }, - }, - }, - } - mgr := immutabletag.NewDefaultRuleManager() - id, err := mgr.CreateImmutableRule(metadata) - if err != nil { - t.Error(err) - } - defer mgr.DeleteImmutableRule(id) - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - method: http.MethodGet, - url: "/api/projects/1/immutabletagrules", - }, - code: http.StatusUnauthorized, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodGet, - url: "/api/projects/1/immutabletagrules", - credential: admin, - }, - postFunc: func(responseRecorder *httptest.ResponseRecorder) error { - var rules []model.Metadata - err := json.Unmarshal([]byte(responseRecorder.Body.String()), &rules) - if err != nil { - return err - } - if len(rules) <= 0 { - return fmt.Errorf("no rules found") - } - if rules[0].TagSelectors[0].Kind != "doublestar" { - return fmt.Errorf("rule is not expected. actual: %v", responseRecorder.Body.String()) - } - return nil - }, - code: http.StatusOK, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodGet, - url: "/api/projects/1/immutabletagrules", - credential: projAdmin, - }, - code: http.StatusOK, - }, - // 403 - { - request: &testingRequest{ - method: http.MethodGet, - url: "/api/projects/1/immutabletagrules", - credential: projGuest, - }, - code: http.StatusForbidden, - }, - } - runCodeCheckingCases(t, cases...) - -} - -func TestImmutableTagRuleAPI_Post(t *testing.T) { - - // body := `{ - // "projectID":1, - // "priority":0, - // "template": "immutable_template", - // "action": "immutable", - // "disabled":false, - // "action":"immutable", - // "template":"immutable_template", - // "tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"**"}], - // "scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"**"}]} - // }` - - metadata := &model.Metadata{ - ProjectID: 1, - Disabled: false, - Priority: 0, - Template: "immutable_template", - Action: "immutable", - TagSelectors: []*model.Selector{ - { - Kind: "doublestar", - Decoration: "matches", - Pattern: "release-[\\d\\.]+", - }, - }, - ScopeSelectors: map[string][]*model.Selector{ - "repository": { - { - Kind: "doublestar", - Decoration: "matches", - Pattern: ".+", - }, - }, - }, - } - - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - method: http.MethodPost, - url: "/api/projects/1/immutabletagrules", - bodyJSON: metadata, - }, - code: http.StatusUnauthorized, - }, - // 201 - { - request: &testingRequest{ - method: http.MethodPost, - url: "/api/projects/1/immutabletagrules", - credential: admin, - bodyJSON: metadata, - }, - code: http.StatusCreated, - }, - // 409 - { - request: &testingRequest{ - method: http.MethodPost, - url: "/api/projects/1/immutabletagrules", - credential: projAdmin, - bodyJSON: metadata, - }, - code: http.StatusConflict, - }, - // 403 - { - request: &testingRequest{ - method: http.MethodPost, - url: "/api/projects/1/immutabletagrules", - credential: projGuest, - bodyJSON: metadata, - }, - code: http.StatusForbidden, - }, - } - runCodeCheckingCases(t, cases...) - -} - -func TestImmutableTagRuleAPI_Put(t *testing.T) { - - metadata := &model.Metadata{ - ProjectID: 1, - Disabled: false, - TagSelectors: []*model.Selector{ - { - Kind: "doublestar", - Decoration: "matches", - Pattern: "release-[\\d\\.]+", - }, - }, - ScopeSelectors: map[string][]*model.Selector{ - "repository": { - { - Kind: "doublestar", - Decoration: "matches", - Pattern: ".+", - }, - }, - }, - } - - metadata2 := &model.Metadata{ - ProjectID: 1, - Disabled: false, - TagSelectors: []*model.Selector{ - { - Kind: "doublestar", - Decoration: "matches", - Pattern: "latest", - }, - }, - ScopeSelectors: map[string][]*model.Selector{ - "repository": { - { - Kind: "doublestar", - Decoration: "matches", - Pattern: ".+", - }, - }, - }, - } - mgr := immutabletag.NewDefaultRuleManager() - id, err := mgr.CreateImmutableRule(metadata) - if err != nil { - t.Error(err) - } - defer mgr.DeleteImmutableRule(id) - - url := fmt.Sprintf("/api/projects/1/immutabletagrules/%d", id) - url2 := fmt.Sprintf("/api/projects/3/immutabletagrules/%d", id) - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - method: http.MethodPut, - url: url, - bodyJSON: metadata2, - }, - code: http.StatusUnauthorized, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodPut, - url: url, - credential: admin, - bodyJSON: metadata2, - }, - code: http.StatusOK, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodPut, - url: url, - credential: projAdmin, - bodyJSON: metadata2, - }, - code: http.StatusOK, - }, - // 403 - { - request: &testingRequest{ - method: http.MethodPut, - url: url, - credential: projGuest, - bodyJSON: metadata2, - }, - code: http.StatusForbidden, - }, - // 404 - { - request: &testingRequest{ - method: http.MethodPut, - url: url2, - credential: projAdmin, - bodyJSON: metadata2, - }, - code: http.StatusNotFound, - }, - } - runCodeCheckingCases(t, cases...) -} - -func TestImmutableTagRuleAPI_Delete(t *testing.T) { - metadata := &model.Metadata{ - ProjectID: 1, - Disabled: false, - TagSelectors: []*model.Selector{ - { - Kind: "doublestar", - Decoration: "matches", - Pattern: "latest", - }, - }, - ScopeSelectors: map[string][]*model.Selector{ - "repository": { - { - Kind: "doublestar", - Decoration: "matches", - Pattern: ".+", - }, - }, - }, - } - - mgr := immutabletag.NewDefaultRuleManager() - id, err := mgr.CreateImmutableRule(metadata) - if err != nil { - t.Error(err) - } - defer mgr.DeleteImmutableRule(id) - - url := fmt.Sprintf("/api/projects/1/immutabletagrules/%d", id) - wrongURL := fmt.Sprintf("/api/projects/3/immutabletagrules/%d", id) - - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - method: http.MethodDelete, - url: url, - }, - code: http.StatusUnauthorized, - }, - // 403 - { - request: &testingRequest{ - method: http.MethodDelete, - url: url, - credential: projGuest, - }, - code: http.StatusForbidden, - }, - // 404 - { - request: &testingRequest{ - method: http.MethodDelete, - url: wrongURL, - credential: projAdmin, - }, - code: http.StatusNotFound, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodDelete, - url: url, - credential: projAdmin, - }, - code: http.StatusOK, - }, - // 404 - { - request: &testingRequest{ - method: http.MethodDelete, - url: url, - credential: projAdmin, - }, - code: http.StatusNotFound, - }, - } - runCodeCheckingCases(t, cases...) -} diff --git a/src/pkg/immutable/dao/dao.go b/src/pkg/immutable/dao/dao.go new file mode 100644 index 000000000..2c07cbd41 --- /dev/null +++ b/src/pkg/immutable/dao/dao.go @@ -0,0 +1,142 @@ +package dao + +import ( + "context" + "github.com/goharbor/harbor/src/lib/q" + + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/orm" + "github.com/goharbor/harbor/src/pkg/immutable/dao/model" +) + +// DAO defines the interface to access the ImmutableRule data model +type DAO interface { + CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) (int64, error) + UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.ImmutableRule) error + ToggleImmutableRule(ctx context.Context, id int64, status bool) error + GetImmutableRule(ctx context.Context, id int64) (*model.ImmutableRule, error) + Count(ctx context.Context, query *q.Query) (int64, error) + ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.ImmutableRule, error) + DeleteImmutableRule(ctx context.Context, id int64) error +} + +// New creates a default implementation for DAO +func New() DAO { + return &iDao{} +} + +type iDao struct{} + +// CreateImmutableRule creates the Immutable Rule +func (i *iDao) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) (int64, error) { + ormer, err := orm.FromContext(ctx) + if err != nil { + return 0, err + } + ir.Disabled = false + id, err := ormer.Insert(ir) + if err != nil { + if e := orm.AsConflictError(err, "immutable rule already exists"); e != nil { + err = e + } + } + return id, err +} + +// UpdateImmutableRule update the immutable rules +func (i *iDao) UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.ImmutableRule) error { + ir.ProjectID = projectID + ormer, err := orm.FromContext(ctx) + if err != nil { + return err + } + n, err := ormer.Update(ir, "TagFilter") + if err != nil { + return err + } + if n == 0 { + return errors.NotFoundError(nil).WithMessage("immutable %d not found", ir.ID) + } + return nil +} + +// ToggleImmutableRule enable/disable immutable rules +func (i *iDao) ToggleImmutableRule(ctx context.Context, id int64, status bool) error { + ormer, err := orm.FromContext(ctx) + if err != nil { + return err + } + ir := &model.ImmutableRule{ID: id, Disabled: status} + n, err := ormer.Update(ir, "Disabled") + if err != nil { + return err + } + if n == 0 { + return errors.NotFoundError(nil).WithMessage("immutable %d not found", ir.ID) + } + return nil +} + +// GetImmutableRule get immutable rule +func (i *iDao) GetImmutableRule(ctx context.Context, id int64) (*model.ImmutableRule, error) { + ormer, err := orm.FromContext(ctx) + if err != nil { + return nil, err + } + ir := &model.ImmutableRule{ID: id} + if err = ormer.Read(ir); err != nil { + if e := orm.AsNotFoundError(err, "immutable rule %d not found", id); e != nil { + err = e + } + return nil, err + } + return ir, nil +} + +// QueryImmutableRuleByProjectID get all immutable rule by project +func (i *iDao) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.ImmutableRule, error) { + rules := []*model.ImmutableRule{} + qs, err := orm.QuerySetter(ctx, &model.ImmutableRule{}, query) + if err != nil { + return nil, err + } + if query.Sorting != "" { + qs = qs.OrderBy(query.Sorting) + } + if _, err = qs.All(&rules); err != nil { + return nil, err + } + return rules, nil +} + +// Count ... +func (i *iDao) Count(ctx context.Context, query *q.Query) (int64, error) { + query = q.MustClone(query) + query.Sorting = "" + query.PageNumber = 0 + query.PageSize = 0 + + qs, err := orm.QuerySetter(ctx, &model.ImmutableRule{}, query) + if err != nil { + return 0, err + } + return qs.Count() +} + +// DeleteImmutableRule delete the immutable rule +func (i *iDao) DeleteImmutableRule(ctx context.Context, id int64) error { + ormer, err := orm.FromContext(ctx) + if err != nil { + return err + } + ir := &model.ImmutableRule{ID: id} + + n, err := ormer.Delete(ir) + if err != nil { + return err + } + if n == 0 { + return errors.NotFoundError(nil).WithMessage("immutable rule %d not found", id) + } + return nil +} diff --git a/src/pkg/immutabletag/dao/immutable_test.go b/src/pkg/immutable/dao/dao_test.go similarity index 68% rename from src/pkg/immutabletag/dao/immutable_test.go rename to src/pkg/immutable/dao/dao_test.go index ce71ad8b4..d471c8ceb 100644 --- a/src/pkg/immutabletag/dao/immutable_test.go +++ b/src/pkg/immutable/dao/dao_test.go @@ -1,21 +1,23 @@ package dao import ( + "github.com/goharbor/harbor/src/lib/q" "strings" "testing" "github.com/goharbor/harbor/src/common/dao" - "github.com/goharbor/harbor/src/pkg/immutabletag/dao/model" + "github.com/goharbor/harbor/src/pkg/immutable/dao/model" + htesting "github.com/goharbor/harbor/src/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type immutableRuleDaoTestSuite struct { - suite.Suite + htesting.Suite require *require.Assertions assert *assert.Assertions - dao ImmutableRuleDao + dao DAO id int64 } @@ -28,52 +30,51 @@ func (t *immutableRuleDaoTestSuite) SetupSuite() { func (t *immutableRuleDaoTestSuite) TestCreateImmutableRule() { ir := &model.ImmutableRule{TagFilter: "**", ProjectID: 1} - id, err := t.dao.CreateImmutableRule(ir) + id, err := t.dao.CreateImmutableRule(t.Context(), ir) t.require.Nil(err) t.require.True(id > 0, "Can not create immutable tag rule") // insert duplicate rows ir2 := &model.ImmutableRule{TagFilter: "**", ProjectID: 1} - id2, err := t.dao.CreateImmutableRule(ir2) - t.require.True(strings.Contains(err.Error(), "duplicate key")) + id2, err := t.dao.CreateImmutableRule(t.Context(), ir2) + t.require.True(strings.Contains(err.Error(), "immutable rule already exist")) t.require.Equal(int64(0), id2) - _, err = t.dao.DeleteImmutableRule(id) + err = t.dao.DeleteImmutableRule(t.Context(), id) t.require.Nil(err) } func (t *immutableRuleDaoTestSuite) TestUpdateImmutableRule() { ir := &model.ImmutableRule{TagFilter: "**", ProjectID: 1} - id, err := t.dao.CreateImmutableRule(ir) + id, err := t.dao.CreateImmutableRule(t.Context(), ir) t.require.Nil(err) t.require.True(id > 0, "Can not create immutable tag rule") updatedIR := &model.ImmutableRule{ID: id, TagFilter: "1.2.0", ProjectID: 1} - updatedCnt, err := t.dao.UpdateImmutableRule(1, updatedIR) + err = t.dao.UpdateImmutableRule(t.Context(), 1, updatedIR) t.require.Nil(err) - t.require.True(updatedCnt > 0, "Failed to update immutable id") - newIr, err := t.dao.GetImmutableRule(id) + newIr, err := t.dao.GetImmutableRule(t.Context(), id) t.require.Nil(err) t.require.True(newIr.TagFilter == "1.2.0", "Failed to update immutable tag") - defer t.dao.DeleteImmutableRule(id) + defer t.dao.DeleteImmutableRule(t.Context(), id) } func (t *immutableRuleDaoTestSuite) TestEnableImmutableRule() { ir := &model.ImmutableRule{TagFilter: "**", ProjectID: 1} - id, err := t.dao.CreateImmutableRule(ir) + id, err := t.dao.CreateImmutableRule(t.Context(), ir) t.require.Nil(err) t.require.True(id > 0, "Can not create immutable tag rule") - t.dao.ToggleImmutableRule(id, true) - newIr, err := t.dao.GetImmutableRule(id) + t.dao.ToggleImmutableRule(t.Context(), id, true) + newIr, err := t.dao.GetImmutableRule(t.Context(), id) t.require.Nil(err) t.require.True(newIr.Disabled, "Failed to disable the immutable rule") - defer t.dao.DeleteImmutableRule(id) + defer t.dao.DeleteImmutableRule(t.Context(), id) } func (t *immutableRuleDaoTestSuite) TestGetImmutableRuleByProject() { @@ -84,10 +85,10 @@ func (t *immutableRuleDaoTestSuite) TestGetImmutableRuleByProject() { {TagFilter: "version4", ProjectID: 99}, } for _, ir := range irs { - t.dao.CreateImmutableRule(ir) + t.dao.CreateImmutableRule(t.Context(), ir) } - qrs, err := t.dao.QueryImmutableRuleByProjectID(99) + qrs, err := t.dao.ListImmutableRules(t.Context(), q.New(q.KeyWords{"ProjectID": 99})) t.require.Nil(err) t.require.True(len(qrs) == 4, "Failed to query 4 rows!") @@ -102,13 +103,13 @@ func (t *immutableRuleDaoTestSuite) TestGetEnabledImmutableRuleByProject() { {TagFilter: "version4", ProjectID: 99}, } for i, ir := range irs { - id, _ := t.dao.CreateImmutableRule(ir) + id, _ := t.dao.CreateImmutableRule(t.Context(), ir) if i == 1 { - t.dao.ToggleImmutableRule(id, true) + t.dao.ToggleImmutableRule(t.Context(), id, true) } } - qrs, err := t.dao.QueryEnabledImmutableRuleByProjectID(99) + qrs, err := t.dao.ListImmutableRules(t.Context(), q.New(q.KeyWords{"ProjectID": 99, "Disabled": "false"})) t.require.Nil(err) t.require.True(len(qrs) == 3, "Failed to query 3 rows!, got %v", len(qrs)) diff --git a/src/pkg/immutabletag/dao/model/rule.go b/src/pkg/immutable/dao/model/rule.go similarity index 100% rename from src/pkg/immutabletag/dao/model/rule.go rename to src/pkg/immutable/dao/model/rule.go diff --git a/src/pkg/immutable/manager.go b/src/pkg/immutable/manager.go new file mode 100644 index 000000000..123c78fec --- /dev/null +++ b/src/pkg/immutable/manager.go @@ -0,0 +1,113 @@ +package immutable + +import ( + "context" + "encoding/json" + "github.com/goharbor/harbor/src/lib/q" + "sort" + + "github.com/goharbor/harbor/src/pkg/immutable/dao" + dao_model "github.com/goharbor/harbor/src/pkg/immutable/dao/model" + "github.com/goharbor/harbor/src/pkg/immutable/model" +) + +var ( + // Mgr is a global variable for the default immutablerule manager implementation + Mgr = NewDefaultRuleManager() +) + +// Manager ... +type Manager interface { + // CreateImmutableRule creates the Immutable Rule + CreateImmutableRule(ctx context.Context, m *model.Metadata) (int64, error) + // UpdateImmutableRule update the immutable rules + UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.Metadata) error + // EnableImmutableRule enable/disable immutable rules + EnableImmutableRule(ctx context.Context, id int64, enabled bool) error + // GetImmutableRule get immutable rule + GetImmutableRule(ctx context.Context, id int64) (*model.Metadata, error) + // Count count the immutable rules + Count(ctx context.Context, query *q.Query) (int64, error) + // ListImmutableRules list the immutable rules + ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.Metadata, error) + // DeleteImmutableRule delete the immutable rule + DeleteImmutableRule(ctx context.Context, id int64) error +} + +type defaultRuleManager struct { + dao dao.DAO +} + +func (drm *defaultRuleManager) CreateImmutableRule(ctx context.Context, ir *model.Metadata) (int64, error) { + daoRule := &dao_model.ImmutableRule{} + daoRule.Disabled = ir.Disabled + daoRule.ProjectID = ir.ProjectID + data, _ := json.Marshal(ir) + daoRule.TagFilter = string(data) + return drm.dao.CreateImmutableRule(ctx, daoRule) +} + +func (drm *defaultRuleManager) UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.Metadata) error { + daoRule := &dao_model.ImmutableRule{} + data, _ := json.Marshal(ir) + daoRule.ID = ir.ID + daoRule.TagFilter = string(data) + return drm.dao.UpdateImmutableRule(ctx, projectID, daoRule) +} + +func (drm *defaultRuleManager) EnableImmutableRule(ctx context.Context, id int64, enabled bool) error { + return drm.dao.ToggleImmutableRule(ctx, id, enabled) +} + +func (drm *defaultRuleManager) GetImmutableRule(ctx context.Context, id int64) (*model.Metadata, error) { + daoRule, err := drm.dao.GetImmutableRule(ctx, id) + if err != nil { + return nil, err + } + rule := &model.Metadata{} + if daoRule == nil { + return nil, nil + } + if err = json.Unmarshal([]byte(daoRule.TagFilter), rule); err != nil { + return nil, err + } + rule.ID = daoRule.ID + rule.Disabled = daoRule.Disabled + return rule, nil +} + +func (drm *defaultRuleManager) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.Metadata, error) { + daoRules, err := drm.dao.ListImmutableRules(ctx, query) + if err != nil { + return nil, err + } + rules := make([]*model.Metadata, 0) + for _, daoRule := range daoRules { + rule := model.Metadata{} + if err = json.Unmarshal([]byte(daoRule.TagFilter), &rule); err != nil { + return nil, err + } + rule.ID = daoRule.ID + rule.Disabled = daoRule.Disabled + rules = append(rules, &rule) + } + sort.Slice(rules, func(i, j int) bool { + return rules[i].ID < rules[j].ID + }) + return rules, nil +} + +func (drm *defaultRuleManager) Count(ctx context.Context, query *q.Query) (int64, error) { + return drm.dao.Count(ctx, query) +} + +func (drm *defaultRuleManager) DeleteImmutableRule(ctx context.Context, id int64) error { + return drm.dao.DeleteImmutableRule(ctx, id) +} + +// NewDefaultRuleManager return a new instance of defaultRuleManager +func NewDefaultRuleManager() Manager { + return &defaultRuleManager{ + dao: dao.New(), + } +} diff --git a/src/pkg/immutabletag/manager_test.go b/src/pkg/immutable/manager_test.go similarity index 57% rename from src/pkg/immutabletag/manager_test.go rename to src/pkg/immutable/manager_test.go index 2079c9273..25d1351f3 100644 --- a/src/pkg/immutabletag/manager_test.go +++ b/src/pkg/immutable/manager_test.go @@ -1,8 +1,11 @@ -package immutabletag +package immutable import ( - dao_model "github.com/goharbor/harbor/src/pkg/immutabletag/dao/model" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" + "context" + "github.com/goharbor/harbor/src/lib/q" + dao_model "github.com/goharbor/harbor/src/pkg/immutable/dao/model" + "github.com/goharbor/harbor/src/pkg/immutable/model" + "github.com/goharbor/harbor/src/testing/pkg/immutable/dao" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -12,64 +15,12 @@ import ( "testing" ) -type mockImmutableDao struct { - mock.Mock -} - -func (m *mockImmutableDao) CreateImmutableRule(ir *dao_model.ImmutableRule) (int64, error) { - args := m.Called(ir) - return int64(args.Int(0)), args.Error(1) -} - -func (m *mockImmutableDao) UpdateImmutableRule(projectID int64, ir *dao_model.ImmutableRule) (int64, error) { - args := m.Called(ir) - return int64(0), args.Error(1) -} - -func (m *mockImmutableDao) QueryImmutableRuleByProjectID(projectID int64) ([]dao_model.ImmutableRule, error) { - args := m.Called() - var irs []dao_model.ImmutableRule - if args.Get(0) != nil { - irs = args.Get(0).([]dao_model.ImmutableRule) - } - return irs, args.Error(1) -} - -func (m *mockImmutableDao) QueryEnabledImmutableRuleByProjectID(projectID int64) ([]dao_model.ImmutableRule, error) { - args := m.Called() - var irs []dao_model.ImmutableRule - if args.Get(0) != nil { - irs = args.Get(0).([]dao_model.ImmutableRule) - } - return irs, args.Error(1) -} - -func (m *mockImmutableDao) DeleteImmutableRule(id int64) (int64, error) { - args := m.Called(id) - return int64(args.Int(0)), args.Error(1) -} - -func (m *mockImmutableDao) ToggleImmutableRule(id int64, enabled bool) (int64, error) { - args := m.Called(id) - return int64(args.Int(0)), args.Error(1) -} - -func (m *mockImmutableDao) GetImmutableRule(id int64) (*dao_model.ImmutableRule, error) { - args := m.Called(id) - var ir *dao_model.ImmutableRule - if args.Get(0) != nil { - ir = args.Get(0).(*dao_model.ImmutableRule) - } - return ir, args.Error(1) - -} - type managerTestingSuite struct { suite.Suite t *testing.T assert *assert.Assertions require *require.Assertions - mockImmutableDao *mockImmutableDao + mockImmutableDao *dao.DAO } func (m *managerTestingSuite) SetupSuite() { @@ -87,7 +38,7 @@ func (m *managerTestingSuite) TearDownSuite() { } func (m *managerTestingSuite) SetupTest() { - m.mockImmutableDao = &mockImmutableDao{} + m.mockImmutableDao = &dao.DAO{} Mgr = &defaultRuleManager{ dao: m.mockImmutableDao, } @@ -98,15 +49,15 @@ func TestManagerTestingSuite(t *testing.T) { } func (m *managerTestingSuite) TestCreateImmutableRule() { - m.mockImmutableDao.On("CreateImmutableRule", mock.Anything).Return(1, nil) - id, err := Mgr.CreateImmutableRule(&model.Metadata{}) - m.mockImmutableDao.AssertCalled(m.t, "CreateImmutableRule", mock.Anything) + m.mockImmutableDao.On("CreateImmutableRule", mock.Anything, mock.Anything).Return(int64(1), nil) + id, err := Mgr.CreateImmutableRule(context.Background(), &model.Metadata{}) + m.mockImmutableDao.AssertCalled(m.t, "CreateImmutableRule", mock.Anything, mock.Anything) m.require.Nil(err) m.assert.Equal(int64(1), id) } func (m *managerTestingSuite) TestQueryImmutableRuleByProjectID() { - m.mockImmutableDao.On("QueryImmutableRuleByProjectID", mock.Anything).Return([]dao_model.ImmutableRule{ + m.mockImmutableDao.On("ListImmutableRules", mock.Anything, mock.Anything).Return([]*dao_model.ImmutableRule{ { ID: 1, ProjectID: 1, @@ -125,15 +76,15 @@ func (m *managerTestingSuite) TestQueryImmutableRuleByProjectID() { "\"tag_selectors\":[{\"kind\":\"doublestar\",\"decoration\":\"matches\",\"pattern\":\"**\"}]," + "\"scope_selectors\":{\"repository\":[{\"kind\":\"doublestar\",\"decoration\":\"repoMatches\",\"pattern\":\"**\"}]}}", }}, nil) - irs, err := Mgr.QueryImmutableRuleByProjectID(int64(1)) - m.mockImmutableDao.AssertCalled(m.t, "QueryImmutableRuleByProjectID", mock.Anything) + irs, err := Mgr.ListImmutableRules(context.Background(), &q.Query{}) + m.mockImmutableDao.AssertCalled(m.t, "ListImmutableRules", mock.Anything, mock.Anything) m.require.Nil(err) m.assert.Equal(len(irs), 2) m.assert.Equal(irs[1].Disabled, false) } func (m *managerTestingSuite) TestQueryEnabledImmutableRuleByProjectID() { - m.mockImmutableDao.On("QueryEnabledImmutableRuleByProjectID", mock.Anything).Return([]dao_model.ImmutableRule{ + m.mockImmutableDao.On("ListImmutableRules", mock.Anything, mock.Anything).Return([]*dao_model.ImmutableRule{ { ID: 1, ProjectID: 1, @@ -152,15 +103,15 @@ func (m *managerTestingSuite) TestQueryEnabledImmutableRuleByProjectID() { "\"tag_selectors\":[{\"kind\":\"doublestar\",\"decoration\":\"matches\",\"pattern\":\"**\"}]," + "\"scope_selectors\":{\"repository\":[{\"kind\":\"doublestar\",\"decoration\":\"repoMatches\",\"pattern\":\"**\"}]}}", }}, nil) - irs, err := Mgr.QueryEnabledImmutableRuleByProjectID(int64(1)) - m.mockImmutableDao.AssertCalled(m.t, "QueryEnabledImmutableRuleByProjectID", mock.Anything) + irs, err := Mgr.ListImmutableRules(context.Background(), &q.Query{}) + m.mockImmutableDao.AssertCalled(m.t, "ListImmutableRules", mock.Anything, mock.Anything) m.require.Nil(err) m.assert.Equal(len(irs), 2) - m.assert.Equal(irs[0].Disabled, false) + m.assert.Equal(irs[0].Disabled, true) } func (m *managerTestingSuite) TestGetImmutableRule() { - m.mockImmutableDao.On("GetImmutableRule", mock.Anything).Return(&dao_model.ImmutableRule{ + m.mockImmutableDao.On("GetImmutableRule", mock.Anything, mock.Anything).Return(&dao_model.ImmutableRule{ ID: 1, ProjectID: 1, Disabled: true, @@ -169,33 +120,30 @@ func (m *managerTestingSuite) TestGetImmutableRule() { "\"tag_selectors\":[{\"kind\":\"doublestar\",\"decoration\":\"matches\",\"pattern\":\"**\"}]," + "\"scope_selectors\":{\"repository\":[{\"kind\":\"doublestar\",\"decoration\":\"repoMatches\",\"pattern\":\"**\"}]}}", }, nil) - ir, err := Mgr.GetImmutableRule(1) - m.mockImmutableDao.AssertCalled(m.t, "GetImmutableRule", mock.Anything) + ir, err := Mgr.GetImmutableRule(context.Background(), 1) + m.mockImmutableDao.AssertCalled(m.t, "GetImmutableRule", mock.Anything, mock.Anything) m.require.Nil(err) m.require.NotNil(ir) m.assert.Equal(int64(1), ir.ID) } func (m *managerTestingSuite) TestUpdateImmutableRule() { - m.mockImmutableDao.On("UpdateImmutableRule", mock.Anything).Return(1, nil) - id, err := Mgr.UpdateImmutableRule(int64(1), &model.Metadata{}) - m.mockImmutableDao.AssertCalled(m.t, "UpdateImmutableRule", mock.Anything) + m.mockImmutableDao.On("UpdateImmutableRule", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := Mgr.UpdateImmutableRule(context.Background(), int64(1), &model.Metadata{}) + m.mockImmutableDao.AssertCalled(m.t, "UpdateImmutableRule", mock.Anything, mock.Anything, mock.Anything) m.require.Nil(err) - m.assert.Equal(int64(0), id) } func (m *managerTestingSuite) TestEnableImmutableRule() { - m.mockImmutableDao.On("ToggleImmutableRule", mock.Anything).Return(1, nil) - id, err := Mgr.EnableImmutableRule(int64(1), true) - m.mockImmutableDao.AssertCalled(m.t, "ToggleImmutableRule", mock.Anything) + m.mockImmutableDao.On("ToggleImmutableRule", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := Mgr.EnableImmutableRule(context.Background(), int64(1), true) + m.mockImmutableDao.AssertCalled(m.t, "ToggleImmutableRule", mock.Anything, mock.Anything, mock.Anything) m.require.Nil(err) - m.assert.Equal(int64(1), id) } func (m *managerTestingSuite) TestDeleteImmutableRule() { - m.mockImmutableDao.On("DeleteImmutableRule", mock.Anything).Return(1, nil) - id, err := Mgr.DeleteImmutableRule(int64(1)) - m.mockImmutableDao.AssertCalled(m.t, "DeleteImmutableRule", mock.Anything) + m.mockImmutableDao.On("DeleteImmutableRule", mock.Anything, mock.Anything).Return(nil) + err := Mgr.DeleteImmutableRule(context.Background(), int64(1)) + m.mockImmutableDao.AssertCalled(m.t, "DeleteImmutableRule", mock.Anything, mock.Anything) m.require.Nil(err) - m.assert.Equal(int64(1), id) } diff --git a/src/pkg/immutabletag/match/matcher.go b/src/pkg/immutable/match/matcher.go similarity index 69% rename from src/pkg/immutabletag/match/matcher.go rename to src/pkg/immutable/match/matcher.go index eda90e7b7..d123ab88d 100644 --- a/src/pkg/immutabletag/match/matcher.go +++ b/src/pkg/immutable/match/matcher.go @@ -1,11 +1,12 @@ package match import ( + "context" "github.com/goharbor/harbor/src/lib/selector" ) // ImmutableTagMatcher ... type ImmutableTagMatcher interface { // Match whether the candidate is in the immutable list - Match(pid int64, c selector.Candidate) (bool, error) + Match(ctx context.Context, pid int64, c selector.Candidate) (bool, error) } diff --git a/src/pkg/immutabletag/match/rule/match.go b/src/pkg/immutable/match/rule/match.go similarity index 74% rename from src/pkg/immutabletag/match/rule/match.go rename to src/pkg/immutable/match/rule/match.go index d54d31908..ad1a3265a 100644 --- a/src/pkg/immutabletag/match/rule/match.go +++ b/src/pkg/immutable/match/rule/match.go @@ -1,21 +1,23 @@ package rule import ( + "context" + "github.com/goharbor/harbor/src/controller/immutable" + "github.com/goharbor/harbor/src/lib/q" iselector "github.com/goharbor/harbor/src/lib/selector" "github.com/goharbor/harbor/src/lib/selector/selectors/index" - "github.com/goharbor/harbor/src/pkg/immutabletag" - "github.com/goharbor/harbor/src/pkg/immutabletag/match" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" + "github.com/goharbor/harbor/src/pkg/immutable/match" + "github.com/goharbor/harbor/src/pkg/immutable/model" ) // Matcher ... type Matcher struct { - rules []model.Metadata + rules []*model.Metadata } // Match ... -func (rm *Matcher) Match(pid int64, c iselector.Candidate) (bool, error) { - if err := rm.getImmutableRules(pid); err != nil { +func (rm *Matcher) Match(ctx context.Context, pid int64, c iselector.Candidate) (bool, error) { + if err := rm.getImmutableRules(ctx, pid); err != nil { return false, err } @@ -70,8 +72,8 @@ func (rm *Matcher) Match(pid int64, c iselector.Candidate) (bool, error) { return false, nil } -func (rm *Matcher) getImmutableRules(pid int64) error { - rules, err := immutabletag.ImmuCtr.ListImmutableRules(pid) +func (rm *Matcher) getImmutableRules(ctx context.Context, pid int64) error { + rules, err := immutable.Ctr.ListImmutableRules(ctx, q.New(q.KeyWords{"ProjectID": pid})) if err != nil { return err } diff --git a/src/pkg/immutabletag/match/rule/match_test.go b/src/pkg/immutable/match/rule/match_test.go similarity index 81% rename from src/pkg/immutabletag/match/rule/match_test.go rename to src/pkg/immutable/match/rule/match_test.go index c29dddb31..c94f88f2f 100644 --- a/src/pkg/immutabletag/match/rule/match_test.go +++ b/src/pkg/immutable/match/rule/match_test.go @@ -2,9 +2,10 @@ package rule import ( "github.com/goharbor/harbor/src/common/dao" + "github.com/goharbor/harbor/src/controller/immutable" + "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/selector" - "github.com/goharbor/harbor/src/pkg/immutabletag" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" + "github.com/goharbor/harbor/src/pkg/immutable/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -18,7 +19,7 @@ type MatchTestSuite struct { t *testing.T assert *assert.Assertions require *require.Assertions - ctr immutabletag.Controller + ctr immutable.Controller ruleID int64 ruleID2 int64 } @@ -28,7 +29,7 @@ func (s *MatchTestSuite) SetupSuite() { s.t = s.T() s.assert = assert.New(s.t) s.require = require.New(s.t) - s.ctr = immutabletag.ImmuCtr + s.ctr = immutable.Ctr } func (s *MatchTestSuite) TestImmuMatch() { @@ -77,11 +78,11 @@ func (s *MatchTestSuite) TestImmuMatch() { }, } - id, err := s.ctr.CreateImmutableRule(rule) + id, err := s.ctr.CreateImmutableRule(orm.Context(), rule) s.ruleID = id s.require.Nil(err) - id, err = s.ctr.CreateImmutableRule(rule2) + id, err = s.ctr.CreateImmutableRule(orm.Context(), rule2) s.ruleID2 = id s.require.Nil(err) @@ -93,7 +94,7 @@ func (s *MatchTestSuite) TestImmuMatch() { Repository: "redis", Tags: []string{"release-1.10"}, } - isMatch, err := match.Match(1, c1) + isMatch, err := match.Match(orm.Context(), 1, c1) s.require.Equal(isMatch, true) s.require.Nil(err) @@ -104,7 +105,7 @@ func (s *MatchTestSuite) TestImmuMatch() { Tags: []string{"1.10"}, Kind: selector.Image, } - isMatch, err = match.Match(1, c2) + isMatch, err = match.Match(orm.Context(), 1, c2) s.require.Equal(isMatch, false) s.require.Nil(err) @@ -115,7 +116,7 @@ func (s *MatchTestSuite) TestImmuMatch() { Tags: []string{"9.4.8"}, Kind: selector.Image, } - isMatch, err = match.Match(1, c3) + isMatch, err = match.Match(orm.Context(), 1, c3) s.require.Equal(isMatch, true) s.require.Nil(err) @@ -126,17 +127,17 @@ func (s *MatchTestSuite) TestImmuMatch() { Tags: []string{"world"}, Kind: selector.Image, } - isMatch, err = match.Match(1, c4) + isMatch, err = match.Match(orm.Context(), 1, c4) s.require.Equal(isMatch, false) s.require.Nil(err) } // TearDownSuite clears env for test suite func (s *MatchTestSuite) TearDownSuite() { - err := s.ctr.DeleteImmutableRule(s.ruleID) + err := s.ctr.DeleteImmutableRule(orm.Context(), s.ruleID) require.NoError(s.T(), err, "delete immutable") - err = s.ctr.DeleteImmutableRule(s.ruleID2) + err = s.ctr.DeleteImmutableRule(orm.Context(), s.ruleID2) require.NoError(s.T(), err, "delete immutable") } diff --git a/src/pkg/immutabletag/model/rule.go b/src/pkg/immutable/model/rule.go similarity index 100% rename from src/pkg/immutabletag/model/rule.go rename to src/pkg/immutable/model/rule.go diff --git a/src/pkg/immutabletag/controller.go b/src/pkg/immutabletag/controller.go deleted file mode 100644 index 95c18d403..000000000 --- a/src/pkg/immutabletag/controller.go +++ /dev/null @@ -1,80 +0,0 @@ -package immutabletag - -import ( - "fmt" - - "github.com/goharbor/harbor/src/pkg/immutabletag/model" -) - -var ( - // ImmuCtr is a global variable for the default immutable controller implementation - ImmuCtr = NewAPIController(NewDefaultRuleManager()) -) - -// Controller to handle the requests related with immutabletag -type Controller interface { - // GetImmutableRule ... - GetImmutableRule(id int64) (*model.Metadata, error) - - // CreateImmutableRule ... - CreateImmutableRule(m *model.Metadata) (int64, error) - - // DeleteImmutableRule ... - DeleteImmutableRule(id int64) error - - // UpdateImmutableRule ... - UpdateImmutableRule(pid int64, m *model.Metadata) error - - // ListImmutableRules ... - ListImmutableRules(pid int64) ([]model.Metadata, error) -} - -// DefaultAPIController ... -type DefaultAPIController struct { - manager Manager -} - -// GetImmutableRule ... -func (r *DefaultAPIController) GetImmutableRule(id int64) (*model.Metadata, error) { - return r.manager.GetImmutableRule(id) -} - -// DeleteImmutableRule ... -func (r *DefaultAPIController) DeleteImmutableRule(id int64) error { - _, err := r.manager.DeleteImmutableRule(id) - return err -} - -// CreateImmutableRule ... -func (r *DefaultAPIController) CreateImmutableRule(m *model.Metadata) (int64, error) { - return r.manager.CreateImmutableRule(m) -} - -// UpdateImmutableRule ... -func (r *DefaultAPIController) UpdateImmutableRule(pid int64, m *model.Metadata) error { - m0, err := r.manager.GetImmutableRule(m.ID) - if err != nil { - return err - } - if m0 == nil { - return fmt.Errorf("the immutable tag rule is not found id:%v", m.ID) - } - if m0.Disabled != m.Disabled { - _, err := r.manager.EnableImmutableRule(m.ID, m.Disabled) - return err - } - _, err = r.manager.UpdateImmutableRule(pid, m) - return err -} - -// ListImmutableRules ... -func (r *DefaultAPIController) ListImmutableRules(pid int64) ([]model.Metadata, error) { - return r.manager.QueryImmutableRuleByProjectID(pid) -} - -// NewAPIController ... -func NewAPIController(immutableMgr Manager) Controller { - return &DefaultAPIController{ - manager: immutableMgr, - } -} diff --git a/src/pkg/immutabletag/dao/immutable.go b/src/pkg/immutabletag/dao/immutable.go deleted file mode 100644 index 2f5a87dac..000000000 --- a/src/pkg/immutabletag/dao/immutable.go +++ /dev/null @@ -1,116 +0,0 @@ -package dao - -import ( - "fmt" - - "github.com/astaxie/beego/orm" - "github.com/goharbor/harbor/src/common/dao" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/immutabletag/dao/model" -) - -// ImmutableRuleDao defines the interface to access the ImmutableRule data model -type ImmutableRuleDao interface { - CreateImmutableRule(ir *model.ImmutableRule) (int64, error) - UpdateImmutableRule(projectID int64, ir *model.ImmutableRule) (int64, error) - ToggleImmutableRule(id int64, status bool) (int64, error) - GetImmutableRule(id int64) (*model.ImmutableRule, error) - QueryImmutableRuleByProjectID(projectID int64) ([]model.ImmutableRule, error) - QueryEnabledImmutableRuleByProjectID(projectID int64) ([]model.ImmutableRule, error) - DeleteImmutableRule(id int64) (int64, error) -} - -// New creates a default implementation for ImmutableRuleDao -func New() ImmutableRuleDao { - return &immutableRuleDao{} -} - -type immutableRuleDao struct{} - -// CreateImmutableRule creates the Immutable Rule -func (i *immutableRuleDao) CreateImmutableRule(ir *model.ImmutableRule) (int64, error) { - ir.Disabled = false - o := dao.GetOrmer() - id, err := o.Insert(ir) - if err != nil { - if dao.IsDupRecErr(err) { - return id, errors.ConflictError(err) - } - return id, err - } - return id, nil -} - -// UpdateImmutableRule update the immutable rules -func (i *immutableRuleDao) UpdateImmutableRule(projectID int64, ir *model.ImmutableRule) (int64, error) { - ir.ProjectID = projectID - o := dao.GetOrmer() - id, err := o.Update(ir, "TagFilter") - if err != nil { - if errors.Is(err, orm.ErrNoRows) { - return id, errors.NotFoundError(err) - } - return id, err - } - return id, nil -} - -// ToggleImmutableRule enable/disable immutable rules -func (i *immutableRuleDao) ToggleImmutableRule(id int64, status bool) (int64, error) { - o := dao.GetOrmer() - ir := &model.ImmutableRule{ID: id, Disabled: status} - id, err := o.Update(ir, "Disabled") - if err != nil { - if errors.Is(err, orm.ErrNoRows) { - return id, errors.NotFoundError(err) - } - return id, err - } - return id, nil -} - -// GetImmutableRule get immutable rule -func (i *immutableRuleDao) GetImmutableRule(id int64) (*model.ImmutableRule, error) { - o := dao.GetOrmer() - ir := &model.ImmutableRule{ID: id} - err := o.Read(ir) - if err != nil { - if errors.Is(err, orm.ErrNoRows) { - return nil, errors.New(err).WithCode(errors.NotFoundCode). - WithMessage(fmt.Sprintf("the immutable rule %d is not found.", id)) - } - return nil, err - } - return ir, nil -} - -// QueryImmutableRuleByProjectID get all immutable rule by project -func (i *immutableRuleDao) QueryImmutableRuleByProjectID(projectID int64) ([]model.ImmutableRule, error) { - o := dao.GetOrmer() - qs := o.QueryTable(&model.ImmutableRule{}).Filter("ProjectID", projectID) - r := make([]model.ImmutableRule, 0) - _, err := qs.All(&r) - if err != nil { - return nil, fmt.Errorf("failed to get immutable tag rule by projectID %d, error: %w", projectID, err) - } - return r, nil -} - -// QueryEnabledImmutableRuleByProjectID get all enabled immutable rule by project -func (i *immutableRuleDao) QueryEnabledImmutableRuleByProjectID(projectID int64) ([]model.ImmutableRule, error) { - o := dao.GetOrmer() - qs := o.QueryTable(&model.ImmutableRule{}).Filter("ProjectID", projectID).Filter("Disabled", false) - var r []model.ImmutableRule - _, err := qs.All(&r) - if err != nil { - return nil, fmt.Errorf("failed to get enabled immutable tag rule for by projectID %d, error: %w", projectID, err) - } - return r, nil -} - -// DeleteImmutableRule delete the immutable rule -func (i *immutableRuleDao) DeleteImmutableRule(id int64) (int64, error) { - o := dao.GetOrmer() - ir := &model.ImmutableRule{ID: id} - return o.Delete(ir) -} diff --git a/src/pkg/immutabletag/manager.go b/src/pkg/immutabletag/manager.go deleted file mode 100644 index 167e00726..000000000 --- a/src/pkg/immutabletag/manager.go +++ /dev/null @@ -1,124 +0,0 @@ -package immutabletag - -import ( - "encoding/json" - "sort" - - "github.com/goharbor/harbor/src/pkg/immutabletag/dao" - dao_model "github.com/goharbor/harbor/src/pkg/immutabletag/dao/model" - "github.com/goharbor/harbor/src/pkg/immutabletag/model" -) - -var ( - // Mgr is a global variable for the default immutablerule manager implementation - Mgr = NewDefaultRuleManager() -) - -// Manager ... -type Manager interface { - // CreateImmutableRule creates the Immutable Rule - CreateImmutableRule(m *model.Metadata) (int64, error) - // UpdateImmutableRule update the immutable rules - UpdateImmutableRule(projectID int64, ir *model.Metadata) (int64, error) - // EnableImmutableRule enable/disable immutable rules - EnableImmutableRule(id int64, enabled bool) (int64, error) - // GetImmutableRule get immutable rule - GetImmutableRule(id int64) (*model.Metadata, error) - // QueryImmutableRuleByProjectID get all immutable rule by project - QueryImmutableRuleByProjectID(projectID int64) ([]model.Metadata, error) - // QueryEnabledImmutableRuleByProjectID get all enabled immutable rule by project - QueryEnabledImmutableRuleByProjectID(projectID int64) ([]model.Metadata, error) - // DeleteImmutableRule delete the immutable rule - DeleteImmutableRule(id int64) (int64, error) -} - -type defaultRuleManager struct { - dao dao.ImmutableRuleDao -} - -func (drm *defaultRuleManager) CreateImmutableRule(ir *model.Metadata) (int64, error) { - daoRule := &dao_model.ImmutableRule{} - daoRule.Disabled = ir.Disabled - daoRule.ProjectID = ir.ProjectID - data, _ := json.Marshal(ir) - daoRule.TagFilter = string(data) - return drm.dao.CreateImmutableRule(daoRule) -} - -func (drm *defaultRuleManager) UpdateImmutableRule(projectID int64, ir *model.Metadata) (int64, error) { - daoRule := &dao_model.ImmutableRule{} - data, _ := json.Marshal(ir) - daoRule.ID = ir.ID - daoRule.TagFilter = string(data) - return drm.dao.UpdateImmutableRule(projectID, daoRule) -} - -func (drm *defaultRuleManager) EnableImmutableRule(id int64, enabled bool) (int64, error) { - return drm.dao.ToggleImmutableRule(id, enabled) -} - -func (drm *defaultRuleManager) GetImmutableRule(id int64) (*model.Metadata, error) { - daoRule, err := drm.dao.GetImmutableRule(id) - if err != nil { - return nil, err - } - rule := &model.Metadata{} - if daoRule == nil { - return nil, nil - } - if err = json.Unmarshal([]byte(daoRule.TagFilter), rule); err != nil { - return nil, err - } - rule.ID = daoRule.ID - rule.Disabled = daoRule.Disabled - return rule, nil -} - -func (drm *defaultRuleManager) QueryImmutableRuleByProjectID(projectID int64) ([]model.Metadata, error) { - daoRules, err := drm.dao.QueryImmutableRuleByProjectID(projectID) - if err != nil { - return nil, err - } - rules := make([]model.Metadata, 0) - for _, daoRule := range daoRules { - rule := model.Metadata{} - if err = json.Unmarshal([]byte(daoRule.TagFilter), &rule); err != nil { - return nil, err - } - rule.ID = daoRule.ID - rule.Disabled = daoRule.Disabled - rules = append(rules, rule) - } - sort.Slice(rules, func(i, j int) bool { - return rules[i].ID < rules[j].ID - }) - return rules, nil -} - -func (drm *defaultRuleManager) QueryEnabledImmutableRuleByProjectID(projectID int64) ([]model.Metadata, error) { - daoRules, err := drm.dao.QueryEnabledImmutableRuleByProjectID(projectID) - if err != nil { - return nil, err - } - var rules []model.Metadata - for _, daoRule := range daoRules { - rule := model.Metadata{} - if err = json.Unmarshal([]byte(daoRule.TagFilter), &rule); err != nil { - return nil, err - } - rule.ID = daoRule.ID - rules = append(rules, rule) - } - return rules, nil -} - -func (drm *defaultRuleManager) DeleteImmutableRule(id int64) (int64, error) { - return drm.dao.DeleteImmutableRule(id) -} - -// NewDefaultRuleManager return a new instance of defaultRuleManager -func NewDefaultRuleManager() Manager { - return &defaultRuleManager{ - dao: dao.New(), - } -} diff --git a/src/pkg/retention/job.go b/src/pkg/retention/job.go index 8a85a732a..223d60f82 100644 --- a/src/pkg/retention/job.go +++ b/src/pkg/retention/job.go @@ -111,7 +111,7 @@ func (pj *Job) Run(ctx job.Context, params job.Parameters) error { } // Run the flow - results, err := processor.Process(allCandidates) + results, err := processor.Process(ctx.SystemContext(), allCandidates) if err != nil { return logError(myLogger, err) } diff --git a/src/pkg/retention/policy/action/index/index_test.go b/src/pkg/retention/policy/action/index/index_test.go index 3292d9da8..9e7f61f7c 100644 --- a/src/pkg/retention/policy/action/index/index_test.go +++ b/src/pkg/retention/policy/action/index/index_test.go @@ -15,6 +15,7 @@ package index import ( + "context" "github.com/goharbor/harbor/src/lib/selector" "testing" "time" @@ -57,7 +58,7 @@ func (suite *IndexTestSuite) TestGet() { require.NoError(suite.T(), err) require.NotNil(suite.T(), p) - results, err := p.Perform(suite.candidates) + results, err := p.Perform(context.TODO(), suite.candidates) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(results)) assert.Condition(suite.T(), func() (success bool) { @@ -77,7 +78,7 @@ type fakePerformer struct { } // Perform the artifacts -func (p *fakePerformer) Perform(candidates []*selector.Candidate) (results []*selector.Result, err error) { +func (p *fakePerformer) Perform(ctx context.Context, candidates []*selector.Candidate) (results []*selector.Result, err error) { for _, c := range candidates { results = append(results, &selector.Result{ Target: c, diff --git a/src/pkg/retention/policy/action/performer.go b/src/pkg/retention/policy/action/performer.go index 6352bb281..550479f6c 100644 --- a/src/pkg/retention/policy/action/performer.go +++ b/src/pkg/retention/policy/action/performer.go @@ -15,10 +15,11 @@ package action import ( + "context" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/selector" - "github.com/goharbor/harbor/src/pkg/immutabletag/match/rule" + "github.com/goharbor/harbor/src/pkg/immutable/match/rule" "github.com/goharbor/harbor/src/pkg/retention/dep" ) @@ -37,7 +38,7 @@ type Performer interface { // Returns: // []*art.Result : result infos // error : common error if any errors occurred - Perform(candidates []*selector.Candidate) ([]*selector.Result, error) + Perform(ctx context.Context, candidates []*selector.Candidate) ([]*selector.Result, error) } // PerformerFactory is factory method for creating Performer @@ -51,7 +52,7 @@ type retainAction struct { } // Perform the action -func (ra *retainAction) Perform(candidates []*selector.Candidate) (results []*selector.Result, err error) { +func (ra *retainAction) Perform(ctx context.Context, candidates []*selector.Candidate) (results []*selector.Result, err error) { retainedShare := make(map[string]bool) immutableShare := make(map[string]bool) for _, c := range candidates { @@ -62,7 +63,7 @@ func (ra *retainAction) Perform(candidates []*selector.Candidate) (results []*se if _, ok := retainedShare[c.Hash()]; ok { continue } - if isImmutable(c) { + if isImmutable(ctx, c) { immutableShare[c.Hash()] = true } } @@ -91,11 +92,11 @@ func (ra *retainAction) Perform(candidates []*selector.Candidate) (results []*se return } -func isImmutable(c *selector.Candidate) bool { +func isImmutable(ctx context.Context, c *selector.Candidate) bool { projectID := c.NamespaceID repo := c.Repository _, repoName := utils.ParseRepository(repo) - matched, err := rule.NewRuleMatcher().Match(projectID, selector.Candidate{ + matched, err := rule.NewRuleMatcher().Match(ctx, projectID, selector.Candidate{ Repository: repoName, Tags: c.Tags, NamespaceID: projectID, diff --git a/src/pkg/retention/policy/action/performer_test.go b/src/pkg/retention/policy/action/performer_test.go index 646113de4..dec39f2b6 100644 --- a/src/pkg/retention/policy/action/performer_test.go +++ b/src/pkg/retention/policy/action/performer_test.go @@ -16,13 +16,14 @@ package action import ( "github.com/goharbor/harbor/src/common/dao" + "github.com/goharbor/harbor/src/controller/immutable" + "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/selector" - "github.com/goharbor/harbor/src/pkg/immutabletag" "testing" "time" "github.com/goharbor/harbor/src/lib/errors" - immumodel "github.com/goharbor/harbor/src/pkg/immutabletag/model" + immumodel "github.com/goharbor/harbor/src/pkg/immutable/model" "github.com/goharbor/harbor/src/pkg/retention/dep" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -93,7 +94,7 @@ func (suite *TestPerformerSuite) TestPerform() { }, } - results, err := p.Perform(candidates) + results, err := p.Perform(orm.Context(), candidates) require.NoError(suite.T(), err) require.Equal(suite.T(), 1, len(results)) require.NotNil(suite.T(), results[0].Target) @@ -171,10 +172,10 @@ func (suite *TestPerformerSuite) TestPerformImmutable() { }, }, } - imid, e := immutabletag.ImmuCtr.CreateImmutableRule(rule) + imid, e := immutable.Ctr.CreateImmutableRule(orm.Context(), rule) assert.NoError(suite.T(), e) defer func() { - assert.NoError(suite.T(), immutabletag.ImmuCtr.DeleteImmutableRule(imid)) + assert.NoError(suite.T(), immutable.Ctr.DeleteImmutableRule(orm.Context(), imid)) }() candidates := []*selector.Candidate{ @@ -190,7 +191,7 @@ func (suite *TestPerformerSuite) TestPerformImmutable() { }, } - results, err := p.Perform(candidates) + results, err := p.Perform(orm.Context(), candidates) require.NoError(suite.T(), err) require.Equal(suite.T(), 3, len(results)) for _, r := range results { diff --git a/src/pkg/retention/policy/alg/or/processor.go b/src/pkg/retention/policy/alg/or/processor.go index a6d1519b6..511549430 100644 --- a/src/pkg/retention/policy/alg/or/processor.go +++ b/src/pkg/retention/policy/alg/or/processor.go @@ -15,6 +15,7 @@ package or import ( + "context" "github.com/goharbor/harbor/src/lib/selector" "sync" @@ -59,7 +60,7 @@ func New(parameters []*alg.Parameter) alg.Processor { } // Process the candidates with the rules -func (p *processor) Process(artifacts []*selector.Candidate) ([]*selector.Result, error) { +func (p *processor) Process(ctx context.Context, artifacts []*selector.Candidate) ([]*selector.Result, error) { if len(artifacts) == 0 { log.Debug("no artifacts to retention") return make([]*selector.Result, 0), nil @@ -181,7 +182,7 @@ func (p *processor) Process(artifacts []*selector.Candidate) ([]*selector.Result cl := hash.toList() if pf, ok := p.performers[act]; ok { - if theRes, err := pf.Perform(cl); err != nil { + if theRes, err := pf.Perform(ctx, cl); err != nil { attachedErr = err } else { results = append(results, theRes...) diff --git a/src/pkg/retention/policy/alg/or/processor_test.go b/src/pkg/retention/policy/alg/or/processor_test.go index 5cb2ad893..d11746e42 100644 --- a/src/pkg/retention/policy/alg/or/processor_test.go +++ b/src/pkg/retention/policy/alg/or/processor_test.go @@ -17,6 +17,7 @@ package or import ( "errors" "github.com/goharbor/harbor/src/common/dao" + "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/selector" "testing" "time" @@ -111,7 +112,7 @@ func (suite *ProcessorTestSuite) TestProcess() { p := New(params) - results, err := p.Process(suite.all) + results, err := p.Process(orm.Context(), suite.all) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(results)) assert.Condition(suite.T(), func() bool { @@ -142,7 +143,7 @@ func (suite *ProcessorTestSuite) TestProcess2() { p := New(params) - results, err := p.Process(suite.all) + results, err := p.Process(orm.Context(), suite.all) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(results)) assert.Condition(suite.T(), func() bool { diff --git a/src/pkg/retention/policy/alg/processor.go b/src/pkg/retention/policy/alg/processor.go index 4ddd7762c..9f9855036 100644 --- a/src/pkg/retention/policy/alg/processor.go +++ b/src/pkg/retention/policy/alg/processor.go @@ -15,6 +15,7 @@ package alg import ( + "context" "github.com/goharbor/harbor/src/lib/selector" "github.com/goharbor/harbor/src/pkg/retention/policy/action" "github.com/goharbor/harbor/src/pkg/retention/policy/rule" @@ -32,7 +33,7 @@ type Processor interface { // Returns: // []*art.Result : the processed results // error : common error object if any errors occurred - Process(artifacts []*selector.Candidate) ([]*selector.Result, error) + Process(ctx context.Context, artifacts []*selector.Candidate) ([]*selector.Result, error) } // Parameter for constructing a processor diff --git a/src/pkg/retention/policy/builder_test.go b/src/pkg/retention/policy/builder_test.go index 92b0e6583..3fe00b778 100644 --- a/src/pkg/retention/policy/builder_test.go +++ b/src/pkg/retention/policy/builder_test.go @@ -16,6 +16,7 @@ package policy import ( "github.com/goharbor/harbor/src/common/dao" + "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/selector" "testing" "time" @@ -148,7 +149,7 @@ func (suite *TestBuilderSuite) TestBuild() { require.NoError(suite.T(), err) require.NotNil(suite.T(), p) - results, err := p.Process(suite.all) + results, err := p.Process(orm.Context(), suite.all) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(results)) assert.Condition(suite.T(), func() (success bool) { diff --git a/src/server/middleware/immutable/pushmf_test.go b/src/server/middleware/immutable/pushmf_test.go index 90c61b29b..f96faa39d 100644 --- a/src/server/middleware/immutable/pushmf_test.go +++ b/src/server/middleware/immutable/pushmf_test.go @@ -3,6 +3,7 @@ package immutable import ( "context" "fmt" + "github.com/goharbor/harbor/src/controller/immutable" "math/rand" "net/http" "net/http/httptest" @@ -15,8 +16,7 @@ import ( "github.com/goharbor/harbor/src/lib" internal_orm "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/pkg/immutabletag" - immu_model "github.com/goharbor/harbor/src/pkg/immutabletag/model" + immu_model "github.com/goharbor/harbor/src/pkg/immutable/model" "github.com/goharbor/harbor/src/pkg/repository" "github.com/goharbor/harbor/src/pkg/tag" tag_model "github.com/goharbor/harbor/src/pkg/tag/model/tag" @@ -141,7 +141,7 @@ func (suite *HandlerSuite) addImmutableRule(pid int64) int64 { }, }, } - id, err := immutabletag.ImmuCtr.CreateImmutableRule(metadata) + id, err := immutable.Ctr.CreateImmutableRule(internal_orm.Context(), metadata) require.NoError(suite.T(), err, "nil error expected but got %s", err) return id } @@ -163,7 +163,7 @@ func (suite *HandlerSuite) TestPutDeleteManifestCreated() { artifact.Mgr.Delete(ctx, afID) repository.Mgr.Delete(ctx, repoID) tag.Mgr.Delete(ctx, tagID) - immutabletag.ImmuCtr.DeleteImmutableRule(immuRuleID) + immutable.Ctr.DeleteImmutableRule(internal_orm.Context(), immuRuleID) }() code1 := doPutManifestRequest(projectID, projectName, "photon", "release-1.10", dgt) diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index 5e9094309..6d3b54630 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -48,6 +48,7 @@ func New() http.Handler { GCAPI: newGCAPI(), QuotaAPI: newQuotaAPI(), RetentionAPI: newRetentionAPI(), + ImmutableAPI: newImmutableAPI(), OidcAPI: newOIDCAPI(), }) if err != nil { diff --git a/src/server/v2.0/handler/immutable.go b/src/server/v2.0/handler/immutable.go new file mode 100644 index 000000000..ada7ac6f2 --- /dev/null +++ b/src/server/v2.0/handler/immutable.go @@ -0,0 +1,143 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/controller/immutable" + "github.com/goharbor/harbor/src/controller/project" + "github.com/goharbor/harbor/src/lib" + "github.com/goharbor/harbor/src/pkg/immutable/model" + handler_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/immutable" + "strings" +) + +func newImmutableAPI() *immutableAPI { + return &immutableAPI{ + immuCtl: immutable.Ctr, + projectCtr: project.Ctl, + } +} + +type immutableAPI struct { + BaseAPI + immuCtl immutable.Controller + projectCtr project.Controller +} + +func (ia *immutableAPI) CreateImmuRule(ctx context.Context, params operation.CreateImmuRuleParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := ia.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceImmutableTag); err != nil { + return ia.SendError(ctx, err) + } + + metadata := model.Metadata{} + lib.JSONCopy(&metadata, params.ImmutableRule) + + projectID, err := ia.getProjectID(ctx, projectNameOrID) + if err != nil { + return ia.SendError(ctx, err) + } + metadata.ProjectID = projectID + + id, err := ia.immuCtl.CreateImmutableRule(ctx, &metadata) + if err != nil { + return ia.SendError(ctx, err) + } + + location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), id) + return operation.NewCreateImmuRuleCreated().WithLocation(location) +} + +func (ia *immutableAPI) DeleteImmuRule(ctx context.Context, params operation.DeleteImmuRuleParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := ia.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete, rbac.ResourceImmutableTag); err != nil { + return ia.SendError(ctx, err) + } + + if err := ia.immuCtl.DeleteImmutableRule(ctx, params.ImmutableRuleID); err != nil { + return ia.SendError(ctx, err) + } + + return operation.NewDeleteImmuRuleOK() +} + +func (ia *immutableAPI) UpdateImmuRule(ctx context.Context, params operation.UpdateImmuRuleParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := ia.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate, rbac.ResourceImmutableTag); err != nil { + return ia.SendError(ctx, err) + } + + metadata := model.Metadata{} + lib.JSONCopy(&metadata, params.ImmutableRule) + + projectID, err := ia.getProjectID(ctx, projectNameOrID) + if err != nil { + return ia.SendError(ctx, err) + } + metadata.ProjectID = projectID + + if err := ia.immuCtl.UpdateImmutableRule(ctx, projectID, &metadata); err != nil { + return ia.SendError(ctx, err) + } + + return operation.NewUpdateImmuRuleOK() +} + +func (ia *immutableAPI) ListImmuRules(ctx context.Context, params operation.ListImmuRulesParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := ia.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceImmutableTag); err != nil { + return ia.SendError(ctx, err) + } + + query, err := ia.BuildQuery(ctx, params.Q, params.Page, params.PageSize) + if err != nil { + return ia.SendError(ctx, err) + } + + projectID, err := ia.getProjectID(ctx, projectNameOrID) + if err != nil { + return ia.SendError(ctx, err) + } + query.Keywords["ProjectID"] = projectID + + total, err := ia.immuCtl.Count(ctx, query) + if err != nil { + return ia.SendError(ctx, err) + } + + rules, err := ia.immuCtl.ListImmutableRules(ctx, query) + if err != nil { + return ia.SendError(ctx, err) + } + + var results []*models.ImmutableRule + for _, r := range rules { + results = append(results, handler_model.NewImmutableRule(r).ToSwagger()) + } + + return operation.NewListImmuRulesOK(). + WithXTotalCount(total). + WithLink(ia.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()). + WithPayload(results) +} + +func (ia *immutableAPI) getProjectID(ctx context.Context, projectNameOrID interface{}) (int64, error) { + projectName, ok := projectNameOrID.(string) + if ok { + p, err := ia.projectCtr.Get(ctx, projectName, project.Metadata(false)) + if err != nil { + return 0, err + } + return p.ProjectID, nil + } + projectID, ok := projectNameOrID.(int64) + if ok { + return projectID, nil + } + return 0, errors.New("unknown project identifier type") +} diff --git a/src/server/v2.0/handler/model/immutable.go b/src/server/v2.0/handler/model/immutable.go new file mode 100644 index 000000000..33b14751a --- /dev/null +++ b/src/server/v2.0/handler/model/immutable.go @@ -0,0 +1,61 @@ +package model + +import ( + pkg_model "github.com/goharbor/harbor/src/pkg/immutable/model" + "github.com/goharbor/harbor/src/server/v2.0/models" +) + +// ImmutableRule ... +type ImmutableRule struct { + *pkg_model.Metadata +} + +// ToSwagger ... +func (ir *ImmutableRule) ToSwagger() *models.ImmutableRule { + return &models.ImmutableRule{ + ID: ir.ID, + Disabled: ir.Disabled, + Action: ir.Action, + Priority: int64(ir.Priority), + ScopeSelectors: ir.ToScopeSelectors(), + TagSelectors: ir.ToTagSelectors(), + Template: ir.Template, + } +} + +// ToTagSelectors ... +func (ir *ImmutableRule) ToTagSelectors() []*models.ImmutableSelector { + var results []*models.ImmutableSelector + for _, t := range ir.TagSelectors { + results = append(results, &models.ImmutableSelector{ + Decoration: t.Decoration, + Kind: t.Kind, + Pattern: t.Pattern, + }) + } + return results +} + +// ToScopeSelectors ... +func (ir *ImmutableRule) ToScopeSelectors() map[string][]models.ImmutableSelector { + results := map[string][]models.ImmutableSelector{} + for k, v := range ir.ScopeSelectors { + var scopeSelectors []models.ImmutableSelector + for _, s := range v { + scopeSelectors = append(scopeSelectors, models.ImmutableSelector{ + Decoration: s.Decoration, + Kind: s.Kind, + Pattern: s.Pattern, + }) + } + results[k] = scopeSelectors + } + return results +} + +// NewImmutableRule ... +func NewImmutableRule(meta *pkg_model.Metadata) *ImmutableRule { + return &ImmutableRule{ + Metadata: meta, + } +} diff --git a/src/server/v2.0/route/legacy.go b/src/server/v2.0/route/legacy.go index 909247b30..40d61f7b3 100755 --- a/src/server/v2.0/route/legacy.go +++ b/src/server/v2.0/route/legacy.go @@ -55,9 +55,6 @@ func registerLegacyRoutes() { beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/events", &api.NotificationPolicyAPI{}, "get:GetSupportedEventTypes") beego.Router("/api/"+version+"/projects/:pid([0-9]+)/webhook/jobs/", &api.NotificationJobAPI{}, "get:List") - beego.Router("/api/"+version+"/projects/:pid([0-9]+)/immutabletagrules", &api.ImmutableTagRuleAPI{}, "get:List;post:Post") - beego.Router("/api/"+version+"/projects/:pid([0-9]+)/immutabletagrules/:id([0-9]+)", &api.ImmutableTagRuleAPI{}) - beego.Router("/api/"+version+"/configurations", &api.ConfigAPI{}, "get:Get;put:Put") beego.Router("/api/"+version+"/statistics", &api.StatisticAPI{}) beego.Router("/api/"+version+"/labels", &api.LabelAPI{}, "post:Post;get:List") @@ -70,9 +67,6 @@ func registerLegacyRoutes() { beego.Router("/api/"+version+"/registries/:id/info", &api.RegistryAPI{}, "get:GetInfo") beego.Router("/api/"+version+"/registries/:id/namespace", &api.RegistryAPI{}, "get:GetNamespace") - beego.Router("/api/"+version+"/projects/:pid([0-9]+)/immutabletagrules", &api.ImmutableTagRuleAPI{}, "get:List;post:Post") - beego.Router("/api/"+version+"/projects/:pid([0-9]+)/immutabletagrules/:id([0-9]+)", &api.ImmutableTagRuleAPI{}) - // APIs for chart repository if config.WithChartMuseum() { // Labels for chart diff --git a/src/testing/pkg/immutable/dao/dao.go b/src/testing/pkg/immutable/dao/dao.go new file mode 100644 index 000000000..e9e9734b2 --- /dev/null +++ b/src/testing/pkg/immutable/dao/dao.go @@ -0,0 +1,148 @@ +// 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/immutable/dao/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 +} + +// CreateImmutableRule provides a mock function with given fields: ctx, ir +func (_m *DAO) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) (int64, error) { + ret := _m.Called(ctx, ir) + + var r0 int64 + if rf, ok := ret.Get(0).(func(context.Context, *model.ImmutableRule) int64); ok { + r0 = rf(ctx, ir) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *model.ImmutableRule) error); ok { + r1 = rf(ctx, ir) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteImmutableRule provides a mock function with given fields: ctx, id +func (_m *DAO) DeleteImmutableRule(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 +} + +// GetImmutableRule provides a mock function with given fields: ctx, id +func (_m *DAO) GetImmutableRule(ctx context.Context, id int64) (*model.ImmutableRule, error) { + ret := _m.Called(ctx, id) + + var r0 *model.ImmutableRule + if rf, ok := ret.Get(0).(func(context.Context, int64) *model.ImmutableRule); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.ImmutableRule) + } + } + + 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 +} + +// ListImmutableRules provides a mock function with given fields: ctx, query +func (_m *DAO) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.ImmutableRule, error) { + ret := _m.Called(ctx, query) + + var r0 []*model.ImmutableRule + if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*model.ImmutableRule); ok { + r0 = rf(ctx, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.ImmutableRule) + } + } + + 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 +} + +// ToggleImmutableRule provides a mock function with given fields: ctx, id, status +func (_m *DAO) ToggleImmutableRule(ctx context.Context, id int64, status bool) error { + ret := _m.Called(ctx, id, status) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { + r0 = rf(ctx, id, status) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateImmutableRule provides a mock function with given fields: ctx, projectID, ir +func (_m *DAO) UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.ImmutableRule) error { + ret := _m.Called(ctx, projectID, ir) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, *model.ImmutableRule) error); ok { + r0 = rf(ctx, projectID, ir) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/src/testing/pkg/immutabletag/matcher.go b/src/testing/pkg/immutable/matcher.go similarity index 64% rename from src/testing/pkg/immutabletag/matcher.go rename to src/testing/pkg/immutable/matcher.go index e38244383..3f9a6decd 100644 --- a/src/testing/pkg/immutabletag/matcher.go +++ b/src/testing/pkg/immutable/matcher.go @@ -1,6 +1,7 @@ -package immutabletag +package immutable import ( + "context" "github.com/goharbor/harbor/src/lib/selector" "github.com/stretchr/testify/mock" ) @@ -11,7 +12,7 @@ type FakeMatcher struct { } // Match ... -func (f *FakeMatcher) Match(pid int64, c selector.Candidate) (bool, error) { +func (f *FakeMatcher) Match(ctx context.Context, pid int64, c selector.Candidate) (bool, error) { args := f.Called() return args.Bool(0), args.Error(1) } diff --git a/src/testing/pkg/pkg.go b/src/testing/pkg/pkg.go index 6aa043272..ba1c05023 100644 --- a/src/testing/pkg/pkg.go +++ b/src/testing/pkg/pkg.go @@ -34,3 +34,4 @@ package pkg //go:generate mockery --case snake --dir ../../pkg/robot --name Manager --output ./robot --outpkg robot //go:generate mockery --case snake --dir ../../pkg/robot/dao --name DAO --output ./robot/dao --outpkg dao //go:generate mockery --case snake --dir ../../pkg/repository/dao --name DAO --output ./repository/dao --outpkg dao +//go:generate mockery --case snake --dir ../../pkg/immutable/dao --name DAO --output ./immutable/dao --outpkg dao diff --git a/tests/apitests/python/library/base.py b/tests/apitests/python/library/base.py index 5688aefac..1e4f9783d 100644 --- a/tests/apitests/python/library/base.py +++ b/tests/apitests/python/library/base.py @@ -28,7 +28,7 @@ def get_endpoint(): def _create_client(server, credential, debug, api_type="products"): cfg = None - if api_type in ('projectv2', 'artifact', 'repository', 'scanner', 'scan', 'scanall', 'preheat', 'quota', 'replication', 'robot', 'gc', 'retention'): + if api_type in ('projectv2', 'artifact', 'repository', 'scanner', 'scan', 'scanall', 'preheat', 'quota', 'replication', 'robot', 'gc', 'retention', "immutable"): cfg = v2_swagger_client.Configuration() else: cfg = swagger_client.Configuration() @@ -65,6 +65,7 @@ def _create_client(server, credential, debug, api_type="products"): "robot": v2_swagger_client.RobotApi(v2_swagger_client.ApiClient(cfg)), "gc": v2_swagger_client.GcApi(v2_swagger_client.ApiClient(cfg)), "retention": v2_swagger_client.RetentionApi(v2_swagger_client.ApiClient(cfg)), + "immutable": v2_swagger_client.ImmutableApi(v2_swagger_client.ApiClient(cfg)), }.get(api_type,'Error: Wrong API type') def _assert_status_code(expect_code, return_code, err_msg = r"HTTPS status code s not as we expected. Expected {}, while actual HTTPS status code is {}."): diff --git a/tests/apitests/python/library/tag_immutability.py b/tests/apitests/python/library/tag_immutability.py index e4b95d25b..dbe47bf7e 100644 --- a/tests/apitests/python/library/tag_immutability.py +++ b/tests/apitests/python/library/tag_immutability.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- import base -import swagger_client -from swagger_client.rest import ApiException +import v2_swagger_client +from v2_swagger_client.rest import ApiException + +class Tag_Immutability(base.Base, object): + def __init__(self): + super(Tag_Immutability,self).__init__(api_type = "immutable") -class Tag_Immutability(base.Base): def create_tag_immutability_policy_rule(self, project_id, selector_repository_decoration = "repoMatches", selector_repository="**", selector_tag_decoration = "matches", selector_tag="**", expect_status_code = 201, **kwargs): #repoExcludes,excludes client = self._get_client(**kwargs) - immutable_rule = swagger_client.ImmutableRule( + immutable_rule = v2_swagger_client.ImmutableRule( action="immutable", template="immutable_template", priority = 0, @@ -32,7 +35,7 @@ class Tag_Immutability(base.Base): ] ) try: - _, status_code, header = client.projects_project_id_immutabletagrules_post_with_http_info(project_id, immutable_rule) + _, status_code, header = client.create_immu_rule_with_http_info(project_id, immutable_rule) except ApiException as e: base._assert_status_code(expect_status_code, e.status) else: @@ -42,11 +45,13 @@ class Tag_Immutability(base.Base): def list_tag_immutability_policy_rules(self, project_id, **kwargs): client = self._get_client(**kwargs) - return client.projects_project_id_immutabletagrules_get(project_id) + return client.list_immu_rules_with_http_info(project_id) def get_rule(self, project_id, rule_id, **kwargs): rules = self.list_tag_immutability_policy_rules(project_id, **kwargs) - for r in rules: + if len(rules) <= 0: + return None + for r in rules[0]: if r.id == rule_id: return r return None @@ -67,7 +72,7 @@ class Tag_Immutability(base.Base): rule.disabled = disabled client = self._get_client(**kwargs) try: - _, status_code, header = client.projects_project_id_immutabletagrules_id_put_with_http_info(project_id, rule_id, rule) + _, status_code, header = client.update_immu_rule_with_http_info(project_id, rule_id, rule) except ApiException as e: base._assert_status_code(expect_status_code, e.status) if expect_response_body is not None: