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,40 +2431,13 @@ paths:
schema:
type: array
items:
$ref: '#/definitions/ReplicationAdapter'
type: string
'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
required: true
description: The adapter type.
tags:
- Products
responses:
'200':
description: Success.
schema:
type: object
$ref: '#/definitions/ReplicationAdapter'
'401':
description: Unauthorized.
'403':
description: Forbidden.
'404':
description: Not found.
'500':
description: Unexpected internal errors.
/registries:
get:
summary: List registries.
@ -2595,6 +2568,30 @@ paths:
description: Registry does not exist.
'500':
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/':
get:
summary: List the target relevant policies.
@ -5279,34 +5276,29 @@ definitions:
action:
type: string
description: The permission action
ReplicationAdapter:
RegistryInfo:
type: object
description: The replication adapter
description: The registry info contains the base info and capability declarations of the registry
properties:
type:
type: string
description: The adapter type
description: The registry type
description:
type: string
description: The adapter description
supported_resource_types:
type: array
description: The resource types that the adapter supports
items:
type: string
description: The description
supported_resource_filters:
type: array
description: The filters that the adapter supports
description: The filters that the registry supports
items:
$ref: '#/definitions/ReplicationAdapterFilter'
$ref: '#/definitions/FilterStyle'
supported_triggers:
type: array
description: The triggers that the adapter supports
description: The triggers that the registry supports
items:
type: string
ReplicationAdapterFilter:
FilterStyle:
type: object
description: The replication adapter filter
description: The style of the resource filter
properties:
type:
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/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/:id([0-9]+)", &ReplicationOperationAPI{}, "put:StopExecution")
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/core/api/models"
"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/registry"
)
@ -211,3 +212,63 @@ func (t *RegistryAPI) Delete() {
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
import (
"fmt"
"github.com/goharbor/harbor/src/replication/ng/adapter"
"github.com/goharbor/harbor/src/replication/ng/model"
)
@ -41,53 +39,7 @@ func (r *ReplicationAdapterAPI) Prepare() {
// List the replication adapters
func (r *ReplicationAdapterAPI) List() {
infos := []*adapter.Info{}
for _, info := range adapter.ListAdapterInfos() {
infos = append(infos, process(info))
}
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
types := []model.RegistryType{}
types = append(types, adapter.ListRegisteredAdapterTypes()...)
r.WriteJSONData(types)
}

View File

@ -24,7 +24,13 @@ import (
"github.com/goharbor/harbor/src/replication/ng/model"
)
func fakedFactory(*model.Registry) (adapter.Adapter, error) {
return nil, nil
}
func TestReplicationAdapterAPIList(t *testing.T) {
err := adapter.RegisterFactory("test", fakedFactory)
require.Nil(t, err)
cases := []*codeCheckingCase{
// 401
{
@ -56,56 +62,3 @@ func TestReplicationAdapterAPIList(t *testing.T) {
runCodeCheckingCases(t, cases...)
}
func fakedFactory(*model.Registry) (adapter.Adapter, error) {
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)
cases := []*codeCheckingCase{
// 401
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters/test",
},
code: http.StatusUnauthorized,
},
// 403
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters/test",
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
// 404
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters/gcs",
credential: sysAdmin,
},
code: http.StatusNotFound,
},
// 200
{
request: &testingRequest{
method: http.MethodGet,
url: "/api/replication/adapters/test",
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}

View File

@ -101,7 +101,6 @@ func initRouters() {
beego.Router("/api/logs", &api.LogAPI{})
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/:id([0-9]+)", &api.ReplicationOperationAPI{}, "put:StopExecution")
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/: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")

View File

@ -21,45 +21,15 @@ import (
"github.com/goharbor/harbor/src/replication/ng/model"
)
// As the Info isn't a valid map key, so we use the slice
// 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"`
}
var registry = map[model.RegistryType]Factory{}
// Factory creates a specific Adapter according to the params
type Factory func(*model.Registry) (Adapter, error)
// Adapter interface defines the capabilities of registry
type Adapter interface {
// Info return the information of this adapter
Info() (*model.RegistryInfo, error)
// Lists the available namespaces under the specified registry with the
// provided credential/token
ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error)
@ -73,56 +43,35 @@ type Adapter interface {
}
// RegisterFactory registers one adapter factory to the registry
func RegisterFactory(info *Info, factory Factory) error {
if len(info.Type) == 0 {
func RegisterFactory(t model.RegistryType, factory Factory) error {
if len(t) == 0 {
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 {
return errors.New("empty adapter factory")
}
for _, item := range registry {
if item.info.Type == info.Type {
return fmt.Errorf("adapter factory for %s already exists", info.Type)
}
if _, exist := registry[t]; exist {
return fmt.Errorf("adapter factory for %s already exists", t)
}
registry = append(registry, &item{
info: info,
factory: factory,
})
registry[t] = factory
return nil
}
// GetFactory gets the adapter factory by the specified name
func GetFactory(t model.RegistryType) (Factory, error) {
for _, item := range registry {
if item.info.Type == t {
return item.factory, nil
}
factory, exist := registry[t]
if !exist {
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)
// ListRegisteredAdapterTypes lists the registered Adapter type
func ListRegisteredAdapterTypes() []model.RegistryType {
types := []model.RegistryType{}
for t := range registry {
types = append(types, t)
}
return infos
}
// GetAdapterInfo returns the info of a specified registry type
func GetAdapterInfo(t model.RegistryType) *Info {
for _, item := range registry {
if item.info.Type == t {
return item.info
}
}
return nil
return types
}

View File

@ -28,49 +28,18 @@ func fakedFactory(*model.Registry) (Adapter, error) {
func TestRegisterFactory(t *testing.T) {
// empty type
assert.NotNil(t, RegisterFactory(&Info{}, 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))
assert.NotNil(t, RegisterFactory("", nil))
// empty factory
assert.NotNil(t, RegisterFactory(
&Info{
Type: "harbor",
SupportedResourceTypes: []model.ResourceType{"image"},
SupportedTriggers: []model.TriggerType{"mannual"},
}, nil))
assert.NotNil(t, RegisterFactory("harbor", nil))
// pass
assert.Nil(t, RegisterFactory(
&Info{
Type: "harbor",
SupportedResourceTypes: []model.ResourceType{"image"},
SupportedTriggers: []model.TriggerType{"mannual"},
}, fakedFactory))
assert.Nil(t, RegisterFactory("harbor", fakedFactory))
// already exists
assert.NotNil(t, RegisterFactory(
&Info{
Type: "harbor",
SupportedResourceTypes: []model.ResourceType{"image"},
SupportedTriggers: []model.TriggerType{"mannual"},
}, fakedFactory))
assert.NotNil(t, RegisterFactory("harbor", fakedFactory))
}
func TestGetFactory(t *testing.T) {
registry = []*item{}
require.Nil(t, RegisterFactory(
&Info{
Type: "harbor",
SupportedResourceTypes: []model.ResourceType{"image"},
SupportedTriggers: []model.TriggerType{"mannual"},
}, fakedFactory))
registry = map[model.RegistryType]Factory{}
require.Nil(t, RegisterFactory("harbor", fakedFactory))
// doesn't exist
_, err := GetFactory("gcr")
assert.NotNil(t, err)
@ -79,40 +48,16 @@ func TestGetFactory(t *testing.T) {
assert.Nil(t, err)
}
func TestListAdapterInfos(t *testing.T) {
registry = []*item{}
func TestListRegisteredAdapterTypes(t *testing.T) {
registry = map[model.RegistryType]Factory{}
// not register, got nothing
infos := ListAdapterInfos()
assert.Equal(t, 0, len(infos))
types := ListRegisteredAdapterTypes()
assert.Equal(t, 0, len(types))
// register one factory
require.Nil(t, RegisterFactory(
&Info{
Type: "harbor",
SupportedResourceTypes: []model.ResourceType{"image"},
SupportedTriggers: []model.TriggerType{"mannual"},
}, fakedFactory))
require.Nil(t, RegisterFactory("harbor", fakedFactory))
infos = ListAdapterInfos()
require.Equal(t, 1, len(infos))
assert.Equal(t, "harbor", string(infos[0].Type))
}
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))
types = ListRegisteredAdapterTypes()
require.Equal(t, 1, len(types))
assert.Equal(t, model.RegistryType("harbor"), types[0])
}

View File

@ -31,36 +31,10 @@ import (
// TODO add UT
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
coreServiceURL := "http://core:8080"
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
}); err != nil {
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
func (a *adapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
return nil, nil

View File

@ -47,12 +47,11 @@ func NewCopyFlow(executionMgr execution.Manager, registryMgr registry.Manager,
}
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 {
return err
}
// TODO after refactoring the adapter register, the "srcRegistry.Type" is not needed
srcResources, err := fetchResources(srcAdapter, srcRegistry.Type, c.policy)
srcResources, err := fetchResources(srcAdapter, c.policy)
if err != nil {
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
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{}
filters := []*model.Filter{}
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))
}
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{}
@ -167,7 +171,7 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
match = false
break
}
case model.FilterTypeVersion:
case model.FilterTypeTag:
pattern, ok := filter.Value.(string)
if !ok {
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{}
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) {
return nil, nil
}
@ -225,15 +235,7 @@ func TestMain(m *testing.M) {
config.Config = &config.Configuration{
RegistryURL: url,
}
if err := adapter.RegisterFactory(
&adapter.Info{
Type: model.RegistryTypeHarbor,
SupportedResourceTypes: []model.ResourceType{
model.ResourceTypeRepository,
model.ResourceTypeChart,
},
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
}, fakedAdapterFactory); err != nil {
if err := adapter.RegisterFactory(model.RegistryTypeHarbor, fakedAdapterFactory); err != nil {
os.Exit(1)
}
os.Exit(m.Run())
@ -255,7 +257,7 @@ func TestInitialize(t *testing.T) {
func TestFetchResources(t *testing.T) {
adapter := &fakedAdapter{}
policy := &model.Policy{}
resources, err := fetchResources(adapter, model.RegistryTypeHarbor, policy)
resources, err := fetchResources(adapter, policy)
require.Nil(t, err)
assert.Equal(t, 2, len(resources))
}
@ -313,7 +315,7 @@ func TestFilterResources(t *testing.T) {
Value: "library/harbor",
},
{
Type: model.FilterTypeVersion,
Type: model.FilterTypeTag,
Value: "0.2.?",
},
}

View File

@ -23,14 +23,14 @@ import (
// const definition
const (
FilterTypeResource = "Resource"
FilterTypeName = "Name"
FilterTypeVersion = "Version"
FilterTypeLabel = "Label"
FilterTypeResource = "resource"
FilterTypeName = "name"
FilterTypeTag = "tag"
FilterTypeLabel = "label"
TriggerTypeManual = "Manual"
TriggerTypeScheduled = "Scheduled"
TriggerTypeEventBased = "EventBased"
TriggerTypeManual = "manual"
TriggerTypeScheduled = "scheduled"
TriggerTypeEventBased = "event_based"
)
// Policy defines the structure of a replication policy

View File

@ -20,9 +20,13 @@ import (
"github.com/goharbor/harbor/src/common/models"
)
// const definition
const (
// RegistryTypeHarbor indicates registry type harbor
RegistryTypeHarbor = "harbor"
FilterStyleTypeText = "input"
FilterStyleTypeRadio = "radio"
)
// RegistryType indicates the type of registry
@ -74,3 +78,19 @@ type RegistryQuery struct {
// Pagination specifies the 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,
},
{
Type: model.FilterTypeVersion,
Type: model.FilterTypeTag,
// only support replicate one tag
Value: resource.Metadata.Vtags[0],
},