mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge pull request #9141 from stonezdj/immutable_tag_api
Immutable tag api
This commit is contained in:
commit
ce824a6eb9
@ -3984,7 +3984,124 @@ paths:
|
|||||||
description: User have no permission to list webhook jobs of the project.
|
description: User have no permission to list webhook jobs of the project.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
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/ImmutableTagRule'
|
||||||
|
'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: immutabletagrule
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ImmutableTagRule'
|
||||||
|
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: immutabletagrule
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ImmutableTagRule'
|
||||||
|
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.
|
||||||
'/retentions/metadatas':
|
'/retentions/metadatas':
|
||||||
get:
|
get:
|
||||||
summary: Get Retention Metadatas
|
summary: Get Retention Metadatas
|
||||||
@ -6264,10 +6381,22 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
retained:
|
retained:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
||||||
QuotaSwitcher:
|
QuotaSwitcher:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
enabled:
|
enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: The quota is enable or disable
|
description: The quota is enable or disable
|
||||||
|
ImmutableTagRule:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
tag_filter:
|
||||||
|
type: string
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
@ -15,7 +15,8 @@ func CreateImmutableRule(ir *models.ImmutableRule) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateImmutableRule update the immutable rules
|
// UpdateImmutableRule update the immutable rules
|
||||||
func UpdateImmutableRule(projectID int, ir *models.ImmutableRule) (int64, error) {
|
func UpdateImmutableRule(projectID int64, ir *models.ImmutableRule) (int64, error) {
|
||||||
|
ir.ProjectID = projectID
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
return o.Update(ir, "TagFilter")
|
return o.Update(ir, "TagFilter")
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ const (
|
|||||||
ResourceReplicationTask = Resource("replication-task")
|
ResourceReplicationTask = Resource("replication-task")
|
||||||
ResourceRepository = Resource("repository")
|
ResourceRepository = Resource("repository")
|
||||||
ResourceTagRetention = Resource("tag-retention")
|
ResourceTagRetention = Resource("tag-retention")
|
||||||
|
ResourceImmutableTag = Resource("immutable-tag")
|
||||||
ResourceRepositoryLabel = Resource("repository-label")
|
ResourceRepositoryLabel = Resource("repository-label")
|
||||||
ResourceRepositoryTag = Resource("repository-tag")
|
ResourceRepositoryTag = Resource("repository-tag")
|
||||||
ResourceRepositoryTagLabel = Resource("repository-tag-label")
|
ResourceRepositoryTagLabel = Resource("repository-tag-label")
|
||||||
|
@ -95,6 +95,11 @@ var (
|
|||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionUpdate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList},
|
||||||
|
|
||||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionCreate},
|
{Resource: rbac.ResourceLabel, Action: rbac.ActionCreate},
|
||||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
||||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionUpdate},
|
{Resource: rbac.ResourceLabel, Action: rbac.ActionUpdate},
|
||||||
|
@ -68,6 +68,11 @@ var (
|
|||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionUpdate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList},
|
||||||
|
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
||||||
@ -153,6 +158,11 @@ var (
|
|||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
||||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionUpdate},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete},
|
||||||
|
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList},
|
||||||
|
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
||||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
||||||
|
@ -180,6 +180,7 @@ func runCodeCheckingCases(t *testing.T, cases ...*codeCheckingCase) {
|
|||||||
if c.postFunc != nil {
|
if c.postFunc != nil {
|
||||||
if err := c.postFunc(resp); err != nil {
|
if err := c.postFunc(resp); err != nil {
|
||||||
t.Logf("error in running post function: %v", err)
|
t.Logf("error in running post function: %v", err)
|
||||||
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,8 @@ func init() {
|
|||||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/policies/test", &NotificationPolicyAPI{}, "post:Test")
|
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/lasttrigger", &NotificationPolicyAPI{}, "get:ListGroupByEventType")
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/jobs/", &NotificationJobAPI{}, "get:List")
|
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
|
// Charts are controlled under projects
|
||||||
chartRepositoryAPIType := &ChartRepositoryAPI{}
|
chartRepositoryAPIType := &ChartRepositoryAPI{}
|
||||||
beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus")
|
beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus")
|
||||||
|
166
src/core/api/immutabletagrule.go
Normal file
166
src/core/api/immutabletagrule.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/immutabletag"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImmutableTagRuleAPI ...
|
||||||
|
type ImmutableTagRuleAPI struct {
|
||||||
|
BaseController
|
||||||
|
manager immutabletag.RuleManager
|
||||||
|
projectID int64
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare validates the user and projectID
|
||||||
|
func (itr *ImmutableTagRuleAPI) Prepare() {
|
||||||
|
itr.BaseController.Prepare()
|
||||||
|
if !itr.SecurityCtx.IsAuthenticated() {
|
||||||
|
itr.SendUnAuthorizedError(errors.New("Unauthorized"))
|
||||||
|
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.SendBadRequestError(errors.New(text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itr.projectID = pid
|
||||||
|
|
||||||
|
ruleID, err := itr.GetInt64FromPath(":id")
|
||||||
|
if err == nil || ruleID > 0 {
|
||||||
|
itr.ID = ruleID
|
||||||
|
}
|
||||||
|
|
||||||
|
itr.manager = immutabletag.NewDefaultRuleManager()
|
||||||
|
|
||||||
|
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.manager.QueryImmutableRuleByProjectID(itr.projectID)
|
||||||
|
if err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itr.WriteJSONData(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post create immutable tag rule
|
||||||
|
func (itr *ImmutableTagRuleAPI) Post() {
|
||||||
|
ir := &models.ImmutableRule{}
|
||||||
|
if err := itr.DecodeJSONReq(ir); err != nil {
|
||||||
|
itr.SendBadRequestError(fmt.Errorf("the filter must be a valid json, failed to parse json, error %+v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValidSelectorJSON(ir.TagFilter) {
|
||||||
|
itr.SendBadRequestError(fmt.Errorf("the filter should be a valid json"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ir.ProjectID = itr.projectID
|
||||||
|
id, err := itr.manager.CreateImmutableRule(ir)
|
||||||
|
if err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itr.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete immutable tag rule
|
||||||
|
func (itr *ImmutableTagRuleAPI) Delete() {
|
||||||
|
if itr.ID <= 0 {
|
||||||
|
itr.SendBadRequestError(fmt.Errorf("invalid immutable rule id %d", itr.ID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := itr.manager.DeleteImmutableRule(itr.ID)
|
||||||
|
if err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put update an immutable tag rule
|
||||||
|
func (itr *ImmutableTagRuleAPI) Put() {
|
||||||
|
ir := &models.ImmutableRule{}
|
||||||
|
if err := itr.DecodeJSONReq(ir); err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ir.ID = itr.ID
|
||||||
|
ir.ProjectID = itr.projectID
|
||||||
|
|
||||||
|
if itr.ID <= 0 {
|
||||||
|
itr.SendBadRequestError(fmt.Errorf("invalid immutable rule id %d", itr.ID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ir.TagFilter) == 0 {
|
||||||
|
if _, err := itr.manager.EnableImmutableRule(itr.ID, ir.Enabled); err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if !isValidSelectorJSON(ir.TagFilter) {
|
||||||
|
itr.SendBadRequestError(fmt.Errorf("the filter should be a valid json"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := itr.manager.UpdateImmutableRule(itr.ID, ir); err != nil {
|
||||||
|
itr.SendInternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidSelectorJSON(filter string) bool {
|
||||||
|
tagSector := &rule.Metadata{}
|
||||||
|
err := json.Unmarshal([]byte(filter), tagSector)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("The json is %v", filter)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
264
src/core/api/immutabletagrule_test.go
Normal file
264
src/core/api/immutabletagrule_test.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/immutabletag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImmutableTagRuleAPI_List(t *testing.T) {
|
||||||
|
|
||||||
|
tagFilter := `{
|
||||||
|
"id":0,
|
||||||
|
"priority":0,
|
||||||
|
"disabled":false,
|
||||||
|
"action":"immutable",
|
||||||
|
"template":"immutable_template",
|
||||||
|
"tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"**"}],
|
||||||
|
"scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"**"}]}
|
||||||
|
}`
|
||||||
|
|
||||||
|
mgr := immutabletag.NewDefaultRuleManager()
|
||||||
|
id, err := mgr.CreateImmutableRule(&models.ImmutableRule{ProjectID: 1, TagFilter: tagFilter})
|
||||||
|
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 []models.ImmutableRule
|
||||||
|
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].TagFilter != tagFilter {
|
||||||
|
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) {
|
||||||
|
|
||||||
|
tagFilter := `{
|
||||||
|
"id":0,
|
||||||
|
"priority":0,
|
||||||
|
"disabled":false,
|
||||||
|
"action":"immutable",
|
||||||
|
"template":"immutable_template",
|
||||||
|
"tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"**"}],
|
||||||
|
"scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"**"}]}
|
||||||
|
}`
|
||||||
|
body := &models.ImmutableRule{ProjectID: 1, TagFilter: tagFilter}
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/immutabletagrules",
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/immutabletagrules",
|
||||||
|
credential: admin,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusCreated,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/immutabletagrules",
|
||||||
|
credential: projAdmin,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusCreated,
|
||||||
|
},
|
||||||
|
// 403
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/immutabletagrules",
|
||||||
|
credential: projGuest,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImmutableTagRuleAPI_Put(t *testing.T) {
|
||||||
|
tagFilter := `{
|
||||||
|
"id":0,
|
||||||
|
"priority":0,
|
||||||
|
"disabled":false,
|
||||||
|
"action":"immutable",
|
||||||
|
"template":"immutable_template",
|
||||||
|
"tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"**"}],
|
||||||
|
"scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"**"}]}
|
||||||
|
}`
|
||||||
|
tagFilter2 := `{
|
||||||
|
"id":0,
|
||||||
|
"priority":0,
|
||||||
|
"disabled":false,
|
||||||
|
"action":"immutable",
|
||||||
|
"template":"immutable_template",
|
||||||
|
"tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"release-1.6.0"}],
|
||||||
|
"scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"regids"}]}
|
||||||
|
}`
|
||||||
|
|
||||||
|
mgr := immutabletag.NewDefaultRuleManager()
|
||||||
|
id, err := mgr.CreateImmutableRule(&models.ImmutableRule{ProjectID: 1, TagFilter: tagFilter})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer mgr.DeleteImmutableRule(id)
|
||||||
|
url := fmt.Sprintf("/api/projects/1/immutabletagrules/%d", id)
|
||||||
|
body := &models.ImmutableRule{ID: id, ProjectID: 1, TagFilter: tagFilter2}
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: url,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: url,
|
||||||
|
credential: admin,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: url,
|
||||||
|
credential: projAdmin,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
// 403
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: url,
|
||||||
|
credential: projGuest,
|
||||||
|
bodyJSON: body,
|
||||||
|
},
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImmutableTagRuleAPI_Delete(t *testing.T) {
|
||||||
|
tagFilter := `{
|
||||||
|
"id":0,
|
||||||
|
"priority":0,
|
||||||
|
"disabled":false,
|
||||||
|
"action":"immutable",
|
||||||
|
"template":"immutable_template",
|
||||||
|
"tag_selectors":[{"kind":"doublestar","decoration":"matches","pattern":"**"}],
|
||||||
|
"scope_selectors":{"repository":[{"kind":"doublestar","decoration":"repoMatches","pattern":"**"}]}
|
||||||
|
}`
|
||||||
|
mgr := immutabletag.NewDefaultRuleManager()
|
||||||
|
id, err := mgr.CreateImmutableRule(&models.ImmutableRule{ProjectID: 1, TagFilter: tagFilter})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer mgr.DeleteImmutableRule(id)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("/api/projects/1/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,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: url,
|
||||||
|
credential: projAdmin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
@ -121,6 +121,9 @@ func initRouters() {
|
|||||||
|
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/webhook/jobs/", &api.NotificationJobAPI{}, "get:List")
|
beego.Router("/api/projects/:pid([0-9]+)/webhook/jobs/", &api.NotificationJobAPI{}, "get:List")
|
||||||
|
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules", &api.ImmutableTagRuleAPI{}, "get:List;post:Post")
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules/:id([0-9]+)", &api.ImmutableTagRuleAPI{})
|
||||||
|
|
||||||
beego.Router("/api/internal/configurations", &api.ConfigAPI{}, "get:GetInternalConfig;put:Put")
|
beego.Router("/api/internal/configurations", &api.ConfigAPI{}, "get:GetInternalConfig;put:Put")
|
||||||
beego.Router("/api/configurations", &api.ConfigAPI{}, "get:Get;put:Put")
|
beego.Router("/api/configurations", &api.ConfigAPI{}, "get:Get;put:Put")
|
||||||
beego.Router("/api/statistics", &api.StatisticAPI{})
|
beego.Router("/api/statistics", &api.StatisticAPI{})
|
||||||
@ -164,6 +167,8 @@ func initRouters() {
|
|||||||
beego.Router("/api/retentions/:id/executions", &api.RetentionAPI{}, "get:ListRetentionExecs")
|
beego.Router("/api/retentions/:id/executions", &api.RetentionAPI{}, "get:ListRetentionExecs")
|
||||||
beego.Router("/api/retentions/:id/executions/:eid/tasks", &api.RetentionAPI{}, "get:ListRetentionExecTasks")
|
beego.Router("/api/retentions/:id/executions/:eid/tasks", &api.RetentionAPI{}, "get:ListRetentionExecTasks")
|
||||||
beego.Router("/api/retentions/:id/executions/:eid/tasks/:tid", &api.RetentionAPI{}, "get:GetRetentionExecTaskLog")
|
beego.Router("/api/retentions/:id/executions/:eid/tasks/:tid", &api.RetentionAPI{}, "get:GetRetentionExecTaskLog")
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules", &api.ImmutableTagRuleAPI{}, "get:List;post:Post")
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/immutabletagrules/:id([0-9]+)", &api.ImmutableTagRuleAPI{})
|
||||||
|
|
||||||
beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
|
beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
|
||||||
|
|
||||||
|
59
src/pkg/immutabletag/rulemanager.go
Normal file
59
src/pkg/immutabletag/rulemanager.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package immutabletag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleManager ...
|
||||||
|
type RuleManager interface {
|
||||||
|
// CreateImmutableRule creates the Immutable Rule
|
||||||
|
CreateImmutableRule(ir *models.ImmutableRule) (int64, error)
|
||||||
|
// UpdateImmutableRule update the immutable rules
|
||||||
|
UpdateImmutableRule(projectID int64, ir *models.ImmutableRule) (int64, error)
|
||||||
|
// EnableImmutableRule enable/disable immutable rules
|
||||||
|
EnableImmutableRule(id int64, enabled bool) (int64, error)
|
||||||
|
// GetImmutableRule get immutable rule
|
||||||
|
GetImmutableRule(id int64) (*models.ImmutableRule, error)
|
||||||
|
// QueryImmutableRuleByProjectID get all immutable rule by project
|
||||||
|
QueryImmutableRuleByProjectID(projectID int64) ([]models.ImmutableRule, error)
|
||||||
|
// QueryEnabledImmutableRuleByProjectID get all enabled immutable rule by project
|
||||||
|
QueryEnabledImmutableRuleByProjectID(projectID int64) ([]models.ImmutableRule, error)
|
||||||
|
// DeleteImmutableRule delete the immutable rule
|
||||||
|
DeleteImmutableRule(id int64) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultRuleManager struct{}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) CreateImmutableRule(ir *models.ImmutableRule) (int64, error) {
|
||||||
|
return dao.CreateImmutableRule(ir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) UpdateImmutableRule(projectID int64, ir *models.ImmutableRule) (int64, error) {
|
||||||
|
return dao.UpdateImmutableRule(projectID, ir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) EnableImmutableRule(id int64, enabled bool) (int64, error) {
|
||||||
|
return dao.ToggleImmutableRule(id, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) GetImmutableRule(id int64) (*models.ImmutableRule, error) {
|
||||||
|
return dao.GetImmutableRule(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) QueryImmutableRuleByProjectID(projectID int64) ([]models.ImmutableRule, error) {
|
||||||
|
return dao.QueryImmutableRuleByProjectID(projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) QueryEnabledImmutableRuleByProjectID(projectID int64) ([]models.ImmutableRule, error) {
|
||||||
|
return dao.QueryEnabledImmutableRuleByProjectID(projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drm *defaultRuleManager) DeleteImmutableRule(id int64) (int64, error) {
|
||||||
|
return dao.DeleteImmutableRule(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultRuleManager return a new instance of defaultRuleManager
|
||||||
|
func NewDefaultRuleManager() RuleManager {
|
||||||
|
return &defaultRuleManager{}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user