mirror of
https://github.com/goharbor/harbor.git
synced 2024-08-26 05:53:11 +02:00
Merge pull request #8317 from steven-zou/feature/retention-selectors
implement label and regexp selectors
This commit is contained in:
commit
2d62b2eb33
|
@ -27,7 +27,7 @@ import (
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/q"
|
"github.com/goharbor/harbor/src/pkg/retention/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||||
_ "github.com/goharbor/harbor/src/pkg/retention/res/selectors/regexp"
|
_ "github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
|
@ -21,8 +21,8 @@ import (
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/lastx"
|
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/lastx"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestk"
|
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestk"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/regexp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -73,7 +73,7 @@ func (suite *ProcessorTestSuite) SetupSuite() {
|
||||||
params = append(params, &alg.Parameter{
|
params = append(params, &alg.Parameter{
|
||||||
Evaluator: lastx.New(lastxParams),
|
Evaluator: lastx.New(lastxParams),
|
||||||
Selectors: []res.Selector{
|
Selectors: []res.Selector{
|
||||||
regexp.New(regexp.Matches, "*dev*"),
|
doublestar.New(doublestar.Matches, "*dev*"),
|
||||||
label.New(label.With, "L1,L2"),
|
label.New(label.With, "L1,L2"),
|
||||||
},
|
},
|
||||||
Performer: perf,
|
Performer: perf,
|
||||||
|
|
99
src/pkg/retention/res/selectors/doublestar/selector.go
Normal file
99
src/pkg/retention/res/selectors/doublestar/selector.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package doublestar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bmatcuk/doublestar"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Kind ...
|
||||||
|
Kind = "doublestar"
|
||||||
|
// Matches [pattern] for tag (default)
|
||||||
|
Matches = "matches"
|
||||||
|
// Excludes [pattern] for tag (default)
|
||||||
|
Excludes = "excludes"
|
||||||
|
// RepoMatches represents repository matches [pattern]
|
||||||
|
RepoMatches = "repoMatches"
|
||||||
|
// RepoExcludes represents repository excludes [pattern]
|
||||||
|
RepoExcludes = "repoExcludes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// selector for regular expression
|
||||||
|
type selector struct {
|
||||||
|
// Pre defined pattern declarator
|
||||||
|
// "matches", "excludes", "repoMatches" or "repoExcludes"
|
||||||
|
decoration string
|
||||||
|
// The pattern expression
|
||||||
|
pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select candidates by regular expressions
|
||||||
|
func (s *selector) Select(artifacts []*res.Candidate) (selected []*res.Candidate, err error) {
|
||||||
|
value := ""
|
||||||
|
excludes := false
|
||||||
|
|
||||||
|
for _, art := range artifacts {
|
||||||
|
switch s.decoration {
|
||||||
|
case Matches:
|
||||||
|
value = art.Tag
|
||||||
|
case Excludes:
|
||||||
|
value = art.Tag
|
||||||
|
excludes = true
|
||||||
|
case RepoMatches:
|
||||||
|
value = art.Repository
|
||||||
|
case RepoExcludes:
|
||||||
|
value = art.Repository
|
||||||
|
excludes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(value) > 0 {
|
||||||
|
matched, err := match(s.pattern, value)
|
||||||
|
if err != nil {
|
||||||
|
// if error occurred, directly throw it out
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched && !excludes) || (!matched && excludes) {
|
||||||
|
selected = append(selected, art)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New is factory method for doublestar selector
|
||||||
|
func New(decoration string, pattern string) res.Selector {
|
||||||
|
return &selector{
|
||||||
|
decoration: decoration,
|
||||||
|
pattern: pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match returns whether the str matches the pattern
|
||||||
|
func match(pattern, str string) (bool, error) {
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return doublestar.Match(pattern, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register doublestar selector
|
||||||
|
selectors.Register(Kind, []string{Matches, Excludes}, New)
|
||||||
|
}
|
198
src/pkg/retention/res/selectors/doublestar/selector_test.go
Normal file
198
src/pkg/retention/res/selectors/doublestar/selector_test.go
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package doublestar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegExpSelectorTestSuite is a suite for testing the label selector
|
||||||
|
type RegExpSelectorTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
artifacts []*res.Candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRegExpSelector is entrance for RegExpSelectorTestSuite
|
||||||
|
func TestRegExpSelector(t *testing.T) {
|
||||||
|
suite.Run(t, new(RegExpSelectorTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSuite to do preparation work
|
||||||
|
func (suite *RegExpSelectorTestSuite) SetupSuite() {
|
||||||
|
suite.artifacts = []*res.Candidate{
|
||||||
|
{
|
||||||
|
NamespaceID: 1,
|
||||||
|
Namespace: "library",
|
||||||
|
Repository: "harbor",
|
||||||
|
Tag: "latest",
|
||||||
|
Kind: res.Image,
|
||||||
|
PushedTime: time.Now().Unix() - 3600,
|
||||||
|
PulledTime: time.Now().Unix(),
|
||||||
|
CreationTime: time.Now().Unix() - 7200,
|
||||||
|
Labels: []string{"label1", "label2", "label3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamespaceID: 1,
|
||||||
|
Namespace: "library",
|
||||||
|
Repository: "redis",
|
||||||
|
Tag: "4.0",
|
||||||
|
Kind: res.Image,
|
||||||
|
PushedTime: time.Now().Unix() - 3600,
|
||||||
|
PulledTime: time.Now().Unix(),
|
||||||
|
CreationTime: time.Now().Unix() - 7200,
|
||||||
|
Labels: []string{"label1", "label4", "label5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamespaceID: 1,
|
||||||
|
Namespace: "library",
|
||||||
|
Repository: "redis",
|
||||||
|
Tag: "4.1",
|
||||||
|
Kind: res.Image,
|
||||||
|
PushedTime: time.Now().Unix() - 3600,
|
||||||
|
PulledTime: time.Now().Unix(),
|
||||||
|
CreationTime: time.Now().Unix() - 7200,
|
||||||
|
Labels: []string{"label1", "label4", "label5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTagMatches tests the tag `matches` case
|
||||||
|
func (suite *RegExpSelectorTestSuite) TestTagMatches() {
|
||||||
|
tagMatches := &selector{
|
||||||
|
decoration: Matches,
|
||||||
|
pattern: "{latest,4.*}",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := tagMatches.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 3, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:latest", "redis:4.0", "redis:4.1"}, selected)
|
||||||
|
})
|
||||||
|
|
||||||
|
tagMatches2 := &selector{
|
||||||
|
decoration: Matches,
|
||||||
|
pattern: "4.*",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err = tagMatches2.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 2, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"redis:4.0", "redis:4.1"}, selected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTagExcludes tests the tag `excludes` case
|
||||||
|
func (suite *RegExpSelectorTestSuite) TestTagExcludes() {
|
||||||
|
tagExcludes := &selector{
|
||||||
|
decoration: Excludes,
|
||||||
|
pattern: "{latest,4.*}",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := tagExcludes.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 0, len(selected))
|
||||||
|
|
||||||
|
tagExcludes2 := &selector{
|
||||||
|
decoration: Excludes,
|
||||||
|
pattern: "4.*",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err = tagExcludes2.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 1, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:latest"}, selected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRepoMatches tests the repository `matches` case
|
||||||
|
func (suite *RegExpSelectorTestSuite) TestRepoMatches() {
|
||||||
|
repoMatches := &selector{
|
||||||
|
decoration: RepoMatches,
|
||||||
|
pattern: "{redis}",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := repoMatches.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 2, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"redis:4.0", "redis:4.1"}, selected)
|
||||||
|
})
|
||||||
|
|
||||||
|
repoMatches2 := &selector{
|
||||||
|
decoration: RepoMatches,
|
||||||
|
pattern: "har*",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err = repoMatches2.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 1, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:latest"}, selected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRepoExcludes tests the repository `excludes` case
|
||||||
|
func (suite *RegExpSelectorTestSuite) TestRepoExcludes() {
|
||||||
|
repoExcludes := &selector{
|
||||||
|
decoration: RepoExcludes,
|
||||||
|
pattern: "{redis}",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := repoExcludes.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 1, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:latest"}, selected)
|
||||||
|
})
|
||||||
|
|
||||||
|
repoExcludes2 := &selector{
|
||||||
|
decoration: RepoExcludes,
|
||||||
|
pattern: "har*",
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err = repoExcludes2.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 2, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"redis:4.0", "redis:4.1"}, selected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the returned result matched the expected ones (only check repo:tag)
|
||||||
|
func expect(expected []string, candidates []*res.Candidate) bool {
|
||||||
|
hash := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, art := range candidates {
|
||||||
|
hash[fmt.Sprintf("%s:%s", art.Repository, art.Tag)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, exp := range expected {
|
||||||
|
if _, ok := hash[exp]; !ok {
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -38,10 +38,15 @@ type selector struct {
|
||||||
labels []string
|
labels []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select candidates by regular expressions
|
// Select candidates by the labels
|
||||||
func (s *selector) Select(artifacts []*res.Candidate) ([]*res.Candidate, error) {
|
func (s *selector) Select(artifacts []*res.Candidate) (selected []*res.Candidate, err error) {
|
||||||
// TODO: REPLACE SAMPLE CODE WITH REAL IMPLEMENTATION
|
for _, art := range artifacts {
|
||||||
return artifacts, nil
|
if isMatched(s.labels, art.Labels, s.decoration) {
|
||||||
|
selected = append(selected, art)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New is factory method for list selector
|
// New is factory method for list selector
|
||||||
|
@ -54,7 +59,30 @@ func New(decoration string, pattern string) res.Selector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the resource labels match the pattern labels
|
||||||
|
func isMatched(patternLbls []string, resLbls []string, decoration string) bool {
|
||||||
|
hash := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, lbl := range resLbls {
|
||||||
|
hash[lbl] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lbl := range patternLbls {
|
||||||
|
_, exists := hash[lbl]
|
||||||
|
|
||||||
|
if decoration == Without && exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if decoration == With && !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Register regexp selector
|
// Register doublestar selector
|
||||||
selectors.Register(Kind, []string{With, Without}, New)
|
selectors.Register(Kind, []string{With, Without}, New)
|
||||||
}
|
}
|
||||||
|
|
148
src/pkg/retention/res/selectors/label/selector_test.go
Normal file
148
src/pkg/retention/res/selectors/label/selector_test.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package label
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelSelectorTestSuite is a suite for testing the label selector
|
||||||
|
type LabelSelectorTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
artifacts []*res.Candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLabelSelector is entrance for LabelSelectorTestSuite
|
||||||
|
func TestLabelSelector(t *testing.T) {
|
||||||
|
suite.Run(t, new(LabelSelectorTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSuite to do preparation work
|
||||||
|
func (suite *LabelSelectorTestSuite) SetupSuite() {
|
||||||
|
suite.artifacts = []*res.Candidate{
|
||||||
|
{
|
||||||
|
NamespaceID: 1,
|
||||||
|
Namespace: "library",
|
||||||
|
Repository: "harbor",
|
||||||
|
Tag: "1.9",
|
||||||
|
Kind: res.Image,
|
||||||
|
PushedTime: time.Now().Unix() - 3600,
|
||||||
|
PulledTime: time.Now().Unix(),
|
||||||
|
CreationTime: time.Now().Unix() - 7200,
|
||||||
|
Labels: []string{"label1", "label2", "label3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamespaceID: 1,
|
||||||
|
Namespace: "library",
|
||||||
|
Repository: "harbor",
|
||||||
|
Tag: "dev",
|
||||||
|
Kind: res.Image,
|
||||||
|
PushedTime: time.Now().Unix() - 3600,
|
||||||
|
PulledTime: time.Now().Unix(),
|
||||||
|
CreationTime: time.Now().Unix() - 7200,
|
||||||
|
Labels: []string{"label1", "label4", "label5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithLabelsUnMatched tests the selector of `with` labels but nothing matched
|
||||||
|
func (suite *LabelSelectorTestSuite) TestWithLabelsUnMatched() {
|
||||||
|
withNothing := &selector{
|
||||||
|
decoration: With,
|
||||||
|
labels: []string{"label6"},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := withNothing.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 0, len(selected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithLabelsMatched tests the selector of `with` labels and matched something
|
||||||
|
func (suite *LabelSelectorTestSuite) TestWithLabelsMatched() {
|
||||||
|
with1 := &selector{
|
||||||
|
decoration: With,
|
||||||
|
labels: []string{"label2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := with1.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 1, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:1.9"}, selected)
|
||||||
|
})
|
||||||
|
|
||||||
|
with2 := &selector{
|
||||||
|
decoration: With,
|
||||||
|
labels: []string{"label1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected2, err := with2.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 2, len(selected2))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:1.9", "harbor:dev"}, selected2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithoutExistingLabels tests the selector of `without` existing labels
|
||||||
|
func (suite *LabelSelectorTestSuite) TestWithoutExistingLabels() {
|
||||||
|
withoutExisting := &selector{
|
||||||
|
decoration: Without,
|
||||||
|
labels: []string{"label1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := withoutExisting.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 0, len(selected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithoutNoneExistingLabels tests the selector of `without` non-existing labels
|
||||||
|
func (suite *LabelSelectorTestSuite) TestWithoutNoneExistingLabels() {
|
||||||
|
withoutNonExisting := &selector{
|
||||||
|
decoration: Without,
|
||||||
|
labels: []string{"label6"},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := withoutNonExisting.Select(suite.artifacts)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
assert.Equal(suite.T(), 2, len(selected))
|
||||||
|
assert.Condition(suite.T(), func() bool {
|
||||||
|
return expect([]string{"harbor:1.9", "harbor:dev"}, selected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the returned result matched the expected ones (only check repo:tag)
|
||||||
|
func expect(expected []string, candidates []*res.Candidate) bool {
|
||||||
|
hash := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, art := range candidates {
|
||||||
|
hash[fmt.Sprintf("%s:%s", art.Repository, art.Tag)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, exp := range expected {
|
||||||
|
if _, ok := hash[exp]; !ok {
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -1,57 +0,0 @@
|
||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package regexp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Kind ...
|
|
||||||
Kind = "regularExpression"
|
|
||||||
// Matches [pattern]
|
|
||||||
Matches = "matches"
|
|
||||||
// Excludes [pattern]
|
|
||||||
Excludes = "excludes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// selector for regular expression
|
|
||||||
type selector struct {
|
|
||||||
// Pre defined pattern declarator
|
|
||||||
// "matches" and "excludes"
|
|
||||||
decoration string
|
|
||||||
// The pattern expression
|
|
||||||
pattern string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select candidates by regular expressions
|
|
||||||
func (s *selector) Select(artifacts []*res.Candidate) ([]*res.Candidate, error) {
|
|
||||||
// TODO: REPLACE SAMPLE CODE WITH REAL IMPLEMENTATION
|
|
||||||
return artifacts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New is factory method for regexp selector
|
|
||||||
func New(decoration string, pattern string) res.Selector {
|
|
||||||
return &selector{
|
|
||||||
decoration: decoration,
|
|
||||||
pattern: pattern,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Register regexp selector
|
|
||||||
selectors.Register(Kind, []string{Matches, Excludes}, New)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user