mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 12:46:03 +01:00
Add filter support when fetching resources for Harbor adapter
Add filter support when fetching resources for Harbor adapter Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
188d66d875
commit
2fe207b77d
@ -24,6 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
common_http "github.com/goharbor/harbor/src/common/http"
|
common_http "github.com/goharbor/harbor/src/common/http"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +34,21 @@ type chart struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// trim the project part
|
||||||
|
_, name := utils.ParseRepository(c.Name)
|
||||||
|
item := &FilterItem{
|
||||||
|
Value: name,
|
||||||
|
}
|
||||||
|
return item.Match(supportedFilters)
|
||||||
|
}
|
||||||
|
|
||||||
type chartVersion struct {
|
type chartVersion struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@ -40,6 +56,19 @@ type chartVersion struct {
|
|||||||
// Labels string `json:"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 {
|
||||||
Metadata *chartVersionMetadata `json:"metadata"`
|
Metadata *chartVersionMetadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
@ -56,12 +85,20 @@ func (a *adapter) FetchCharts(namespaces []string, filters []*model.Filter) ([]*
|
|||||||
if err := a.client.Get(url, &charts); err != nil {
|
if err := a.client.Get(url, &charts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
charts, err := filterCharts(charts, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, chart := range charts {
|
for _, chart := range charts {
|
||||||
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.coreServiceURL, namespace, chart.Name)
|
url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.coreServiceURL, namespace, chart.Name)
|
||||||
chartVersions := []*chartVersion{}
|
chartVersions := []*chartVersion{}
|
||||||
if err := a.client.Get(url, &chartVersions); err != nil {
|
if err := a.client.Get(url, &chartVersions); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
chartVersions, err = filterChartVersions(chartVersions, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, version := range chartVersions {
|
for _, version := range chartVersions {
|
||||||
resources = append(resources, &model.Resource{
|
resources = append(resources, &model.Resource{
|
||||||
Type: model.ResourceTypeChart,
|
Type: model.ResourceTypeChart,
|
||||||
@ -195,6 +232,7 @@ func (a *adapter) DeleteChart(name, version string) error {
|
|||||||
return a.client.Delete(url)
|
return a.client.Delete(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO merge this method and utils.ParseRepository?
|
||||||
func parseChartName(name string) (string, string, error) {
|
func parseChartName(name string) (string, string, error) {
|
||||||
strs := strings.Split(name, "/")
|
strs := strings.Split(name, "/")
|
||||||
if len(strs) == 2 && len(strs[0]) > 0 && len(strs[1]) > 0 {
|
if len(strs) == 2 && len(strs[0]) > 0 && len(strs[1]) > 0 {
|
||||||
@ -202,3 +240,31 @@ 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
|
||||||
|
}
|
||||||
|
59
src/replication/ng/adapter/harbor/filter.go
Normal file
59
src/replication/ng/adapter/harbor/filter.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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/ng/model"
|
||||||
|
"github.com/goharbor/harbor/src/replication/ng/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
|
||||||
|
}
|
87
src/replication/ng/adapter/harbor/filter_test.go
Normal file
87
src/replication/ng/adapter/harbor/filter_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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/ng/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)
|
||||||
|
}
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"github.com/goharbor/harbor/src/replication/ng/model"
|
"github.com/goharbor/harbor/src/replication/ng/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,11 +26,38 @@ type repository struct {
|
|||||||
Name string `json:"name"`
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// trim the project part
|
||||||
|
_, name := utils.ParseRepository(r.Name)
|
||||||
|
item := &FilterItem{
|
||||||
|
Value: name,
|
||||||
|
}
|
||||||
|
return item.Match(supportedFilters)
|
||||||
|
}
|
||||||
|
|
||||||
type tag struct {
|
type tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO implement filter
|
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(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*model.Resource, error) {
|
||||||
resources := []*model.Resource{}
|
resources := []*model.Resource{}
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
@ -42,13 +70,23 @@ func (a *adapter) FetchImages(namespaces []string, filters []*model.Filter) ([]*
|
|||||||
if err = a.client.Get(url, &repositories); err != nil {
|
if err = a.client.Get(url, &repositories); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
repositories, err = filterRepositories(repositories, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, repository := range repositories {
|
for _, repository := range repositories {
|
||||||
url := fmt.Sprintf("%s/api/repositories/%s/tags", a.coreServiceURL, repository.Name)
|
url := fmt.Sprintf("%s/api/repositories/%s/tags", a.coreServiceURL, repository.Name)
|
||||||
tags := []*tag{}
|
tags := []*tag{}
|
||||||
if err = a.client.Get(url, &tags); err != nil {
|
if err = a.client.Get(url, &tags); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tags, err = filterTags(tags, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(tags) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
vtags := []string{}
|
vtags := []string{}
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
vtags = append(vtags, tag.Name)
|
vtags = append(vtags, tag.Name)
|
||||||
@ -79,3 +117,31 @@ func (a *adapter) DeleteManifest(repository, reference string) error {
|
|||||||
url := fmt.Sprintf("%s/api/repositories/%s/tags/%s", a.coreServiceURL, repository, reference)
|
url := fmt.Sprintf("%s/api/repositories/%s/tags/%s", a.coreServiceURL, repository, reference)
|
||||||
return a.client.Delete(url)
|
return a.client.Delete(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRepositories(repositories []*repository, filters []*model.Filter) ([]*repository, error) {
|
||||||
|
result := []*repository{}
|
||||||
|
for _, repository := range repositories {
|
||||||
|
match, err := repository.Match(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
result = append(result, repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTags(tags []*tag, filters []*model.Filter) ([]*tag, error) {
|
||||||
|
result := []*tag{}
|
||||||
|
for _, tag := range tags {
|
||||||
|
match, err := tag.Match(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user