Merge pull request #7220 from ywk253100/190324_adapter

Update the registry adapter interface
This commit is contained in:
Wenkai Yin 2019-03-25 21:59:04 +08:00 committed by GitHub
commit 1d4cf53462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 228 additions and 339 deletions

View File

@ -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

View File

@ -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")

View File

@ -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
}

View File

@ -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
} }

View File

@ -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,

View File

@ -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")

View File

@ -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
} }

View File

@ -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))
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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)

View File

@ -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.?",
}, },
} }

View File

@ -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

View File

@ -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"`
}

View File

@ -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],
}, },