Implement replication policy management API

This commit implements the replication policy management API

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2019-03-04 11:51:34 +08:00
parent 52eb89c6f2
commit d1f4c20e64
15 changed files with 952 additions and 19 deletions

View File

@ -2035,6 +2035,167 @@ paths:
description: The resource does not exist.
'500':
description: Unexpected internal errors.
/replication/policies:
get:
summary: List replication policies
description: |
This endpoint let user list replication policies
parameters:
- name: name
in: query
type: string
required: false
description: The replication policy name.
- name: page
in: query
type: integer
format: int32
required: false
description: The page nubmer.
- name: page_size
in: query
type: integer
format: int32
required: false
description: The size of per page.
tags:
- Products
responses:
'200':
description: Get policy successfully.
schema:
type: array
items:
$ref: '#/definitions/ReplicationPolicy'
'400':
$ref: '#/responses/BadRequest'
'401':
$ref: '#/responses/Unauthorized'
'403':
$ref: '#/responses/Forbidden'
'500':
$ref: '#/responses/InternalServerError'
post:
summary: Create a replication policy
description: |
This endpoint let user create a replication policy
parameters:
- name: policy
in: body
description: The policy model.
required: true
schema:
$ref: '#/definitions/ReplicationPolicy'
tags:
- Products
responses:
'201':
$ref: '#/responses/Created'
'400':
$ref: '#/responses/BadRequest'
'401':
$ref: '#/responses/Unauthorized'
'403':
$ref: '#/responses/Forbidden'
'404':
$ref: '#/responses/NotFound'
'409':
$ref: '#/responses/Conflict'
'415':
$ref: '#/responses/UnsupportedMediaType'
'500':
$ref: '#/responses/InternalServerError'
'/replication/policies/{id}':
get:
summary: Get replication policy.
description: |
This endpoint let user get replication policy by specific ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: policy ID
tags:
- Products
responses:
'200':
description: Get the replication policy successfully.
schema:
$ref: '#/definitions/ReplicationPolicy'
'400':
$ref: '#/responses/BadRequest'
'401':
$ref: '#/responses/Unauthorized'
'403':
$ref: '#/responses/Forbidden'
'404':
$ref: '#/responses/NotFound'
'500':
$ref: '#/responses/InternalServerError'
put:
summary: Update the replication policy
description: |
This endpoint let user update policy.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: policy ID
- name: policy
in: body
description: The replication policy model.
required: true
schema:
$ref: '#/definitions/ReplicationPolicy'
tags:
- Products
responses:
'200':
$ref: '#/responses/OK'
'400':
$ref: '#/responses/BadRequest'
'401':
$ref: '#/responses/Unauthorized'
'403':
$ref: '#/responses/Forbidden'
'404':
$ref: '#/responses/NotFound'
'409':
$ref: '#/responses/Conflict'
'500':
$ref: '#/responses/InternalServerError'
delete:
summary: Delete the replication policy specified by ID.
description: |
Delete the replication policy specified by ID.
parameters:
- name: id
in: path
type: integer
format: int64
required: true
description: Replication policy ID
tags:
- Products
responses:
'200':
$ref: '#/responses/OK'
'400':
$ref: '#/responses/BadRequest'
'401':
$ref: '#/responses/Unauthorized'
'403':
$ref: '#/responses/Forbidden'
'404':
$ref: '#/responses/NotFound'
'412':
$ref: '#/responses/PreconditionFailed'
'500':
$ref: '#/responses/InternalServerError'
/labels:
get:
summary: List labels according to the query strings.
@ -3505,8 +3666,26 @@ paths:
'500':
description: Unexpected internal errors.
responses:
OK:
description: 'Success'
Created:
description: 'Created'
BadRequest:
description: 'Bad Request'
Unauthorized:
description: 'Unauthorized'
Forbidden:
description: 'Forbidden'
NotFound:
description: 'Not Found'
Conflict:
description: 'Conflict'
PreconditionFailed:
description: 'Precondition Failed'
UnsupportedMediaType:
description: 'The Media Type of the request is not supported, it has to be "application/json"'
InternalServerError:
description: 'Internal Server Error'
definitions:
Search:
type: object
@ -3836,6 +4015,57 @@ definitions:
error_job_count:
type: integer
description: The error job count number for the policy.
ReplicationPolicy:
type: object
properties:
id:
type: integer
format: int64
description: The policy ID.
name:
type: string
description: The policy name.
description:
type: string
description: The description of the policy.
src_registry_id:
type: integer
format: int64
description: The source registry ID.
src_namespaces:
type: array
description: The source namespaces
items:
type: string
dest_registry_id:
type: integer
format: int64
description: The destination registry ID.
dest_namespace:
type: string
description: The destination namespace.
trigger:
$ref: '#/definitions/RepTrigger'
filters:
type: array
description: The replication policy filter array.
items:
$ref: '#/definitions/ReplicationFilter'
deletion:
type: boolean
description: Whether to replicate the deletion operation.
override:
type: boolean
description: Whether to override the resources on the destination registry.
enabled:
type: boolean
description: Whether the policy is enabled or not.
creation_time:
type: string
description: The create time of the policy.
update_time:
type: string
description: The update time of the policy.
RepTrigger:
type: object
properties:
@ -3873,6 +4103,15 @@ definitions:
metadata:
type: object
description: This map object is the replication policy filter metadata.
ReplicationFilter:
type: object
properties:
type:
type: string
description: 'The replication policy filter type.'
value:
type: string
description: 'The value of replication policy filter.'
RegistryCredential:
type: object
properties:

