mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-20 09:15:19 +01:00
Remove the namespace concept in replication
Update the replication logic to remove the "namespace" Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
d6f08dead6
commit
2f1d2257d5
@ -12,8 +12,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
helm_repo "k8s.io/helm/pkg/repo"
|
helm_repo "k8s.io/helm/pkg/repo"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListCharts gets the chart list under the namespace
|
// ListCharts gets the chart list under the namespace
|
||||||
@ -84,11 +85,8 @@ func (c *Controller) DeleteChartVersion(namespace, chartName, version string) er
|
|||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Deleted: true,
|
Deleted: true,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: namespace,
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: chartName,
|
Name: fmt.Sprintf("%s/%s", namespace, chartName),
|
||||||
},
|
},
|
||||||
Vtags: []string{version},
|
Vtags: []string{version},
|
||||||
},
|
},
|
||||||
|
@ -4,11 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/common"
|
|
||||||
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
"github.com/goharbor/harbor/src/replication"
|
|
||||||
rep_event "github.com/goharbor/harbor/src/replication/event"
|
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -17,6 +12,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common"
|
||||||
|
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/replication"
|
||||||
|
rep_event "github.com/goharbor/harbor/src/replication/event"
|
||||||
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -100,11 +101,8 @@ func modifyResponse(res *http.Response) error {
|
|||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: chartUploadSplitted[0],
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: chartUploadSplitted[1],
|
Name: fmt.Sprintf("%s/%s", chartUploadSplitted[0], chartUploadSplitted[1]),
|
||||||
},
|
},
|
||||||
Vtags: []string{chartUploadSplitted[2]},
|
Vtags: []string{chartUploadSplitted[2]},
|
||||||
},
|
},
|
||||||
|
@ -389,57 +389,60 @@ func (t *RegistryAPI) GetInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetNamespace get the namespace of a registry
|
// GetNamespace get the namespace of a registry
|
||||||
|
// TODO remove
|
||||||
func (t *RegistryAPI) GetNamespace() {
|
func (t *RegistryAPI) GetNamespace() {
|
||||||
var registry *model.Registry
|
/*
|
||||||
var err error
|
var registry *model.Registry
|
||||||
|
var err error
|
||||||
|
|
||||||
id, err := t.GetInt64FromPath(":id")
|
id, err := t.GetInt64FromPath(":id")
|
||||||
if err != nil || id < 0 {
|
if err != nil || id < 0 {
|
||||||
t.HandleBadRequest(fmt.Sprintf("invalid registry ID %s", t.GetString(":id")))
|
t.HandleBadRequest(fmt.Sprintf("invalid registry ID %s", t.GetString(":id")))
|
||||||
return
|
|
||||||
}
|
|
||||||
if id > 0 {
|
|
||||||
registry, err = t.manager.Get(id)
|
|
||||||
if err != nil {
|
|
||||||
t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if id == 0 {
|
if id > 0 {
|
||||||
registry = event.GetLocalRegistry()
|
registry, err = t.manager.Get(id)
|
||||||
}
|
if err != nil {
|
||||||
|
t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if id == 0 {
|
||||||
|
registry = event.GetLocalRegistry()
|
||||||
|
}
|
||||||
|
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
|
t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !adapter.HasFactory(registry.Type) {
|
if !adapter.HasFactory(registry.Type) {
|
||||||
t.HandleInternalServerError(fmt.Sprintf("no adapter factory found for %s", registry.Type))
|
t.HandleInternalServerError(fmt.Sprintf("no adapter factory found for %s", registry.Type))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
regFactory, err := adapter.GetFactory(registry.Type)
|
regFactory, err := adapter.GetFactory(registry.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter factory %s", registry.Type))
|
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter factory %s", registry.Type))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
regAdapter, err := regFactory(registry)
|
regAdapter, err := regFactory(registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter %s", registry.Type))
|
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter %s", registry.Type))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := &model.NamespaceQuery{
|
query := &model.NamespaceQuery{
|
||||||
Name: t.GetString("name"),
|
Name: t.GetString("name"),
|
||||||
}
|
}
|
||||||
npResults, err := regAdapter.ListNamespaces(query)
|
npResults, err := regAdapter.ListNamespaces(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.HandleInternalServerError(fmt.Sprintf("fail to list namespaces %s %v", registry.Type, err))
|
t.HandleInternalServerError(fmt.Sprintf("fail to list namespaces %s %v", registry.Type, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Data["json"] = npResults
|
t.Data["json"] = npResults
|
||||||
t.ServeJSON()
|
t.ServeJSON()
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
|
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
|
||||||
@ -450,7 +453,6 @@ func process(info *model.RegistryInfo) *model.RegistryInfo {
|
|||||||
in := &model.RegistryInfo{
|
in := &model.RegistryInfo{
|
||||||
Type: info.Type,
|
Type: info.Type,
|
||||||
Description: info.Description,
|
Description: info.Description,
|
||||||
SupportNamespace: info.SupportNamespace,
|
|
||||||
SupportedTriggers: info.SupportedTriggers,
|
SupportedTriggers: info.SupportedTriggers,
|
||||||
}
|
}
|
||||||
filters := []*model.FilterStyle{}
|
filters := []*model.FilterStyle{}
|
||||||
|
@ -89,7 +89,6 @@ func (f *fakedPolicyManager) Get(id int64) (*model.Policy, error) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
if id == 2 {
|
if id == 2 {
|
||||||
@ -99,7 +98,6 @@ func (f *fakedPolicyManager) Get(id int64) (*model.Policy, error) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -128,7 +128,6 @@ func TestReplicationPolicyAPICreate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
@ -140,8 +139,7 @@ func TestReplicationPolicyAPICreate(t *testing.T) {
|
|||||||
url: "/api/replication/policies",
|
url: "/api/replication/policies",
|
||||||
credential: sysAdmin,
|
credential: sysAdmin,
|
||||||
bodyJSON: &model.Policy{
|
bodyJSON: &model.Policy{
|
||||||
Name: "policy01",
|
Name: "policy01",
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
@ -157,7 +155,6 @@ func TestReplicationPolicyAPICreate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusConflict,
|
code: http.StatusConflict,
|
||||||
@ -173,7 +170,6 @@ func TestReplicationPolicyAPICreate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
@ -189,7 +185,6 @@ func TestReplicationPolicyAPICreate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusCreated,
|
code: http.StatusCreated,
|
||||||
@ -296,7 +291,6 @@ func TestReplicationPolicyAPIUpdate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
@ -312,7 +306,6 @@ func TestReplicationPolicyAPIUpdate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusConflict,
|
code: http.StatusConflict,
|
||||||
@ -328,7 +321,6 @@ func TestReplicationPolicyAPIUpdate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
@ -344,7 +336,6 @@ func TestReplicationPolicyAPIUpdate(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: http.StatusOK,
|
code: http.StatusOK,
|
||||||
|
@ -333,12 +333,8 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: projectName,
|
|
||||||
// TODO filling the metadata
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: strings.TrimPrefix(repoName, projectName+"/"),
|
Name: repoName,
|
||||||
},
|
},
|
||||||
Vtags: []string{tag},
|
Vtags: []string{tag},
|
||||||
},
|
},
|
||||||
|
@ -119,12 +119,9 @@ func (n *NotificationHandler) Post() {
|
|||||||
Resource: &model.Resource{
|
Resource: &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: project,
|
|
||||||
// TODO filling the metadata
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: strings.TrimPrefix(repository, project+"/"),
|
Name: repository,
|
||||||
|
// TODO filling the metadata
|
||||||
},
|
},
|
||||||
Vtags: []string{tag},
|
Vtags: []string{tag},
|
||||||
},
|
},
|
||||||
|
@ -30,12 +30,6 @@ type Factory func(*model.Registry) (Adapter, error)
|
|||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
// Info return the information of this adapter
|
// Info return the information of this adapter
|
||||||
Info() (*model.RegistryInfo, error)
|
Info() (*model.RegistryInfo, error)
|
||||||
// Lists the available namespaces under the specified registry with the
|
|
||||||
// provided credential/token
|
|
||||||
ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error)
|
|
||||||
// ConvertResourceMetadata converts the namespace and repository part of the resource metadata
|
|
||||||
// to the one that the adapter can handle
|
|
||||||
ConvertResourceMetadata(*model.ResourceMetadata, *model.Namespace) (*model.ResourceMetadata, error)
|
|
||||||
// PrepareForPush does the prepare work that needed for pushing/uploading the resource
|
// PrepareForPush does the prepare work that needed for pushing/uploading the resource
|
||||||
// eg: create the namespace or repository
|
// eg: create the namespace or repository
|
||||||
PrepareForPush(*model.Resource) error
|
PrepareForPush(*model.Resource) error
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
// ChartRegistry defines the capabilities that a chart registry should have
|
// ChartRegistry defines the capabilities that a chart registry should have
|
||||||
type ChartRegistry interface {
|
type ChartRegistry interface {
|
||||||
FetchCharts(namespaces []string, filters []*model.Filter) ([]*model.Resource, error)
|
FetchCharts(filters []*model.Filter) ([]*model.Resource, error)
|
||||||
ChartExist(name, version string) (bool, error)
|
ChartExist(name, version string) (bool, error)
|
||||||
DownloadChart(name, version string) (io.ReadCloser, error)
|
DownloadChart(name, version string) (io.ReadCloser, error)
|
||||||
UploadChart(name, version string, chart io.Reader) error
|
UploadChart(name, version string, chart io.Reader) error
|
||||||
|
@ -56,8 +56,7 @@ var _ adp.Adapter = (*adapter)(nil)
|
|||||||
// Info returns information of the registry
|
// Info returns information of the registry
|
||||||
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
||||||
return &model.RegistryInfo{
|
return &model.RegistryInfo{
|
||||||
Type: model.RegistryTypeDockerHub,
|
Type: model.RegistryTypeDockerHub,
|
||||||
SupportNamespace: true,
|
|
||||||
SupportedResourceTypes: []model.ResourceType{
|
SupportedResourceTypes: []model.ResourceType{
|
||||||
model.ResourceTypeRepository,
|
model.ResourceTypeRepository,
|
||||||
},
|
},
|
||||||
@ -78,12 +77,6 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertResourceMetadata converts the namespace and repository part of the resource metadata
|
|
||||||
// to the one that the adapter can handle
|
|
||||||
func (a *adapter) ConvertResourceMetadata(meta *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
|
||||||
return meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareForPush does the prepare work that needed for pushing/uploading the resource
|
// PrepareForPush does the prepare work that needed for pushing/uploading the resource
|
||||||
// eg: create the namespace or repository
|
// eg: create the namespace or repository
|
||||||
func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
||||||
@ -93,18 +86,25 @@ func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
|||||||
if resource.Metadata == nil {
|
if resource.Metadata == nil {
|
||||||
return errors.New("the metadata of resource cannot be null")
|
return errors.New("the metadata of resource cannot be null")
|
||||||
}
|
}
|
||||||
if resource.Metadata.Namespace == nil {
|
if resource.Metadata.Repository == nil {
|
||||||
return errors.New("the namespace of resource cannot be null")
|
return errors.New("the namespace of resource cannot be null")
|
||||||
}
|
}
|
||||||
if len(resource.Metadata.Namespace.Name) == 0 {
|
if len(resource.Metadata.Repository.Name) == 0 {
|
||||||
return errors.New("the name of the namespace cannot be null")
|
return errors.New("the name of the namespace cannot be null")
|
||||||
}
|
}
|
||||||
|
namespace, _ := util.ParseRepository(resource.Metadata.Repository.Name)
|
||||||
|
// Docker Hub doesn't support the repository contains no "/"
|
||||||
|
// just skip here and the following task will fail
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
log.Debug("the namespace is empty, skip")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := a.CreateNamespace(&model.Namespace{
|
err := a.CreateNamespace(&model.Namespace{
|
||||||
Name: resource.Metadata.Namespace.Name,
|
Name: namespace,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create namespace '%s' in DockerHub error: %v", resource.Metadata.Namespace.Name, err)
|
return fmt.Errorf("create namespace '%s' in DockerHub error: %v", namespace, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -218,7 +218,7 @@ func (a *adapter) getNamespace(namespace string) (*model.Namespace, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchImages fetches images
|
// FetchImages fetches images
|
||||||
func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
var repos []Repo
|
var repos []Repo
|
||||||
nameFilter, err := a.getStringFilterValue(model.FilterTypeName, filters)
|
nameFilter, err := a.getStringFilterValue(model.FilterTypeName, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,11 +229,15 @@ func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespaces, err := a.ListNamespaces(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, ns := range namespaces {
|
for _, ns := range namespaces {
|
||||||
page := 1
|
page := 1
|
||||||
pageSize := 100
|
pageSize := 100
|
||||||
for {
|
for {
|
||||||
pageRepos, err := a.getRepos(ns, "", page, pageSize)
|
pageRepos, err := a.getRepos(ns.Name, "", page, pageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get repos for namespace '%s' from DockerHub error: %v", ns, err)
|
return nil, fmt.Errorf("get repos for namespace '%s' from DockerHub error: %v", ns, err)
|
||||||
}
|
}
|
||||||
@ -300,11 +304,8 @@ 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: &model.Namespace{
|
|
||||||
Name: repo.Namespace,
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: repo.Name,
|
Name: fmt.Sprintf("%s/%s", repo.Namespace, repo.Name),
|
||||||
},
|
},
|
||||||
Vtags: tags,
|
Vtags: tags,
|
||||||
},
|
},
|
||||||
|
@ -39,7 +39,8 @@ func TestListNamespaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
adapter := getAdapter(t)
|
ad := getAdapter(t)
|
||||||
|
adapter := ad.(*adapter)
|
||||||
|
|
||||||
namespaces, err := adapter.ListNamespaces(nil)
|
namespaces, err := adapter.ListNamespaces(nil)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"errors"
|
"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"
|
||||||
@ -91,8 +90,7 @@ func newAdapter(registry *model.Registry) (*adapter, error) {
|
|||||||
|
|
||||||
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,
|
||||||
},
|
},
|
||||||
@ -130,65 +128,6 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *adapter) ListNamespaces(query *model.NamespaceQuery) ([]*model.Namespace, error) {
|
|
||||||
var namespaces []*model.Namespace
|
|
||||||
name := ""
|
|
||||||
if query != nil {
|
|
||||||
name = query.Name
|
|
||||||
}
|
|
||||||
projects, err := a.getProjects(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, project := range projects {
|
|
||||||
namespaces = append(namespaces, &model.Namespace{
|
|
||||||
Name: project.Name,
|
|
||||||
Metadata: project.Metadata,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return namespaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
||||||
if resource == nil {
|
if resource == nil {
|
||||||
return errors.New("the resource cannot be null")
|
return errors.New("the resource cannot be null")
|
||||||
@ -196,18 +135,25 @@ func (a *adapter) PrepareForPush(resource *model.Resource) error {
|
|||||||
if resource.Metadata == nil {
|
if resource.Metadata == nil {
|
||||||
return errors.New("the metadata of resource cannot be null")
|
return errors.New("the metadata of resource cannot be null")
|
||||||
}
|
}
|
||||||
if resource.Metadata.Namespace == nil {
|
if resource.Metadata.Repository == nil {
|
||||||
return errors.New("the namespace of resource cannot be null")
|
return errors.New("the repository of resource cannot be null")
|
||||||
}
|
}
|
||||||
if len(resource.Metadata.Namespace.Name) == 0 {
|
if len(resource.Metadata.Repository.Name) == 0 {
|
||||||
return errors.New("the name of the namespace cannot be null")
|
return errors.New("the name of the repository cannot be null")
|
||||||
|
}
|
||||||
|
projectName, _ := util.ParseRepository(resource.Metadata.Repository.Name)
|
||||||
|
// harbor doesn't support the repository contains no "/"
|
||||||
|
// just skip here and the following task will fail
|
||||||
|
if len(projectName) == 0 {
|
||||||
|
log.Debug("the project name is empty, skip")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
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: resource.Metadata.Namespace.Name,
|
Name: projectName,
|
||||||
Metadata: resource.Metadata.Namespace.Metadata,
|
// TODO handle the public
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@ -234,7 +180,7 @@ func (a *adapter) PrepareForPush(resource *model.Resource) 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", resource.Metadata.Namespace.Name)
|
log.Debugf("got 409 when trying to create project %s", projectName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -73,38 +73,6 @@ func TestInfo(t *testing.T) {
|
|||||||
server.Close()
|
server.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListNamespaces(t *testing.T) {
|
|
||||||
// project exists
|
|
||||||
server := test.NewServer(&test.RequestHandlerMapping{
|
|
||||||
Method: http.MethodGet,
|
|
||||||
Pattern: "/api/projects",
|
|
||||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := `[{
|
|
||||||
"name": "library",
|
|
||||||
"metadata": {"public":true}
|
|
||||||
},{
|
|
||||||
"name": "library1",
|
|
||||||
"metadata": {"public":true}
|
|
||||||
}]`
|
|
||||||
w.Write([]byte(data))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer server.Close()
|
|
||||||
registry := &model.Registry{
|
|
||||||
URL: server.URL,
|
|
||||||
}
|
|
||||||
adapter, err := newAdapter(registry)
|
|
||||||
require.Nil(t, err)
|
|
||||||
npQuery := &model.NamespaceQuery{
|
|
||||||
Name: "lib",
|
|
||||||
}
|
|
||||||
namespace, err := adapter.ListNamespaces(npQuery)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, 2, len(namespace))
|
|
||||||
assert.Equal(t, "library", namespace[0].Name)
|
|
||||||
assert.True(t, namespace[0].Metadata["public"].(bool))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrepareForPush(t *testing.T) {
|
func TestPrepareForPush(t *testing.T) {
|
||||||
server := test.NewServer(&test.RequestHandlerMapping{
|
server := test.NewServer(&test.RequestHandlerMapping{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@ -124,30 +92,23 @@ func TestPrepareForPush(t *testing.T) {
|
|||||||
// nil metadata
|
// nil metadata
|
||||||
err = adapter.PrepareForPush(&model.Resource{})
|
err = adapter.PrepareForPush(&model.Resource{})
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
// nil namespace
|
// nil repository
|
||||||
err = adapter.PrepareForPush(&model.Resource{
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{},
|
Metadata: &model.ResourceMetadata{},
|
||||||
})
|
})
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
// nil namespace name
|
// nil repository name
|
||||||
err = adapter.PrepareForPush(&model.Resource{
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{},
|
Repository: &model.Repository{},
|
||||||
},
|
|
||||||
})
|
|
||||||
require.NotNil(t, err)
|
|
||||||
// nil namespace name
|
|
||||||
err = adapter.PrepareForPush(&model.Resource{
|
|
||||||
Metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: &model.Namespace{},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
// project doesn't exist
|
// project doesn't exist
|
||||||
err = adapter.PrepareForPush(&model.Resource{
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
Repository: &model.Repository{
|
||||||
Name: "library",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -170,8 +131,8 @@ func TestPrepareForPush(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
err = adapter.PrepareForPush(&model.Resource{
|
err = adapter.PrepareForPush(&model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
Repository: &model.Repository{
|
||||||
Name: "library",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -24,14 +24,14 @@ import (
|
|||||||
"strings"
|
"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/utils"
|
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO review the logic in this file
|
// TODO review the logic in this file
|
||||||
|
|
||||||
type chart struct {
|
type chart struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Project string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *chart) Match(filters []*model.Filter) (bool, error) {
|
func (c *chart) Match(filters []*model.Filter) (bool, error) {
|
||||||
@ -41,10 +41,8 @@ func (c *chart) Match(filters []*model.Filter) (bool, error) {
|
|||||||
supportedFilters = append(supportedFilters, filter)
|
supportedFilters = append(supportedFilters, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// trim the project part
|
|
||||||
_, name := utils.ParseRepository(c.Name)
|
|
||||||
item := &FilterItem{
|
item := &FilterItem{
|
||||||
Value: name,
|
Value: fmt.Sprintf("%s/%s", c.Project, c.Name),
|
||||||
}
|
}
|
||||||
return item.Match(supportedFilters)
|
return item.Match(supportedFilters)
|
||||||
}
|
}
|
||||||
@ -77,20 +75,28 @@ type chartVersionMetadata struct {
|
|||||||
URLs []string `json:"urls"`
|
URLs []string `json:"urls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *adapter) FetchCharts(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (a *adapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
|
// TODO optimize the performance
|
||||||
|
projects, err := a.getProjects("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
resources := []*model.Resource{}
|
resources := []*model.Resource{}
|
||||||
for _, namespace := range namespaces {
|
for _, project := range projects {
|
||||||
url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.coreServiceURL, namespace)
|
url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.coreServiceURL, project.Name)
|
||||||
charts := []*chart{}
|
charts := []*chart{}
|
||||||
if err := a.client.Get(url, &charts); err != nil {
|
if err := a.client.Get(url, &charts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, chart := range charts {
|
||||||
|
chart.Project = project.Name
|
||||||
|
}
|
||||||
charts, err := filterCharts(charts, filters)
|
charts, err := filterCharts(charts, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, chart := range charts {
|
for _, chart := range charts {
|
||||||
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.coreServiceURL, namespace, chart.Name)
|
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.coreServiceURL, project.Name, chart.Name)
|
||||||
chartVersions := []*chartVersion{}
|
chartVersions := []*chartVersion{}
|
||||||
if err := a.client.Get(url, &chartVersions); err != nil {
|
if err := a.client.Get(url, &chartVersions); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -104,18 +110,14 @@ 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: &model.Namespace{
|
|
||||||
Name: namespace,
|
|
||||||
// TODO filling the metadata
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: chart.Name,
|
Name: fmt.Sprintf("%s/%s", project.Name, chart.Name),
|
||||||
|
// TODO handle the metadata
|
||||||
},
|
},
|
||||||
Vtags: []string{version.Version},
|
Vtags: []string{version.Version},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resources, nil
|
return resources, nil
|
||||||
|
@ -27,6 +27,17 @@ import (
|
|||||||
|
|
||||||
func TestFetchCharts(t *testing.T) {
|
func TestFetchCharts(t *testing.T) {
|
||||||
server := test.NewServer([]*test.RequestHandlerMapping{
|
server := test.NewServer([]*test.RequestHandlerMapping{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Pattern: "/api/projects",
|
||||||
|
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := `[{
|
||||||
|
"name": "library",
|
||||||
|
"metadata": {"public":true}
|
||||||
|
}]`
|
||||||
|
w.Write([]byte(data))
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Pattern: "/api/chartrepo/library/charts/harbor",
|
Pattern: "/api/chartrepo/library/charts/harbor",
|
||||||
@ -58,12 +69,30 @@ func TestFetchCharts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
adapter, err := newAdapter(registry)
|
adapter, err := newAdapter(registry)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
resources, err := adapter.FetchCharts([]string{"library"}, nil)
|
// nil filter
|
||||||
|
resources, err := adapter.FetchCharts(nil)
|
||||||
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, "harbor", resources[0].Metadata.Repository.Name)
|
assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, "library", resources[0].Metadata.Namespace.Name)
|
assert.Equal(t, 1, len(resources[0].Metadata.Vtags))
|
||||||
|
assert.Equal(t, "1.0", resources[0].Metadata.Vtags[0])
|
||||||
|
// not nil filter
|
||||||
|
filters := []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeTag,
|
||||||
|
Value: "1.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resources, err = adapter.FetchCharts(filters)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, len(resources))
|
||||||
|
assert.Equal(t, model.ResourceTypeChart, resources[0].Type)
|
||||||
|
assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.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,9 +16,7 @@ package harbor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,10 +31,8 @@ func (r *repository) Match(filters []*model.Filter) (bool, error) {
|
|||||||
supportedFilters = append(supportedFilters, filter)
|
supportedFilters = append(supportedFilters, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// trim the project part
|
|
||||||
_, name := utils.ParseRepository(r.Name)
|
|
||||||
item := &FilterItem{
|
item := &FilterItem{
|
||||||
Value: name,
|
Value: r.Name,
|
||||||
}
|
}
|
||||||
return item.Match(supportedFilters)
|
return item.Match(supportedFilters)
|
||||||
}
|
}
|
||||||
@ -58,22 +54,14 @@ func (t *tag) Match(filters []*model.Filter) (bool, error) {
|
|||||||
return item.Match(supportedFilters)
|
return item.Match(supportedFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
if len(namespaces) == 0 {
|
// TODO optimize the performance
|
||||||
nms, err := a.ListNamespaces(nil)
|
projects, err := a.getProjects("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
for _, nm := range nms {
|
|
||||||
namespaces = append(namespaces, nm.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resources := []*model.Resource{}
|
resources := []*model.Resource{}
|
||||||
for _, namespace := range namespaces {
|
for _, project := range projects {
|
||||||
project, err := a.getProject(namespace)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
repositories := []*repository{}
|
repositories := []*repository{}
|
||||||
url := fmt.Sprintf("%s/api/repositories?project_id=%d", a.coreServiceURL, project.ID)
|
url := fmt.Sprintf("%s/api/repositories?project_id=%d", a.coreServiceURL, project.ID)
|
||||||
if err = a.client.Get(url, &repositories); err != nil {
|
if err = a.client.Get(url, &repositories); err != nil {
|
||||||
@ -104,12 +92,9 @@ 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: &model.Namespace{
|
|
||||||
Name: namespace,
|
|
||||||
// TODO filling the metadata
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: strings.TrimPrefix(repository.Name, namespace+"/"),
|
Name: repository.Name,
|
||||||
|
// TODO handle the metadata
|
||||||
},
|
},
|
||||||
Vtags: vtags,
|
Vtags: vtags,
|
||||||
},
|
},
|
||||||
|
@ -66,26 +66,33 @@ func TestFetchImages(t *testing.T) {
|
|||||||
}
|
}
|
||||||
adapter, err := newAdapter(registry)
|
adapter, err := newAdapter(registry)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
// not nil namespaces
|
// nil filter
|
||||||
resources, err := adapter.FetchImages([]string{"library"}, nil)
|
resources, err := adapter.FetchImages(nil)
|
||||||
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, "hello-world", resources[0].Metadata.Repository.Name)
|
assert.Equal(t, "library/hello-world", resources[0].Metadata.Repository.Name)
|
||||||
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])
|
||||||
// nil namespaces
|
// not nil filter
|
||||||
resources, err = adapter.FetchImages(nil, nil)
|
filters := []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeTag,
|
||||||
|
Value: "1.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resources, err = adapter.FetchImages(filters)
|
||||||
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, "hello-world", resources[0].Metadata.Repository.Name)
|
assert.Equal(t, "library/hello-world", resources[0].Metadata.Repository.Name)
|
||||||
assert.Equal(t, "library", resources[0].Metadata.Namespace.Name)
|
assert.Equal(t, 1, 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])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteManifest(t *testing.T) {
|
func TestDeleteManifest(t *testing.T) {
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/replication/util"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/replication/adapter"
|
"github.com/goharbor/harbor/src/replication/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
@ -110,7 +112,6 @@ func (adapter Adapter) ListNamespaces(query *model.NamespaceQuery) ([]*model.Nam
|
|||||||
// ConvertResourceMetadata convert resource metadata for Huawei SWR
|
// ConvertResourceMetadata convert resource metadata for Huawei SWR
|
||||||
func (adapter Adapter) ConvertResourceMetadata(resourceMetadata *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
func (adapter Adapter) ConvertResourceMetadata(resourceMetadata *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
||||||
metadata := &model.ResourceMetadata{
|
metadata := &model.ResourceMetadata{
|
||||||
Namespace: namespace,
|
|
||||||
Repository: resourceMetadata.Repository,
|
Repository: resourceMetadata.Repository,
|
||||||
Vtags: resourceMetadata.Vtags,
|
Vtags: resourceMetadata.Vtags,
|
||||||
Labels: resourceMetadata.Labels,
|
Labels: resourceMetadata.Labels,
|
||||||
@ -121,12 +122,12 @@ func (adapter Adapter) ConvertResourceMetadata(resourceMetadata *model.ResourceM
|
|||||||
// PrepareForPush prepare for push to Huawei SWR
|
// PrepareForPush prepare for push to Huawei SWR
|
||||||
func (adapter Adapter) PrepareForPush(resource *model.Resource) error {
|
func (adapter Adapter) PrepareForPush(resource *model.Resource) error {
|
||||||
|
|
||||||
namespace := resource.Metadata.Namespace
|
namespace, _ := util.ParseRepository(resource.Metadata.Repository.Name)
|
||||||
ns, err := adapter.GetNamespace(namespace.Name)
|
ns, err := adapter.GetNamespace(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
} else {
|
} else {
|
||||||
if ns.Name == namespace.Name {
|
if ns.Name == namespace {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +135,7 @@ func (adapter Adapter) PrepareForPush(resource *model.Resource) error {
|
|||||||
url := fmt.Sprintf("%s/dockyard/v2/namespaces", adapter.Registry.URL)
|
url := fmt.Sprintf("%s/dockyard/v2/namespaces", adapter.Registry.URL)
|
||||||
namespacebyte, err := json.Marshal(struct {
|
namespacebyte, err := json.Marshal(struct {
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
}{Namespace: namespace.Name})
|
}{Namespace: namespace})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -39,49 +39,15 @@ func TestAdapter_Info(t *testing.T) {
|
|||||||
t.Log(info)
|
t.Log(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdapter_ListNamespaces(t *testing.T) {
|
|
||||||
namespaces, err := hwAdapter.ListNamespaces(&model.NamespaceQuery{Name: "o"})
|
|
||||||
if err != nil {
|
|
||||||
if strings.HasPrefix(err.Error(), "[401]") {
|
|
||||||
t.Log("huawei ak/sk is not available", err.Error())
|
|
||||||
} else {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, namespace := range namespaces {
|
|
||||||
t.Log(namespace.Name, namespace.Metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdapter_ConvertResourceMetadata(t *testing.T) {
|
|
||||||
metadata := &model.ResourceMetadata{}
|
|
||||||
|
|
||||||
namespace := &model.Namespace{
|
|
||||||
Name: "domain_repo_new",
|
|
||||||
Metadata: make(map[string]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata, err := hwAdapter.ConvertResourceMetadata(metadata, namespace)
|
|
||||||
if err != nil {
|
|
||||||
if strings.HasPrefix(err.Error(), "[401]") {
|
|
||||||
t.Log("huawei ak/sk is not available", err.Error())
|
|
||||||
} else {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Log("success convert resource metadata")
|
|
||||||
t.Log(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdapter_PrepareForPush(t *testing.T) {
|
func TestAdapter_PrepareForPush(t *testing.T) {
|
||||||
namespace := &model.Namespace{
|
repository := &model.Repository{
|
||||||
Name: "domain_repo_new",
|
Name: "domain_repo_new",
|
||||||
Metadata: make(map[string]interface{}),
|
Metadata: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
resource := &model.Resource{}
|
resource := &model.Resource{}
|
||||||
metadata := &model.ResourceMetadata{Namespace: namespace}
|
metadata := &model.ResourceMetadata{
|
||||||
|
Repository: repository,
|
||||||
|
}
|
||||||
resource.Metadata = metadata
|
resource.Metadata = metadata
|
||||||
err := hwAdapter.PrepareForPush(resource)
|
err := hwAdapter.PrepareForPush(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -84,16 +84,12 @@ func parseRepoQueryResultToResource(repo hwRepoQueryResult) *model.Resource {
|
|||||||
info["status"] = repo.Status
|
info["status"] = repo.Status
|
||||||
info["total_range"] = repo.TotalRange
|
info["total_range"] = repo.TotalRange
|
||||||
|
|
||||||
namespace := &model.Namespace{
|
|
||||||
Name: repo.NamespaceName,
|
|
||||||
}
|
|
||||||
repository := &model.Repository{
|
repository := &model.Repository{
|
||||||
Name: repo.Name,
|
Name: repo.Name,
|
||||||
Metadata: info,
|
Metadata: info,
|
||||||
}
|
}
|
||||||
resource.ExtendedInfo = info
|
resource.ExtendedInfo = info
|
||||||
resource.Metadata = &model.ResourceMetadata{
|
resource.Metadata = &model.ResourceMetadata{
|
||||||
Namespace: namespace,
|
|
||||||
Repository: repository,
|
Repository: repository,
|
||||||
Vtags: repo.Tags,
|
Vtags: repo.Tags,
|
||||||
Labels: []string{},
|
Labels: []string{},
|
||||||
@ -101,7 +97,6 @@ func parseRepoQueryResultToResource(repo hwRepoQueryResult) *model.Resource {
|
|||||||
resource.Deleted = false
|
resource.Deleted = false
|
||||||
resource.Override = false
|
resource.Override = false
|
||||||
resource.Type = model.ResourceTypeRepository
|
resource.Type = model.ResourceTypeRepository
|
||||||
resource.URI = repo.Path
|
|
||||||
|
|
||||||
return &resource
|
return &resource
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ const (
|
|||||||
|
|
||||||
// ImageRegistry defines the capabilities that an image registry should have
|
// ImageRegistry defines the capabilities that an image registry should have
|
||||||
type ImageRegistry interface {
|
type ImageRegistry interface {
|
||||||
FetchImages(namespaces []string, filters []*model.Filter) ([]*model.Resource, error)
|
FetchImages(filters []*model.Filter) ([]*model.Resource, error)
|
||||||
ManifestExist(repository, reference string) (exist bool, digest string, err error)
|
ManifestExist(repository, reference string) (exist bool, digest string, err error)
|
||||||
PullManifest(repository, reference string, accepttedMediaTypes []string) (manifest distribution.Manifest, digest string, err error)
|
PullManifest(repository, reference string, accepttedMediaTypes []string) (manifest distribution.Manifest, digest string, err error)
|
||||||
PushManifest(repository, reference, mediaType string, payload []byte) error
|
PushManifest(repository, reference, mediaType string, payload []byte) error
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
adp "github.com/goharbor/harbor/src/replication/adapter"
|
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
@ -75,33 +73,5 @@ func (native) Info() (info *model.RegistryInfo, err error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertResourceMetadata convert src to dst resource
|
|
||||||
func (native) ConvertResourceMetadata(metadata *model.ResourceMetadata, namespace *model.Namespace) (*model.ResourceMetadata, error) {
|
|
||||||
if metadata == nil {
|
|
||||||
return nil, errors.New("the metadata cannot be null")
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = &model.ResourceMetadata{
|
|
||||||
Namespace: metadata.Namespace,
|
|
||||||
Repository: metadata.Repository,
|
|
||||||
Vtags: metadata.Vtags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if dest namespace is set, rename metadata namespace
|
|
||||||
if namespace != nil {
|
|
||||||
result.Namespace = namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Repository = &model.Repository{Name: result.GetResourceName()}
|
|
||||||
result.Namespace = nil
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareForPush nothing need to do.
|
// PrepareForPush nothing need to do.
|
||||||
func (native) PrepareForPush(*model.Resource) error { return nil }
|
func (native) PrepareForPush(*model.Resource) error { return nil }
|
||||||
|
|
||||||
// ListNamespaces native registry no namespaces, so list empty array.
|
|
||||||
func (native) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
|
||||||
return []*model.Namespace{}, nil
|
|
||||||
}
|
|
||||||
|
@ -50,81 +50,6 @@ func Test_native_Info(t *testing.T) {
|
|||||||
assert.Equal(t, model.ResourceTypeRepository, info.SupportedResourceTypes[0])
|
assert.Equal(t, model.ResourceTypeRepository, info.SupportedResourceTypes[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_native_ConvertResourceMetadata(t *testing.T) {
|
|
||||||
var registry = &model.Registry{URL: "abc"}
|
|
||||||
var reg, _ = adp.NewDefaultImageRegistry(registry)
|
|
||||||
var adapter = native{
|
|
||||||
DefaultImageRegistry: reg,
|
|
||||||
registry: registry,
|
|
||||||
}
|
|
||||||
assert.NotNil(t, adapter)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
metadata *model.ResourceMetadata
|
|
||||||
namespace *model.Namespace
|
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{name: "nil metadata", metadata: nil, wantErr: true},
|
|
||||||
{
|
|
||||||
name: "2 level",
|
|
||||||
metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: &model.Namespace{Name: "a"},
|
|
||||||
Repository: &model.Repository{Name: "b"},
|
|
||||||
},
|
|
||||||
namespace: nil,
|
|
||||||
want: "a/b",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "2 level rename reomte repository",
|
|
||||||
metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: &model.Namespace{Name: "a"},
|
|
||||||
Repository: &model.Repository{Name: "b"},
|
|
||||||
},
|
|
||||||
namespace: &model.Namespace{Name: "c"},
|
|
||||||
want: "c/b",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "3 level",
|
|
||||||
metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: &model.Namespace{Name: "a"},
|
|
||||||
Repository: &model.Repository{Name: "b/c"},
|
|
||||||
},
|
|
||||||
namespace: nil,
|
|
||||||
want: "a/b/c",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "3 level rename reomte repository",
|
|
||||||
metadata: &model.ResourceMetadata{
|
|
||||||
Namespace: &model.Namespace{Name: "a"},
|
|
||||||
Repository: &model.Repository{Name: "b/c"},
|
|
||||||
},
|
|
||||||
namespace: &model.Namespace{Name: "d"},
|
|
||||||
want: "d/b/c",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
var convert, err = adapter.ConvertResourceMetadata(tt.metadata, tt.namespace)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
} else {
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, convert)
|
|
||||||
assert.Nil(t, convert.Namespace)
|
|
||||||
assert.Equal(t, tt.want, convert.Repository.Name)
|
|
||||||
assert.Equal(t, tt.want, convert.GetResourceName())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_native_PrepareForPush(t *testing.T) {
|
func Test_native_PrepareForPush(t *testing.T) {
|
||||||
var registry = &model.Registry{URL: "abc"}
|
var registry = &model.Registry{URL: "abc"}
|
||||||
var reg, _ = adp.NewDefaultImageRegistry(registry)
|
var reg, _ = adp.NewDefaultImageRegistry(registry)
|
||||||
@ -137,17 +62,3 @@ func Test_native_PrepareForPush(t *testing.T) {
|
|||||||
var err = adapter.PrepareForPush(nil)
|
var err = adapter.PrepareForPush(nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_native_ListNamespaces(t *testing.T) {
|
|
||||||
var registry = &model.Registry{URL: "abc"}
|
|
||||||
var reg, _ = adp.NewDefaultImageRegistry(registry)
|
|
||||||
var adapter = native{
|
|
||||||
DefaultImageRegistry: reg,
|
|
||||||
registry: registry,
|
|
||||||
}
|
|
||||||
assert.NotNil(t, adapter)
|
|
||||||
|
|
||||||
var ns, err = adapter.ListNamespaces(nil)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, ns)
|
|
||||||
}
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -27,11 +26,7 @@ import (
|
|||||||
|
|
||||||
var _ adp.ImageRegistry = native{}
|
var _ adp.ImageRegistry = native{}
|
||||||
|
|
||||||
func (n native) FetchImages(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (n native) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
if len(namespaces) > 0 {
|
|
||||||
return nil, errors.New("native registry adapter not support namespace")
|
|
||||||
}
|
|
||||||
|
|
||||||
nameFilterPattern := ""
|
nameFilterPattern := ""
|
||||||
tagFilterPattern := ""
|
tagFilterPattern := ""
|
||||||
for _, filter := range filters {
|
for _, filter := range filters {
|
||||||
|
@ -65,13 +65,11 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
assert.NotNil(t, adapter)
|
assert.NotNil(t, adapter)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
namespaces []string
|
filters []*model.Filter
|
||||||
filters []*model.Filter
|
want []*model.Resource
|
||||||
want []*model.Resource
|
wantErr bool
|
||||||
wantErr bool
|
|
||||||
}{
|
}{
|
||||||
{name: "namespace not empty", namespaces: []string{"ns"}, wantErr: true},
|
|
||||||
// TODO: discuss: should we report error if not found in the source native registry.
|
// TODO: discuss: should we report error if not found in the source native registry.
|
||||||
// {
|
// {
|
||||||
// name: "repository not exist",
|
// name: "repository not exist",
|
||||||
@ -119,8 +117,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only special repository",
|
name: "only special repository",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeName,
|
Type: model.FilterTypeName,
|
||||||
@ -138,8 +135,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only special tag",
|
name: "only special tag",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeTag,
|
Type: model.FilterTypeTag,
|
||||||
@ -163,8 +159,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "special repository and special tag",
|
name: "special repository and special tag",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeName,
|
Type: model.FilterTypeName,
|
||||||
@ -187,8 +182,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only wildcard repository",
|
name: "only wildcard repository",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeName,
|
Type: model.FilterTypeName,
|
||||||
@ -206,8 +200,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only wildcard tag",
|
name: "only wildcard tag",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeTag,
|
Type: model.FilterTypeTag,
|
||||||
@ -231,8 +224,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wildcard repository and wildcard tag",
|
name: "wildcard repository and wildcard tag",
|
||||||
namespaces: []string{},
|
|
||||||
filters: []*model.Filter{
|
filters: []*model.Filter{
|
||||||
{
|
{
|
||||||
Type: model.FilterTypeName,
|
Type: model.FilterTypeName,
|
||||||
@ -257,7 +249,7 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var resources, err = adapter.FetchImages(tt.namespaces, tt.filters)
|
var resources, err = adapter.FetchImages(tt.filters)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Len(t, resources, 0)
|
require.Len(t, resources, 0)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
@ -265,7 +257,6 @@ func Test_native_FetchImages(t *testing.T) {
|
|||||||
require.Equal(t, len(tt.want), len(resources))
|
require.Equal(t, len(tt.want), len(resources))
|
||||||
for i, resource := range resources {
|
for i, resource := range resources {
|
||||||
require.NotNil(t, resource.Metadata)
|
require.NotNil(t, resource.Metadata)
|
||||||
assert.Equal(t, tt.want[i].Metadata.Namespace, resource.Metadata.Namespace)
|
|
||||||
assert.Equal(t, tt.want[i].Metadata.Repository, resource.Metadata.Repository)
|
assert.Equal(t, tt.want[i].Metadata.Repository, resource.Metadata.Repository)
|
||||||
assert.Equal(t, tt.want[i].Metadata.Vtags, resource.Metadata.Vtags)
|
assert.Equal(t, tt.want[i].Metadata.Vtags, resource.Metadata.Vtags)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ type RepPolicy struct {
|
|||||||
Description string `orm:"column(description)" json:"description"`
|
Description string `orm:"column(description)" json:"description"`
|
||||||
Creator string `orm:"column(creator)" json:"creator"`
|
Creator string `orm:"column(creator)" json:"creator"`
|
||||||
SrcRegistryID int64 `orm:"column(src_registry_id)" json:"src_registry_id"`
|
SrcRegistryID int64 `orm:"column(src_registry_id)" json:"src_registry_id"`
|
||||||
SrcNamespaces string `orm:"column(src_namespaces)" json:"src_namespaces"`
|
|
||||||
DestRegistryID int64 `orm:"column(dest_registry_id)" json:"dest_registry_id"`
|
DestRegistryID int64 `orm:"column(dest_registry_id)" json:"dest_registry_id"`
|
||||||
DestNamespace string `orm:"column(dest_namespace)" json:"dest_namespace"`
|
DestNamespace string `orm:"column(dest_namespace)" json:"dest_namespace"`
|
||||||
Override bool `orm:"column(override)" json:"override"`
|
Override bool `orm:"column(override)" json:"override"`
|
||||||
|
@ -17,7 +17,6 @@ var (
|
|||||||
Description: "Policy Description",
|
Description: "Policy Description",
|
||||||
Creator: "someone",
|
Creator: "someone",
|
||||||
SrcRegistryID: 123,
|
SrcRegistryID: 123,
|
||||||
SrcNamespaces: "ns1,ns2,ns3",
|
|
||||||
DestRegistryID: 456,
|
DestRegistryID: 456,
|
||||||
DestNamespace: "target_ns",
|
DestNamespace: "target_ns",
|
||||||
ReplicateDeletion: true,
|
ReplicateDeletion: true,
|
||||||
@ -33,7 +32,6 @@ var (
|
|||||||
Description: "Policy Description",
|
Description: "Policy Description",
|
||||||
Creator: "someone",
|
Creator: "someone",
|
||||||
SrcRegistryID: 123,
|
SrcRegistryID: 123,
|
||||||
SrcNamespaces: "ns1,ns2,ns3",
|
|
||||||
DestRegistryID: 456,
|
DestRegistryID: 456,
|
||||||
DestNamespace: "target_ns",
|
DestNamespace: "target_ns",
|
||||||
ReplicateDeletion: true,
|
ReplicateDeletion: true,
|
||||||
@ -49,7 +47,6 @@ var (
|
|||||||
Description: "Policy Description",
|
Description: "Policy Description",
|
||||||
Creator: "someone",
|
Creator: "someone",
|
||||||
SrcRegistryID: 123,
|
SrcRegistryID: 123,
|
||||||
SrcNamespaces: "ns1,ns2,ns3",
|
|
||||||
DestRegistryID: 456,
|
DestRegistryID: 456,
|
||||||
DestNamespace: "target_ns",
|
DestNamespace: "target_ns",
|
||||||
ReplicateDeletion: true,
|
ReplicateDeletion: true,
|
||||||
@ -126,7 +123,6 @@ func TestGetPolicies(t *testing.T) {
|
|||||||
assert.Equal(t, tt.wantPolicies[i].Description, gotPolicy.Description)
|
assert.Equal(t, tt.wantPolicies[i].Description, gotPolicy.Description)
|
||||||
assert.Equal(t, tt.wantPolicies[i].Creator, gotPolicy.Creator)
|
assert.Equal(t, tt.wantPolicies[i].Creator, gotPolicy.Creator)
|
||||||
assert.Equal(t, tt.wantPolicies[i].SrcRegistryID, gotPolicy.SrcRegistryID)
|
assert.Equal(t, tt.wantPolicies[i].SrcRegistryID, gotPolicy.SrcRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicies[i].SrcNamespaces, gotPolicy.SrcNamespaces)
|
|
||||||
assert.Equal(t, tt.wantPolicies[i].DestRegistryID, gotPolicy.DestRegistryID)
|
assert.Equal(t, tt.wantPolicies[i].DestRegistryID, gotPolicy.DestRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicies[i].DestNamespace, gotPolicy.DestNamespace)
|
assert.Equal(t, tt.wantPolicies[i].DestNamespace, gotPolicy.DestNamespace)
|
||||||
assert.Equal(t, tt.wantPolicies[i].ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
assert.Equal(t, tt.wantPolicies[i].ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
||||||
@ -163,7 +159,6 @@ func TestGetRepPolicy(t *testing.T) {
|
|||||||
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
||||||
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
||||||
assert.Equal(t, tt.wantPolicy.SrcRegistryID, gotPolicy.SrcRegistryID)
|
assert.Equal(t, tt.wantPolicy.SrcRegistryID, gotPolicy.SrcRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicy.SrcNamespaces, gotPolicy.SrcNamespaces)
|
|
||||||
assert.Equal(t, tt.wantPolicy.DestRegistryID, gotPolicy.DestRegistryID)
|
assert.Equal(t, tt.wantPolicy.DestRegistryID, gotPolicy.DestRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicy.DestNamespace, gotPolicy.DestNamespace)
|
assert.Equal(t, tt.wantPolicy.DestNamespace, gotPolicy.DestNamespace)
|
||||||
assert.Equal(t, tt.wantPolicy.ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
assert.Equal(t, tt.wantPolicy.ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
||||||
@ -202,7 +197,6 @@ func TestGetRepPolicyByName(t *testing.T) {
|
|||||||
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
assert.Equal(t, tt.wantPolicy.Description, gotPolicy.Description)
|
||||||
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
assert.Equal(t, tt.wantPolicy.Creator, gotPolicy.Creator)
|
||||||
assert.Equal(t, tt.wantPolicy.SrcRegistryID, gotPolicy.SrcRegistryID)
|
assert.Equal(t, tt.wantPolicy.SrcRegistryID, gotPolicy.SrcRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicy.SrcNamespaces, gotPolicy.SrcNamespaces)
|
|
||||||
assert.Equal(t, tt.wantPolicy.DestRegistryID, gotPolicy.DestRegistryID)
|
assert.Equal(t, tt.wantPolicy.DestRegistryID, gotPolicy.DestRegistryID)
|
||||||
assert.Equal(t, tt.wantPolicy.DestNamespace, gotPolicy.DestNamespace)
|
assert.Equal(t, tt.wantPolicy.DestNamespace, gotPolicy.DestNamespace)
|
||||||
assert.Equal(t, tt.wantPolicy.ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
assert.Equal(t, tt.wantPolicy.ReplicateDeletion, gotPolicy.ReplicateDeletion)
|
||||||
|
@ -18,6 +18,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/replication/util"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/replication/config"
|
"github.com/goharbor/harbor/src/replication/config"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
@ -55,10 +57,9 @@ func (h *handler) Handle(event *Event) error {
|
|||||||
var policies []*model.Policy
|
var policies []*model.Policy
|
||||||
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.Name)
|
EventTypeImageDelete, EventTypeChartDelete:
|
||||||
case EventTypeImageDelete, EventTypeChartDelete:
|
policies, err = h.getRelatedPolicies(event.Resource)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -84,22 +85,15 @@ func (h *handler) Handle(event *Event) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) getRelatedPolicies(namespace string, replicateDeletion ...bool) ([]*model.Policy, error) {
|
func (h *handler) getRelatedPolicies(resource *model.Resource) ([]*model.Policy, error) {
|
||||||
_, policies, err := h.policyCtl.List()
|
_, policies, err := h.policyCtl.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result := []*model.Policy{}
|
result := []*model.Policy{}
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
exist := false
|
// disabled
|
||||||
for _, ns := range policy.SrcNamespaces {
|
if !policy.Enabled {
|
||||||
if ns == namespace {
|
|
||||||
exist = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// contains no namespace that is specified
|
|
||||||
if !exist {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// has no trigger
|
// has no trigger
|
||||||
@ -110,8 +104,16 @@ func (h *handler) getRelatedPolicies(namespace string, replicateDeletion ...bool
|
|||||||
if policy.Trigger.Type != model.TriggerTypeEventBased {
|
if policy.Trigger.Type != model.TriggerTypeEventBased {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// whether replicate deletion doesn't match the value specified in policy
|
// doesn't replicate deletion
|
||||||
if len(replicateDeletion) > 0 && replicateDeletion[0] != policy.Deletion {
|
if resource.Deleted && !policy.Deletion {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// doesn't match the name filter
|
||||||
|
m, err := match(policy.Filters, resource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !m {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, policy)
|
result = append(result, policy)
|
||||||
@ -119,6 +121,26 @@ func (h *handler) getRelatedPolicies(namespace string, replicateDeletion ...bool
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO unify the match logic with other?
|
||||||
|
func match(filters []*model.Filter, resource *model.Resource) (bool, error) {
|
||||||
|
match := true
|
||||||
|
repository := resource.Metadata.Repository.Name
|
||||||
|
for _, filter := range filters {
|
||||||
|
if filter.Type != model.FilterTypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m, err := util.Match(filter.Value.(string), repository)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !m {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PopulateRegistries populates the source registry and destination registry properties for policy
|
// PopulateRegistries populates the source registry and destination registry properties for policy
|
||||||
func PopulateRegistries(registryMgr registry.Manager, policy *model.Policy) error {
|
func PopulateRegistries(registryMgr registry.Manager, policy *model.Policy) error {
|
||||||
if policy == nil {
|
if policy == nil {
|
||||||
|
@ -59,34 +59,76 @@ func (f *fakedPolicyController) Create(*model.Policy) (int64, error) {
|
|||||||
func (f *fakedPolicyController) List(...*model.PolicyQuery) (int64, []*model.Policy, error) {
|
func (f *fakedPolicyController) List(...*model.PolicyQuery) (int64, []*model.Policy, error) {
|
||||||
polices := []*model.Policy{
|
polices := []*model.Policy{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
SrcNamespaces: []string{"test"},
|
Enabled: true,
|
||||||
Deletion: false,
|
Deletion: true,
|
||||||
Trigger: &model.Trigger{
|
Trigger: &model.Trigger{
|
||||||
Type: model.TriggerTypeEventBased,
|
Type: model.TriggerTypeEventBased,
|
||||||
},
|
},
|
||||||
|
Filters: []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "test/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
// nil trigger
|
||||||
{
|
{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
SrcNamespaces: []string{"library"},
|
Enabled: true,
|
||||||
Deletion: true,
|
Deletion: true,
|
||||||
Trigger: nil,
|
Trigger: nil,
|
||||||
|
Filters: []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
// doesn't replicate deletion
|
||||||
{
|
{
|
||||||
ID: 3,
|
ID: 3,
|
||||||
SrcNamespaces: []string{"library"},
|
Enabled: true,
|
||||||
Deletion: false,
|
Deletion: false,
|
||||||
Trigger: &model.Trigger{
|
Trigger: &model.Trigger{
|
||||||
Type: model.TriggerTypeEventBased,
|
Type: model.TriggerTypeEventBased,
|
||||||
},
|
},
|
||||||
|
Filters: []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
// replicate deletion
|
||||||
{
|
{
|
||||||
ID: 4,
|
ID: 4,
|
||||||
SrcNamespaces: []string{"library"},
|
Enabled: true,
|
||||||
Deletion: true,
|
Deletion: true,
|
||||||
Trigger: &model.Trigger{
|
Trigger: &model.Trigger{
|
||||||
Type: model.TriggerTypeEventBased,
|
Type: model.TriggerTypeEventBased,
|
||||||
},
|
},
|
||||||
|
Filters: []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// disabled
|
||||||
|
{
|
||||||
|
ID: 5,
|
||||||
|
Enabled: false,
|
||||||
|
Deletion: true,
|
||||||
|
Trigger: &model.Trigger{
|
||||||
|
Type: model.TriggerTypeEventBased,
|
||||||
|
},
|
||||||
|
Filters: []*model.Filter{
|
||||||
|
{
|
||||||
|
Type: model.FilterTypeName,
|
||||||
|
Value: "library/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return int64(len(polices)), polices, nil
|
return int64(len(polices)), polices, nil
|
||||||
@ -134,13 +176,26 @@ func TestGetRelatedPolicies(t *testing.T) {
|
|||||||
handler := &handler{
|
handler := &handler{
|
||||||
policyCtl: &fakedPolicyController{},
|
policyCtl: &fakedPolicyController{},
|
||||||
}
|
}
|
||||||
policies, err := handler.getRelatedPolicies("library")
|
policies, err := handler.getRelatedPolicies(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "library/hello-world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(policies))
|
assert.Equal(t, 2, len(policies))
|
||||||
assert.Equal(t, int64(3), policies[0].ID)
|
assert.Equal(t, int64(3), policies[0].ID)
|
||||||
assert.Equal(t, int64(4), policies[1].ID)
|
assert.Equal(t, int64(4), policies[1].ID)
|
||||||
|
|
||||||
policies, err = handler.getRelatedPolicies("library", true)
|
policies, err = handler.getRelatedPolicies(&model.Resource{
|
||||||
|
Metadata: &model.ResourceMetadata{
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: "library/hello-world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Deleted: true,
|
||||||
|
})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(policies))
|
assert.Equal(t, 1, len(policies))
|
||||||
assert.Equal(t, int64(4), policies[0].ID)
|
assert.Equal(t, int64(4), policies[0].ID)
|
||||||
@ -159,11 +214,8 @@ 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{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{},
|
Vtags: []string{},
|
||||||
},
|
},
|
||||||
@ -176,11 +228,8 @@ 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{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -193,11 +242,8 @@ 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{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -210,11 +256,8 @@ 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{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
// TODO remove
|
||||||
|
|
||||||
// Namespace represents the full path of resource isolation unit;
|
// Namespace represents the full path of resource isolation unit;
|
||||||
// if the namespace has hierarchical structure, e.g organization->team,
|
// if the namespace has hierarchical structure, e.g organization->team,
|
||||||
// it should be converted to organization.team
|
// it should be converted to organization.team
|
||||||
|
@ -42,8 +42,7 @@ type Policy struct {
|
|||||||
// TODO consider to remove this property?
|
// TODO consider to remove this property?
|
||||||
Creator string `json:"creator"`
|
Creator string `json:"creator"`
|
||||||
// source
|
// source
|
||||||
SrcRegistry *Registry `json:"src_registry"`
|
SrcRegistry *Registry `json:"src_registry"`
|
||||||
SrcNamespaces []string `json:"src_namespaces"`
|
|
||||||
// destination
|
// destination
|
||||||
// TODO rename to DstRegistry
|
// TODO rename to DstRegistry
|
||||||
DestRegistry *Registry `json:"dest_registry"`
|
DestRegistry *Registry `json:"dest_registry"`
|
||||||
|
@ -61,7 +61,6 @@ func TestValidOfPolicy(t *testing.T) {
|
|||||||
DestRegistry: &Registry{
|
DestRegistry: &Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
Filters: []*Filter{
|
Filters: []*Filter{
|
||||||
{
|
{
|
||||||
Type: "invalid_type",
|
Type: "invalid_type",
|
||||||
@ -80,7 +79,6 @@ func TestValidOfPolicy(t *testing.T) {
|
|||||||
DestRegistry: &Registry{
|
DestRegistry: &Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
Filters: []*Filter{
|
Filters: []*Filter{
|
||||||
{
|
{
|
||||||
Type: FilterTypeName,
|
Type: FilterTypeName,
|
||||||
@ -103,7 +101,6 @@ func TestValidOfPolicy(t *testing.T) {
|
|||||||
DestRegistry: &Registry{
|
DestRegistry: &Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
Filters: []*Filter{
|
Filters: []*Filter{
|
||||||
{
|
{
|
||||||
Type: FilterTypeName,
|
Type: FilterTypeName,
|
||||||
@ -126,7 +123,6 @@ func TestValidOfPolicy(t *testing.T) {
|
|||||||
DestRegistry: &Registry{
|
DestRegistry: &Registry{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"library"},
|
|
||||||
Filters: []*Filter{
|
Filters: []*Filter{
|
||||||
{
|
{
|
||||||
Type: FilterTypeName,
|
Type: FilterTypeName,
|
||||||
|
@ -109,7 +109,6 @@ 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,7 +30,6 @@ func (r ResourceType) Valid() bool {
|
|||||||
|
|
||||||
// ResourceMetadata of resource
|
// ResourceMetadata of resource
|
||||||
type ResourceMetadata struct {
|
type ResourceMetadata struct {
|
||||||
Namespace *Namespace `json:"namespace"`
|
|
||||||
Repository *Repository `json:"repository"`
|
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?
|
// TODO the labels should be put into tag and repository level?
|
||||||
@ -38,18 +37,12 @@ type ResourceMetadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetResourceName returns the name of the resource
|
// GetResourceName returns the name of the resource
|
||||||
|
// TODO remove
|
||||||
func (r *ResourceMetadata) GetResourceName() string {
|
func (r *ResourceMetadata) GetResourceName() string {
|
||||||
name := ""
|
if r.Repository == nil {
|
||||||
if r.Namespace != nil && len(r.Namespace.Name) > 0 {
|
return ""
|
||||||
name += r.Namespace.Name
|
|
||||||
}
|
}
|
||||||
if r.Repository != nil && len(r.Repository.Name) > 0 {
|
return r.Repository.Name
|
||||||
if len(name) > 0 {
|
|
||||||
name += "/"
|
|
||||||
}
|
|
||||||
name += r.Repository.Name
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository info of the resource
|
// Repository info of the resource
|
||||||
@ -61,7 +54,6 @@ type Repository struct {
|
|||||||
// 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"`
|
||||||
URI string `json:"uri"`
|
|
||||||
Metadata *ResourceMetadata `json:"metadata"`
|
Metadata *ResourceMetadata `json:"metadata"`
|
||||||
Registry *Registry `json:"registry"`
|
Registry *Registry `json:"registry"`
|
||||||
ExtendedInfo map[string]interface{} `json:"extended_info"`
|
ExtendedInfo map[string]interface{} `json:"extended_info"`
|
||||||
|
@ -25,7 +25,7 @@ func TestGetResourceName(t *testing.T) {
|
|||||||
assert.Equal(t, "", r.GetResourceName())
|
assert.Equal(t, "", r.GetResourceName())
|
||||||
|
|
||||||
r = &ResourceMetadata{
|
r = &ResourceMetadata{
|
||||||
Namespace: &Namespace{
|
Repository: &Repository{
|
||||||
Name: "library",
|
Name: "library",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -39,11 +39,8 @@ func TestGetResourceName(t *testing.T) {
|
|||||||
assert.Equal(t, "hello-world", r.GetResourceName())
|
assert.Equal(t, "hello-world", r.GetResourceName())
|
||||||
|
|
||||||
r = &ResourceMetadata{
|
r = &ResourceMetadata{
|
||||||
Namespace: &Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &Repository{
|
Repository: &Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, "library/hello-world", r.GetResourceName())
|
assert.Equal(t, "library/hello-world", r.GetResourceName())
|
||||||
|
@ -58,6 +58,9 @@ type controller struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) StartReplication(policy *model.Policy, resource *model.Resource, trigger model.TriggerType) (int64, error) {
|
func (c *controller) StartReplication(policy *model.Policy, resource *model.Resource, trigger model.TriggerType) (int64, error) {
|
||||||
|
if !policy.Enabled {
|
||||||
|
return 0, fmt.Errorf("the policy %d is diabled", policy.ID)
|
||||||
|
}
|
||||||
// only support one tag if the resource is specified as we append the tag name as a filter
|
// only support one tag if the resource is specified as we append the tag name as a filter
|
||||||
// when creating the flow in function "createFlow"
|
// when creating the flow in function "createFlow"
|
||||||
if resource != nil && len(resource.Metadata.Vtags) != 1 {
|
if resource != nil && len(resource.Metadata.Vtags) != 1 {
|
||||||
|
@ -129,48 +129,20 @@ func (f *fakedAdapter) Info() (*model.RegistryInfo, error) {
|
|||||||
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
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 {
|
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) {
|
||||||
return model.Healthy, nil
|
return model.Healthy, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) GetNamespace(ns string) (*model.Namespace, error) {
|
|
||||||
var namespace *model.Namespace
|
|
||||||
if ns == "library" {
|
|
||||||
namespace = &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
Metadata: map[string]interface{}{
|
|
||||||
"public": true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return namespace, nil
|
|
||||||
}
|
|
||||||
func (f *fakedAdapter) FetchImages(namespace []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (f *fakedAdapter) FetchImages(namespace []string, filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
return []*model.Resource{
|
return []*model.Resource{
|
||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -205,11 +177,8 @@ func (f *fakedAdapter) FetchCharts(namespaces []string, filters []*model.Filter)
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "harbor",
|
Name: "library/harbor",
|
||||||
},
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
@ -239,7 +208,6 @@ func TestStartReplication(t *testing.T) {
|
|||||||
err := adapter.RegisterFactory(model.RegistryTypeHarbor, fakedAdapterFactory)
|
err := adapter.RegisterFactory(model.RegistryTypeHarbor, fakedAdapterFactory)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
config.Config = &config.Configuration{}
|
config.Config = &config.Configuration{}
|
||||||
// the resource contains Vtags whose length isn't 1
|
|
||||||
policy := &model.Policy{
|
policy := &model.Policy{
|
||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
Type: model.RegistryTypeHarbor,
|
Type: model.RegistryTypeHarbor,
|
||||||
@ -251,15 +219,18 @@ func TestStartReplication(t *testing.T) {
|
|||||||
resource := &model.Resource{
|
resource := &model.Resource{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"1.0", "2.0"},
|
Vtags: []string{"1.0", "2.0"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// policy is disabled
|
||||||
|
_, err = ctl.StartReplication(policy, resource, model.TriggerTypeEventBased)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
policy.Enabled = true
|
||||||
|
// the resource contains Vtags whose length isn't 1
|
||||||
_, err = ctl.StartReplication(policy, resource, model.TriggerTypeEventBased)
|
_, err = ctl.StartReplication(policy, resource, model.TriggerTypeEventBased)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
@ -57,10 +57,8 @@ 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
|
||||||
}
|
}
|
||||||
dstResources, err := assembleDestinationResources(dstAdapter, srcResources, c.policy)
|
|
||||||
if err != nil {
|
dstResources := assembleDestinationResources(srcResources, c.policy)
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = prepareForPush(dstAdapter, dstResources); err != nil {
|
if err = prepareForPush(dstAdapter, dstResources); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -43,10 +43,6 @@ 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
|
||||||
@ -60,10 +56,8 @@ 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, err := assembleDestinationResources(dstAdapter, srcResources, d.policy)
|
|
||||||
if err != nil {
|
dstResources := assembleDestinationResources(srcResources, d.policy)
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
items, err := preprocess(d.scheduler, srcResources, dstResources)
|
items, err := preprocess(d.scheduler, srcResources, dstResources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,11 +36,8 @@ func TestRunOfDeletionFlow(t *testing.T) {
|
|||||||
resources := []*model.Resource{
|
resources := []*model.Resource{
|
||||||
{
|
{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
|
@ -87,14 +87,14 @@ func fetchResources(adapter adp.Adapter, policy *model.Policy) ([]*model.Resourc
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the adapter doesn't implement the ImageRegistry interface")
|
return nil, fmt.Errorf("the adapter doesn't implement the ImageRegistry interface")
|
||||||
}
|
}
|
||||||
res, err = reg.FetchImages(policy.SrcNamespaces, filters)
|
res, err = reg.FetchImages(filters)
|
||||||
} else if typ == model.ResourceTypeChart {
|
} else if typ == model.ResourceTypeChart {
|
||||||
// charts
|
// charts
|
||||||
reg, ok := adapter.(adp.ChartRegistry)
|
reg, ok := adapter.(adp.ChartRegistry)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the adapter doesn't implement the ChartRegistry interface")
|
return nil, fmt.Errorf("the adapter doesn't implement the ChartRegistry interface")
|
||||||
}
|
}
|
||||||
res, err = reg.FetchCharts(policy.SrcNamespaces, filters)
|
res, err = reg.FetchCharts(filters)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unsupported resource type %s", typ)
|
return nil, fmt.Errorf("unsupported resource type %s", typ)
|
||||||
}
|
}
|
||||||
@ -135,8 +135,7 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
|
|||||||
match = false
|
match = false
|
||||||
break FILTER_LOOP
|
break FILTER_LOOP
|
||||||
}
|
}
|
||||||
// TODO filter only the repository part?
|
m, err := util.Match(pattern, resource.Metadata.Repository.Name)
|
||||||
m, err := util.Match(pattern, resource.Metadata.GetResourceName())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -184,39 +183,35 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// assemble the destination resources by filling the metadata, registry and override properties
|
// assemble the destination resources by filling the metadata, registry and override properties
|
||||||
func assembleDestinationResources(adapter adp.Adapter, resources []*model.Resource,
|
func assembleDestinationResources(resources []*model.Resource,
|
||||||
policy *model.Policy) ([]*model.Resource, error) {
|
policy *model.Policy) []*model.Resource {
|
||||||
var result []*model.Resource
|
var 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: metadata,
|
|
||||||
Registry: policy.DestRegistry,
|
Registry: policy.DestRegistry,
|
||||||
ExtendedInfo: resource.ExtendedInfo,
|
ExtendedInfo: resource.ExtendedInfo,
|
||||||
Deleted: resource.Deleted,
|
Deleted: resource.Deleted,
|
||||||
Override: policy.Override,
|
Override: policy.Override,
|
||||||
}
|
}
|
||||||
|
res.Metadata = &model.ResourceMetadata{
|
||||||
|
Repository: &model.Repository{
|
||||||
|
Name: replaceNamespace(resource.Metadata.Repository.Name, policy.DestNamespace),
|
||||||
|
Metadata: resource.Metadata.Repository.Metadata,
|
||||||
|
},
|
||||||
|
Vtags: resource.Metadata.Vtags,
|
||||||
|
}
|
||||||
result = append(result, res)
|
result = append(result, res)
|
||||||
}
|
}
|
||||||
log.Debug("assemble the destination resources completed")
|
log.Debug("assemble the destination resources completed")
|
||||||
return result, nil
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the prepare work for pushing/uploading the resources: create the namespace or repository
|
// do the prepare work for pushing/uploading the resources: create the namespace or repository
|
||||||
func prepareForPush(adapter adp.Adapter, resources []*model.Resource) error {
|
func prepareForPush(adapter adp.Adapter, resources []*model.Resource) error {
|
||||||
// TODO need to consider how to handle that both contains public/private namespace
|
// TODO need to consider how to handle that both contains public/private namespace
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
name := resource.Metadata.GetResourceName()
|
name := resource.Metadata.Repository.Name
|
||||||
if err := adapter.PrepareForPush(resource); err != nil {
|
if err := adapter.PrepareForPush(resource); err != nil {
|
||||||
return fmt.Errorf("failed to do the prepare work for pushing/uploading %s: %v", name, err)
|
return fmt.Errorf("failed to do the prepare work for pushing/uploading %s: %v", name, err)
|
||||||
}
|
}
|
||||||
@ -318,12 +313,23 @@ func getResourceName(res *model.Resource) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(meta.Vtags) == 0 {
|
if len(meta.Vtags) == 0 {
|
||||||
return meta.GetResourceName()
|
return meta.Repository.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(meta.Vtags) <= 5 {
|
if len(meta.Vtags) <= 5 {
|
||||||
return meta.GetResourceName() + ":[" + strings.Join(meta.Vtags, ",") + "]"
|
return meta.Repository.Name + ":[" + strings.Join(meta.Vtags, ",") + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s:[%s ... %d in total]", meta.GetResourceName(), strings.Join(meta.Vtags[:5], ","), len(meta.Vtags))
|
return fmt.Sprintf("%s:[%s ... %d in total]", meta.GetResourceName(), strings.Join(meta.Vtags[:5], ","), len(meta.Vtags))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// repository:c namespace:n -> n/c
|
||||||
|
// repository:b/c namespace:n -> n/c
|
||||||
|
// repository:a/b/c namespace:n -> n/c
|
||||||
|
func replaceNamespace(repository string, namespace string) string {
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
return repository
|
||||||
|
}
|
||||||
|
_, rest := util.ParseRepository(repository)
|
||||||
|
return fmt.Sprintf("%s/%s", namespace, rest)
|
||||||
|
}
|
||||||
|
@ -45,15 +45,6 @@ func (f *fakedAdapter) Info() (*model.RegistryInfo, error) {
|
|||||||
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
SupportedTriggers: []model.TriggerType{model.TriggerTypeManual},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) ListNamespaces(*model.NamespaceQuery) ([]*model.Namespace, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
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 {
|
func (f *fakedAdapter) PrepareForPush(*model.Resource) error {
|
||||||
return nil
|
return nil
|
||||||
@ -61,28 +52,13 @@ func (f *fakedAdapter) PrepareForPush(*model.Resource) error {
|
|||||||
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
func (f *fakedAdapter) HealthCheck() (model.HealthStatus, error) {
|
||||||
return model.Healthy, nil
|
return model.Healthy, nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) GetNamespace(ns string) (*model.Namespace, error) {
|
func (f *fakedAdapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
var namespace *model.Namespace
|
|
||||||
if ns == "library" {
|
|
||||||
namespace = &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
Metadata: map[string]interface{}{
|
|
||||||
"public": true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return namespace, nil
|
|
||||||
}
|
|
||||||
func (f *fakedAdapter) FetchImages(namespace []string, filters []*model.Filter) ([]*model.Resource, error) {
|
|
||||||
return []*model.Resource{
|
return []*model.Resource{
|
||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -112,16 +88,13 @@ func (f *fakedAdapter) PullBlob(repository, digest string) (size int64, blob io.
|
|||||||
func (f *fakedAdapter) PushBlob(repository, digest string, size int64, blob io.Reader) error {
|
func (f *fakedAdapter) PushBlob(repository, digest string, size int64, blob io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *fakedAdapter) FetchCharts(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (f *fakedAdapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
return []*model.Resource{
|
return []*model.Resource{
|
||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "harbor",
|
Name: "library/harbor",
|
||||||
},
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
@ -240,11 +213,8 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeRepository,
|
Type: model.ResourceTypeRepository,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
@ -256,11 +226,8 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "harbor",
|
Name: "library/harbor",
|
||||||
},
|
},
|
||||||
Vtags: []string{"0.2.0", "0.3.0"},
|
Vtags: []string{"0.2.0", "0.3.0"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
@ -272,11 +239,8 @@ func TestFilterResources(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "mysql",
|
Name: "library/mysql",
|
||||||
},
|
},
|
||||||
Vtags: []string{"1.0"},
|
Vtags: []string{"1.0"},
|
||||||
// TODO test labels
|
// TODO test labels
|
||||||
@ -307,23 +271,18 @@ 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", res[0].Metadata.Namespace.Name)
|
assert.Equal(t, "library/harbor", res[0].Metadata.Repository.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 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{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -335,12 +294,10 @@ func TestAssembleDestinationResources(t *testing.T) {
|
|||||||
DestNamespace: "test",
|
DestNamespace: "test",
|
||||||
Override: true,
|
Override: true,
|
||||||
}
|
}
|
||||||
res, err := assembleDestinationResources(adapter, resources, policy)
|
res := assembleDestinationResources(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, "hello-world", res[0].Metadata.Repository.Name)
|
assert.Equal(t, "test/hello-world", res[0].Metadata.Repository.Name)
|
||||||
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])
|
||||||
}
|
}
|
||||||
@ -351,11 +308,8 @@ func TestPreprocess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "library/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -366,11 +320,8 @@ func TestPreprocess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "test",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "hello-world",
|
Name: "test/hello-world",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
},
|
},
|
||||||
@ -409,3 +360,26 @@ func TestSchedule(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 1, n)
|
assert.Equal(t, 1, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReplaceNamespace(t *testing.T) {
|
||||||
|
// empty namespace
|
||||||
|
repository := "c"
|
||||||
|
namespace := ""
|
||||||
|
result := replaceNamespace(repository, namespace)
|
||||||
|
assert.Equal(t, "c", result)
|
||||||
|
// repository contains no "/"
|
||||||
|
repository = "c"
|
||||||
|
namespace = "n"
|
||||||
|
result = replaceNamespace(repository, namespace)
|
||||||
|
assert.Equal(t, "n/c", result)
|
||||||
|
// repository contains only one "/"
|
||||||
|
repository = "b/c"
|
||||||
|
namespace = "n"
|
||||||
|
result = replaceNamespace(repository, namespace)
|
||||||
|
assert.Equal(t, "n/c", result)
|
||||||
|
// repository contains more than one "/"
|
||||||
|
repository = "a/b/c"
|
||||||
|
namespace = "n"
|
||||||
|
result = replaceNamespace(repository, namespace)
|
||||||
|
assert.Equal(t, "n/c", result)
|
||||||
|
}
|
||||||
|
@ -50,7 +50,7 @@ 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: &model.Namespace{
|
Repository: &model.Repository{
|
||||||
Name: "namespace1",
|
Name: "namespace1",
|
||||||
},
|
},
|
||||||
Vtags: []string{"latest"},
|
Vtags: []string{"latest"},
|
||||||
@ -62,7 +62,7 @@ func generateData() ([]*ScheduleItem, error) {
|
|||||||
}
|
}
|
||||||
destResource := &model.Resource{
|
destResource := &model.Resource{
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
Repository: &model.Repository{
|
||||||
Name: "namespace2",
|
Name: "namespace2",
|
||||||
},
|
},
|
||||||
Vtags: []string{"v1", "v2"},
|
Vtags: []string{"v1", "v2"},
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
@ -58,19 +57,14 @@ func convertFromPersistModel(policy *persist_models.RepPolicy) (*model.Policy, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. parse SrcNamespaces to array
|
// parse Filters
|
||||||
if len(policy.SrcNamespaces) > 0 {
|
|
||||||
ply.SrcNamespaces = strings.Split(policy.SrcNamespaces, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. parse Filters
|
|
||||||
filters, err := parseFilters(policy.Filters)
|
filters, err := parseFilters(policy.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ply.Filters = filters
|
ply.Filters = filters
|
||||||
|
|
||||||
// 3. parse Trigger
|
// parse Trigger
|
||||||
trigger, err := parseTrigger(policy.Trigger)
|
trigger, err := parseTrigger(policy.Trigger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -90,7 +84,6 @@ func convertToPersistModel(policy *model.Policy) (*persist_models.RepPolicy, err
|
|||||||
Name: policy.Name,
|
Name: policy.Name,
|
||||||
Description: policy.Description,
|
Description: policy.Description,
|
||||||
Creator: policy.Creator,
|
Creator: policy.Creator,
|
||||||
SrcNamespaces: strings.Join(policy.SrcNamespaces, ","),
|
|
||||||
DestNamespace: policy.DestNamespace,
|
DestNamespace: policy.DestNamespace,
|
||||||
Override: policy.Override,
|
Override: policy.Override,
|
||||||
Enabled: policy.Enabled,
|
Enabled: policy.Enabled,
|
||||||
|
@ -54,7 +54,6 @@ func Test_convertFromPersistModel(t *testing.T) {
|
|||||||
Description: "Policy Description",
|
Description: "Policy Description",
|
||||||
Creator: "someone",
|
Creator: "someone",
|
||||||
SrcRegistryID: 123,
|
SrcRegistryID: 123,
|
||||||
SrcNamespaces: "ns1,ns2,ns3",
|
|
||||||
DestRegistryID: 456,
|
DestRegistryID: 456,
|
||||||
DestNamespace: "target_ns",
|
DestNamespace: "target_ns",
|
||||||
ReplicateDeletion: true,
|
ReplicateDeletion: true,
|
||||||
@ -70,7 +69,6 @@ func Test_convertFromPersistModel(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
||||||
DestRegistry: &model.Registry{
|
DestRegistry: &model.Registry{
|
||||||
ID: 456,
|
ID: 456,
|
||||||
},
|
},
|
||||||
@ -103,7 +101,6 @@ func Test_convertFromPersistModel(t *testing.T) {
|
|||||||
assert.Equal(t, tt.want.Description, got.Description)
|
assert.Equal(t, tt.want.Description, got.Description)
|
||||||
assert.Equal(t, tt.want.Creator, got.Creator)
|
assert.Equal(t, tt.want.Creator, got.Creator)
|
||||||
assert.Equal(t, tt.want.SrcRegistry.ID, got.SrcRegistry.ID)
|
assert.Equal(t, tt.want.SrcRegistry.ID, got.SrcRegistry.ID)
|
||||||
assert.Equal(t, tt.want.SrcNamespaces, got.SrcNamespaces)
|
|
||||||
assert.Equal(t, tt.want.DestRegistry.ID, got.DestRegistry.ID)
|
assert.Equal(t, tt.want.DestRegistry.ID, got.DestRegistry.ID)
|
||||||
assert.Equal(t, tt.want.DestNamespace, got.DestNamespace)
|
assert.Equal(t, tt.want.DestNamespace, got.DestNamespace)
|
||||||
assert.Equal(t, tt.want.Deletion, got.Deletion)
|
assert.Equal(t, tt.want.Deletion, got.Deletion)
|
||||||
@ -133,7 +130,6 @@ func Test_convertToPersistModel(t *testing.T) {
|
|||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
},
|
},
|
||||||
SrcNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
||||||
DestRegistry: &model.Registry{
|
DestRegistry: &model.Registry{
|
||||||
ID: 456,
|
ID: 456,
|
||||||
},
|
},
|
||||||
@ -149,7 +145,6 @@ func Test_convertToPersistModel(t *testing.T) {
|
|||||||
Description: "Policy Description",
|
Description: "Policy Description",
|
||||||
Creator: "someone",
|
Creator: "someone",
|
||||||
SrcRegistryID: 123,
|
SrcRegistryID: 123,
|
||||||
SrcNamespaces: "ns1,ns2,ns3",
|
|
||||||
DestRegistryID: 456,
|
DestRegistryID: 456,
|
||||||
DestNamespace: "target_ns",
|
DestNamespace: "target_ns",
|
||||||
ReplicateDeletion: true,
|
ReplicateDeletion: true,
|
||||||
@ -175,7 +170,6 @@ func Test_convertToPersistModel(t *testing.T) {
|
|||||||
assert.Equal(t, tt.want.Description, got.Description)
|
assert.Equal(t, tt.want.Description, got.Description)
|
||||||
assert.Equal(t, tt.want.Creator, got.Creator)
|
assert.Equal(t, tt.want.Creator, got.Creator)
|
||||||
assert.Equal(t, tt.want.SrcRegistryID, got.SrcRegistryID)
|
assert.Equal(t, tt.want.SrcRegistryID, got.SrcRegistryID)
|
||||||
assert.Equal(t, tt.want.SrcNamespaces, got.SrcNamespaces)
|
|
||||||
assert.Equal(t, tt.want.DestRegistryID, got.DestRegistryID)
|
assert.Equal(t, tt.want.DestRegistryID, got.DestRegistryID)
|
||||||
assert.Equal(t, tt.want.DestNamespace, got.DestNamespace)
|
assert.Equal(t, tt.want.DestNamespace, got.DestNamespace)
|
||||||
assert.Equal(t, tt.want.ReplicateDeletion, got.ReplicateDeletion)
|
assert.Equal(t, tt.want.ReplicateDeletion, got.ReplicateDeletion)
|
||||||
|
@ -28,9 +28,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/replication/registry"
|
"github.com/goharbor/harbor/src/replication/registry"
|
||||||
|
|
||||||
// register the Harbor adapter
|
// register the Harbor adapter
|
||||||
_ "github.com/goharbor/harbor/src/replication/adapter/dockerhub"
|
|
||||||
// register the DockerHub adapter
|
|
||||||
_ "github.com/goharbor/harbor/src/replication/adapter/harbor"
|
_ "github.com/goharbor/harbor/src/replication/adapter/harbor"
|
||||||
|
// register the DockerHub adapter
|
||||||
|
_ "github.com/goharbor/harbor/src/replication/adapter/dockerhub"
|
||||||
// register the Native adapter
|
// register the Native adapter
|
||||||
_ "github.com/goharbor/harbor/src/replication/adapter/native"
|
_ "github.com/goharbor/harbor/src/replication/adapter/native"
|
||||||
)
|
)
|
||||||
|
@ -29,16 +29,13 @@ import (
|
|||||||
|
|
||||||
type fakeRegistry struct{}
|
type fakeRegistry struct{}
|
||||||
|
|
||||||
func (f *fakeRegistry) FetchCharts(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (f *fakeRegistry) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
return []*model.Resource{
|
return []*model.Resource{
|
||||||
{
|
{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Namespace: &model.Namespace{
|
|
||||||
Name: "library",
|
|
||||||
},
|
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: "harbor",
|
Name: "library/harbor",
|
||||||
},
|
},
|
||||||
Vtags: []string{"0.2.0"},
|
Vtags: []string{"0.2.0"},
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
type fakeRegistry struct{}
|
type fakeRegistry struct{}
|
||||||
|
|
||||||
func (f *fakeRegistry) FetchImages([]string, []*model.Filter) ([]*model.Resource, error) {
|
func (f *fakeRegistry) FetchImages([]*model.Filter) ([]*model.Resource, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||||
)
|
)
|
||||||
@ -30,3 +31,19 @@ func Match(pattern, str string) (bool, error) {
|
|||||||
func GetHTTPTransport(insecure bool) *http.Transport {
|
func GetHTTPTransport(insecure bool) *http.Transport {
|
||||||
return registry.GetHTTPTransport(insecure)
|
return registry.GetHTTPTransport(insecure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseRepository parses the "repository" provided into two parts: namespace and the rest
|
||||||
|
// the string before the last "/" is the namespace part
|
||||||
|
// c -> [,c]
|
||||||
|
// b/c -> [b,c]
|
||||||
|
// a/b/c -> [a/b,c]
|
||||||
|
func ParseRepository(repository string) (string, string) {
|
||||||
|
if len(repository) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
index := strings.LastIndex(repository, "/")
|
||||||
|
if index == -1 {
|
||||||
|
return "", repository
|
||||||
|
}
|
||||||
|
return repository[:index], repository[index+1:]
|
||||||
|
}
|
||||||
|
@ -82,3 +82,26 @@ func TestGetHTTPTransport(t *testing.T) {
|
|||||||
transport = GetHTTPTransport(false)
|
transport = GetHTTPTransport(false)
|
||||||
assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRepository(t *testing.T) {
|
||||||
|
// empty repository
|
||||||
|
repository := ""
|
||||||
|
namespace, rest := ParseRepository(repository)
|
||||||
|
assert.Equal(t, "", namespace)
|
||||||
|
assert.Equal(t, "", rest)
|
||||||
|
// repository contains no "/"
|
||||||
|
repository = "c"
|
||||||
|
namespace, rest = ParseRepository(repository)
|
||||||
|
assert.Equal(t, "", namespace)
|
||||||
|
assert.Equal(t, "c", rest)
|
||||||
|
// repository contains only one "/"
|
||||||
|
repository = "b/c"
|
||||||
|
namespace, rest = ParseRepository(repository)
|
||||||
|
assert.Equal(t, "b", namespace)
|
||||||
|
assert.Equal(t, "c", rest)
|
||||||
|
// repository contains more than one "/"
|
||||||
|
repository = "a/b/c"
|
||||||
|
namespace, rest = ParseRepository(repository)
|
||||||
|
assert.Equal(t, "a/b", namespace)
|
||||||
|
assert.Equal(t, "c", rest)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user