mirror of
https://github.com/goharbor/harbor.git
synced 2025-04-12 06:56:58 +02:00
Merge pull request #8032 from ywk253100/190529_filter
Support label filter for replication
This commit is contained in:
commit
8da61464b5
@ -97,11 +97,6 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
Type: model.FilterTypeTag,
|
Type: model.FilterTypeTag,
|
||||||
Style: model.FilterStyleTypeText,
|
Style: model.FilterStyleTypeText,
|
||||||
},
|
},
|
||||||
// TODO add support for label filter
|
|
||||||
// {
|
|
||||||
// Type: model.FilterTypeLabel,
|
|
||||||
// Style: model.FilterStyleTypeText,
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
SupportedTriggers: []model.TriggerType{
|
SupportedTriggers: []model.TriggerType{
|
||||||
model.TriggerTypeManual,
|
model.TriggerTypeManual,
|
||||||
@ -118,6 +113,26 @@ func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|||||||
if sys.ChartRegistryEnabled {
|
if sys.ChartRegistryEnabled {
|
||||||
info.SupportedResourceTypes = append(info.SupportedResourceTypes, model.ResourceTypeChart)
|
info.SupportedResourceTypes = append(info.SupportedResourceTypes, model.ResourceTypeChart)
|
||||||
}
|
}
|
||||||
|
labels := []*struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{}
|
||||||
|
// label isn't supported in some previous version of Harbor
|
||||||
|
if err := a.client.Get(a.getURL()+"/api/labels?scope=g", &labels); err != nil {
|
||||||
|
if e, ok := err.(*common_http.Error); !ok || e.Code != http.StatusNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ls := []string{}
|
||||||
|
for _, label := range labels {
|
||||||
|
ls = append(ls, label.Name)
|
||||||
|
}
|
||||||
|
labelFilter := &model.FilterStyle{
|
||||||
|
Type: model.FilterTypeLabel,
|
||||||
|
Style: model.FilterStyleTypeList,
|
||||||
|
Values: ls,
|
||||||
|
}
|
||||||
|
info.SupportedResourceFilters = append(info.SupportedResourceFilters, labelFilter)
|
||||||
|
}
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,12 +259,15 @@ func (a *adapter) getProject(name string) (*project, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *adapter) getRepositories(projectID int64) ([]*repository, error) {
|
func (a *adapter) getRepositories(projectID int64) ([]*adp.Repository, error) {
|
||||||
repositories := []*repository{}
|
repositories := []*adp.Repository{}
|
||||||
url := fmt.Sprintf("%s/api/repositories?project_id=%d&page=1&page_size=500", a.getURL(), projectID)
|
url := fmt.Sprintf("%s/api/repositories?project_id=%d&page=1&page_size=500", a.getURL(), projectID)
|
||||||
if err := a.client.GetAndIteratePagination(url, &repositories); err != nil {
|
if err := a.client.GetAndIteratePagination(url, &repositories); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, repository := range repositories {
|
||||||
|
repository.ResourceType = string(model.ResourceTypeImage)
|
||||||
|
}
|
||||||
return repositories, nil
|
return repositories, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,45 +24,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
common_http "github.com/goharbor/harbor/src/common/http"
|
common_http "github.com/goharbor/harbor/src/common/http"
|
||||||
|
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type chart struct {
|
type label struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Project string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chart) Match(filters []*model.Filter) (bool, error) {
|
|
||||||
supportedFilters := []*model.Filter{}
|
|
||||||
for _, filter := range filters {
|
|
||||||
if filter.Type == model.FilterTypeName {
|
|
||||||
supportedFilters = append(supportedFilters, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item := &FilterItem{
|
|
||||||
Value: fmt.Sprintf("%s/%s", c.Project, c.Name),
|
|
||||||
}
|
|
||||||
return item.Match(supportedFilters)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type chartVersion struct {
|
type chartVersion struct {
|
||||||
Name string `json:"name"`
|
Version string `json:"version"`
|
||||||
Version string `json:"version"`
|
Labels []*label `json:"labels"`
|
||||||
// TODO handle system/project level labels
|
|
||||||
// Labels string `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chartVersion) Match(filters []*model.Filter) (bool, error) {
|
|
||||||
supportedFilters := []*model.Filter{}
|
|
||||||
for _, filter := range filters {
|
|
||||||
if filter.Type == model.FilterTypeTag {
|
|
||||||
supportedFilters = append(supportedFilters, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item := &FilterItem{
|
|
||||||
Value: c.Version,
|
|
||||||
}
|
|
||||||
return item.Match(supportedFilters)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type chartVersionDetail struct {
|
type chartVersionDetail struct {
|
||||||
@ -81,37 +53,60 @@ func (a *adapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error
|
|||||||
resources := []*model.Resource{}
|
resources := []*model.Resource{}
|
||||||
for _, project := range projects {
|
for _, project := range projects {
|
||||||
url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.getURL(), project.Name)
|
url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.getURL(), project.Name)
|
||||||
charts := []*chart{}
|
repositories := []*adp.Repository{}
|
||||||
if err := a.client.Get(url, &charts); err != nil {
|
if err := a.client.Get(url, &repositories); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, chart := range charts {
|
if len(repositories) == 0 {
|
||||||
chart.Project = project.Name
|
continue
|
||||||
}
|
}
|
||||||
charts, err := filterCharts(charts, filters)
|
for _, repository := range repositories {
|
||||||
if err != nil {
|
repository.Name = fmt.Sprintf("%s/%s", project.Name, repository.Name)
|
||||||
return nil, err
|
repository.ResourceType = string(model.ResourceTypeChart)
|
||||||
}
|
}
|
||||||
for _, chart := range charts {
|
for _, filter := range filters {
|
||||||
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.getURL(), project.Name, chart.Name)
|
if err = filter.DoFilter(&repositories); err != nil {
|
||||||
chartVersions := []*chartVersion{}
|
|
||||||
if err := a.client.Get(url, &chartVersions); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
chartVersions, err = filterChartVersions(chartVersions, filters)
|
}
|
||||||
if err != nil {
|
for _, repository := range repositories {
|
||||||
|
name := strings.SplitN(repository.Name, "/", 2)[1]
|
||||||
|
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.getURL(), project.Name, name)
|
||||||
|
versions := []*chartVersion{}
|
||||||
|
if err := a.client.Get(url, &versions); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, version := range chartVersions {
|
if len(versions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vTags := []*adp.VTag{}
|
||||||
|
for _, version := range versions {
|
||||||
|
var labels []string
|
||||||
|
for _, label := range version.Labels {
|
||||||
|
labels = append(labels, label.Name)
|
||||||
|
}
|
||||||
|
vTags = append(vTags, &adp.VTag{
|
||||||
|
Name: version.Version,
|
||||||
|
Labels: labels,
|
||||||
|
ResourceType: string(model.ResourceTypeChart),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, filter := range filters {
|
||||||
|
if err = filter.DoFilter(&vTags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vTag := range vTags {
|
||||||
resources = append(resources, &model.Resource{
|
resources = append(resources, &model.Resource{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
Registry: a.registry,
|
Registry: a.registry,
|
||||||
Metadata: &model.ResourceMetadata{
|
Metadata: &model.ResourceMetadata{
|
||||||
Repository: &model.Repository{
|
Repository: &model.Repository{
|
||||||
Name: fmt.Sprintf("%s/%s", project.Name, chart.Name),
|
Name: repository.Name,
|
||||||
Metadata: project.Metadata,
|
Metadata: project.Metadata,
|
||||||
},
|
},
|
||||||
Vtags: []string{version.Version},
|
Vtags: []string{vTag.Name},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -232,31 +227,3 @@ func parseChartName(name string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
return "", "", fmt.Errorf("invalid chart name format: %s", name)
|
return "", "", fmt.Errorf("invalid chart name format: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterCharts(charts []*chart, filters []*model.Filter) ([]*chart, error) {
|
|
||||||
result := []*chart{}
|
|
||||||
for _, chart := range charts {
|
|
||||||
match, err := chart.Match(filters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
result = append(result, chart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterChartVersions(chartVersions []*chartVersion, filters []*model.Filter) ([]*chartVersion, error) {
|
|
||||||
result := []*chartVersion{}
|
|
||||||
for _, chartVersion := range chartVersions {
|
|
||||||
match, err := chartVersion.Match(filters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
result = append(result, chartVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
@ -1,59 +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 harbor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
|
||||||
"github.com/goharbor/harbor/src/replication/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO unify the filter logic from different adapters into one?
|
|
||||||
// and move the code into a separated common package
|
|
||||||
|
|
||||||
// Filterable defines the interface that an object should implement
|
|
||||||
// if the object can be filtered
|
|
||||||
type Filterable interface {
|
|
||||||
Match([]*model.Filter) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterItem is a filterable object that can be used to match string pattern
|
|
||||||
type FilterItem struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match ...
|
|
||||||
func (f *FilterItem) Match(filters []*model.Filter) (bool, error) {
|
|
||||||
if len(filters) == 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
matched := true
|
|
||||||
for _, filter := range filters {
|
|
||||||
pattern, ok := filter.Value.(string)
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("the type of filter value isn't string: %v", filter)
|
|
||||||
}
|
|
||||||
m, err := util.Match(pattern, f.Value)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if !m {
|
|
||||||
matched = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matched, nil
|
|
||||||
}
|
|
@ -1,86 +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 harbor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
|
||||||
// nil filters
|
|
||||||
item := &FilterItem{}
|
|
||||||
match, err := item.Match(nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.True(t, match)
|
|
||||||
// contains filter whose value isn't string
|
|
||||||
item = &FilterItem{}
|
|
||||||
filters := []*model.Filter{
|
|
||||||
{
|
|
||||||
Type: "test",
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
match, err = item.Match(filters)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
// both filters match
|
|
||||||
item = &FilterItem{
|
|
||||||
Value: "b/c",
|
|
||||||
}
|
|
||||||
filters = []*model.Filter{
|
|
||||||
{
|
|
||||||
Value: "b/*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: "*/c",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
match, err = item.Match(filters)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.True(t, match)
|
|
||||||
// one filter matches and the other one doesn't
|
|
||||||
item = &FilterItem{
|
|
||||||
Value: "b/c",
|
|
||||||
}
|
|
||||||
filters = []*model.Filter{
|
|
||||||
{
|
|
||||||
Value: "b/*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: "d",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
match, err = item.Match(filters)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.False(t, match)
|
|
||||||
// both filters don't match
|
|
||||||
item = &FilterItem{
|
|
||||||
Value: "b/c",
|
|
||||||
}
|
|
||||||
filters = []*model.Filter{
|
|
||||||
{
|
|
||||||
Value: "f",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: "d",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
match, err = item.Match(filters)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.False(t, match)
|
|
||||||
}
|
|
@ -19,44 +19,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"github.com/goharbor/harbor/src/replication/model"
|
||||||
"github.com/goharbor/harbor/src/replication/util"
|
"github.com/goharbor/harbor/src/replication/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type repository struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *repository) Match(filters []*model.Filter) (bool, error) {
|
|
||||||
supportedFilters := []*model.Filter{}
|
|
||||||
for _, filter := range filters {
|
|
||||||
if filter.Type == model.FilterTypeName {
|
|
||||||
supportedFilters = append(supportedFilters, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item := &FilterItem{
|
|
||||||
Value: r.Name,
|
|
||||||
}
|
|
||||||
return item.Match(supportedFilters)
|
|
||||||
}
|
|
||||||
|
|
||||||
type tag struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tag) Match(filters []*model.Filter) (bool, error) {
|
|
||||||
supportedFilters := []*model.Filter{}
|
|
||||||
for _, filter := range filters {
|
|
||||||
if filter.Type == model.FilterTypeTag {
|
|
||||||
supportedFilters = append(supportedFilters, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item := &FilterItem{
|
|
||||||
Value: t.Name,
|
|
||||||
}
|
|
||||||
return item.Match(supportedFilters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
projects, err := a.listCandidateProjects(filters)
|
projects, err := a.listCandidateProjects(filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,26 +35,33 @@ func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
repositories, err = filterRepositories(repositories, filters)
|
if len(repositories) == 0 {
|
||||||
if err != nil {
|
continue
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
for _, repository := range repositories {
|
for _, filter := range filters {
|
||||||
url := fmt.Sprintf("%s/api/repositories/%s/tags", a.getURL(), repository.Name)
|
if err = filter.DoFilter(&repositories); err != nil {
|
||||||
tags := []*tag{}
|
|
||||||
if err = a.client.Get(url, &tags); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tags, err = filterTags(tags, filters)
|
}
|
||||||
|
for _, repository := range repositories {
|
||||||
|
vTags, err := a.getTags(repository.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(tags) == 0 {
|
if len(vTags) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vtags := []string{}
|
for _, filter := range filters {
|
||||||
for _, tag := range tags {
|
if err = filter.DoFilter(&vTags); err != nil {
|
||||||
vtags = append(vtags, tag.Name)
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(vTags) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tags := []string{}
|
||||||
|
for _, vTag := range vTags {
|
||||||
|
tags = append(tags, vTag.Name)
|
||||||
}
|
}
|
||||||
resources = append(resources, &model.Resource{
|
resources = append(resources, &model.Resource{
|
||||||
Type: model.ResourceTypeImage,
|
Type: model.ResourceTypeImage,
|
||||||
@ -97,7 +71,7 @@ func (a *adapter) FetchImages(filters []*model.Filter) ([]*model.Resource, error
|
|||||||
Name: repository.Name,
|
Name: repository.Name,
|
||||||
Metadata: project.Metadata,
|
Metadata: project.Metadata,
|
||||||
},
|
},
|
||||||
Vtags: vtags,
|
Vtags: tags,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -150,30 +124,28 @@ func (a *adapter) DeleteManifest(repository, reference string) error {
|
|||||||
return a.client.Delete(url)
|
return a.client.Delete(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterRepositories(repositories []*repository, filters []*model.Filter) ([]*repository, error) {
|
func (a *adapter) getTags(repository string) ([]*adp.VTag, error) {
|
||||||
result := []*repository{}
|
url := fmt.Sprintf("%s/api/repositories/%s/tags", a.getURL(), repository)
|
||||||
for _, repository := range repositories {
|
tags := []*struct {
|
||||||
match, err := repository.Match(filters)
|
Name string `json:"name"`
|
||||||
if err != nil {
|
Labels []*struct {
|
||||||
return nil, err
|
Name string `json:"name"`
|
||||||
}
|
|
||||||
if match {
|
|
||||||
result = append(result, repository)
|
|
||||||
}
|
}
|
||||||
|
}{}
|
||||||
|
if err := a.client.Get(url, &tags); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
vTags := []*adp.VTag{}
|
||||||
}
|
|
||||||
|
|
||||||
func filterTags(tags []*tag, filters []*model.Filter) ([]*tag, error) {
|
|
||||||
result := []*tag{}
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
match, err := tag.Match(filters)
|
var labels []string
|
||||||
if err != nil {
|
for _, label := range tag.Labels {
|
||||||
return nil, err
|
labels = append(labels, label.Name)
|
||||||
}
|
|
||||||
if match {
|
|
||||||
result = append(result, tag)
|
|
||||||
}
|
}
|
||||||
|
vTags = append(vTags, &adp.VTag{
|
||||||
|
Name: tag.Name,
|
||||||
|
Labels: labels,
|
||||||
|
ResourceType: string(model.ResourceTypeImage),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return vTags, nil
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/replication/filter"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/goharbor/harbor/src/common/http/modifier"
|
"github.com/goharbor/harbor/src/common/http/modifier"
|
||||||
@ -50,6 +52,59 @@ type ImageRegistry interface {
|
|||||||
PushBlob(repository, digest string, size int64, blob io.Reader) error
|
PushBlob(repository, digest string, size int64, blob io.Reader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repository defines an repository object, it can be image repository, chart repository and etc.
|
||||||
|
type Repository struct {
|
||||||
|
ResourceType string `json:"resource_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name
|
||||||
|
func (r *Repository) GetName() string {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilterableType returns the filterable type
|
||||||
|
func (r *Repository) GetFilterableType() filter.FilterableType {
|
||||||
|
return filter.FilterableTypeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResourceType returns the resource type
|
||||||
|
func (r *Repository) GetResourceType() string {
|
||||||
|
return r.ResourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabels returns the labels
|
||||||
|
func (r *Repository) GetLabels() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTag defines an vTag object, it can be image tag, chart version and etc.
|
||||||
|
type VTag struct {
|
||||||
|
ResourceType string `json:"resource_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilterableType returns the filterable type
|
||||||
|
func (v *VTag) GetFilterableType() filter.FilterableType {
|
||||||
|
return filter.FilterableTypeVTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResourceType returns the resource type
|
||||||
|
func (v *VTag) GetResourceType() string {
|
||||||
|
return v.ResourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name
|
||||||
|
func (v *VTag) GetName() string {
|
||||||
|
return v.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabels returns the labels
|
||||||
|
func (v *VTag) GetLabels() []string {
|
||||||
|
return v.Labels
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultImageRegistry provides a default implementation for interface ImageRegistry
|
// DefaultImageRegistry provides a default implementation for interface ImageRegistry
|
||||||
type DefaultImageRegistry struct {
|
type DefaultImageRegistry struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
247
src/replication/filter/filter.go
Normal file
247
src/replication/filter/filter.go
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// 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 filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/replication/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// const definitions
|
||||||
|
const (
|
||||||
|
FilterableTypeRepository = "repository"
|
||||||
|
FilterableTypeVTag = "vtag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterableType specifies the type of the filterable
|
||||||
|
type FilterableType string
|
||||||
|
|
||||||
|
// Filterable defines the methods that a filterable object must implement
|
||||||
|
type Filterable interface {
|
||||||
|
// return what the type of the filterable object is(repository or vtag)
|
||||||
|
GetFilterableType() FilterableType
|
||||||
|
// return the resource type of the filterable object(image, chart, ...)
|
||||||
|
GetResourceType() string
|
||||||
|
GetName() string
|
||||||
|
GetLabels() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter defines the methods that a filter must implement
|
||||||
|
type Filter interface {
|
||||||
|
// return whether the filter is applied to the specified Filterable
|
||||||
|
ApplyTo(Filterable) bool
|
||||||
|
Filter(...Filterable) ([]Filterable, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResourceTypeFilter return a Filter to filter candidates according to the resource type
|
||||||
|
func NewResourceTypeFilter(resourceType string) Filter {
|
||||||
|
return &resourceTypeFilter{
|
||||||
|
resourceType: resourceType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepositoryNameFilter return a Filter to filter the repositories according to the name
|
||||||
|
func NewRepositoryNameFilter(pattern string) Filter {
|
||||||
|
return &nameFilter{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
pattern: pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVTagNameFilter return a Filter to filter the vtags according to the name
|
||||||
|
func NewVTagNameFilter(pattern string) Filter {
|
||||||
|
return &nameFilter{
|
||||||
|
filterableType: FilterableTypeVTag,
|
||||||
|
pattern: pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVTagLabelFilter return a Filter to filter vtags according to the label
|
||||||
|
func NewVTagLabelFilter(label string) Filter {
|
||||||
|
return &labelFilter{
|
||||||
|
label: label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceTypeFilter struct {
|
||||||
|
resourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceTypeFilter) ApplyTo(filterable Filterable) bool {
|
||||||
|
if filterable == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch filterable.GetFilterableType() {
|
||||||
|
case FilterableTypeRepository, FilterableTypeVTag:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceTypeFilter) Filter(filterables ...Filterable) ([]Filterable, error) {
|
||||||
|
result := []Filterable{}
|
||||||
|
for _, filterable := range filterables {
|
||||||
|
if filterable.GetResourceType() == r.resourceType {
|
||||||
|
result = append(result, filterable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type nameFilter struct {
|
||||||
|
filterableType FilterableType
|
||||||
|
pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nameFilter) ApplyTo(filterable Filterable) bool {
|
||||||
|
if filterable == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if filterable.GetFilterableType() == n.filterableType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nameFilter) Filter(filterables ...Filterable) ([]Filterable, error) {
|
||||||
|
result := []Filterable{}
|
||||||
|
for _, filterable := range filterables {
|
||||||
|
name := filterable.GetName()
|
||||||
|
match, err := util.Match(n.pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
log.Debugf("%q matches the pattern %q of name filter", name, n.pattern)
|
||||||
|
result = append(result, filterable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("%q doesn't match the pattern %q of name filter, skip", name, n.pattern)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type labelFilter struct {
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *labelFilter) ApplyTo(filterable Filterable) bool {
|
||||||
|
if filterable == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if filterable.GetFilterableType() == FilterableTypeVTag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *labelFilter) Filter(filterables ...Filterable) ([]Filterable, error) {
|
||||||
|
// if no specified label in the filter, just returns the input filterable
|
||||||
|
// candidate as the result
|
||||||
|
if len(l.label) == 0 {
|
||||||
|
return filterables, nil
|
||||||
|
}
|
||||||
|
result := []Filterable{}
|
||||||
|
for _, filterable := range filterables {
|
||||||
|
match := false
|
||||||
|
for _, label := range filterable.GetLabels() {
|
||||||
|
if label == l.label {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
result = append(result, filterable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoFilter is a util function to help filter filterables easily.
|
||||||
|
// The parameter "filterables" must be a pointer points to a slice
|
||||||
|
// whose elements must be Filterable. After applying all the "filters"
|
||||||
|
// to the "filterables", the result is put back into the variable
|
||||||
|
// "filterables"
|
||||||
|
func DoFilter(filterables interface{}, filters ...Filter) error {
|
||||||
|
if filterables == nil || len(filters) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(filterables)
|
||||||
|
// make sure the input is a pointer
|
||||||
|
if value.Kind() != reflect.Ptr {
|
||||||
|
return errors.New("the type of input should be pointer to a Filterable slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceValue := value.Elem()
|
||||||
|
// make sure the input is a pointer points to a slice
|
||||||
|
if sliceValue.Type().Kind() != reflect.Slice {
|
||||||
|
return errors.New("the type of input should be pointer to a Filterable slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
filterableType := reflect.TypeOf((*Filterable)(nil)).Elem()
|
||||||
|
elemType := sliceValue.Type().Elem()
|
||||||
|
// make sure the input is a pointer points to a Filterable slice
|
||||||
|
if !elemType.Implements(filterableType) {
|
||||||
|
return errors.New("the type of input should be pointer to a Filterable slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the input to Filterable slice
|
||||||
|
items := []Filterable{}
|
||||||
|
for i := 0; i < sliceValue.Len(); i++ {
|
||||||
|
items = append(items, sliceValue.Index(i).Interface().(Filterable))
|
||||||
|
}
|
||||||
|
|
||||||
|
// do filter
|
||||||
|
var err error
|
||||||
|
items, err = doFilter(items, filters...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to the origin type
|
||||||
|
result := reflect.MakeSlice(reflect.SliceOf(elemType), 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
result = reflect.Append(result, reflect.ValueOf(item))
|
||||||
|
}
|
||||||
|
value.Elem().Set(result)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doFilter(filterables []Filterable, filters ...Filter) ([]Filterable, error) {
|
||||||
|
var appliedTo, notAppliedTo []Filterable
|
||||||
|
var err error
|
||||||
|
for _, filter := range filters {
|
||||||
|
appliedTo, notAppliedTo = nil, nil
|
||||||
|
for _, filterable := range filterables {
|
||||||
|
if filter.ApplyTo(filterable) {
|
||||||
|
appliedTo = append(appliedTo, filterable)
|
||||||
|
} else {
|
||||||
|
notAppliedTo = append(notAppliedTo, filterable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterables, err = filter.Filter(appliedTo...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filterables = append(filterables, notAppliedTo...)
|
||||||
|
}
|
||||||
|
return filterables, nil
|
||||||
|
}
|
170
src/replication/filter/filter_test.go
Normal file
170
src/replication/filter/filter_test.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// 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 filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeFilterable struct {
|
||||||
|
filterableType FilterableType
|
||||||
|
resourceType string
|
||||||
|
name string
|
||||||
|
labels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFilterable) GetFilterableType() FilterableType {
|
||||||
|
return f.filterableType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFilterable) GetResourceType() string {
|
||||||
|
return f.resourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeFilterable) GetName() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
func (f *fakeFilterable) GetLabels() []string {
|
||||||
|
return f.labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterOfResourceTypeFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
resourceType: "image",
|
||||||
|
name: "library/hello-world",
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := NewResourceTypeFilter("image")
|
||||||
|
result, err := filter.Filter(filterable)
|
||||||
|
require.Nil(t, nil, err)
|
||||||
|
if assert.Equal(t, 1, len(result)) {
|
||||||
|
assert.True(t, reflect.DeepEqual(filterable, result[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = NewResourceTypeFilter("chart")
|
||||||
|
result, err = filter.Filter(filterable)
|
||||||
|
require.Nil(t, nil, err)
|
||||||
|
assert.Equal(t, 0, len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyToOfResourceTypeFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := NewResourceTypeFilter("image")
|
||||||
|
assert.True(t, filter.ApplyTo(filterable))
|
||||||
|
|
||||||
|
filterable.filterableType = FilterableTypeVTag
|
||||||
|
assert.True(t, filter.ApplyTo(filterable))
|
||||||
|
|
||||||
|
filterable.filterableType = FilterableType("unknown")
|
||||||
|
assert.False(t, filter.ApplyTo(filterable))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterOfNameFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
name: "foo",
|
||||||
|
}
|
||||||
|
// pass the filter
|
||||||
|
filter := &nameFilter{
|
||||||
|
pattern: "*",
|
||||||
|
}
|
||||||
|
result, err := filter.Filter(filterable)
|
||||||
|
require.Nil(t, err)
|
||||||
|
if assert.Equal(t, 1, len(result)) {
|
||||||
|
assert.True(t, reflect.DeepEqual(filterable, result[0].(*fakeFilterable)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot pass the filter
|
||||||
|
filter.pattern = "cannotpass"
|
||||||
|
result, err = filter.Filter(filterable)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyToOfNameFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &nameFilter{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
}
|
||||||
|
assert.True(t, filter.ApplyTo(filterable))
|
||||||
|
|
||||||
|
filterable.filterableType = FilterableTypeVTag
|
||||||
|
assert.False(t, filter.ApplyTo(filterable))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterOfLabelFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
labels: []string{"production"},
|
||||||
|
}
|
||||||
|
// pass the filter
|
||||||
|
filter := &labelFilter{
|
||||||
|
label: "production",
|
||||||
|
}
|
||||||
|
result, err := filter.Filter(filterable)
|
||||||
|
require.Nil(t, err)
|
||||||
|
if assert.Equal(t, 1, len(result)) {
|
||||||
|
assert.True(t, reflect.DeepEqual(filterable, result[0].(*fakeFilterable)))
|
||||||
|
}
|
||||||
|
// cannot pass the filter
|
||||||
|
filter.label = "cannotpass"
|
||||||
|
result, err = filter.Filter(filterable)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyToOfLabelFilter(t *testing.T) {
|
||||||
|
filterable := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := labelFilter{}
|
||||||
|
assert.False(t, filter.ApplyTo(filterable))
|
||||||
|
|
||||||
|
filterable.filterableType = FilterableTypeVTag
|
||||||
|
assert.True(t, filter.ApplyTo(filterable))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoFilter(t *testing.T) {
|
||||||
|
tag1 := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeVTag,
|
||||||
|
name: "1.0",
|
||||||
|
labels: []string{"production"},
|
||||||
|
}
|
||||||
|
tag2 := &fakeFilterable{
|
||||||
|
filterableType: FilterableTypeVTag,
|
||||||
|
name: "latest",
|
||||||
|
labels: []string{"dev"},
|
||||||
|
}
|
||||||
|
filterables := []Filterable{tag1, tag2}
|
||||||
|
filters := []Filter{
|
||||||
|
NewVTagNameFilter("*"),
|
||||||
|
NewVTagLabelFilter("production"),
|
||||||
|
}
|
||||||
|
err := DoFilter(&filterables, filters...)
|
||||||
|
require.Nil(t, err)
|
||||||
|
if assert.Equal(t, 1, len(filterables)) {
|
||||||
|
assert.True(t, reflect.DeepEqual(tag1, filterables[0]))
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/replication/filter"
|
||||||
|
|
||||||
"github.com/astaxie/beego/validation"
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
@ -133,6 +135,29 @@ type Filter struct {
|
|||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoFilter filter the filterables
|
||||||
|
// The parameter "filterables" must be a pointer points to a slice
|
||||||
|
// whose elements must be Filterable. After applying the filter
|
||||||
|
// to the "filterables", the result is put back into the variable
|
||||||
|
// "filterables"
|
||||||
|
func (f *Filter) DoFilter(filterables interface{}) error {
|
||||||
|
var ft filter.Filter
|
||||||
|
switch f.Type {
|
||||||
|
case FilterTypeName:
|
||||||
|
ft = filter.NewRepositoryNameFilter(f.Value.(string))
|
||||||
|
case FilterTypeTag:
|
||||||
|
ft = filter.NewVTagNameFilter(f.Value.(string))
|
||||||
|
case FilterTypeLabel:
|
||||||
|
ft = filter.NewVTagLabelFilter(f.Value.(string))
|
||||||
|
case FilterTypeResource:
|
||||||
|
ft = filter.NewResourceTypeFilter(f.Value.(string))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported filter type: %s", f.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter.DoFilter(filterables, ft)
|
||||||
|
}
|
||||||
|
|
||||||
// TriggerType represents the type of trigger.
|
// TriggerType represents the type of trigger.
|
||||||
type TriggerType string
|
type TriggerType string
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ const (
|
|||||||
|
|
||||||
FilterStyleTypeText = "input"
|
FilterStyleTypeText = "input"
|
||||||
FilterStyleTypeRadio = "radio"
|
FilterStyleTypeRadio = "radio"
|
||||||
|
FilterStyleTypeList = "list"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegistryType indicates the type of registry
|
// RegistryType indicates the type of registry
|
||||||
|
@ -258,7 +258,7 @@ func parseFilters(str string) ([]*model.Filter, error) {
|
|||||||
case "tag":
|
case "tag":
|
||||||
filter.Type = model.FilterTypeTag
|
filter.Type = model.FilterTypeTag
|
||||||
case "label":
|
case "label":
|
||||||
// TODO if we support the label filter, remove the checking logic here
|
// drop all legend label filters
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
log.Warningf("unknown filter type: %s", filter.Type)
|
log.Warningf("unknown filter type: %s", filter.Type)
|
||||||
|
Loading…
Reference in New Issue
Block a user