View File

@ -158,6 +158,9 @@ func init() {
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &ReplicationOperationAPI{}, "get:ListTasks")
beego.Router("/api/replication/executions/:id([0-9]+)/tasks/:tid([0-9]+)/log", &ReplicationOperationAPI{}, "get:GetTaskLog")
beego.Router("/api/replication/policies", &ReplicationPolicyAPI{}, "get:List;post:Create")
beego.Router("/api/replication/policies/:id([0-9]+)", &ReplicationPolicyAPI{}, "get:Get;put:Update;delete:Delete")
// Charts are controlled under projects
chartRepositoryAPIType := &ChartRepositoryAPI{}
beego.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus")

View File

@ -87,6 +87,14 @@ func (f *fakedPolicyManager) Get(id int64) (*model.Policy, error) {
}
return nil, nil
}
func (f *fakedPolicyManager) GetByName(name string) (*model.Policy, error) {
if name == "duplicate_name" {
return &model.Policy{
Name: "duplicate_name",
}, nil
}
return nil, nil
}
func (f *fakedPolicyManager) Update(*model.Policy, ...string) error {
return nil
}

View File

@ -0,0 +1,214 @@
// Copyright 2018 Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"fmt"
"net/http"
"strconv"
"github.com/goharbor/harbor/src/replication/ng"
"github.com/goharbor/harbor/src/replication/ng/model"
)
// TODO rename the file to "replication.go"
// ReplicationPolicyAPI handles the replication policy requests
type ReplicationPolicyAPI struct {
BaseController
}
// Prepare ...
func (r *ReplicationPolicyAPI) Prepare() {
r.BaseController.Prepare()
if !r.SecurityCtx.IsSysAdmin() {
if !r.SecurityCtx.IsAuthenticated() {
r.HandleUnauthorized()
return
}
r.HandleForbidden(r.SecurityCtx.GetUsername())
return
}
}
// List the replication policies
func (r *ReplicationPolicyAPI) List() {
// TODO: support more query
query := &model.PolicyQuery{
Name: r.GetString("name"),
}
query.Page, query.Size = r.GetPaginationParams()
total, policies, err := ng.PolicyMgr.List(query)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to list policies: %v", err))
return
}
r.SetPaginationHeader(total, query.Page, query.Size)
r.WriteJSONData(policies)
}
// Create the replication policy
func (r *ReplicationPolicyAPI) Create() {
policy := &model.Policy{}
r.DecodeJSONReqAndValidate(policy)
if !r.validateName(policy) {
return
}
if !r.validateRegistry(policy) {
return
}
id, err := ng.PolicyMgr.Create(policy)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to create the policy: %v", err))
return
}
// TODO handle replication_now?
r.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
}
// make sure the policy name doesn't exist
func (r *ReplicationPolicyAPI) validateName(policy *model.Policy) bool {
p, err := ng.PolicyMgr.GetByName(policy.Name)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get policy %s: %v", policy.Name, err))
return false
}
if p != nil {
r.HandleConflict(fmt.Sprintf("policy %s already exists", policy.Name))
return false
}
return true
}
// make the registry referenced exists
func (r *ReplicationPolicyAPI) validateRegistry(policy *model.Policy) bool {
registryID := policy.SrcRegistryID
if registryID == 0 {
registryID = policy.DestRegistryID
}
registry, err := ng.RegistryMgr.Get(registryID)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", registryID, err))
return false
}
if registry == nil {
r.HandleNotFound(fmt.Sprintf("registry %d not found", registryID))
return false
}
return true
}
// TODO validate trigger in create and update
// Get the specified replication policy
func (r *ReplicationPolicyAPI) Get() {
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.HandleBadRequest("invalid policy ID")
return
}
policy, err := ng.PolicyMgr.Get(id)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
return
}
if policy == nil {
r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
return
}
r.WriteJSONData(policy)
}
// Update the replication policy
func (r *ReplicationPolicyAPI) Update() {
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.HandleBadRequest("invalid policy ID")
return
}
originalPolicy, err := ng.PolicyMgr.Get(id)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
return
}
if originalPolicy == nil {
r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
return
}
policy := &model.Policy{}
r.DecodeJSONReqAndValidate(policy)
if policy.Name != originalPolicy.Name &&
!r.validateName(policy) {
return
}
if !r.validateRegistry(policy) {
return
}
// TODO passing the properties need to be updated?
if err := ng.PolicyMgr.Update(policy); err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to update the policy %d: %v", id, err))
return
}
}
// Delete the replication policy
func (r *ReplicationPolicyAPI) Delete() {
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.HandleBadRequest("invalid policy ID")
return
}
policy, err := ng.PolicyMgr.Get(id)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
return
}
if policy == nil {
r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
return
}
_, executions, err := ng.OperationCtl.ListExecutions(&model.ExecutionQuery{
PolicyID: id,
})
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to get the executions of policy %d: %v", id, err))
return
}
for _, execution := range executions {
if execution.Status == model.ExecutionStatusInProgress {
r.HandleStatusPreconditionFailed(fmt.Sprintf("the policy %d has running executions, can not be deleted", id))
return
}
}
if err := ng.PolicyMgr.Remove(id); err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to delete the policy %d: %v", id, err))
return
}
}

