Add label filter to filter chain

Label filters are used to filter the resources according to the labels, adding the filters to the chain to enable the function.
This commit is contained in:
Wenkai Yin 79628 2018-05-14 15:58:55 +08:00
parent 92782abce9
commit c1ebf0ba1e
9 changed files with 198 additions and 20 deletions

View File

@ -7,6 +7,8 @@ const (
FilterItemKindRepository = "repository" FilterItemKindRepository = "repository"
//FilterItemKindTag : Kind of filter item is 'tag' //FilterItemKindTag : Kind of filter item is 'tag'
FilterItemKindTag = "tag" FilterItemKindTag = "tag"
//FilterItemKindLabel : Kind of filter item is 'label'
FilterItemKindLabel = "label"
//AdaptorKindHarbor : Kind of adaptor of Harbor //AdaptorKindHarbor : Kind of adaptor of Harbor
AdaptorKindHarbor = "Harbor" AdaptorKindHarbor = "Harbor"

View File

@ -259,17 +259,34 @@ func getCandidates(policy *models.ReplicationPolicy, sourcer *source.Sourcer,
func buildFilterChain(policy *models.ReplicationPolicy, sourcer *source.Sourcer) source.FilterChain { func buildFilterChain(policy *models.ReplicationPolicy, sourcer *source.Sourcer) source.FilterChain {
filters := []source.Filter{} filters := []source.Filter{}
patterns := map[string]string{} fm := map[string][]models.Filter{}
for _, f := range policy.Filters { for _, filter := range policy.Filters {
patterns[f.Kind] = f.Pattern fm[filter.Kind] = append(fm[filter.Kind], filter)
} }
registry := sourcer.GetAdaptor(replication.AdaptorKindHarbor) registry := sourcer.GetAdaptor(replication.AdaptorKindHarbor)
// only support repository and tag filter for now // repository filter
pattern := ""
repoFilters := fm[replication.FilterItemKindRepository]
if len(repoFilters) > 0 {
pattern = repoFilters[0].Value.(string)
}
filters = append(filters, filters = append(filters,
source.NewRepositoryFilter(patterns[replication.FilterItemKindRepository], registry)) source.NewRepositoryFilter(pattern, registry))
// tag filter
pattern = ""
tagFilters := fm[replication.FilterItemKindTag]
if len(tagFilters) > 0 {
pattern = tagFilters[0].Value.(string)
}
filters = append(filters, filters = append(filters,
source.NewTagFilter(patterns[replication.FilterItemKindTag], registry)) source.NewTagFilter(pattern, registry))
// label filters
var labelID int64
for _, labelFilter := range fm[replication.FilterItemKindLabel] {
labelID = labelFilter.Value.(int64)
filters = append(filters, source.NewLabelFilter(labelID))
}
return source.NewDefaultFilterChain(filters) return source.NewDefaultFilterChain(filters)
} }

View File

@ -83,7 +83,7 @@ func TestGetCandidates(t *testing.T) {
Filters: []models.Filter{ Filters: []models.Filter{
models.Filter{ models.Filter{
Kind: replication.FilterItemKindTag, Kind: replication.FilterItemKindTag,
Pattern: "*", Value: "*",
}, },
}, },
Trigger: &models.Trigger{ Trigger: &models.Trigger{
@ -112,11 +112,22 @@ func TestGetCandidates(t *testing.T) {
policy.Filters = []models.Filter{ policy.Filters = []models.Filter{
models.Filter{ models.Filter{
Kind: replication.FilterItemKindTag, Kind: replication.FilterItemKindTag,
Pattern: "release-*", Value: "release-*",
}, },
} }
result = getCandidates(policy, sourcer, metadata) result = getCandidates(policy, sourcer, metadata)
assert.Equal(t, 1, len(result)) assert.Equal(t, 1, len(result))
// test label filter
test.InitDatabaseFromEnv()
policy.Filters = []models.Filter{
models.Filter{
Kind: replication.FilterItemKindLabel,
Value: int64(1),
},
}
result = getCandidates(policy, sourcer, metadata)
assert.Equal(t, 0, len(result))
} }
func TestBuildFilterChain(t *testing.T) { func TestBuildFilterChain(t *testing.T) {
@ -125,11 +136,17 @@ func TestBuildFilterChain(t *testing.T) {
Filters: []models.Filter{ Filters: []models.Filter{
models.Filter{ models.Filter{
Kind: replication.FilterItemKindRepository, Kind: replication.FilterItemKindRepository,
Pattern: "*", Value: "*",
}, },
models.Filter{ models.Filter{
Kind: replication.FilterItemKindTag, Kind: replication.FilterItemKindTag,
Pattern: "*", Value: "*",
},
models.Filter{
Kind: replication.FilterItemKindLabel,
Value: int64(1),
}, },
}, },
} }
@ -137,5 +154,5 @@ func TestBuildFilterChain(t *testing.T) {
sourcer := source.NewSourcer() sourcer := source.NewSourcer()
chain := buildFilterChain(policy, sourcer) chain := buildFilterChain(policy, sourcer)
assert.Equal(t, 2, len(chain.Filters())) assert.Equal(t, 3, len(chain.Filters()))
} }

View File

@ -24,7 +24,8 @@ import (
// Filter is the data model represents the filter defined by user. // Filter is the data model represents the filter defined by user.
type Filter struct { type Filter struct {
Kind string `json:"kind"` Kind string `json:"kind"`
Pattern string `json:"pattern"` Pattern string `json:"pattern"` // deprecated, use Value instead
Value interface{} `json:"value"`
} }
// Valid ... // Valid ...

View File

@ -106,6 +106,11 @@ func convertFromPersistModel(policy *persist_models.RepPolicy) (models.Replicati
if err := json.Unmarshal([]byte(policy.Filters), &filters); err != nil { if err := json.Unmarshal([]byte(policy.Filters), &filters); err != nil {
return models.ReplicationPolicy{}, err return models.ReplicationPolicy{}, err
} }
for i := range filters {
if filters[i].Value == nil && len(filters[i].Pattern) > 0 {
filters[i].Value = filters[i].Pattern
}
}
ply.Filters = filters ply.Filters = filters
} }

View File

@ -0,0 +1,88 @@
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
//
// 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 source
import (
"fmt"
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/replication"
"github.com/vmware/harbor/src/replication/models"
)
// LabelFilter filter resources according to label
type LabelFilter struct {
labelID int64
}
// Init ...
func (l *LabelFilter) Init() error {
return nil
}
// GetConvertor ...
func (l *LabelFilter) GetConvertor() Convertor {
return nil
}
// NewLabelFilter returns an instance of LabelFilter
func NewLabelFilter(labelID int64) *LabelFilter {
return &LabelFilter{
labelID: labelID,
}
}
// DoFilter filter the resources according to the label
func (l *LabelFilter) DoFilter(items []models.FilterItem) []models.FilterItem {
candidates := []string{}
for _, item := range items {
candidates = append(candidates, item.Value)
}
log.Debugf("label filter candidates: %v", candidates)
result := []models.FilterItem{}
for _, item := range items {
hasLabel, err := hasLabel(item, l.labelID)
if err != nil {
log.Errorf("failed to check the label of resouce %v: %v, skip it", item, err)
continue
}
if hasLabel {
log.Debugf("has label %d, add %s to the label filter result list", l.labelID, item.Value)
result = append(result, item)
}
}
return result
}
func hasLabel(resource models.FilterItem, labelID int64) (bool, error) {
rType := ""
switch resource.Kind {
case replication.FilterItemKindProject:
rType = common.ResourceTypeProject
case replication.FilterItemKindRepository:
rType = common.ResourceTypeRepository
case replication.FilterItemKindTag:
rType = common.ResourceTypeImage
default:
return false, fmt.Errorf("invalid resource type: %s", resource.Kind)
}
rl, err := dao.GetResourceLabel(rType, resource.Value, labelID)
if err != nil {
return false, err
}
return rl != nil, nil
}

View File

@ -0,0 +1,48 @@
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
//
// 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 source
import (
"testing"
"github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/replication"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/replication/models"
)
func TestInitOfLabelFilter(t *testing.T) {
filter := NewLabelFilter(1)
assert.Nil(t, filter.Init())
}
func TestGetConvertorOfLabelFilter(t *testing.T) {
filter := NewLabelFilter(1)
assert.Nil(t, filter.GetConvertor())
}
func TestDoFilterOfLabelFilter(t *testing.T) {
test.InitDatabaseFromEnv()
filter := NewLabelFilter(1)
items := []models.FilterItem{
models.FilterItem{
Kind: replication.FilterItemKindTag,
Value: "library/hello-world:latest",
},
}
result := filter.DoFilter(items)
assert.Equal(t, 0, len(result))
}

View File

@ -76,8 +76,8 @@ func (r *RepositoryFilter) DoFilter(items []models.FilterItem) []models.FilterIt
_, repository = utils.ParseRepository(repository) _, repository = utils.ParseRepository(repository)
matched, err := match(r.pattern, repository) matched, err := match(r.pattern, repository)
if err != nil { if err != nil {
log.Errorf("failed to match pattern %s to value %s: %v", r.pattern, repository, err) log.Errorf("failed to match pattern %s to value %s: %v, skip it", r.pattern, repository, err)
break continue
} }
if matched { if matched {
log.Debugf("pattern %s matched, add %s to the repository filter result list", r.pattern, item.Value) log.Debugf("pattern %s matched, add %s to the repository filter result list", r.pattern, item.Value)

View File

@ -71,7 +71,7 @@ func (t *TagFilter) DoFilter(items []models.FilterItem) []models.FilterItem {
tag := strings.SplitN(item.Value, ":", 2)[1] tag := strings.SplitN(item.Value, ":", 2)[1]
matched, err := match(t.pattern, tag) matched, err := match(t.pattern, tag)
if err != nil { if err != nil {
log.Errorf("failed to match pattern %s to value %s: %v", t.pattern, tag, err) log.Errorf("failed to match pattern %s to value %s: %v, skip it", t.pattern, tag, err)
continue continue
} }