mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-27 19:17:47 +01:00
Declare the capability of supported repository path component for registries
Declare the capability of supported repository path component for registries fixes #14981 fixes #14980 Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
37d161c141
commit
741793a553
@ -78,7 +78,11 @@ func (c *copyFlow) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srcResources = assembleSourceResources(srcResources, c.policy)
|
srcResources = assembleSourceResources(srcResources, c.policy)
|
||||||
dstResources, err := assembleDestinationResources(srcResources, c.policy)
|
info, err := dstAdapter.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dstResources, err := assembleDestinationResources(srcResources, c.policy, info.SupportedRepositoryPathComponentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,16 @@ func NewDeletionFlow(executionID int64, policy *repctlmodel.Policy, resources ..
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *deletionFlow) Run(ctx context.Context) error {
|
func (d *deletionFlow) Run(ctx context.Context) error {
|
||||||
|
_, dstAdapter, err := initialize(d.policy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
srcResources := assembleSourceResources(d.resources, d.policy)
|
srcResources := assembleSourceResources(d.resources, d.policy)
|
||||||
dstResources, err := assembleDestinationResources(srcResources, d.policy)
|
info, err := dstAdapter.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dstResources, err := assembleDestinationResources(srcResources, d.policy, info.SupportedRepositoryPathComponentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package flow
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/reg/adapter"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
|
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
|
||||||
@ -30,15 +31,27 @@ type deletionFlowTestSuite struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *deletionFlowTestSuite) TestRun() {
|
func (d *deletionFlowTestSuite) TestRun() {
|
||||||
|
adp := &mockAdapter{}
|
||||||
|
factory := &mockFactory{}
|
||||||
|
factory.On("AdapterPattern").Return(nil)
|
||||||
|
factory.On("Create", mock.Anything).Return(adp, nil)
|
||||||
|
adapter.RegisterFactory("TEST_FOR_DELETION_FLOW", factory)
|
||||||
|
|
||||||
|
adp.On("Info").Return(&model.RegistryInfo{
|
||||||
|
SupportedResourceTypes: []string{
|
||||||
|
model.ResourceTypeArtifact,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
taskMgr := &task.Manager{}
|
taskMgr := &task.Manager{}
|
||||||
taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||||
|
|
||||||
policy := &repctlmodel.Policy{
|
policy := &repctlmodel.Policy{
|
||||||
SrcRegistry: &model.Registry{
|
SrcRegistry: &model.Registry{
|
||||||
Type: model.RegistryTypeHarbor,
|
Type: "TEST_FOR_DELETION_FLOW",
|
||||||
},
|
},
|
||||||
DestRegistry: &model.Registry{
|
DestRegistry: &model.Registry{
|
||||||
Type: model.RegistryTypeHarbor,
|
Type: "TEST_FOR_DELETION_FLOW",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resources := []*model.Resource{
|
resources := []*model.Resource{
|
||||||
|
@ -124,10 +124,10 @@ func assembleSourceResources(resources []*model.Resource,
|
|||||||
|
|
||||||
// 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(resources []*model.Resource,
|
func assembleDestinationResources(resources []*model.Resource,
|
||||||
policy *repctlmodel.Policy) ([]*model.Resource, error) {
|
policy *repctlmodel.Policy, dstRepoComponentPathType string) ([]*model.Resource, error) {
|
||||||
var result []*model.Resource
|
var result []*model.Resource
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
name, err := replaceNamespace(resource.Metadata.Repository.Name, policy.DestNamespace, policy.DestNamespaceReplaceCount)
|
name, err := replaceNamespace(resource.Metadata.Repository.Name, policy.DestNamespace, policy.DestNamespaceReplaceCount, dstRepoComponentPathType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -197,7 +197,7 @@ func getResourceName(res *model.Resource) string {
|
|||||||
// repository:a/b/c namespace:n replaceCount: 1 -> n/b/c
|
// repository:a/b/c namespace:n replaceCount: 1 -> n/b/c
|
||||||
// repository:a/b/c namespace:n replaceCount: 2 -> n/c
|
// repository:a/b/c namespace:n replaceCount: 2 -> n/c
|
||||||
// repository:a/b/c namespace:n replaceCount: 3 -> n
|
// repository:a/b/c namespace:n replaceCount: 3 -> n
|
||||||
func replaceNamespace(repository string, namespace string, replaceCount int8) (string, error) {
|
func replaceNamespace(repository string, namespace string, replaceCount int8, dstRepoComponentPathType string) (string, error) {
|
||||||
if len(namespace) == 0 {
|
if len(namespace) == 0 {
|
||||||
return repository, nil
|
return repository, nil
|
||||||
}
|
}
|
||||||
@ -208,18 +208,36 @@ func replaceNamespace(repository string, namespace string, replaceCount int8) (s
|
|||||||
return fmt.Sprintf("%s/%s", namespace, rest), nil
|
return fmt.Sprintf("%s/%s", namespace, rest), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subs := strings.Split(repository, "/")
|
var dstRepo string
|
||||||
len := len(subs)
|
srcRepoPathComponents := strings.Split(repository, "/")
|
||||||
|
srcLength := len(srcRepoPathComponents)
|
||||||
switch {
|
switch {
|
||||||
case replaceCount == 0:
|
case replaceCount == 0:
|
||||||
return fmt.Sprintf("%s/%s", namespace, repository), nil
|
dstRepo = fmt.Sprintf("%s/%s", namespace, repository)
|
||||||
case int(replaceCount) == len:
|
case int(replaceCount) == srcLength:
|
||||||
return namespace, nil
|
dstRepo = namespace
|
||||||
case int(replaceCount) > len:
|
case int(replaceCount) > srcLength:
|
||||||
return "", errors.New(nil).WithCode(errors.BadRequestCode).
|
return "", errors.New(nil).WithCode(errors.BadRequestCode).
|
||||||
WithMessage("the repository %s contains only %d substrings, but the destination namespace replace count is %d",
|
WithMessage("the source repository %q contains only %d path components %v, but the destination namespace flattening level is %d",
|
||||||
repository, len, replaceCount)
|
repository, srcLength, srcRepoPathComponents, replaceCount)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%s/%s", namespace, strings.Join(subs[replaceCount:], "/")), nil
|
dstRepo = fmt.Sprintf("%s/%s", namespace, strings.Join(srcRepoPathComponents[replaceCount:], "/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dstRepoPathComponents := strings.Split(dstRepo, "/")
|
||||||
|
dstLength := len(dstRepoPathComponents)
|
||||||
|
switch dstRepoComponentPathType {
|
||||||
|
case model.RepositoryPathComponentTypeOnlyTwo:
|
||||||
|
if dstLength != 2 {
|
||||||
|
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("the destination repository %q contains %d path components %v, but the destination registry only supports 2",
|
||||||
|
dstRepo, dstLength, dstRepoPathComponents)
|
||||||
|
}
|
||||||
|
case model.RepositoryPathComponentTypeAtLeastTwo:
|
||||||
|
if dstLength < 2 {
|
||||||
|
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("the destination repository %q contains only %d path components %v, but the destination registry requires at least 2",
|
||||||
|
dstRepo, dstLength, dstRepoPathComponents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dstRepo, nil
|
||||||
|
}
|
||||||
|
@ -110,7 +110,7 @@ func (s *stageTestSuite) TestAssembleDestinationResources() {
|
|||||||
DestNamespaceReplaceCount: -1,
|
DestNamespaceReplaceCount: -1,
|
||||||
Override: true,
|
Override: true,
|
||||||
}
|
}
|
||||||
res, err := assembleDestinationResources(resources, policy)
|
res, err := assembleDestinationResources(resources, policy, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Len(res, 1)
|
s.Len(res, 1)
|
||||||
s.Equal(model.ResourceTypeChart, res[0].Type)
|
s.Equal(model.ResourceTypeChart, res[0].Type)
|
||||||
@ -126,7 +126,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
namespace string = ""
|
namespace string = ""
|
||||||
replaceCount int8 = 0
|
replaceCount int8 = 0
|
||||||
)
|
)
|
||||||
result, err := replaceNamespace(repository, namespace, replaceCount)
|
result, err := replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("c", result)
|
s.Equal("c", result)
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "c"
|
repository = "c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = -1
|
replaceCount = -1
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/c", result)
|
s.Equal("n/c", result)
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "b/c"
|
repository = "b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = -1
|
replaceCount = -1
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/c", result)
|
s.Equal("n/c", result)
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "a/b/c"
|
repository = "a/b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = -1
|
replaceCount = -1
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/c", result)
|
s.Equal("n/c", result)
|
||||||
|
|
||||||
@ -158,14 +158,14 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "a/b"
|
repository = "a/b"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = 3
|
replaceCount = 3
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().NotNil(err)
|
s.Require().NotNil(err)
|
||||||
|
|
||||||
// replace count = 0
|
// replace count = 0
|
||||||
repository = "a/b/c"
|
repository = "a/b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = 0
|
replaceCount = 0
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/a/b/c", result)
|
s.Equal("n/a/b/c", result)
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "a/b/c"
|
repository = "a/b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = 1
|
replaceCount = 1
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/b/c", result)
|
s.Equal("n/b/c", result)
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "a/b/c"
|
repository = "a/b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = 2
|
replaceCount = 2
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n/c", result)
|
s.Equal("n/c", result)
|
||||||
|
|
||||||
@ -189,10 +189,16 @@ func (s *stageTestSuite) TestReplaceNamespace() {
|
|||||||
repository = "a/b/c"
|
repository = "a/b/c"
|
||||||
namespace = "n"
|
namespace = "n"
|
||||||
replaceCount = 3
|
replaceCount = 3
|
||||||
result, err = replaceNamespace(repository, namespace, replaceCount)
|
result, err = replaceNamespace(repository, namespace, replaceCount, "")
|
||||||
s.Require().Nil(err)
|
s.Require().Nil(err)
|
||||||
s.Equal("n", result)
|
s.Equal("n", result)
|
||||||
|
|
||||||
|
// the generated destination namespace contains 1 path component, but the destination registry requires at least 2
|
||||||
|
repository = "a/b/c"
|
||||||
|
namespace = "n"
|
||||||
|
replaceCount = 3
|
||||||
|
result, err = replaceNamespace(repository, namespace, replaceCount, model.RepositoryPathComponentTypeAtLeastTwo)
|
||||||
|
s.Require().NotNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStage(t *testing.T) {
|
func TestStage(t *testing.T) {
|
||||||
|
@ -90,6 +90,7 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
model.TriggerTypeManual,
|
model.TriggerTypeManual,
|
||||||
model.TriggerTypeScheduled,
|
model.TriggerTypeScheduled,
|
||||||
},
|
},
|
||||||
|
SupportedRepositoryPathComponentType: model.RepositoryPathComponentTypeOnlyTwo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func TestInfo(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, 1, len(info.SupportedResourceTypes))
|
require.Equal(t, 1, len(info.SupportedResourceTypes))
|
||||||
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
|
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
|
||||||
|
assert.Equal(t, model.RepositoryPathComponentTypeOnlyTwo, info.SupportedRepositoryPathComponentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListCandidateNamespaces(t *testing.T) {
|
func TestListCandidateNamespaces(t *testing.T) {
|
||||||
|
@ -114,6 +114,7 @@ func (a *Adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
model.TriggerTypeManual,
|
model.TriggerTypeManual,
|
||||||
model.TriggerTypeScheduled,
|
model.TriggerTypeScheduled,
|
||||||
},
|
},
|
||||||
|
SupportedRepositoryPathComponentType: model.RepositoryPathComponentTypeAtLeastTwo,
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled, err := a.Client.ChartRegistryEnabled()
|
enabled, err := a.Client.ChartRegistryEnabled()
|
||||||
|
@ -55,6 +55,7 @@ func TestInfo(t *testing.T) {
|
|||||||
assert.Equal(t, 2, len(info.SupportedResourceTypes))
|
assert.Equal(t, 2, len(info.SupportedResourceTypes))
|
||||||
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
|
assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0])
|
||||||
assert.Equal(t, model.ResourceTypeChart, info.SupportedResourceTypes[1])
|
assert.Equal(t, model.ResourceTypeChart, info.SupportedResourceTypes[1])
|
||||||
|
assert.Equal(t, model.RepositoryPathComponentTypeAtLeastTwo, info.SupportedRepositoryPathComponentType)
|
||||||
server.Close()
|
server.Close()
|
||||||
|
|
||||||
// chart museum disabled
|
// chart museum disabled
|
||||||
|
@ -70,6 +70,9 @@ const (
|
|||||||
Healthy = "healthy"
|
Healthy = "healthy"
|
||||||
// Unhealthy indicates registry is unhealthy
|
// Unhealthy indicates registry is unhealthy
|
||||||
Unhealthy = "unhealthy"
|
Unhealthy = "unhealthy"
|
||||||
|
|
||||||
|
RepositoryPathComponentTypeOnlyTwo = "ONLY_TWO"
|
||||||
|
RepositoryPathComponentTypeAtLeastTwo = "AT_LEAST_TWO"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Credential keeps the access key and/or secret for the related registry
|
// Credential keeps the access key and/or secret for the related registry
|
||||||
@ -135,6 +138,7 @@ type RegistryInfo struct {
|
|||||||
SupportedResourceTypes []string `json:"-"`
|
SupportedResourceTypes []string `json:"-"`
|
||||||
SupportedResourceFilters []*FilterStyle `json:"supported_resource_filters"`
|
SupportedResourceFilters []*FilterStyle `json:"supported_resource_filters"`
|
||||||
SupportedTriggers []string `json:"supported_triggers"`
|
SupportedTriggers []string `json:"supported_triggers"`
|
||||||
|
SupportedRepositoryPathComponentType string `json:"supported_repository_path_component_type"` // how many path components are allowed in the repository name
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdapterPattern provides base info and capability declarations of the registry
|
// AdapterPattern provides base info and capability declarations of the registry
|
||||||
|
Loading…
Reference in New Issue
Block a user