View File

@ -0,0 +1,397 @@
// Copyright 2018 Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"net/http"
"testing"
"github.com/goharbor/harbor/src/replication/ng/model"
"github.com/goharbor/harbor/src/replication/ng"
)
// TODO rename the file to "replication.go"
type fakedRegistryManager struct{}
func (f *fakedRegistryManager) Add(*model.Registry) (int64, error) {
return 0, nil
}
func (f *fakedRegistryManager) List(...*model.RegistryQuery) (int64, []*model.Registry, error) {
return 0, nil, nil
}
func (f *fakedRegistryManager) Get(id int64) (*model.Registry, error) {
if id == 1 {
return &model.Registry{
Type: "faked_registry",
}, nil
}
return nil, nil
}
func (f *fakedRegistryManager) GetByName(string) (*model.Registry, error) {
return nil, nil
}
func (f *fakedRegistryManager) Update(*model.Registry, ...string) error {
return nil
}
func (f *fakedRegistryManager) Remove(int64) error {
return nil
}
func (f *fakedRegistryManager) HealthCheck() error {
return nil
}
func TestReplicationPolicyAPIList(t *testing.T) {
policyMgr := ng.PolicyMgr
defer func() {
ng.PolicyMgr = policyMgr
}()
ng.PolicyMgr = &fakedPolicyManager{}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 200
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies",
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}
func TestReplicationPolicyAPICreate(t *testing.T) {
policyMgr := ng.PolicyMgr
registryMgr := ng.RegistryMgr
defer func() {
ng.PolicyMgr = policyMgr
ng.RegistryMgr = registryMgr
}()
ng.PolicyMgr = &fakedPolicyManager{}
ng.RegistryMgr = &fakedRegistryManager{}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 400 empty policy name
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusBadRequest,
},
// 400 empty registry
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcNamespaces: []string{"library"},
},
},
code: http.StatusBadRequest,
},
// 400 empty source namespaces
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcRegistryID: 1,
},
},
code: http.StatusBadRequest,
},
// 409, duplicate policy name
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "duplicate_name",
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusConflict,
},
// 404, registry not found
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcRegistryID: 2,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusNotFound,
},
// 201
{
request: &testingRequest{
method: http.MethodPost,
url: "/api/replication/policies",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusCreated,
},
}
runCodeCheckingCases(t, cases...)
}
func TestReplicationPolicyAPIGet(t *testing.T) {
policyMgr := ng.PolicyMgr
defer func() {
ng.PolicyMgr = policyMgr
}()
ng.PolicyMgr = &fakedPolicyManager{}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies/1",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies/1",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 404, policy not found
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies/2",
credential: sysAdmin,
},
code: http.StatusNotFound,
},
// 200
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/policies/1",
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}
func TestReplicationPolicyAPIUpdate(t *testing.T) {
policyMgr := ng.PolicyMgr
registryMgr := ng.RegistryMgr
defer func() {
ng.PolicyMgr = policyMgr
ng.RegistryMgr = registryMgr
}()
ng.PolicyMgr = &fakedPolicyManager{}
ng.RegistryMgr = &fakedRegistryManager{}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 404 policy not found
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/2",
credential: sysAdmin,
bodyJSON: &model.Policy{},
},
code: http.StatusNotFound,
},
// 400 empty policy name
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
credential: sysAdmin,
bodyJSON: &model.Policy{
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusBadRequest,
},
// 409, duplicate policy name
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "duplicate_name",
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusConflict,
},
// 404, registry not found
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcRegistryID: 2,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusNotFound,
},
// 200
{
request: &testingRequest{
method: http.MethodPut,
url: "/api/replication/policies/1",
credential: sysAdmin,
bodyJSON: &model.Policy{
Name: "policy01",
SrcRegistryID: 1,
SrcNamespaces: []string{"library"},
},
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}
func TestReplicationPolicyAPIDelete(t *testing.T) {
policyMgr := ng.PolicyMgr
defer func() {
ng.PolicyMgr = policyMgr
}()
ng.PolicyMgr = &fakedPolicyManager{}
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodDelete,
url: "/api/replication/policies/1",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodDelete,
url: "/api/replication/policies/1",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 404, policy not found
{
request: &testingRequest{
method: http.MethodDelete,
url: "/api/replication/policies/2",
credential: sysAdmin,
},
code: http.StatusNotFound,
},
// 200
{
request: &testingRequest{
method: http.MethodDelete,
url: "/api/replication/policies/1",
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}

View File

@ -106,6 +106,9 @@ func initRouters() {
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &api.ReplicationOperationAPI{}, "get:ListTasks")
beego.Router("/api/replication/executions/:id([0-9]+)/tasks/:tid([0-9]+)/log", &api.ReplicationOperationAPI{}, "get:GetTaskLog")
beego.Router("/api/replication/policies", &api.ReplicationPolicyAPI{}, "get:List;post:Create")
beego.Router("/api/replication/policies/:id([0-9]+)", &api.ReplicationPolicyAPI{}, "get:Get;put:Update;delete:Delete")
beego.Router("/api/internal/configurations", &api.ConfigAPI{}, "get:GetInternalConfig;put:Put")
beego.Router("/api/configurations", &api.ConfigAPI{}, "get:Get;put:Put")
beego.Router("/api/statistics", &api.StatisticAPI{})

View File

@ -53,6 +53,9 @@ func GetRepPolicy(id int64) (policy *models.RepPolicy, err error) {
policy = new(models.RepPolicy)
err = common_dao.GetOrmer().QueryTable(policy).
Filter("id", id).One(policy)
if err == orm.ErrNoRows {
return nil, nil
}
return
}
@ -62,6 +65,9 @@ func GetRepPolicyByName(name string) (policy *models.RepPolicy, err error) {
policy = new(models.RepPolicy)
err = common_dao.GetOrmer().QueryTable(policy).
Filter("name", name).One(policy)
if err == orm.ErrNoRows {
return nil, nil
}
return
}

View File

@ -3,7 +3,6 @@ package dao
import (
"testing"
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/replication/ng/dao/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -303,9 +302,9 @@ func TestDeleteRepPolicy(t *testing.T) {
}
require.Nil(t, err)
_, err = GetRepPolicy(tt.id)
require.NotNil(t, err)
assert.Equal(t, err, orm.ErrNoRows)
policy, err := GetRepPolicy(tt.id)
require.Nil(t, err)
assert.Nil(t, policy)
})
}
}

