mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-12 14:08:23 +02:00
Merge pull request #7183 from ywk253100/190320_filter
Update listing/getting replication adapter API
This commit is contained in:
commit
224f059993
@ -5261,6 +5261,26 @@ definitions:
|
||||
supported_resource_filters:
|
||||
type: array
|
||||
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:
|
||||
type: string
|
||||
ReplicationExecution:
|
||||
|
@ -41,7 +41,10 @@ func (r *ReplicationAdapterAPI) Prepare() {
|
||||
|
||||
// List the replication adapters
|
||||
func (r *ReplicationAdapterAPI) List() {
|
||||
infos := adapter.ListAdapterInfos()
|
||||
infos := []*adapter.Info{}
|
||||
for _, info := range adapter.ListAdapterInfos() {
|
||||
infos = append(infos, process(info))
|
||||
}
|
||||
r.WriteJSONData(infos)
|
||||
}
|
||||
|
||||
@ -53,5 +56,38 @@ func (r *ReplicationAdapterAPI) Get() {
|
||||
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
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ func TestReplicationAdapterAPIGet(t *testing.T) {
|
||||
&adapter.Info{
|
||||
Type: "test",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -25,17 +25,34 @@ import (
|
||||
// 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:"supported_resource_types"`
|
||||
SupportedResourceFilters []model.FilterType `json:"supported_resource_filters"`
|
||||
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
|
||||
@ -63,6 +80,9 @@ func RegisterFactory(info *Info, factory Factory) error {
|
||||
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")
|
||||
}
|
||||
|
@ -34,23 +34,32 @@ func TestRegisterFactory(t *testing.T) {
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
}, nil))
|
||||
// empty trigger
|
||||
assert.NotNil(t, RegisterFactory(
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
}, nil))
|
||||
// empty factory
|
||||
assert.NotNil(t, RegisterFactory(
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, nil))
|
||||
// pass
|
||||
assert.Nil(t, RegisterFactory(
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory))
|
||||
// already exists
|
||||
assert.NotNil(t, RegisterFactory(
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory))
|
||||
}
|
||||
|
||||
@ -60,6 +69,7 @@ func TestGetFactory(t *testing.T) {
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory))
|
||||
// doesn't exist
|
||||
_, err := GetFactory("gcr")
|
||||
@ -80,6 +90,7 @@ func TestListAdapterInfos(t *testing.T) {
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory))
|
||||
|
||||
infos = ListAdapterInfos()
|
||||
@ -93,6 +104,7 @@ func TestGetAdapterInfo(t *testing.T) {
|
||||
&Info{
|
||||
Type: "harbor",
|
||||
SupportedResourceTypes: []model.ResourceType{"image"},
|
||||
SupportedTriggers: []model.TriggerType{"mannual"},
|
||||
}, fakedFactory))
|
||||
|
||||
// doesn't exist
|
||||
|
@ -31,11 +31,31 @@ import (
|
||||
// TODO add UT
|
||||
|
||||
func init() {
|
||||
// TODO add more information to the info
|
||||
info := &adp.Info{
|
||||
Type: model.RegistryTypeHarbor,
|
||||
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
|
||||
coreServiceURL := "http://core:8080"
|
||||
|
@ -241,6 +241,7 @@ func TestStartReplication(t *testing.T) {
|
||||
model.ResourceTypeRepository,
|
||||
model.ResourceTypeChart,
|
||||
},
|
||||
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
||||
}, fakedAdapterFactory)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -27,6 +27,10 @@ const (
|
||||
FilterTypeName = "Name"
|
||||
FilterTypeVersion = "Version"
|
||||
FilterTypeLabel = "Label"
|
||||
|
||||
TriggerTypeManual = "Manual"
|
||||
TriggerTypeScheduled = "Scheduled"
|
||||
TriggerTypeEventBased = "EventBased"
|
||||
)
|
||||
|
||||
// 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