mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-30 06:03:45 +01:00
Merge pull request #7220 from ywk253100/190324_adapter
Update the registry adapter interface
This commit is contained in:
commit
1d4cf53462
@ -2431,38 +2431,11 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/ReplicationAdapter'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized.
|
|
||||||
'403':
|
|
||||||
description: Forbidden.
|
|
||||||
'500':
|
|
||||||
description: Unexpected internal errors.
|
|
||||||
/replication/adapters/{type}:
|
|
||||||
get:
|
|
||||||
summary: Get the specified adapter.
|
|
||||||
description: |
|
|
||||||
This endpoint let user get the specified adapter.
|
|
||||||
parameters:
|
|
||||||
- name: type
|
|
||||||
in: path
|
|
||||||
type: string
|
type: string
|
||||||
required: true
|
|
||||||
description: The adapter type.
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Success.
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
$ref: '#/definitions/ReplicationAdapter'
|
|
||||||
'401':
|
'401':
|
||||||
description: Unauthorized.
|
description: Unauthorized.
|
||||||
'403':
|
'403':
|
||||||
description: Forbidden.
|
description: Forbidden.
|
||||||
'404':
|
|
||||||
description: Not found.
|
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
/registries:
|
/registries:
|
||||||
@ -2595,6 +2568,30 @@ paths:
|
|||||||
description: Registry does not exist.
|
description: Registry does not exist.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
'/registries/{id}/info':
|
||||||
|
get:
|
||||||
|
summary: Get registry info.
|
||||||
|
description: Get the info of one specific registry.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: The registry ID.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Get registry successfully.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RegistryInfo'
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'404':
|
||||||
|
description: Registry not found
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
'/targets/{id}/policies/':
|
'/targets/{id}/policies/':
|
||||||
get:
|
get:
|
||||||
summary: List the target relevant policies.
|
summary: List the target relevant policies.
|
||||||
@ -5279,34 +5276,29 @@ definitions:
|
|||||||
action:
|
action:
|
||||||
type: string
|
type: string
|
||||||
description: The permission action
|
description: The permission action
|
||||||
ReplicationAdapter:
|
RegistryInfo:
|
||||||
type: object
|
type: object
|
||||||
description: The replication adapter
|
description: The registry info contains the base info and capability declarations of the registry
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: The adapter type
|
description: The registry type
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: The adapter description
|
description: The description
|
||||||
supported_resource_types:
|
|
||||||
type: array
|
|
||||||
description: The resource types that the adapter supports
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
supported_resource_filters:
|
supported_resource_filters:
|
||||||
type: array
|
type: array
|
||||||
description: The filters that the adapter supports
|
description: The filters that the registry supports
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/ReplicationAdapterFilter'
|
$ref: '#/definitions/FilterStyle'
|
||||||
supported_triggers:
|
supported_triggers:
|
||||||
type: array
|
type: array
|
||||||
description: The triggers that the adapter supports
|
description: The triggers that the registry supports
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
ReplicationAdapterFilter:
|
FilterStyle:
|
||||||
type: object
|
type: object
|
||||||
description: The replication adapter filter
|
description: The style of the resource filter
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
@ -154,7 +154,6 @@ func init() {
|
|||||||
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||||
|
|
||||||
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")
|
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")
|
||||||
beego.Router("/api/replication/adapters/:type", &ReplicationAdapterAPI{}, "get:Get")
|
|
||||||
beego.Router("/api/replication/executions", &ReplicationOperationAPI{}, "get:ListExecutions;post:CreateExecution")
|
beego.Router("/api/replication/executions", &ReplicationOperationAPI{}, "get:ListExecutions;post:CreateExecution")
|
||||||
beego.Router("/api/replication/executions/:id([0-9]+)", &ReplicationOperationAPI{}, "put:StopExecution")
|
beego.Router("/api/replication/executions/:id([0-9]+)", &ReplicationOperationAPI{}, "put:StopExecution")
|
||||||
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &ReplicationOperationAPI{}, "get:ListTasks")
|
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &ReplicationOperationAPI{}, "get:ListTasks")
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/core/api/models"
|
"github.com/goharbor/harbor/src/core/api/models"
|
||||||
"github.com/goharbor/harbor/src/replication/ng"
|
"github.com/goharbor/harbor/src/replication/ng"
|
||||||
|
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/registry"
|
"github.com/goharbor/harbor/src/replication/ng/registry"
|
||||||
)
|
)
|
||||||
@ -211,3 +212,63 @@ func (t *RegistryAPI) Delete() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInfo returns the base info and capability declarations of the registry
|
||||||
|
func (t *RegistryAPI) GetInfo() {
|
||||||
|
id := t.GetIDFromURL()
|
||||||
|
registry, err := t.manager.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if registry == nil {
|
||||||
|
t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
factory, err := adapter.GetFactory(registry.Type)
|
||||||
|
if err != nil {
|
||||||
|
t.HandleInternalServerError(fmt.Sprintf("failed to get the adapter factory for registry type %s: %v", registry.Type, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adp, err := factory(registry)
|
||||||
|
if err != nil {
|
||||||
|
t.HandleInternalServerError(fmt.Sprintf("failed to create the adapter for registry %d: %v", registry.ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, err := adp.Info()
|
||||||
|
if err != nil {
|
||||||
|
t.HandleInternalServerError(fmt.Sprintf("failed to get registry info %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.WriteJSONData(process(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
|
||||||
|
func process(info *model.RegistryInfo) *model.RegistryInfo {
|
||||||
|
if info == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
in := &model.RegistryInfo{
|
||||||
|
Type: info.Type,
|
||||||
|
Description: info.Description,
|
||||||
|
SupportedTriggers: info.SupportedTriggers,
|
||||||
|
}
|
||||||
|
filters := []*model.FilterStyle{}
|
||||||
|
for _, filter := range info.SupportedResourceFilters {
|
||||||
|
if filter.Type != model.FilterTypeResource {
|
||||||
|
filters = append(filters, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values := []string{}
|
||||||
|
for _, resourceType := range info.SupportedResourceTypes {
|
||||||
|
values = append(values, string(resourceType))
|
||||||
|
}
|
||||||
|
filters = append(filters, &model.FilterStyle{
|
||||||
|
Type: model.FilterTypeResource,
|
||||||
|
Style: model.FilterStyleTypeRadio,
|
||||||
|
Values: values,
|
||||||
|
})
|
||||||
|
in.SupportedResourceFilters = filters
|
||||||
|
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
@ -41,53 +39,7 @@ func (r *ReplicationAdapterAPI) Prepare() {
|
|||||||
|
|
||||||
// List the replication adapters
|
// List the replication adapters
|
||||||
func (r *ReplicationAdapterAPI) List() {
|
func (r *ReplicationAdapterAPI) List() {
|
||||||
infos := []*adapter.Info{}
|
types := []model.RegistryType{}
|
||||||
for _, info := range adapter.ListAdapterInfos() {
|
types = append(types, adapter.ListRegisteredAdapterTypes()...)
|
||||||
infos = append(infos, process(info))
|
r.WriteJSONData(types)
|
||||||
}
|
|
||||||
r.WriteJSONData(infos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get one specified replication adapter
|
|
||||||
func (r *ReplicationAdapterAPI) Get() {
|
|
||||||
t := r.GetStringFromPath(":type")
|
|
||||||
info := adapter.GetAdapterInfo(model.RegistryType(t))
|
|
||||||
if info == nil {
|
|
||||||
r.HandleNotFound(fmt.Sprintf("adapter for %s not found", t))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info = process(info)
|
|
||||||
r.WriteJSONData(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
|
|
||||||
func process(info *adapter.Info) *adapter.Info {
|
|
||||||
if info == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
in := &adapter.Info{
|
|
||||||
Type: info.Type,
|
|
||||||
Description: info.Description,
|
|
||||||
SupportedTriggers: info.SupportedTriggers,
|
|
||||||
}
|
|
||||||
|
|
||||||
filters := []*adapter.Filter{}
|
|
||||||
for _, filter := range info.SupportedResourceFilters {
|
|
||||||
if filter.Type != model.FilterTypeResource {
|
|
||||||
filters = append(filters, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values := []string{}
|
|
||||||
for _, resourceType := range info.SupportedResourceTypes {
|
|
||||||
values = append(values, string(resourceType))
|
|
||||||
}
|
|
||||||
filters = append(filters, &adapter.Filter{
|
|
||||||
Type: model.FilterTypeResource,
|
|
||||||
Style: adapter.FilterStyleRadio,
|
|
||||||
Values: values,
|
|
||||||
})
|
|
||||||
in.SupportedResourceFilters = filters
|
|
||||||
|
|
||||||
return in
|
|
||||||
}
|
}
|
||||||
|
@ -24,57 +24,19 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReplicationAdapterAPIList(t *testing.T) {
|
|
||||||
cases := []*codeCheckingCase{
|
|
||||||
// 401
|
|
||||||
{
|
|
||||||
request: &testingRequest{
|
|
||||||
method: http.MethodGet,
|
|
||||||
url: "/api/replication/adapters",
|
|
||||||
},
|
|
||||||
code: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
// 403
|
|
||||||
{
|
|
||||||
request: &testingRequest{
|
|
||||||
method: http.MethodGet,
|
|
||||||
url: "/api/replication/adapters",
|
|
||||||
credential: nonSysAdmin,
|
|
||||||
},
|
|
||||||
code: http.StatusForbidden,
|
|
||||||
},
|
|
||||||
// 200
|
|
||||||
{
|
|
||||||
request: &testingRequest{
|
|
||||||
method: http.MethodGet,
|
|
||||||
url: "/api/replication/adapters",
|
|
||||||
credential: sysAdmin,
|
|
||||||
},
|
|
||||||
code: http.StatusOK,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
runCodeCheckingCases(t, cases...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fakedFactory(*model.Registry) (adapter.Adapter, error) {
|
func fakedFactory(*model.Registry) (adapter.Adapter, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func TestReplicationAdapterAPIGet(t *testing.T) {
|
|
||||||
err := adapter.RegisterFactory(
|
|
||||||
&adapter.Info{
|
|
||||||
Type: "test",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
|
func TestReplicationAdapterAPIList(t *testing.T) {
|
||||||
|
err := adapter.RegisterFactory("test", fakedFactory)
|
||||||
|
require.Nil(t, err)
|
||||||
cases := []*codeCheckingCase{
|
cases := []*codeCheckingCase{
|
||||||
// 401
|
// 401
|
||||||
{
|
{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
url: "/api/replication/adapters/test",
|
url: "/api/replication/adapters",
|
||||||
},
|
},
|
||||||
code: http.StatusUnauthorized,
|
code: http.StatusUnauthorized,
|
||||||
},
|
},
|
||||||
@ -82,25 +44,16 @@ func TestReplicationAdapterAPIGet(t *testing.T) {
|
|||||||
{
|
{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
url: "/api/replication/adapters/test",
|
url: "/api/replication/adapters",
|
||||||
credential: nonSysAdmin,
|
credential: nonSysAdmin,
|
||||||
},
|
},
|
||||||
code: http.StatusForbidden,
|
code: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
// 404
|
|
||||||
{
|
|
||||||
request: &testingRequest{
|
|
||||||
method: http.MethodGet,
|
|
||||||
url: "/api/replication/adapters/gcs",
|
|
||||||
credential: sysAdmin,
|
|
||||||
},
|
|
||||||
code: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// 200
|
// 200
|
||||||
{
|
{
|
||||||
request: &testingRequest{
|
request: &testingRequest{
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
url: "/api/replication/adapters/test",
|
url: "/api/replication/adapters",
|
||||||
credential: sysAdmin,
|
credential: sysAdmin,
|
||||||
},
|
},
|
||||||
code: http.StatusOK,
|
code: http.StatusOK,
|
||||||
|
@ -101,7 +101,6 @@ func initRouters() {
|
|||||||
beego.Router("/api/logs", &api.LogAPI{})
|
beego.Router("/api/logs", &api.LogAPI{})
|
||||||
|
|
||||||
beego.Router("/api/replication/adapters", &api.ReplicationAdapterAPI{}, "get:List")
|
beego.Router("/api/replication/adapters", &api.ReplicationAdapterAPI{}, "get:List")
|
||||||
beego.Router("/api/replication/adapters/:type", &api.ReplicationAdapterAPI{}, "get:Get")
|
|
||||||
beego.Router("/api/replication/executions", &api.ReplicationOperationAPI{}, "get:ListExecutions;post:CreateExecution")
|
beego.Router("/api/replication/executions", &api.ReplicationOperationAPI{}, "get:ListExecutions;post:CreateExecution")
|
||||||
beego.Router("/api/replication/executions/:id([0-9]+)", &api.ReplicationOperationAPI{}, "put:StopExecution")
|
beego.Router("/api/replication/executions/:id([0-9]+)", &api.ReplicationOperationAPI{}, "put:StopExecution")
|
||||||
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &api.ReplicationOperationAPI{}, "get:ListTasks")
|
beego.Router("/api/replication/executions/:id([0-9]+)/tasks", &api.ReplicationOperationAPI{}, "get:ListTasks")
|
||||||
@ -136,6 +135,7 @@ func initRouters() {
|
|||||||
|
|
||||||
beego.Router("/api/registries", &api.RegistryAPI{}, "get:List;post:Post")
|
beego.Router("/api/registries", &api.RegistryAPI{}, "get:List;post:Post")
|
||||||
beego.Router("/api/registries/:id([0-9]+)", &api.RegistryAPI{}, "get:Get;put:Put;delete:Delete")
|
beego.Router("/api/registries/:id([0-9]+)", &api.RegistryAPI{}, "get:Get;put:Put;delete:Delete")
|
||||||
|
beego.Router("/api/registries/:id([0-9]+)/info", &api.RegistryAPI{}, "get:GetInfo")
|
||||||
|
|
||||||
beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
|
beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
|
||||||
|
|
||||||
|
@ -21,45 +21,15 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// As the Info isn't a valid map key, so we use the slice
|
var registry = map[model.RegistryType]Factory{}
|
||||||
// as the adapter registry
|
|
||||||
var registry = []*item{}
|
|
||||||
|
|
||||||
// const definition
|
|
||||||
const (
|
|
||||||
FilterStyleText = "input"
|
|
||||||
FilterStyleRadio = "radio"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterStyle is used for UI to determine how to render the filter
|
|
||||||
type FilterStyle string
|
|
||||||
|
|
||||||
type item struct {
|
|
||||||
info *Info
|
|
||||||
factory Factory
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter ...
|
|
||||||
type Filter struct {
|
|
||||||
Type model.FilterType `json:"type"`
|
|
||||||
Style FilterStyle `json:"style"`
|
|
||||||
Values []string `json:"values,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info provides base info and capability declarations of the adapter
|
|
||||||
type Info struct {
|
|
||||||
Type model.RegistryType `json:"type"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
SupportedResourceTypes []model.ResourceType `json:"-"`
|
|
||||||
SupportedResourceFilters []*Filter `json:"supported_resource_filters"`
|
|
||||||
SupportedTriggers []model.TriggerType `json:"supported_triggers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Factory creates a specific Adapter according to the params
|
// Factory creates a specific Adapter according to the params
|
||||||
type Factory func(*model.Registry) (Adapter, error)
|
type Factory func(*model.Registry) (Adapter, error)
|
||||||
|
|
||||||
// Adapter interface defines the capabilities of registry
|
// Adapter interface defines the capabilities of registry
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
|
// Info return the information of this adapter
|
||||||
|
Info() (*model.RegistryInfo, error)
|
||||||
// Lists the available namespaces under the specified registry with the
|
// Lists the available namespaces under the specified registry with the
|
||||||
// provided credential/token
|
// provided credential/token
|
||||||
ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error)
|
ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error)
|
||||||
@ -73,56 +43,35 @@ type Adapter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterFactory registers one adapter factory to the registry
|
// RegisterFactory registers one adapter factory to the registry
|
||||||
func RegisterFactory(info *Info, factory Factory) error {
|
func RegisterFactory(t model.RegistryType, factory Factory) error {
|
||||||
if len(info.Type) == 0 {
|
if len(t) == 0 {
|
||||||
return errors.New("invalid registry type")
|
return errors.New("invalid registry type")
|
||||||
}
|
}
|
||||||
if len(info.SupportedResourceTypes) == 0 {
|
|
||||||
return errors.New("must support at least one resource type")
|
|
||||||
}
|
|
||||||
if len(info.SupportedTriggers) == 0 {
|
|
||||||
return errors.New("must support at least one trigger")
|
|
||||||
}
|
|
||||||
if factory == nil {
|
if factory == nil {
|
||||||
return errors.New("empty adapter factory")
|
return errors.New("empty adapter factory")
|
||||||
}
|
}
|
||||||
for _, item := range registry {
|
|
||||||
if item.info.Type == info.Type {
|
if _, exist := registry[t]; exist {
|
||||||
return fmt.Errorf("adapter factory for %s already exists", info.Type)
|
return fmt.Errorf("adapter factory for %s already exists", t)
|
||||||
}
|
}
|
||||||
}
|
registry[t] = factory
|
||||||
registry = append(registry, &item{
|
|
||||||
info: info,
|
|
||||||
factory: factory,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFactory gets the adapter factory by the specified name
|
// GetFactory gets the adapter factory by the specified name
|
||||||
func GetFactory(t model.RegistryType) (Factory, error) {
|
func GetFactory(t model.RegistryType) (Factory, error) {
|
||||||
for _, item := range registry {
|
factory, exist := registry[t]
|
||||||
if item.info.Type == t {
|
if !exist {
|
||||||
return item.factory, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("adapter factory for %s not found", t)
|
return nil, fmt.Errorf("adapter factory for %s not found", t)
|
||||||
}
|
}
|
||||||
|
return factory, nil
|
||||||
// ListAdapterInfos lists the info of registered Adapters
|
|
||||||
func ListAdapterInfos() []*Info {
|
|
||||||
infos := []*Info{}
|
|
||||||
for _, item := range registry {
|
|
||||||
infos = append(infos, item.info)
|
|
||||||
}
|
|
||||||
return infos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAdapterInfo returns the info of a specified registry type
|
// ListRegisteredAdapterTypes lists the registered Adapter type
|
||||||
func GetAdapterInfo(t model.RegistryType) *Info {
|
func ListRegisteredAdapterTypes() []model.RegistryType {
|
||||||
for _, item := range registry {
|
types := []model.RegistryType{}
|
||||||
if item.info.Type == t {
|
for t := range registry {
|
||||||
return item.info
|
types = append(types, t)
|
||||||
}
|
}
|
||||||
}
|
return types
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -28,49 +28,18 @@ func fakedFactory(*model.Registry) (Adapter, error) {
|
|||||||
|
|
||||||
func TestRegisterFactory(t *testing.T) {
|
func TestRegisterFactory(t *testing.T) {
|
||||||
// empty type
|
// empty type
|
||||||
assert.NotNil(t, RegisterFactory(&Info{}, nil))
|
assert.NotNil(t, RegisterFactory("", nil))
|
||||||
// empty supportted resource type
|
|
||||||
assert.NotNil(t, RegisterFactory(
|
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
}, nil))
|
|
||||||
// empty trigger
|
|
||||||
assert.NotNil(t, RegisterFactory(
|
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
}, nil))
|
|
||||||
// empty factory
|
// empty factory
|
||||||
assert.NotNil(t, RegisterFactory(
|
assert.NotNil(t, RegisterFactory("harbor", nil))
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, nil))
|
|
||||||
// pass
|
// pass
|
||||||
assert.Nil(t, RegisterFactory(
|
assert.Nil(t, RegisterFactory("harbor", fakedFactory))
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory))
|
|
||||||
// already exists
|
// already exists
|
||||||
assert.NotNil(t, RegisterFactory(
|
assert.NotNil(t, RegisterFactory("harbor", fakedFactory))
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFactory(t *testing.T) {
|
func TestGetFactory(t *testing.T) {
|
||||||
registry = []*item{}
|
registry = map[model.RegistryType]Factory{}
|
||||||
require.Nil(t, RegisterFactory(
|
require.Nil(t, RegisterFactory("harbor", fakedFactory))
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory))
|
|
||||||
// doesn't exist
|
// doesn't exist
|
||||||
_, err := GetFactory("gcr")
|
_, err := GetFactory("gcr")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
@ -79,40 +48,16 @@ func TestGetFactory(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListAdapterInfos(t *testing.T) {
|
func TestListRegisteredAdapterTypes(t *testing.T) {
|
||||||
registry = []*item{}
|
registry = map[model.RegistryType]Factory{}
|
||||||
// not register, got nothing
|
// not register, got nothing
|
||||||
infos := ListAdapterInfos()
|
types := ListRegisteredAdapterTypes()
|
||||||
assert.Equal(t, 0, len(infos))
|
assert.Equal(t, 0, len(types))
|
||||||
|
|
||||||
// register one factory
|
// register one factory
|
||||||
require.Nil(t, RegisterFactory(
|
require.Nil(t, RegisterFactory("harbor", fakedFactory))
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory))
|
|
||||||
|
|
||||||
infos = ListAdapterInfos()
|
types = ListRegisteredAdapterTypes()
|
||||||
require.Equal(t, 1, len(infos))
|
require.Equal(t, 1, len(types))
|
||||||
assert.Equal(t, "harbor", string(infos[0].Type))
|
assert.Equal(t, model.RegistryType("harbor"), types[0])
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAdapterInfo(t *testing.T) {
|
|
||||||
registry = []*item{}
|
|
||||||
require.Nil(t, RegisterFactory(
|
|
||||||
&Info{
|
|
||||||
Type: "harbor",
|
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
|
||||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
|
||||||
}, fakedFactory))
|
|
||||||
|
|
||||||
// doesn't exist
|
|
||||||
info := GetAdapterInfo("gcr")
|
|
||||||
assert.Nil(t, info)
|
|
||||||
|
|
||||||
// exist
|
|
||||||
info = GetAdapterInfo("harbor")
|
|
||||||
require.NotNil(t, info)
|
|
||||||
assert.Equal(t, "harbor", string(info.Type))
|
|
||||||
}
|
}
|
||||||
|
@ -31,36 +31,10 @@ import (
|
|||||||
// TODO add UT
|
// TODO add UT
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
info := &adp.Info{
|
|
||||||
Type: model.RegistryTypeHarbor,
|
|
||||||
SupportedResourceTypes: []model.ResourceType{
|
|
||||||
model.ResourceTypeRepository,
|
|
||||||
model.ResourceTypeChart,
|
|
||||||
},
|
|
||||||
SupportedResourceFilters: []*adp.Filter{
|
|
||||||
{
|
|
||||||
Type: model.FilterTypeName,
|
|
||||||
Style: adp.FilterStyleText,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: model.FilterTypeVersion,
|
|
||||||
Style: adp.FilterStyleText,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: model.FilterTypeLabel,
|
|
||||||
Style: adp.FilterStyleText,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SupportedTriggers: []model.TriggerType{
|
|
||||||
model.TriggerTypeManual,
|
|
||||||
model.TriggerTypeScheduled,
|
|
||||||
model.TriggerTypeEventBased,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// TODO passing coreServiceURL and tokenServiceURL
|
// TODO passing coreServiceURL and tokenServiceURL
|
||||||
coreServiceURL := "http://core:8080"
|
coreServiceURL := "http://core:8080"
|
||||||
tokenServiceURL := ""
|
tokenServiceURL := ""
|
||||||
if err := adp.RegisterFactory(info, func(registry *model.Registry) (adp.Adapter, error) {
|
if err := adp.RegisterFactory(model.RegistryTypeHarbor, func(registry *model.Registry) (adp.Adapter, error) {
|
||||||
return newAdapter(registry, coreServiceURL, tokenServiceURL), nil
|
return newAdapter(registry, coreServiceURL, tokenServiceURL), nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Errorf("failed to register factory for %s: %v", model.RegistryTypeHarbor, err)
|
log.Errorf("failed to register factory for %s: %v", model.RegistryTypeHarbor, err)
|
||||||
@ -110,6 +84,45 @@ func newAdapter(registry *model.Registry, coreServiceURL string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
||||||
|
info := &model.RegistryInfo{
|
||||||
|
Type: model.RegistryTypeHarbor,
|
||||||
|
SupportedResourceTypes: []model.ResourceType{
|
||||||
|
model.ResourceTypeRepository,
|
||||||
|
},
|
||||||
|
SupportedResourceFilters: []*model.FilterStyle{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Style: model.FilterStyleTypeText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeTag,
|
||||||
|
Style: model.FilterStyleTypeText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeLabel,
|
||||||
|
Style: model.FilterStyleTypeText,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SupportedTriggers: []model.TriggerType{
|
||||||
|
model.TriggerTypeManual,
|
||||||
|
model.TriggerTypeScheduled,
|
||||||
|
model.TriggerTypeEventBased,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sys := &struct {
|
||||||
|
ChartRegistryEnabled bool `json:"with_chartmuseum"`
|
||||||
|
}{}
|
||||||
|
if err := a.client.Get(a.coreServiceURL+"/api/systeminfo", sys); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sys.ChartRegistryEnabled {
|
||||||
|
info.SupportedResourceTypes = append(info.SupportedResourceTypes, model.ResourceTypeChart)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO implement the function
|
// TODO implement the function
|
||||||
func (a *adapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
func (a *adapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -47,12 +47,11 @@ func NewCopyFlow(executionMgr execution.Manager, registryMgr registry.Manager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *copyFlow) Run(interface{}) error {
|
func (c *copyFlow) Run(interface{}) error {
|
||||||
srcRegistry, dstRegistry, srcAdapter, dstAdapter, err := initialize(c.registryMgr, c.policy)
|
_, dstRegistry, srcAdapter, dstAdapter, err := initialize(c.registryMgr, c.policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO after refactoring the adapter register, the "srcRegistry.Type" is not needed
|
srcResources, err := fetchResources(srcAdapter, c.policy)
|
||||||
srcResources, err := fetchResources(srcAdapter, srcRegistry.Type, c.policy)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func initialize(mgr registry.Manager, policy *model.Policy) (*model.Registry, *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch resources from the source registry
|
// fetch resources from the source registry
|
||||||
func fetchResources(adapter adp.Adapter, adapterType model.RegistryType, policy *model.Policy) ([]*model.Resource, error) {
|
func fetchResources(adapter adp.Adapter, policy *model.Policy) ([]*model.Resource, error) {
|
||||||
resTypes := []model.ResourceType{}
|
resTypes := []model.ResourceType{}
|
||||||
filters := []*model.Filter{}
|
filters := []*model.Filter{}
|
||||||
for _, filter := range policy.Filters {
|
for _, filter := range policy.Filters {
|
||||||
@ -98,7 +98,11 @@ func fetchResources(adapter adp.Adapter, adapterType model.RegistryType, policy
|
|||||||
resTypes = append(resTypes, filter.Value.(model.ResourceType))
|
resTypes = append(resTypes, filter.Value.(model.ResourceType))
|
||||||
}
|
}
|
||||||
if len(resTypes) == 0 {
|
if len(resTypes) == 0 {
|
||||||
resTypes = append(resTypes, adp.GetAdapterInfo(adapterType).SupportedResourceTypes...)
|
info, err := adapter.Info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get the adapter info: %v", err)
|
||||||
|
}
|
||||||
|
resTypes = append(resTypes, info.SupportedResourceTypes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []*model.Resource{}
|
resources := []*model.Resource{}
|
||||||
@ -167,7 +171,7 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
|
|||||||
match = false
|
match = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case model.FilterTypeVersion:
|
case model.FilterTypeTag:
|
||||||
pattern, ok := filter.Value.(string)
|
pattern, ok := filter.Value.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%v is not a valid string", filter.Value)
|
return nil, fmt.Errorf("%v is not a valid string", filter.Value)
|
||||||
|
@ -67,6 +67,16 @@ func fakedAdapterFactory(*model.Registry) (adapter.Adapter, error) {
|
|||||||
|
|
||||||
type fakedAdapter struct{}
|
type fakedAdapter struct{}
|
||||||
|
|
||||||
|
func (f *fakedAdapter) Info() (*model.RegistryInfo, error) {
|
||||||
|
return &model.RegistryInfo{
|
||||||
|
Type: model.RegistryTypeHarbor,
|
||||||
|
SupportedResourceTypes: []model.ResourceType{
|
||||||
|
model.ResourceTypeRepository,
|
||||||
|
model.ResourceTypeChart,
|
||||||
|
},
|
||||||
|
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -225,15 +235,7 @@ func TestMain(m *testing.M) {
|
|||||||
config.Config = &config.Configuration{
|
config.Config = &config.Configuration{
|
||||||
RegistryURL: url,
|
RegistryURL: url,
|
||||||
}
|
}
|
||||||
if err := adapter.RegisterFactory(
|
if err := adapter.RegisterFactory(model.RegistryTypeHarbor, fakedAdapterFactory); err != nil {
|
||||||
&adapter.Info{
|
|
||||||
Type: model.RegistryTypeHarbor,
|
|
||||||
SupportedResourceTypes: []model.ResourceType{
|
|
||||||
model.ResourceTypeRepository,
|
|
||||||
model.ResourceTypeChart,
|
|
||||||
},
|
|
||||||
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
|
||||||
}, fakedAdapterFactory); err != nil {
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
@ -255,7 +257,7 @@ func TestInitialize(t *testing.T) {
|
|||||||
func TestFetchResources(t *testing.T) {
|
func TestFetchResources(t *testing.T) {
|
||||||
adapter := &fakedAdapter{}
|
adapter := &fakedAdapter{}
|
||||||
policy := &model.Policy{}
|
policy := &model.Policy{}
|
||||||
resources, err := fetchResources(adapter, model.RegistryTypeHarbor, policy)
|
resources, err := fetchResources(adapter, policy)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(resources))
|
assert.Equal(t, 2, len(resources))
|
||||||
}
|
}
|
||||||
@ -313,7 +315,7 @@ func TestFilterResources(t *testing.T) {
|
|||||||
Value: "library/harbor",
|
Value: "library/harbor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeVersion,
|
Type: model.FilterTypeTag,
|
||||||
Value: "0.2.?",
|
Value: "0.2.?",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,14 @@ import (
|
|||||||
|
|
||||||
// const definition
|
// const definition
|
||||||
const (
|
const (
|
||||||
FilterTypeResource = "Resource"
|
FilterTypeResource = "resource"
|
||||||
FilterTypeName = "Name"
|
FilterTypeName = "name"
|
||||||
FilterTypeVersion = "Version"
|
FilterTypeTag = "tag"
|
||||||
FilterTypeLabel = "Label"
|
FilterTypeLabel = "label"
|
||||||
|
|
||||||
TriggerTypeManual = "Manual"
|
TriggerTypeManual = "manual"
|
||||||
TriggerTypeScheduled = "Scheduled"
|
TriggerTypeScheduled = "scheduled"
|
||||||
TriggerTypeEventBased = "EventBased"
|
TriggerTypeEventBased = "event_based"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Policy defines the structure of a replication policy
|
// Policy defines the structure of a replication policy
|
||||||
|
@ -20,9 +20,13 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// const definition
|
||||||
const (
|
const (
|
||||||
// RegistryTypeHarbor indicates registry type harbor
|
// RegistryTypeHarbor indicates registry type harbor
|
||||||
RegistryTypeHarbor = "harbor"
|
RegistryTypeHarbor = "harbor"
|
||||||
|
|
||||||
|
FilterStyleTypeText = "input"
|
||||||
|
FilterStyleTypeRadio = "radio"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegistryType indicates the type of registry
|
// RegistryType indicates the type of registry
|
||||||
@ -74,3 +78,19 @@ type RegistryQuery struct {
|
|||||||
// Pagination specifies the pagination
|
// Pagination specifies the pagination
|
||||||
Pagination *models.Pagination
|
Pagination *models.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterStyle ...
|
||||||
|
type FilterStyle struct {
|
||||||
|
Type FilterType `json:"type"`
|
||||||
|
Style string `json:"style"`
|
||||||
|
Values []string `json:"values,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistryInfo provides base info and capability declarations of the registry
|
||||||
|
type RegistryInfo struct {
|
||||||
|
Type RegistryType `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
SupportedResourceTypes []ResourceType `json:"-"`
|
||||||
|
SupportedResourceFilters []*FilterStyle `json:"supported_resource_filters"`
|
||||||
|
SupportedTriggers []TriggerType `json:"supported_triggers"`
|
||||||
|
}
|
||||||
|
@ -98,7 +98,7 @@ func (d *defaultController) createFlow(executionID int64, policy *model.Policy,
|
|||||||
Value: resource.Metadata.Name,
|
Value: resource.Metadata.Name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeVersion,
|
Type: model.FilterTypeTag,
|
||||||
// only support replicate one tag
|
// only support replicate one tag
|
||||||
Value: resource.Metadata.Vtags[0],
|
Value: resource.Metadata.Vtags[0],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user