View File

@ -82,7 +82,9 @@ func ListRegistries(query ...*ListRegistryQuery) (int64, []*models.Registry, err
if err != nil {
return total, nil, err
}
if registries == nil {
registries = []*models.Registry{}
}
return total, registries, nil
}

View File

@ -17,6 +17,7 @@ package model
import (
"time"
"github.com/astaxie/beego/validation"
"github.com/goharbor/harbor/src/common/models"
)
@ -33,7 +34,8 @@ type Policy struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Creator string `json:"creator"`
// TODO consider to remove this property?
Creator string `json:"creator"`
// source
SrcRegistryID int64 `json:"src_registry_id"`
SrcNamespaces []string `json:"src_namespaces"`
@ -58,6 +60,33 @@ type Policy struct {
UpdateTime time.Time `json:"update_time"`
}
// Valid the policy
func (p *Policy) Valid(v *validation.Validation) {
if len(p.Name) == 0 {
v.SetError("name", "cannot be empty")
}
// one of the source registry and destination registry must be Harbor itself
if p.SrcRegistryID != 0 && p.DestRegistryID != 0 ||
p.SrcRegistryID == 0 && p.DestRegistryID == 0 {
v.SetError("src_registry_id, dest_registry_id", "one of them should be empty and the other one shouldn't be empty")
}
// source namespaces cannot be empty
if len(p.SrcNamespaces) == 0 {
v.SetError("src_namespaces", "cannot be empty")
} else {
for _, namespace := range p.SrcNamespaces {
if len(namespace) == 0 {
v.SetError("src_namespaces", "cannot contain empty namespace")
break
}
}
}
// TODO valid trigger and filters
}
// FilterType represents the type info of the filter.
type FilterType string

