diff --git a/src/pkg/reg/adapter/tencentcr/adapter.go b/src/pkg/reg/adapter/tencentcr/adapter.go index f0204496b..8c27be60a 100644 --- a/src/pkg/reg/adapter/tencentcr/adapter.go +++ b/src/pkg/reg/adapter/tencentcr/adapter.go @@ -90,7 +90,7 @@ func newAdapter(registry *model.Registry) (a *adapter, err error) { // only validate registryURL.Host in non-UT scenario if os.Getenv("UTTEST") != "true" { - if strings.Index(registryURL.Host, ".tencentcloudcr.com") < 0 { + if !strings.Contains(registryURL.Host, ".tencentcloudcr.com") { log.Errorf("[tencent-tcr.newAdapter] errInvalidTcrEndpoint=%v", err) return nil, errInvalidTcrEndpoint } diff --git a/src/pkg/reg/adapter/tencentcr/chart_registry.go b/src/pkg/reg/adapter/tencentcr/chart_registry.go index 3f9b119a9..08e9ef7c5 100644 --- a/src/pkg/reg/adapter/tencentcr/chart_registry.go +++ b/src/pkg/reg/adapter/tencentcr/chart_registry.go @@ -42,7 +42,7 @@ var _ adp.ChartRegistry = &adapter{} func (a *adapter) FetchCharts(filters []*model.Filter) (resources []*model.Resource, err error) { log.Debugf("[tencent-tcr.FetchCharts]filters: %#v", filters) - // 1. list namespaces + // 1. list namespaces via TCR Special API var nsPattern, _, _ = filterToPatterns(filters) var nms []string nms, err = a.listCandidateNamespaces(nsPattern) @@ -50,8 +50,12 @@ func (a *adapter) FetchCharts(filters []*model.Filter) (resources []*model.Resou return } - // 2. list repositories - for _, ns := range nms { + return a.fetchCharts(nms, filters) +} + +func (a *adapter) fetchCharts(namespaces []string, filters []*model.Filter) (resources []*model.Resource, err error) { + // 1. list repositories + for _, ns := range namespaces { var url = fmt.Sprintf(chartListURL, a.registry.URL, ns) var repositories = []*model.Repository{} err = a.client.Get(url, &repositories) @@ -70,7 +74,7 @@ func (a *adapter) FetchCharts(filters []*model.Filter) (resources []*model.Resou return } - // 3. list versions + // 2. list versions for _, repository := range repositories { var name = strings.SplitN(repository.Name, "/", 2)[1] var url = fmt.Sprintf(chartVersionURL, a.registry.URL, ns, name) diff --git a/src/pkg/reg/adapter/tencentcr/chart_registry_test.go b/src/pkg/reg/adapter/tencentcr/chart_registry_test.go new file mode 100644 index 000000000..b906087f5 --- /dev/null +++ b/src/pkg/reg/adapter/tencentcr/chart_registry_test.go @@ -0,0 +1,172 @@ +// 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 tencentcr + +import ( + "bytes" + "net/http" + "testing" + + commonhttp "github.com/goharbor/harbor/src/common/http" + "github.com/goharbor/harbor/src/common/utils/test" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockChartClient(registry *model.Registry) *adapter { + return &adapter{ + registry: registry, + client: commonhttp.NewClient( + &http.Client{ + Transport: commonhttp.GetHTTPTransport(commonhttp.WithInsecure(registry.Insecure)), + }, + ), + } +} + +func TestFetchCharts(t *testing.T) { + server := test.NewServer([]*test.RequestHandlerMapping{ + { + Method: http.MethodGet, + Pattern: "/api/chartrepo/library/charts/harbor", + Handler: func(w http.ResponseWriter, r *http.Request) { + data := `[{ + "name": "harbor", + "version":"1.0" + },{ + "name": "harbor", + "version":"2.0" + }]` + w.Write([]byte(data)) + }, + }, + { + Method: http.MethodGet, + Pattern: "/api/chartrepo/library/charts", + Handler: func(w http.ResponseWriter, r *http.Request) { + data := `[{ + "name": "harbor" + }]` + w.Write([]byte(data)) + }, + }, + }...) + defer server.Close() + var adapter = mockChartClient(&model.Registry{URL: server.URL}) + + // nil filter + resources, err := adapter.fetchCharts([]string{"library"}, nil) + require.Nil(t, err) + assert.Equal(t, 2, len(resources)) + assert.Equal(t, model.ResourceTypeChart, resources[0].Type) + assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) + assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) + assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) + // not nil filter + filters := []*model.Filter{ + { + Type: model.FilterTypeName, + Value: "library/*", + }, + { + Type: model.FilterTypeTag, + Value: "1.0", + }, + } + resources, err = adapter.fetchCharts([]string{"library"}, filters) + require.Nil(t, err) + require.Equal(t, 1, len(resources)) + assert.Equal(t, model.ResourceTypeChart, resources[0].Type) + assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) + assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) + assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) +} + +func TestChartExist(t *testing.T) { + server := test.NewServer(&test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/api/chartrepo/library/charts/harbor/1.0", + Handler: func(w http.ResponseWriter, r *http.Request) { + data := `{ + "metadata": { + "urls":["http://127.0.0.1/charts"] + } + }` + w.Write([]byte(data)) + }, + }) + defer server.Close() + var adapter = mockChartClient(&model.Registry{URL: server.URL}) + var exist, err = adapter.ChartExist("library/harbor", "1.0") + require.Nil(t, err) + require.True(t, exist) +} + +func TestDownloadChart(t *testing.T) { + server := test.NewServer([]*test.RequestHandlerMapping{ + { + Method: http.MethodGet, + Pattern: "/api/chartrepo/library/charts/harbor/1.0", + Handler: func(w http.ResponseWriter, r *http.Request) { + data := `{ + "metadata": { + "urls":["charts/harbor-1.0.tgz"] + } + }` + w.Write([]byte(data)) + }, + }, + { + Method: http.MethodGet, + Pattern: "/chartrepo/library/charts/harbor-1.0.tgz", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }, + }, + }...) + defer server.Close() + var adapter = mockChartClient(&model.Registry{URL: server.URL}) + var _, err = adapter.DownloadChart("library/harbor", "1.0", "") + require.Nil(t, err) +} + +func TestUploadChart(t *testing.T) { + server := test.NewServer(&test.RequestHandlerMapping{ + Method: http.MethodPost, + Pattern: "/api/chartrepo/library/charts", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }, + }) + defer server.Close() + var adapter = mockChartClient(&model.Registry{URL: server.URL}) + var err = adapter.UploadChart("library/harbor", "1.0", bytes.NewBuffer(nil)) + require.Nil(t, err) +} + +func TestDeleteChart(t *testing.T) { + server := test.NewServer(&test.RequestHandlerMapping{ + Method: http.MethodDelete, + Pattern: "/api/chartrepo/library/charts/harbor/1.0", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }, + }) + defer server.Close() + var adapter = mockChartClient(&model.Registry{URL: server.URL}) + var err = adapter.DeleteChart("library/harbor", "1.0") + require.Nil(t, err) +} diff --git a/src/pkg/reg/adapter/tencentcr/sdk.go b/src/pkg/reg/adapter/tencentcr/sdk.go index 328d8ea77..bb1067fa4 100644 --- a/src/pkg/reg/adapter/tencentcr/sdk.go +++ b/src/pkg/reg/adapter/tencentcr/sdk.go @@ -131,10 +131,18 @@ func (a *adapter) isNamespaceExist(namespace string) (exist bool, err error) { } log.Warningf("[tencent-tcr.PrepareForPush.isNamespaceExist] namespace=%s, total=%d", namespace, *resp.Response.TotalCount) - if int(*resp.Response.TotalCount) != 1 { - return + exist = isTcrNsExist(namespace, resp.Response.NamespaceList) + + return +} + +func isTcrNsExist(name string, list []*tcr.TcrNamespaceInfo) (exist bool) { + for _, ns := range list { + if *ns.Name == name { + exist = true + return + } } - exist = true return } diff --git a/src/pkg/reg/adapter/tencentcr/sdk_test.go b/src/pkg/reg/adapter/tencentcr/sdk_test.go index 0a492027b..bcb43ec98 100644 --- a/src/pkg/reg/adapter/tencentcr/sdk_test.go +++ b/src/pkg/reg/adapter/tencentcr/sdk_test.go @@ -3,6 +3,9 @@ package tencentcr import ( "reflect" "testing" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924" ) func Test_adapter_createPrivateNamespace(t *testing.T) { @@ -131,3 +134,42 @@ func Test_adapter_getImages(t *testing.T) { }) } } + +func Test_isTcrNsExist(t *testing.T) { + tests := []struct { + name string + list []*tcr.TcrNamespaceInfo + wantExist bool + }{ + { + name: "not_found_any_ns", list: []*tcr.TcrNamespaceInfo{}, wantExist: false, + }, + { + name: "found_one_ns", list: []*tcr.TcrNamespaceInfo{ + {Name: common.StringPtr("found_one_ns")}, + }, + wantExist: true, + }, + { + name: "found_multi_ns", list: []*tcr.TcrNamespaceInfo{ + {Name: common.StringPtr("found_multi_ns")}, + {Name: common.StringPtr("found_multi_ns_2")}, + }, + wantExist: true, + }, + { + name: "found_but_not_exist", list: []*tcr.TcrNamespaceInfo{ + {Name: common.StringPtr("found_multi_ns_2")}, + {Name: common.StringPtr("found_multi_ns_3")}, + }, + wantExist: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotExist := isTcrNsExist(tt.name, tt.list); gotExist != tt.wantExist { + t.Errorf("isTcrNsExist() = %v, want %v", gotExist, tt.wantExist) + } + }) + } +}