mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-13 02:11:28 +01:00
Update listing/getting replication adapter API
This commit updates the listing/getting replication adapter API Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
bb76a4d97d
commit
c65d5e6669
@ -5261,6 +5261,26 @@ definitions:
|
|||||||
supported_resource_filters:
|
supported_resource_filters:
|
||||||
type: array
|
type: array
|
||||||
description: The filters that the adapter supports
|
description: The filters that the adapter supports
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ReplicationAdapterFilter'
|
||||||
|
supported_triggers:
|
||||||
|
type: array
|
||||||
|
description: The triggers that the adapter supports
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
ReplicationAdapterFilter:
|
||||||
|
type: object
|
||||||
|
description: The replication adapter filter
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: The filter type
|
||||||
|
style:
|
||||||
|
type: string
|
||||||
|
description: The filter style
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
description: The filter values
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
ReplicationExecution:
|
ReplicationExecution:
|
||||||
|
@ -41,7 +41,10 @@ func (r *ReplicationAdapterAPI) Prepare() {
|
|||||||
|
|
||||||
// List the replication adapters
|
// List the replication adapters
|
||||||
func (r *ReplicationAdapterAPI) List() {
|
func (r *ReplicationAdapterAPI) List() {
|
||||||
infos := adapter.ListAdapterInfos()
|
infos := []*adapter.Info{}
|
||||||
|
for _, info := range adapter.ListAdapterInfos() {
|
||||||
|
infos = append(infos, process(info))
|
||||||
|
}
|
||||||
r.WriteJSONData(infos)
|
r.WriteJSONData(infos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,5 +56,38 @@ func (r *ReplicationAdapterAPI) Get() {
|
|||||||
r.HandleNotFound(fmt.Sprintf("adapter for %s not found", t))
|
r.HandleNotFound(fmt.Sprintf("adapter for %s not found", t))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
info = process(info)
|
||||||
r.WriteJSONData(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
|
||||||
|
}
|
||||||
|
@ -65,6 +65,7 @@ func TestReplicationAdapterAPIGet(t *testing.T) {
|
|||||||
&adapter.Info{
|
&adapter.Info{
|
||||||
Type: "test",
|
Type: "test",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory)
|
}, fakedFactory)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -25,17 +25,34 @@ import (
|
|||||||
// as the adapter registry
|
// as the adapter registry
|
||||||
var registry = []*item{}
|
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 {
|
type item struct {
|
||||||
info *Info
|
info *Info
|
||||||
factory Factory
|
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
|
// Info provides base info and capability declarations of the adapter
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Type model.RegistryType `json:"type"`
|
Type model.RegistryType `json:"type"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
SupportedResourceTypes []model.ResourceType `json:"supported_resource_types"`
|
SupportedResourceTypes []model.ResourceType `json:"-"`
|
||||||
SupportedResourceFilters []model.FilterType `json:"supported_resource_filters"`
|
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
|
||||||
@ -63,6 +80,9 @@ func RegisterFactory(info *Info, factory Factory) error {
|
|||||||
if len(info.SupportedResourceTypes) == 0 {
|
if len(info.SupportedResourceTypes) == 0 {
|
||||||
return errors.New("must support at least one resource type")
|
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")
|
||||||
}
|
}
|
||||||
|
@ -34,23 +34,32 @@ func TestRegisterFactory(t *testing.T) {
|
|||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
}, nil))
|
}, 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(
|
||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, nil))
|
}, nil))
|
||||||
// pass
|
// pass
|
||||||
assert.Nil(t, RegisterFactory(
|
assert.Nil(t, RegisterFactory(
|
||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory))
|
}, fakedFactory))
|
||||||
// already exists
|
// already exists
|
||||||
assert.NotNil(t, RegisterFactory(
|
assert.NotNil(t, RegisterFactory(
|
||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory))
|
}, fakedFactory))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +69,7 @@ func TestGetFactory(t *testing.T) {
|
|||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory))
|
}, fakedFactory))
|
||||||
// doesn't exist
|
// doesn't exist
|
||||||
_, err := GetFactory("gcr")
|
_, err := GetFactory("gcr")
|
||||||
@ -80,6 +90,7 @@ func TestListAdapterInfos(t *testing.T) {
|
|||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory))
|
}, fakedFactory))
|
||||||
|
|
||||||
infos = ListAdapterInfos()
|
infos = ListAdapterInfos()
|
||||||
@ -93,6 +104,7 @@ func TestGetAdapterInfo(t *testing.T) {
|
|||||||
&Info{
|
&Info{
|
||||||
Type: "harbor",
|
Type: "harbor",
|
||||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||||
|
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||||
}, fakedFactory))
|
}, fakedFactory))
|
||||||
|
|
||||||
// doesn't exist
|
// doesn't exist
|
||||||
|
@ -31,11 +31,31 @@ import (
|
|||||||
// TODO add UT
|
// TODO add UT
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// TODO add more information to the info
|
|
||||||
info := &adp.Info{
|
info := &adp.Info{
|
||||||
Type: model.RegistryTypeHarbor,
|
Type: model.RegistryTypeHarbor,
|
||||||
SupportedResourceTypes: []model.ResourceType{
|
SupportedResourceTypes: []model.ResourceType{
|
||||||
model.ResourceTypeRepository, model.ResourceTypeChart},
|
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"
|
||||||
|
@ -241,6 +241,7 @@ func TestStartReplication(t *testing.T) {
|
|||||||
model.ResourceTypeRepository,
|
model.ResourceTypeRepository,
|
||||||
model.ResourceTypeChart,
|
model.ResourceTypeChart,
|
||||||
},
|
},
|
||||||
|
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
||||||
}, fakedAdapterFactory)
|
}, fakedAdapterFactory)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -27,6 +27,10 @@ const (
|
|||||||
FilterTypeName = "Name"
|
FilterTypeName = "Name"
|
||||||
FilterTypeVersion = "Version"
|
FilterTypeVersion = "Version"
|
||||||
FilterTypeLabel = "Label"
|
FilterTypeLabel = "Label"
|
||||||
|
|
||||||
|
TriggerTypeManual = "Manual"
|
||||||
|
TriggerTypeScheduled = "Scheduled"
|
||||||
|
TriggerTypeEventBased = "EventBased"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Policy defines the structure of a replication policy
|
// Policy defines the structure of a replication policy
|
||||||
|
116
src/replication/ng/transfer/chart/transfer_test.go
Normal file
116
src/replication/ng/transfer/chart/transfer_test.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 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 chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
|
trans "github.com/goharbor/harbor/src/replication/ng/transfer"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeRegistry struct{}
|
||||||
|
|
||||||
|
func (f *fakeRegistry) FetchCharts(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
|
return []*model.Resource{
|
||||||
|
{
|
||||||
|
Type: model.ResourceTypeChart,
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Name: "library/harbor",
|
||||||
|
Namespace: "library",
|
||||||
|
Vtags: []string{"0.2.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRegistry) ChartExist(name, version string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRegistry) DownloadChart(name, version string) (io.ReadCloser, error) {
|
||||||
|
r := ioutil.NopCloser(bytes.NewReader([]byte{'a'}))
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRegistry) UploadChart(name, version string, chart io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (f *fakeRegistry) DeleteChart(name, version string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFactory(t *testing.T) {
|
||||||
|
tr, err := factory(nil, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, ok := tr.(trans.Transfer)
|
||||||
|
assert.True(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldStop(t *testing.T) {
|
||||||
|
// should stop
|
||||||
|
stopFunc := func() bool { return true }
|
||||||
|
tr := &transfer{
|
||||||
|
logger: log.DefaultLogger(),
|
||||||
|
isStopped: stopFunc,
|
||||||
|
}
|
||||||
|
assert.True(t, tr.shouldStop())
|
||||||
|
|
||||||
|
// should not stop
|
||||||
|
stopFunc = func() bool { return false }
|
||||||
|
tr = &transfer{
|
||||||
|
isStopped: stopFunc,
|
||||||
|
}
|
||||||
|
assert.False(t, tr.shouldStop())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
stopFunc := func() bool { return false }
|
||||||
|
transfer := &transfer{
|
||||||
|
logger: log.DefaultLogger(),
|
||||||
|
isStopped: stopFunc,
|
||||||
|
src: &fakeRegistry{},
|
||||||
|
dst: &fakeRegistry{},
|
||||||
|
}
|
||||||
|
src := &chart{
|
||||||
|
name: "library/harbor",
|
||||||
|
version: "0.2.0",
|
||||||
|
}
|
||||||
|
dst := &chart{
|
||||||
|
name: "dest/harbor",
|
||||||
|
version: "0.2.0",
|
||||||
|
}
|
||||||
|
err := transfer.copy(src, dst, true)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
stopFunc := func() bool { return false }
|
||||||
|
transfer := &transfer{
|
||||||
|
logger: log.DefaultLogger(),
|
||||||
|
isStopped: stopFunc,
|
||||||
|
src: &fakeRegistry{},
|
||||||
|
dst: &fakeRegistry{},
|
||||||
|
}
|
||||||
|
chart := &chart{
|
||||||
|
name: "dest/harbor",
|
||||||
|
version: "0.2.0",
|
||||||
|
}
|
||||||
|
err := transfer.delete(chart)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user