View File

@ -49,6 +49,8 @@ type Credential struct {
AccessSecret string `json:"access_secret"`
}
// TODO add validation for Registry
// Registry keeps the related info of registry
// Data required for the secure access way is not contained here.
// DAO layer is not considered here

View File

@ -29,7 +29,7 @@ var errNilPolicyModel = errors.New("nil policy model")
func convertFromPersistModel(policy *persist_models.RepPolicy) (*model.Policy, error) {
if policy == nil {
return &model.Policy{}, nil
return nil, nil
}
ply := model.Policy{
@ -56,7 +56,7 @@ func convertFromPersistModel(policy *persist_models.RepPolicy) (*model.Policy, e
if len(policy.Filters) > 0 {
filters := []*model.Filter{}
if err := json.Unmarshal([]byte(policy.Filters), &filters); err != nil {
return &model.Policy{}, err
return nil, err
}
ply.Filters = filters
}
@ -65,7 +65,7 @@ func convertFromPersistModel(policy *persist_models.RepPolicy) (*model.Policy, e
if len(policy.Trigger) > 0 {
trigger := &model.Trigger{}
if err := json.Unmarshal([]byte(policy.Trigger), trigger); err != nil {
return &model.Policy{}, err
return nil, err
}
ply.Trigger = trigger
}
@ -121,6 +121,8 @@ type Manager interface {
List(...*model.PolicyQuery) (int64, []*model.Policy, error)
// Get policy with specified ID
Get(int64) (*model.Policy, error)
// Get policy by the name
GetByName(string) (*model.Policy, error)
// Update the specified policy, the "props" are the properties of policy
// that need to be updated
Update(policy *model.Policy, props ...string) error
@ -150,7 +152,7 @@ func (m *DefaultManager) Create(policy *model.Policy) (int64, error) {
}
// List returns all the policies
func (m *DefaultManager) List(queries ...*model.PolicyQuery) (total int64, polices []*model.Policy, err error) {
func (m *DefaultManager) List(queries ...*model.PolicyQuery) (total int64, policies []*model.Policy, err error) {
// default query parameters
var name = ""
var namespace = ""
@ -180,7 +182,11 @@ func (m *DefaultManager) List(queries ...*model.PolicyQuery) (total int64, polic
return 0, nil, err
}
polices = append(polices, ply)
policies = append(policies, ply)
}
if policies == nil {
policies = []*model.Policy{}
}
return
@ -190,7 +196,17 @@ func (m *DefaultManager) List(queries ...*model.PolicyQuery) (total int64, polic
func (m *DefaultManager) Get(policyID int64) (*model.Policy, error) {
policy, err := dao.GetRepPolicy(policyID)
if err != nil {
return &model.Policy{}, err
return nil, err
}
return convertFromPersistModel(policy)
}
// GetByName returns the policy with the specified name
func (m *DefaultManager) GetByName(name string) (*model.Policy, error) {
policy, err := dao.GetRepPolicyByName(name)
if err != nil {
return nil, err
}
return convertFromPersistModel(policy)

View File

@ -31,16 +31,21 @@ func Test_convertFromPersistModel(t *testing.T) {
want *model.Policy
wantErr bool
}{
{name: "Nil Persist Model", from: nil, want: &model.Policy{}},
{
name: "Nil Persist Model",
from: nil,
want: nil,
wantErr: false,
},
{
name: "parse Filters Error",
from: &persist_models.RepPolicy{Filters: "abc"},
want: &model.Policy{}, wantErr: true,
want: nil, wantErr: true,
},
{
name: "parse Trigger Error",
from: &persist_models.RepPolicy{Trigger: "abc"},
want: &model.Policy{}, wantErr: true,
want: nil, wantErr: true,
},
{
name: "Persist Model", from: &persist_models.RepPolicy{
@ -83,6 +88,11 @@ func Test_convertFromPersistModel(t *testing.T) {
return
}
if tt.want == nil {
require.Nil(t, got)
return
}
require.Nil(t, err, tt.name)
assert.Equal(t, tt.want.ID, got.ID)
assert.Equal(t, tt.want.Name, got.Name)

View File

@ -298,9 +298,13 @@ func fromDaoModel(registry *models.Registry) (*model.Registry, error) {
// toDaoModel converts registry model from replication to DAO layer model.
// Also, if access secret is provided, encrypt it.
func toDaoModel(registry *model.Registry) (*models.Registry, error) {
encrypted, err := encrypt(registry.Credential.AccessSecret)
if err != nil {
return nil, err
var encrypted string
var err error
if registry.Credential != nil {
encrypted, err = encrypt(registry.Credential.AccessSecret)
if err != nil {
return nil, err
}
}
return &models.Registry{

View File

@ -45,7 +45,8 @@ var (
func Init() error {
// Init registry manager
RegistryMgr = registry.NewDefaultManager()
// TODO init PolicyMgr
// init policy manager
PolicyMgr = policy.NewDefaultManager()
// TODO init ExecutionMgr
var executionMgr execution.Manager