mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 18:55:18 +01:00
Update the adapter interface
Add ConvertResourceMetadata and PrepareForPush methods Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
ba20da5dd4
commit
5a047a7eb6
@ -333,8 +333,13 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: repoName,
|
Namespace: &model.Namespace{
|
||||||
Namespace: projectName,
|
Name: projectName,
|
||||||
|
// TODO filling the metadata
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: strings.TrimPrefix(repoName, projectName+"/"),
|
||||||
|
},
|
||||||
Vtags: []string{tag},
|
Vtags: []string{tag},
|
||||||
},
|
},
|
||||||
Deleted: true,
|
Deleted: true,
|
||||||
|
@ -120,8 +120,13 @@ func (n *NotificationHandler) Post() {
|
|||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: repository,
|
Namespace: &model.Namespace{
|
||||||
Namespace: project,
|
Name: project,
|
||||||
|
// TODO filling the metadata
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: strings.TrimPrefix(repository, project+"/"),
|
||||||
|
},
|
||||||
Vtags: []string{tag},
|
Vtags: []string{tag},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -33,12 +33,15 @@ type Adapter interface {
|
|||||||
// 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)
|
||||||
// Create a new namespace
|
// ConvertResourceMetadata converts the namespace and repository part of the resource metadata
|
||||||
// This method should guarantee it's idempotent
|
// to the one that the adapter can handle
|
||||||
// And returns nil if a namespace with the same name already exists
|
ConvertResourceMetadata(*model.ResourceMetadata, *model.Namespace) (*model.ResourceMetadata, error)
|
||||||
CreateNamespace(*model.Namespace) error
|
// PrepareForPush does the prepare work that needed for pushing/uploading the resource
|
||||||
|
// eg: create the namespace or repository
|
||||||
|
PrepareForPush(*model.Resource) error
|
||||||
// Get the namespace specified by the name, the returning value should
|
// Get the namespace specified by the name, the returning value should
|
||||||
// contain the metadata about the namespace if it has
|
// contain the metadata about the namespace if it has
|
||||||
|
// TODO remove this method?
|
||||||
GetNamespace(string) (*model.Namespace, error)
|
GetNamespace(string) (*model.Namespace, error)
|
||||||
// HealthCheck checks health status of registry
|
// HealthCheck checks health status of registry
|
||||||
HealthCheck() (model.HealthStatus, error)
|
HealthCheck() (model.HealthStatus, error)
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
package harbor
|
package harbor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
common_http "github.com/goharbor/harbor/src/common/http"
|
common_http "github.com/goharbor/harbor/src/common/http"
|
||||||
"github.com/goharbor/harbor/src/common/http/modifier"
|
"github.com/goharbor/harbor/src/common/http/modifier"
|
||||||
@ -86,6 +88,7 @@ func newAdapter(registry *model.Registry) *adapter {
|
|||||||
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
||||||
info := &model.RegistryInfo{
|
info := &model.RegistryInfo{
|
||||||
Type: model.RegistryTypeHarbor,
|
Type: model.RegistryTypeHarbor,
|
||||||
|
SupportNamespace: true,
|
||||||
SupportedResourceTypes: []model.ResourceType{
|
SupportedResourceTypes: []model.ResourceType{
|
||||||
model.ResourceTypeRepository,
|
model.ResourceTypeRepository,
|
||||||
},
|
},
|
||||||
@ -126,13 +129,63 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
func (a *adapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
func (a *adapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (a *adapter) CreateNamespace(namespace *model.Namespace) error {
|
func (a *adapter) ConvertResourceMetadata(metadata *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
||||||
|
if metadata == nil {
|
||||||
|
return nil, errors.New("the metadata cannot be null")
|
||||||
|
}
|
||||||
|
name := metadata.GetResourceName()
|
||||||
|
strs := strings.SplitN(name, "/", 2)
|
||||||
|
if len(strs) < 2 {
|
||||||
|
return nil, fmt.Errorf("unsupported resource name %s, at least contains one '/'", name)
|
||||||
|
}
|
||||||
|
meta := &model.ResourceMetadata{
|
||||||
|
Vtags: metadata.Vtags,
|
||||||
|
Labels: metadata.Labels,
|
||||||
|
}
|
||||||
|
meta.Namespace = &model.Namespace{
|
||||||
|
Name: strs[0],
|
||||||
|
}
|
||||||
|
if metadata.Namespace != nil {
|
||||||
|
meta.Namespace.Metadata = metadata.Namespace.Metadata
|
||||||
|
}
|
||||||
|
meta.Repository = &model.Repository{
|
||||||
|
Name: strs[1],
|
||||||
|
}
|
||||||
|
if metadata.Repository != nil {
|
||||||
|
meta.Repository.Metadata = metadata.Repository.Metadata
|
||||||
|
}
|
||||||
|
// replace the namespace if it is specified
|
||||||
|
if namespace == nil || len(namespace.Name) == 0 {
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(namespace.Name, "/") {
|
||||||
|
return nil, fmt.Errorf("the namespace %s cannot contain '/'", namespace.Name)
|
||||||
|
}
|
||||||
|
meta.Namespace.Name = namespace.Name
|
||||||
|
if namespace.Metadata != nil {
|
||||||
|
meta.Namespace.Metadata = namespace.Metadata
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
||||||
|
if resource == nil {
|
||||||
|
return errors.New("the resource cannot be null")
|
||||||
|
}
|
||||||
|
if resource.Metadata == nil {
|
||||||
|
return errors.New("the metadata of resource cannot be null")
|
||||||
|
}
|
||||||
|
if resource.Metadata.Namespace == nil {
|
||||||
|
return errors.New("the namespace of resource cannot be null")
|
||||||
|
}
|
||||||
|
if len(resource.Metadata.Namespace.Name) == 0 {
|
||||||
|
return errors.New("the name of the namespace cannot be null")
|
||||||
|
}
|
||||||
project := &struct {
|
project := &struct {
|
||||||
Name string `json:"project_name"`
|
Name string `json:"project_name"`
|
||||||
Metadata map[string]interface{} `json:"metadata"`
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
}{
|
}{
|
||||||
Name: namespace.Name,
|
Name: resource.Metadata.Namespace.Name,
|
||||||
Metadata: namespace.Metadata,
|
Metadata: resource.Metadata.Namespace.Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@ -160,11 +213,13 @@ func (a *adapter) CreateNamespace(namespace *model.Namespace) error {
|
|||||||
|
|
||||||
err := a.client.Post(a.coreServiceURL+"/api/projects", project)
|
err := a.client.Post(a.coreServiceURL+"/api/projects", project)
|
||||||
if httpErr, ok := err.(*common_http.Error); ok && httpErr.Code == http.StatusConflict {
|
if httpErr, ok := err.(*common_http.Error); ok && httpErr.Code == http.StatusConflict {
|
||||||
log.Debugf("got 409 when trying to create project %s", namespace.Name)
|
log.Debugf("got 409 when trying to create project %s", resource.Metadata.Namespace.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove this method
|
||||||
func (a *adapter) GetNamespace(namespace string) (*model.Namespace, error) {
|
func (a *adapter) GetNamespace(namespace string) (*model.Namespace, error) {
|
||||||
project, err := a.getProject(namespace)
|
project, err := a.getProject(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,12 +18,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/test"
|
"github.com/goharbor/harbor/src/common/utils/test"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
func TestInfo(t *testing.T) {
|
||||||
@ -77,8 +75,7 @@ func TestListNamespaces(t *testing.T) {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateNamespace(t *testing.T) {
|
func TestPrepareForPush(t *testing.T) {
|
||||||
// project doesn't exist
|
|
||||||
server := test.NewServer(&test.RequestHandlerMapping{
|
server := test.NewServer(&test.RequestHandlerMapping{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Pattern: "/api/projects",
|
Pattern: "/api/projects",
|
||||||
@ -90,10 +87,41 @@ func TestCreateNamespace(t *testing.T) {
|
|||||||
URL: server.URL,
|
URL: server.URL,
|
||||||
}
|
}
|
||||||
adapter := newAdapter(registry)
|
adapter := newAdapter(registry)
|
||||||
err := adapter.CreateNamespace(&model.Namespace{
|
// nil resource
|
||||||
|
err := adapter.PrepareForPush(nil)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
// nil metadata
|
||||||
|
err = adapter.PrepareForPush(&model.Resource{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
// nil namespace
|
||||||
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{},
|
||||||
|
})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
// nil namespace name
|
||||||
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Namespace: &model.Namespace{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
// nil namespace name
|
||||||
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Namespace: &model.Namespace{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
// project doesn't exist
|
||||||
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Namespace: &model.Namespace{
|
||||||
Name: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
server.Close()
|
server.Close()
|
||||||
|
|
||||||
// project already exists
|
// project already exists
|
||||||
@ -108,11 +136,14 @@ func TestCreateNamespace(t *testing.T) {
|
|||||||
URL: server.URL,
|
URL: server.URL,
|
||||||
}
|
}
|
||||||
adapter = newAdapter(registry)
|
adapter = newAdapter(registry)
|
||||||
err = adapter.CreateNamespace(&model.Namespace{
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Namespace: &model.Namespace{
|
||||||
Name: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
server.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetNamespace(t *testing.T) {
|
func TestGetNamespace(t *testing.T) {
|
||||||
|
@ -67,8 +67,13 @@ func (a *adapter) FetchCharts(namespaces []string, filters []*model.Filter) ([]*
|
|||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Registry: a.registry,
|
Registry: a.registry,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: namespace,
|
Namespace: &model.Namespace{
|
||||||
Name: fmt.Sprintf("%s/%s", namespace, chart.Name),
|
Name: namespace,
|
||||||
|
// TODO filling the metadata
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: chart.Name,
|
||||||
|
},
|
||||||
Vtags: []string{version.Version},
|
Vtags: []string{version.Version},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -61,8 +61,8 @@ func TestFetchCharts(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(resources))
|
assert.Equal(t, 2, len(resources))
|
||||||
assert.Equal(t, model.ResourceTypeChart, resources[0].Type)
|
assert.Equal(t, model.ResourceTypeChart, resources[0].Type)
|
||||||
assert.Equal(t, "library/harbor", resources[0].Metadata.Name)
|
assert.Equal(t, "harbor", resources[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, "library", resources[0].Metadata.Namespace)
|
assert.Equal(t, "library", resources[0].Metadata.Namespace.Name)
|
||||||
assert.Equal(t, 1, len(resources[0].Metadata.Vtags))
|
assert.Equal(t, 1, len(resources[0].Metadata.Vtags))
|
||||||
assert.Equal(t, "1.0", resources[0].Metadata.Vtags[0])
|
assert.Equal(t, "1.0", resources[0].Metadata.Vtags[0])
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package harbor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
@ -56,8 +57,13 @@ func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*
|
|||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Registry: a.registry,
|
Registry: a.registry,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: namespace,
|
Namespace: &model.Namespace{
|
||||||
Name: repository.Name,
|
Name: namespace,
|
||||||
|
// TODO filling the metadata
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: strings.TrimPrefix(repository.Name, namespace+"/"),
|
||||||
|
},
|
||||||
Vtags: vtags,
|
Vtags: vtags,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -71,8 +71,8 @@ func TestFetchImages(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(resources))
|
assert.Equal(t, 1, len(resources))
|
||||||
assert.Equal(t, model.ResourceTypeRepository, resources[0].Type)
|
assert.Equal(t, model.ResourceTypeRepository, resources[0].Type)
|
||||||
assert.Equal(t, "library/hello-world", resources[0].Metadata.Name)
|
assert.Equal(t, "hello-world", resources[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, "library", resources[0].Metadata.Namespace)
|
assert.Equal(t, "library", resources[0].Metadata.Namespace.Name)
|
||||||
assert.Equal(t, 2, len(resources[0].Metadata.Vtags))
|
assert.Equal(t, 2, len(resources[0].Metadata.Vtags))
|
||||||
assert.Equal(t, "1.0", resources[0].Metadata.Vtags[0])
|
assert.Equal(t, "1.0", resources[0].Metadata.Vtags[0])
|
||||||
assert.Equal(t, "2.0", resources[0].Metadata.Vtags[1])
|
assert.Equal(t, "2.0", resources[0].Metadata.Vtags[1])
|
||||||
|
@ -57,9 +57,9 @@ func (h *handler) Handle(event *Event) error {
|
|||||||
var err error
|
var err error
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case EventTypeImagePush, EventTypeChartUpload:
|
case EventTypeImagePush, EventTypeChartUpload:
|
||||||
policies, err = h.getRelatedPolicies(event.Resource.Metadata.Namespace)
|
policies, err = h.getRelatedPolicies(event.Resource.Metadata.Namespace.Name)
|
||||||
case EventTypeImageDelete, EventTypeChartDelete:
|
case EventTypeImageDelete, EventTypeChartDelete:
|
||||||
policies, err = h.getRelatedPolicies(event.Resource.Metadata.Namespace, true)
|
policies, err = h.getRelatedPolicies(event.Resource.Metadata.Namespace.Name, true)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported event type %s", event.Type)
|
return fmt.Errorf("unsupported event type %s", event.Type)
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,12 @@ func TestHandle(t *testing.T) {
|
|||||||
err = handler.Handle(&Event{
|
err = handler.Handle(&Event{
|
||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{},
|
Vtags: []string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -174,8 +178,12 @@ func TestHandle(t *testing.T) {
|
|||||||
err = handler.Handle(&Event{
|
err = handler.Handle(&Event{
|
||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -187,8 +195,12 @@ func TestHandle(t *testing.T) {
|
|||||||
err = handler.Handle(&Event{
|
err = handler.Handle(&Event{
|
||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -200,8 +212,12 @@ func TestHandle(t *testing.T) {
|
|||||||
err = handler.Handle(&Event{
|
err = handler.Handle(&Event{
|
||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -108,6 +108,7 @@ type FilterStyle struct {
|
|||||||
type RegistryInfo struct {
|
type RegistryInfo struct {
|
||||||
Type RegistryType `json:"type"`
|
Type RegistryType `json:"type"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
SupportNamespace bool `json:"support_namespace"`
|
||||||
SupportedResourceTypes []ResourceType `json:"-"`
|
SupportedResourceTypes []ResourceType `json:"-"`
|
||||||
SupportedResourceFilters []*FilterStyle `json:"supported_resource_filters"`
|
SupportedResourceFilters []*FilterStyle `json:"supported_resource_filters"`
|
||||||
SupportedTriggers []TriggerType `json:"supported_triggers"`
|
SupportedTriggers []TriggerType `json:"supported_triggers"`
|
||||||
|
@ -30,12 +30,34 @@ func (r ResourceType) Valid() bool {
|
|||||||
|
|
||||||
// ResourceMetadata of resource
|
// ResourceMetadata of resource
|
||||||
type ResourceMetadata struct {
|
type ResourceMetadata struct {
|
||||||
Namespace string `json:"namespace"`
|
Namespace *Namespace `json:"namespace"`
|
||||||
Name string `json:"name"`
|
Repository *Repository `json:"repository"`
|
||||||
Vtags []string `json:"v_tags"`
|
Vtags []string `json:"v_tags"`
|
||||||
|
// TODO the labels should be put into tag and repository level?
|
||||||
Labels []string `json:"labels"`
|
Labels []string `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResourceName returns the name of the resource
|
||||||
|
func (r *ResourceMetadata) GetResourceName() string {
|
||||||
|
name := ""
|
||||||
|
if r.Namespace != nil && len(r.Namespace.Name) > 0 {
|
||||||
|
name += r.Namespace.Name
|
||||||
|
}
|
||||||
|
if r.Repository != nil && len(r.Repository.Name) > 0 {
|
||||||
|
if len(name) > 0 {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
name += r.Repository.Name
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository info of the resource
|
||||||
|
type Repository struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
// Resource represents the general replicating content
|
// Resource represents the general replicating content
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Type ResourceType `json:"type"`
|
Type ResourceType `json:"type"`
|
||||||
|
50
src/replication/ng/model/resource_test.go
Normal file
50
src/replication/ng/model/resource_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetResourceName(t *testing.T) {
|
||||||
|
r := &ResourceMetadata{}
|
||||||
|
assert.Equal(t, "", r.GetResourceName())
|
||||||
|
|
||||||
|
r = &ResourceMetadata{
|
||||||
|
Namespace: &Namespace{
|
||||||
|
Name: "library",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "library", r.GetResourceName())
|
||||||
|
|
||||||
|
r = &ResourceMetadata{
|
||||||
|
Repository: &Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "hello-world", r.GetResourceName())
|
||||||
|
|
||||||
|
r = &ResourceMetadata{
|
||||||
|
Namespace: &Namespace{
|
||||||
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "library/hello-world", r.GetResourceName())
|
||||||
|
}
|
@ -105,7 +105,8 @@ func (c *controller) createFlow(executionID int64, policy *model.Policy, resourc
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeName,
|
Type: model.FilterTypeName,
|
||||||
Value: resource.Metadata.Name,
|
// TODO only filter the repo part?
|
||||||
|
Value: resource.Metadata.GetResourceName(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeTag,
|
Type: model.FilterTypeTag,
|
||||||
|
@ -132,7 +132,18 @@ func (f *fakedAdapter) Info() (*model.RegistryInfo, error) {
|
|||||||
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) CreateNamespace(*model.Namespace) error {
|
func (f *fakedAdapter) ConvertResourceMetadata(*model.ResourceMetadata, *model.Namespace) (*model.ResourceMetadata, error) {
|
||||||
|
return &model.ResourceMetadata{
|
||||||
|
Namespace: &model.Namespace{
|
||||||
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
|
Vtags: []string{"latest"},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (f *fakedAdapter) PrepareForPush(*model.Resource) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
||||||
@ -155,8 +166,12 @@ func (f *fakedAdapter) FetchImages(namespace []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
Override: false,
|
Override: false,
|
||||||
@ -190,8 +205,12 @@ func (f *fakedAdapter) FetchCharts(namespaces []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/harbor",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "harbor",
|
||||||
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -232,7 +251,12 @@ func TestStartReplication(t *testing.T) {
|
|||||||
resource := &model.Resource{
|
resource := &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"1.0", "2.0"},
|
Vtags: []string{"1.0", "2.0"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,14 +57,13 @@ func (c *copyFlow) Run(interface{}) (int, error) {
|
|||||||
log.Infof("no resources need to be replicated for the execution %d, skip", c.executionID)
|
log.Infof("no resources need to be replicated for the execution %d, skip", c.executionID)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
dstNamespaces, err := assembleDestinationNamespaces(srcAdapter, srcResources, c.policy.DestNamespace)
|
dstResources, err := assembleDestinationResources(dstAdapter, srcResources, c.policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if err = createNamespaces(dstAdapter, dstNamespaces); err != nil {
|
if err = prepareForPush(dstAdapter, dstResources); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
dstResources := assembleDestinationResources(srcResources, c.policy.DestRegistry, c.policy.DestNamespace, c.policy.Override)
|
|
||||||
items, err := preprocess(c.scheduler, srcResources, dstResources)
|
items, err := preprocess(c.scheduler, srcResources, dstResources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -43,6 +43,10 @@ func NewDeletionFlow(executionMgr execution.Manager, scheduler scheduler.Schedul
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *deletionFlow) Run(interface{}) (int, error) {
|
func (d *deletionFlow) Run(interface{}) (int, error) {
|
||||||
|
_, dstAdapter, err := initialize(d.policy)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
// filling the registry information
|
// filling the registry information
|
||||||
for _, resource := range d.resources {
|
for _, resource := range d.resources {
|
||||||
resource.Registry = d.policy.SrcRegistry
|
resource.Registry = d.policy.SrcRegistry
|
||||||
@ -56,7 +60,10 @@ func (d *deletionFlow) Run(interface{}) (int, error) {
|
|||||||
log.Infof("no resources need to be replicated for the execution %d, skip", d.executionID)
|
log.Infof("no resources need to be replicated for the execution %d, skip", d.executionID)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
dstResources := assembleDestinationResources(srcResources, d.policy.DestRegistry, d.policy.DestNamespace, d.policy.Override)
|
dstResources, err := assembleDestinationResources(dstAdapter, srcResources, d.policy)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
items, err := preprocess(d.scheduler, srcResources, dstResources)
|
items, err := preprocess(d.scheduler, srcResources, dstResources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -37,8 +37,12 @@ func TestRunOfDeletionFlow(t *testing.T) {
|
|||||||
resources := []*model.Resource{
|
resources := []*model.Resource{
|
||||||
{
|
{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -134,7 +134,8 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
|
|||||||
match = false
|
match = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
m, err := util.Match(pattern, resource.Metadata.Name)
|
// TODO filter only the repository part?
|
||||||
|
m, err := util.Match(pattern, resource.Metadata.GetResourceName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -181,73 +182,46 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the namespaces that need to be created on the destination registry:
|
// assemble the destination resources by filling the metadata, registry and override properties
|
||||||
// step 1: get the detail information for each of the source namespaces
|
func assembleDestinationResources(adapter adp.Adapter, resources []*model.Resource,
|
||||||
// step 2: if the destination namespace isn't specified in the policy, then the
|
policy *model.Policy) ([]*model.Resource, error) {
|
||||||
// same namespaces with the source will be returned. If it is specified, then
|
|
||||||
// returns the specified one with the merged metadatas of all source namespaces
|
|
||||||
func assembleDestinationNamespaces(srcAdapter adp.Adapter, srcResources []*model.Resource, dstNamespace string) ([]*model.Namespace, error) {
|
|
||||||
namespaces := []*model.Namespace{}
|
|
||||||
for _, srcResource := range srcResources {
|
|
||||||
namespace, err := srcAdapter.GetNamespace(srcResource.Metadata.Namespace)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
namespaces = append(namespaces, namespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dstNamespace) != 0 {
|
|
||||||
namespaces = []*model.Namespace{
|
|
||||||
{
|
|
||||||
Name: dstNamespace,
|
|
||||||
// TODO merge the metadata
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("assemble the destination namespaces completed")
|
|
||||||
return namespaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the namespaces on the destination registry
|
|
||||||
func createNamespaces(adapter adp.Adapter, namespaces []*model.Namespace) error {
|
|
||||||
for _, namespace := range namespaces {
|
|
||||||
if err := adapter.CreateNamespace(namespace); err != nil {
|
|
||||||
return fmt.Errorf("failed to create the namespace %s on the destination registry: %v", namespace.Name, err)
|
|
||||||
}
|
|
||||||
log.Debugf("namespace %s created on the destination registry", namespace.Name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assemble the destination resources by filling the registry, namespace and override properties
|
|
||||||
func assembleDestinationResources(resources []*model.Resource,
|
|
||||||
registry *model.Registry, namespace string, override bool) []*model.Resource {
|
|
||||||
result := []*model.Resource{}
|
result := []*model.Resource{}
|
||||||
|
var namespace *model.Namespace
|
||||||
|
if len(policy.DestNamespace) > 0 {
|
||||||
|
namespace = &model.Namespace{
|
||||||
|
Name: policy.DestNamespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
|
metadata, err := adapter.ConvertResourceMetadata(resource.Metadata, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert the resource metadata of %s: %v", resource.Metadata.GetResourceName(), err)
|
||||||
|
}
|
||||||
res := &model.Resource{
|
res := &model.Resource{
|
||||||
Type: resource.Type,
|
Type: resource.Type,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: metadata,
|
||||||
Name: resource.Metadata.Name,
|
Registry: policy.DestRegistry,
|
||||||
Namespace: resource.Metadata.Namespace,
|
|
||||||
Vtags: resource.Metadata.Vtags,
|
|
||||||
},
|
|
||||||
Registry: registry,
|
|
||||||
ExtendedInfo: resource.ExtendedInfo,
|
ExtendedInfo: resource.ExtendedInfo,
|
||||||
Deleted: resource.Deleted,
|
Deleted: resource.Deleted,
|
||||||
Override: override,
|
Override: policy.Override,
|
||||||
}
|
|
||||||
// if the destination namespace is specified, use the specified one
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
res.Metadata.Name = strings.Replace(resource.Metadata.Name,
|
|
||||||
resource.Metadata.Namespace, namespace, 1)
|
|
||||||
res.Metadata.Namespace = namespace
|
|
||||||
}
|
}
|
||||||
result = append(result, res)
|
result = append(result, res)
|
||||||
}
|
}
|
||||||
log.Debug("assemble the destination resources completed")
|
log.Debug("assemble the destination resources completed")
|
||||||
return result
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the prepare work for pushing/uploading the resources: create the namespace or repository
|
||||||
|
func prepareForPush(adapter adp.Adapter, resources []*model.Resource) error {
|
||||||
|
// TODO need to consider how to handle that both contains public/private namespace
|
||||||
|
for _, resource := range resources {
|
||||||
|
name := resource.Metadata.GetResourceName()
|
||||||
|
if err := adapter.PrepareForPush(resource); err != nil {
|
||||||
|
return fmt.Errorf("failed to do the prepare work for pushing/uploading %s: %v", name, err)
|
||||||
|
}
|
||||||
|
log.Debugf("the prepare work for pushing/uploading %s completed", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// preprocess
|
// preprocess
|
||||||
@ -341,7 +315,7 @@ func getResourceName(res *model.Resource) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(meta.Vtags) == 0 {
|
if len(meta.Vtags) == 0 {
|
||||||
return meta.Name
|
return meta.GetResourceName()
|
||||||
}
|
}
|
||||||
return meta.Name + ":[" + strings.Join(meta.Vtags, ",") + "]"
|
return meta.GetResourceName() + ":[" + strings.Join(meta.Vtags, ",") + "]"
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,14 @@ func (f *fakedAdapter) Info() (*model.RegistryInfo, error) {
|
|||||||
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) CreateNamespace(*model.Namespace) error {
|
func (f *fakedAdapter) ConvertResourceMetadata(metadata *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
||||||
|
if namespace != nil {
|
||||||
|
metadata.Namespace = namespace
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakedAdapter) PrepareForPush(*model.Resource) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
||||||
@ -72,8 +79,12 @@ func (f *fakedAdapter) FetchImages(namespace []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
Override: false,
|
Override: false,
|
||||||
@ -107,8 +118,12 @@ func (f *fakedAdapter) FetchCharts(namespaces []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/harbor",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "harbor",
|
||||||
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -226,8 +241,12 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
Labels: nil,
|
Labels: nil,
|
||||||
@ -238,8 +257,12 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/harbor",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "harbor",
|
||||||
|
},
|
||||||
Vtags: []string{"0.2.0", "0.3.0"},
|
Vtags: []string{"0.2.0", "0.3.0"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
Labels: nil,
|
Labels: nil,
|
||||||
@ -250,8 +273,12 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/mysql",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "mysql",
|
||||||
|
},
|
||||||
Vtags: []string{"1.0"},
|
Vtags: []string{"1.0"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
Labels: nil,
|
Labels: nil,
|
||||||
@ -281,67 +308,40 @@ func TestFilterResources(t *testing.T) {
|
|||||||
res, err := filterResources(resources, filters)
|
res, err := filterResources(resources, filters)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(res))
|
assert.Equal(t, 1, len(res))
|
||||||
assert.Equal(t, "library/harbor", res[0].Metadata.Name)
|
assert.Equal(t, "library", res[0].Metadata.Namespace.Name)
|
||||||
|
assert.Equal(t, "harbor", res[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, 1, len(res[0].Metadata.Vtags))
|
assert.Equal(t, 1, len(res[0].Metadata.Vtags))
|
||||||
assert.Equal(t, "0.2.0", res[0].Metadata.Vtags[0])
|
assert.Equal(t, "0.2.0", res[0].Metadata.Vtags[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAssembleDestinationNamespaces(t *testing.T) {
|
|
||||||
adapter := &fakedAdapter{}
|
|
||||||
resources := []*model.Resource{
|
|
||||||
{
|
|
||||||
Metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: "library",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
namespace := ""
|
|
||||||
ns, err := assembleDestinationNamespaces(adapter, resources, namespace)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, 1, len(ns))
|
|
||||||
assert.Equal(t, "library", ns[0].Name)
|
|
||||||
assert.Equal(t, true, ns[0].Metadata["public"].(bool))
|
|
||||||
|
|
||||||
namespace = "test"
|
|
||||||
ns, err = assembleDestinationNamespaces(adapter, resources, namespace)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, 1, len(ns))
|
|
||||||
assert.Equal(t, "test", ns[0].Name)
|
|
||||||
// TODO add test for merged metadata
|
|
||||||
// assert.Equal(t, true, ns[0].Metadata["public"].(bool))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateNamespaces(t *testing.T) {
|
|
||||||
adapter := &fakedAdapter{}
|
|
||||||
namespaces := []*model.Namespace{
|
|
||||||
{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := createNamespaces(adapter, namespaces)
|
|
||||||
require.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAssembleDestinationResources(t *testing.T) {
|
func TestAssembleDestinationResources(t *testing.T) {
|
||||||
|
adapter := &fakedAdapter{}
|
||||||
resources := []*model.Resource{
|
resources := []*model.Resource{
|
||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
Override: false,
|
Override: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
registry := &model.Registry{}
|
policy := &model.Policy{
|
||||||
namespace := "test"
|
DestRegistry: &model.Registry{},
|
||||||
override := true
|
DestNamespace: "test",
|
||||||
res := assembleDestinationResources(resources, registry, namespace, override)
|
Override: true,
|
||||||
|
}
|
||||||
|
res, err := assembleDestinationResources(adapter, resources, policy)
|
||||||
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(res))
|
assert.Equal(t, 1, len(res))
|
||||||
assert.Equal(t, model.ResourceTypeChart, res[0].Type)
|
assert.Equal(t, model.ResourceTypeChart, res[0].Type)
|
||||||
assert.Equal(t, "test/hello-world", res[0].Metadata.Name)
|
assert.Equal(t, "hello-world", res[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, namespace, res[0].Metadata.Namespace)
|
assert.Equal(t, "test", res[0].Metadata.Namespace.Name)
|
||||||
assert.Equal(t, 1, len(res[0].Metadata.Vtags))
|
assert.Equal(t, 1, len(res[0].Metadata.Vtags))
|
||||||
assert.Equal(t, "latest", res[0].Metadata.Vtags[0])
|
assert.Equal(t, "latest", res[0].Metadata.Vtags[0])
|
||||||
}
|
}
|
||||||
@ -352,8 +352,12 @@ func TestPreprocess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
Override: false,
|
Override: false,
|
||||||
@ -363,8 +367,12 @@ func TestPreprocess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "test/hello-world",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "test",
|
Name: "test",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "hello-world",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
Override: false,
|
Override: false,
|
||||||
|
@ -50,7 +50,9 @@ func TestStop(t *testing.T) {
|
|||||||
func generateData() ([]*ScheduleItem, error) {
|
func generateData() ([]*ScheduleItem, error) {
|
||||||
srcResource := &model.Resource{
|
srcResource := &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: "namespace1",
|
Namespace: &model.Namespace{
|
||||||
|
Name: "namespace1",
|
||||||
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
Labels: []string{"latest"},
|
Labels: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -60,7 +62,9 @@ func generateData() ([]*ScheduleItem, error) {
|
|||||||
}
|
}
|
||||||
destResource := &model.Resource{
|
destResource := &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: "namespace2",
|
Namespace: &model.Namespace{
|
||||||
|
Name: "namespace2",
|
||||||
|
},
|
||||||
Vtags: []string{"v1", "v2"},
|
Vtags: []string{"v1", "v2"},
|
||||||
Labels: []string{"latest"},
|
Labels: []string{"latest"},
|
||||||
},
|
},
|
||||||
|
@ -17,10 +17,9 @@ package chart
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/jobservice/errs"
|
"github.com/goharbor/harbor/src/jobservice/errs"
|
||||||
|
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
trans "github.com/goharbor/harbor/src/replication/ng/transfer"
|
trans "github.com/goharbor/harbor/src/replication/ng/transfer"
|
||||||
)
|
)
|
||||||
@ -63,17 +62,17 @@ func (t *transfer) Transfer(src *model.Resource, dst *model.Resource) error {
|
|||||||
// delete the chart on destination registry
|
// delete the chart on destination registry
|
||||||
if dst.Deleted {
|
if dst.Deleted {
|
||||||
return t.delete(&chart{
|
return t.delete(&chart{
|
||||||
name: dst.Metadata.Name,
|
name: dst.Metadata.GetResourceName(),
|
||||||
version: dst.Metadata.Vtags[0],
|
version: dst.Metadata.Vtags[0],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
srcChart := &chart{
|
srcChart := &chart{
|
||||||
name: src.Metadata.Name,
|
name: src.Metadata.GetResourceName(),
|
||||||
version: src.Metadata.Vtags[0],
|
version: src.Metadata.Vtags[0],
|
||||||
}
|
}
|
||||||
dstChart := &chart{
|
dstChart := &chart{
|
||||||
name: dst.Metadata.Name,
|
name: dst.Metadata.GetResourceName(),
|
||||||
version: dst.Metadata.Vtags[0],
|
version: dst.Metadata.Vtags[0],
|
||||||
}
|
}
|
||||||
// copy the chart from source registry to the destination
|
// copy the chart from source registry to the destination
|
||||||
|
@ -34,8 +34,12 @@ func (f *fakeRegistry) FetchCharts(namespaces []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Name: "library/harbor",
|
Namespace: &model.Namespace{
|
||||||
Namespace: "library",
|
Name: "library",
|
||||||
|
},
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "harbor",
|
||||||
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -18,13 +18,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/jobservice/errs"
|
"github.com/goharbor/harbor/src/jobservice/errs"
|
||||||
|
"github.com/goharbor/harbor/src/replication/ng/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
trans "github.com/goharbor/harbor/src/replication/ng/transfer"
|
trans "github.com/goharbor/harbor/src/replication/ng/transfer"
|
||||||
)
|
)
|
||||||
@ -67,17 +66,17 @@ func (t *transfer) Transfer(src *model.Resource, dst *model.Resource) error {
|
|||||||
// delete the repository on destination registry
|
// delete the repository on destination registry
|
||||||
if dst.Deleted {
|
if dst.Deleted {
|
||||||
return t.delete(&repository{
|
return t.delete(&repository{
|
||||||
repository: dst.Metadata.Name,
|
repository: dst.Metadata.GetResourceName(),
|
||||||
tags: dst.Metadata.Vtags,
|
tags: dst.Metadata.Vtags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRepo := &repository{
|
srcRepo := &repository{
|
||||||
repository: src.Metadata.Name,
|
repository: src.Metadata.GetResourceName(),
|
||||||
tags: src.Metadata.Vtags,
|
tags: src.Metadata.Vtags,
|
||||||
}
|
}
|
||||||
dstRepo := &repository{
|
dstRepo := &repository{
|
||||||
repository: dst.Metadata.Name,
|
repository: dst.Metadata.GetResourceName(),
|
||||||
tags: dst.Metadata.Vtags,
|
tags: dst.Metadata.Vtags,
|
||||||
}
|
}
|
||||||
// copy the repository from source registry to the destination
|
// copy the repository from source registry to the destination
|
||||||
|
Loading…
Reference in New Issue
Block a user