diff --git a/make/photon/prepare/templates/core/env.jinja b/make/photon/prepare/templates/core/env.jinja index acfa34467fd..6be51d8ddbc 100644 --- a/make/photon/prepare/templates/core/env.jinja +++ b/make/photon/prepare/templates/core/env.jinja @@ -40,7 +40,7 @@ REGISTRY_CREDENTIAL_USERNAME={{registry_username}} REGISTRY_CREDENTIAL_PASSWORD={{registry_password}} CSRF_KEY={{csrf_key}} ROBOT_SCANNER_NAME_PREFIX={{scan_robot_prefix}} -PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE=docker-hub,harbor,azure-acr,aws-ecr,google-gcr,quay,docker-registry,github-ghcr,jfrog-artifactory +PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE=docker-hub,harbor,azure-acr,ali-acr,aws-ecr,google-gcr,quay,docker-registry,github-ghcr,jfrog-artifactory HTTP_PROXY={{core_http_proxy}} HTTPS_PROXY={{core_https_proxy}} diff --git a/src/go.mod b/src/go.mod index ebe06261ea2..4e5c9647f15 100644 --- a/src/go.mod +++ b/src/go.mod @@ -5,7 +5,7 @@ go 1.23.2 require ( github.com/FZambia/sentinel v1.1.0 github.com/Masterminds/semver v1.5.0 - github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1193 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/aws/aws-sdk-go v1.55.5 github.com/beego/beego/v2 v2.2.1 diff --git a/src/go.sum b/src/go.sum index 08e7744280e..1104fc0a364 100644 --- a/src/go.sum +++ b/src/go.sum @@ -55,6 +55,8 @@ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3Uu github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 h1:bNE5ID4C3YOkROfvBjXJUG53gyb+8az3TQN02LqnGBk= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1193 h1:C5LuIDWuQlugv30EBsSLKFF6jdtrqoVH84nYCdVYTC4= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1193/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= diff --git a/src/pkg/reg/adapter/aliacr/adapter.go b/src/pkg/reg/adapter/aliacr/adapter.go index a0c66d949d2..94e366de986 100644 --- a/src/pkg/reg/adapter/aliacr/adapter.go +++ b/src/pkg/reg/adapter/aliacr/adapter.go @@ -15,16 +15,12 @@ package aliacr import ( - "encoding/json" "errors" "fmt" "path/filepath" "regexp" "strings" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" - "github.com/aliyun/alibaba-cloud-sdk-go/services/cr" - commonhttp "github.com/goharbor/harbor/src/common/http" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib/log" @@ -45,48 +41,80 @@ func init() { } // example: -// https://registry.%s.aliyuncs.com // https://cr.%s.aliyuncs.com -// https://registry-vpc.%s.aliyuncs.com -// https://registry-internal.%s.aliyuncs.com -var regRegion = regexp.MustCompile(`https://(registry|cr|registry-vpc|registry-internal)\.([\w\-]+)\.aliyuncs\.com`) +var regACRServiceURL = regexp.MustCompile(`https://cr\.([\w\-]+)\.aliyuncs\.com`) -func getRegion(url string) (region string, err error) { +func getRegistryURL(url string) (string, error) { if url == "" { return "", errors.New("empty url") } - rs := regRegion.FindStringSubmatch(url) + rs := regACRServiceURL.FindStringSubmatch(url) if rs == nil { - return "", errors.New("invalid Rgistry|CR service url") + return url, nil + } + return fmt.Sprintf(registryEndpointTpl, rs[1]), nil +} + +// example: +// registry.aliyuncs.com:cn-hangzhou:china:cri-xxxxxxxxx +// registry.aliyuncs.com:cn-hangzhou:26842 +func parseRegistryService(service string) (*registryServiceInfo, error) { + parts := strings.Split(service, ":") + length := len(parts) + if length < 2 { + return nil, errors.New("invalid service format: expected 'registry.aliyuncs.com:region:xxxxx'") + } + + if !strings.EqualFold(parts[0], registryACRService) { + return nil, errors.New("not a acr service") + } + + if strings.HasPrefix(parts[length-1], "cri-") { + return ®istryServiceInfo{ + IsACREE: true, + RegionID: parts[1], + InstanceID: parts[length-1], + }, nil } - // fmt.Println(rs) - return rs[2], nil + return ®istryServiceInfo{ + IsACREE: false, + RegionID: parts[1], + }, nil } func newAdapter(registry *model.Registry) (*adapter, error) { - region, err := getRegion(registry.URL) + url, err := getRegistryURL(registry.URL) if err != nil { return nil, err } - switch true { - case strings.Contains(registry.URL, "registry-vpc"): - registry.URL = fmt.Sprintf(registryVPCEndpointTpl, region) - case strings.Contains(registry.URL, "registry-internal"): - registry.URL = fmt.Sprintf(registryInternalEndpointTpl, region) - default: - // fix url (allow user input cr service url) - registry.URL = fmt.Sprintf(registryEndpointTpl, region) - } + registry.URL = url + realm, service, err := util.Ping(registry) if err != nil { return nil, err } - credential := NewAuth(region, registry.Credential.AccessKey, registry.Credential.AccessSecret) - authorizer := bearer.NewAuthorizer(realm, service, credential, commonhttp.GetHTTPTransport(commonhttp.WithInsecure(registry.Insecure))) + + info, err := parseRegistryService(service) + if err != nil { + return nil, err + } + + var acrAPI openapi + if !info.IsACREE { + acrAPI, err = newAcrOpenapi(registry.Credential.AccessKey, registry.Credential.AccessSecret, info.RegionID) + if err != nil { + return nil, err + } + } else { + acrAPI, err = newAcreeOpenapi(registry.Credential.AccessKey, registry.Credential.AccessSecret, info.RegionID, info.InstanceID) + if err != nil { + return nil, err + } + } + authorizer := bearer.NewAuthorizer(realm, service, NewAuth(acrAPI), commonhttp.GetHTTPTransport(commonhttp.WithInsecure(registry.Insecure))) return &adapter{ - region: region, + acrAPI: acrAPI, registry: registry, - domain: fmt.Sprintf(endpointTpl, region), Adapter: native.NewAdapterWithAuthorizer(registry, authorizer), }, nil } @@ -112,16 +140,15 @@ var ( // adapter for to aliyun docker registry type adapter struct { *native.Adapter - region string - domain string + acrAPI openapi registry *model.Registry } var _ adp.Adapter = &adapter{} // Info ... -func (a *adapter) Info() (info *model.RegistryInfo, err error) { - info = &model.RegistryInfo{ +func (a *adapter) Info() (*model.RegistryInfo, error) { + info := &model.RegistryInfo{ Type: model.RegistryTypeAliAcr, SupportedResourceTypes: []string{ model.ResourceTypeImage, @@ -141,7 +168,7 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) { model.TriggerTypeScheduled, }, } - return + return info, nil } func getAdapterInfo() *model.AdapterPattern { @@ -184,6 +211,16 @@ func getAdapterInfo() *model.AdapterPattern { Key: e + "-internal", Value: fmt.Sprintf("https://registry-internal.%s.aliyuncs.com", e), }) + + endpoints = append(endpoints, &model.Endpoint{ + Key: e + "-ee-vpc", + Value: fmt.Sprintf("https://instanceName-registry-vpc.%s.cr.aliyuncs.com", e), + }) + + endpoints = append(endpoints, &model.Endpoint{ + Key: e + "-ee", + Value: fmt.Sprintf("https://instanceName-registry.%s.cr.aliyuncs.com", e), + }) } info := &model.AdapterPattern{ EndpointPattern: &model.EndpointPattern{ @@ -194,30 +231,8 @@ func getAdapterInfo() *model.AdapterPattern { return info } -func (a *adapter) listNamespaces(c *cr.Client) (namespaces []string, err error) { - // list namespaces - var nsReq = cr.CreateGetNamespaceListRequest() - var nsResp *cr.GetNamespaceListResponse - nsReq.SetDomain(a.domain) - nsResp, err = c.GetNamespaceList(nsReq) - if err != nil { - return - } - var resp = &aliACRNamespaceResp{} - err = json.Unmarshal(nsResp.GetHttpContentBytes(), resp) - if err != nil { - return - } - for _, ns := range resp.Data.Namespaces { - namespaces = append(namespaces, ns.Namespace) - } - - log.Debugf("FetchArtifacts.listNamespaces: %#v\n", namespaces) - - return -} - -func (a *adapter) listCandidateNamespaces(c *cr.Client, namespacePattern string) (namespaces []string, err error) { +func (a *adapter) listCandidateNamespaces(namespacePattern string) ([]string, error) { + var namespaces []string if len(namespacePattern) > 0 { if nms, ok := util.IsSpecificPathComponent(namespacePattern); ok { namespaces = append(namespaces, nms...) @@ -228,19 +243,22 @@ func (a *adapter) listCandidateNamespaces(c *cr.Client, namespacePattern string) } } - return a.listNamespaces(c) + if a.acrAPI == nil { + return nil, errors.New("acr api is nil") + } + + return a.acrAPI.ListNamespace() } // FetchArtifacts AliACR not support /v2/_catalog of Registry, we'll list all resources via Aliyun's API -func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Resource, err error) { +func (a *adapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) { log.Debugf("FetchArtifacts.filters: %#v\n", filters) - var client *cr.Client - client, err = cr.NewClientWithAccessKey(a.region, a.registry.Credential.AccessKey, a.registry.Credential.AccessSecret) - if err != nil { - return + if a.acrAPI == nil { + return nil, errors.New("acr api is nil") } + var resources []*model.Resource // get filter pattern var repoPattern string var tagsPattern string @@ -254,31 +272,29 @@ func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Re log.Debugf("\nrepoPattern=%s tagsPattern=%s\n\n", repoPattern, tagsPattern) // get namespaces - var namespaces []string - namespaces, err = a.listCandidateNamespaces(client, namespacePattern) + namespaces, err := a.listCandidateNamespaces(namespacePattern) if err != nil { - return + return nil, err } log.Debugf("got namespaces: %v \n", namespaces) // list repos - var repositories []aliRepo + var repositories []*repository for _, namespace := range namespaces { - var repos []aliRepo - repos, err = a.listReposByNamespace(a.region, namespace, client) + repos, err := a.acrAPI.ListRepository(namespace) if err != nil { - return + return nil, err } log.Debugf("\nnamespace: %s \t repositories: %#v\n\n", namespace, repos) for _, repo := range repos { var ok bool - var repoName = filepath.Join(repo.RepoNamespace, repo.RepoName) + var repoName = filepath.Join(repo.Namespace, repo.Name) ok, err = util.Match(repoPattern, repoName) log.Debugf("\n Repository: %s\t repoPattern: %s\t Match: %v\n", repoName, repoPattern, ok) if err != nil { - return + return nil, err } if ok { repositories = append(repositories, repo) @@ -295,9 +311,9 @@ func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Re repo := r runner.AddTask(func() error { var tags []string - tags, err = a.getTags(repo, client) + tags, err = a.acrAPI.ListRepoTag(repo) if err != nil { - return fmt.Errorf("list tags for repo '%s' error: %v", repo.RepoName, err) + return fmt.Errorf("list tags for repo '%s' error: %v", repo.Name, err) } var artifacts []*model.Artifact @@ -317,13 +333,12 @@ func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Re Registry: a.registry, Metadata: &model.ResourceMetadata{ Repository: &model.Repository{ - Name: filepath.Join(repo.RepoNamespace, repo.RepoName), + Name: filepath.Join(repo.Namespace, repo.Name), }, Artifacts: filterArtifacts, }, } } - return nil }) } @@ -336,65 +351,5 @@ func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Re } } - return -} - -func (a *adapter) listReposByNamespace(_ string, namespace string, c *cr.Client) (repos []aliRepo, err error) { - var reposReq = cr.CreateGetRepoListByNamespaceRequest() - var reposResp = cr.CreateGetRepoListByNamespaceResponse() - reposReq.SetDomain(a.domain) - reposReq.RepoNamespace = namespace - var page = 1 - for { - reposReq.Page = requests.NewInteger(page) - reposResp, err = c.GetRepoListByNamespace(reposReq) - if err != nil { - return - } - var resp = &aliReposResp{} - err = json.Unmarshal(reposResp.GetHttpContentBytes(), resp) - if err != nil { - return - } - repos = append(repos, resp.Data.Repos...) - - if resp.Data.Total-(resp.Data.Page*resp.Data.PageSize) <= 0 { - break - } - page++ - } - return -} - -func (a *adapter) getTags(repo aliRepo, c *cr.Client) (tags []string, err error) { - log.Debugf("[ali-acr.getTags]%s: %#v\n", a.domain, repo) - var tagsReq = cr.CreateGetRepoTagsRequest() - var tagsResp = cr.CreateGetRepoTagsResponse() - tagsReq.SetDomain(a.domain) - tagsReq.RepoNamespace = repo.RepoNamespace - tagsReq.RepoName = repo.RepoName - var page = 1 - for { - tagsReq.Page = requests.NewInteger(page) - tagsResp, err = c.GetRepoTags(tagsReq) - if err != nil { - return - } - - var resp = &aliTagResp{} - err = json.Unmarshal(tagsResp.GetHttpContentBytes(), resp) - if err != nil { - return - } - for _, tag := range resp.Data.Tags { - tags = append(tags, tag.Tag) - } - - if resp.Data.Total-(resp.Data.Page*resp.Data.PageSize) <= 0 { - break - } - page++ - } - - return + return resources, nil } diff --git a/src/pkg/reg/adapter/aliacr/adapter_test.go b/src/pkg/reg/adapter/aliacr/adapter_test.go index e62238a9c80..ffacd30cf00 100644 --- a/src/pkg/reg/adapter/aliacr/adapter_test.go +++ b/src/pkg/reg/adapter/aliacr/adapter_test.go @@ -62,8 +62,6 @@ func getMockAdapter(t *testing.T, hasCred, health bool) (*adapter, *httptest.Ser } return &adapter{ Adapter: native.NewAdapter(registry), - region: "test-region", - domain: server.URL, registry: registry, }, server } @@ -79,46 +77,91 @@ func TestAdapter_Info(t *testing.T) { assert.EqualValues(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) } -func Test_getRegion(t *testing.T) { +func Test_getRegistryURL(t *testing.T) { tests := []struct { - name string - url string - wantRegion string - wantErr bool + name string + url string + want string + wantErr bool }{ - {"registry shanghai", "https://registry.cn-shanghai.aliyuncs.com", "cn-shanghai", false}, - {"invalid registry shanghai", "http://registry.cn-shanghai.aliyuncs.com", "", true}, - {"registry hangzhou", "https://registry.cn-hangzhou.aliyuncs.com", "cn-hangzhou", false}, - {"registry hangzhou vpc", "https://registry-vpc.cn-hangzhou.aliyuncs.com", "cn-hangzhou", false}, - {"registry hangzhou internal", "https://registry-internal.cn-hangzhou.aliyuncs.com", "cn-hangzhou", false}, - {"cr shanghai", "https://cr.cn-shanghai.aliyuncs.com", "cn-shanghai", false}, - {"cr hangzhou", "https://cr.cn-hangzhou.aliyuncs.com", "cn-hangzhou", false}, - {"invalid cr url", "https://acr.cn-hangzhou.aliyuncs.com", "", true}, - {"invalid registry url", "https://registry.cn-hangzhou.ali.com", "", true}, + { + "empty url", + "", + "", + true, + }, + { + "just return url", + "https://cr.cn-hangzhou.aliyun.com", + "https://cr.cn-hangzhou.aliyun.com", + false, + }, + { + "change match url", + "https://cr.cn-hangzhou.aliyuncs.com", + "https://registry.cn-hangzhou.aliyuncs.com", + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotRegion, err := getRegion(tt.url) + got, err := getRegistryURL(tt.url) if tt.wantErr { assert.NotNil(t, err) } - assert.Equal(t, tt.wantRegion, gotRegion) + assert.Equal(t, tt.want, got) }) } } -var urlForBenchmark = []string{ - "https://cr.cn-hangzhou.aliyuncs.com", - "https://registry.cn-shanghai.aliyuncs.com", - "https://registry-vpc.cn-shanghai.aliyuncs.com", - "https://registry-internal.cn-shanghai.aliyuncs.com", -} - -func BenchmarkGetRegion(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, url := range urlForBenchmark { - getRegion(url) - } +func Test_parseRegistryService(t *testing.T) { + tests := []struct { + name string + service string + wantInfo *registryServiceInfo + wantErr bool + }{ + { + "not acr Service", + "otherregistry.cn-hangzhou:china", + nil, + true, + }, + { + "empty Service", + "", + nil, + true, + }, + { + "acr ee service", + "registry.aliyuncs.com:cn-hangzhou:china:cri-xxxxxxxxx", + ®istryServiceInfo{ + IsACREE: true, + RegionID: "cn-hangzhou", + InstanceID: "cri-xxxxxxxxx", + }, + false, + }, + { + "acr service", + "registry.aliyuncs.com:cn-hangzhou:26842", + ®istryServiceInfo{ + IsACREE: false, + RegionID: "cn-hangzhou", + InstanceID: "", + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + info, err := parseRegistryService(tt.service) + if tt.wantErr { + assert.NotNil(t, err) + } + assert.Equal(t, tt.wantInfo, info) + }) } } @@ -132,9 +175,6 @@ func Test_adapter_FetchArtifacts(t *testing.T) { } func Test_aliyunAuthCredential_isCacheTokenValid(t *testing.T) { type fields struct { - region string - accessKey string - secretKey string cacheToken *registryTemporaryToken cacheTokenExpiredAt time.Time } @@ -145,23 +185,22 @@ func Test_aliyunAuthCredential_isCacheTokenValid(t *testing.T) { fields fields want bool }{ - {"nil cacheTokenExpiredAt", fields{"test-region", "MockAccessKey", "MockSecretKey", nil, nilTime}, false}, - {"nil cacheToken", fields{"test-region", "MockAccessKey", "MockSecretKey", nil, time.Time{}}, false}, - {"expired", fields{"test-region", "MockAccessKey", "MockSecretKey", ®istryTemporaryToken{}, time.Now().AddDate(0, 0, -1)}, false}, - {"ok", fields{"test-region", "MockAccessKey", "MockSecretKey", ®istryTemporaryToken{}, time.Now().AddDate(0, 0, 1)}, true}, + {"nil cacheTokenExpiredAt", fields{nil, nilTime}, false}, + {"nil cacheToken", fields{nil, time.Time{}}, false}, + {"expired", fields{®istryTemporaryToken{}, time.Now().AddDate(0, 0, -1)}, false}, + {"ok", fields{®istryTemporaryToken{}, time.Now().AddDate(0, 0, 1)}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := &aliyunAuthCredential{ - region: tt.fields.region, - accessKey: tt.fields.accessKey, - secretKey: tt.fields.secretKey, cacheToken: tt.fields.cacheToken, cacheTokenExpiredAt: tt.fields.cacheTokenExpiredAt, } if got := a.isCacheTokenValid(); got != tt.want { + fmt.Println(got) assert.Equal(t, got, tt.want) } }) } + } diff --git a/src/pkg/reg/adapter/aliacr/auth.go b/src/pkg/reg/adapter/aliacr/auth.go index 375b1510ae1..38f006d5e85 100644 --- a/src/pkg/reg/adapter/aliacr/auth.go +++ b/src/pkg/reg/adapter/aliacr/auth.go @@ -15,13 +15,10 @@ package aliacr import ( - "encoding/json" - "fmt" + "errors" "net/http" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/services/cr" - "github.com/goharbor/harbor/src/common/http/modifier" "github.com/goharbor/harbor/src/lib/log" ) @@ -31,9 +28,7 @@ type Credential modifier.Modifier // Implements interface Credential type aliyunAuthCredential struct { - region string - accessKey string - secretKey string + acrAPI openapi cacheToken *registryTemporaryToken cacheTokenExpiredAt time.Time } @@ -46,11 +41,9 @@ type registryTemporaryToken struct { var _ Credential = &aliyunAuthCredential{} // NewAuth will get a temporary docker registry username and password via aliyun cr service API. -func NewAuth(region, accessKey, secretKey string) Credential { +func NewAuth(acrAPI openapi) Credential { return &aliyunAuthCredential{ - region: region, - accessKey: accessKey, - secretKey: secretKey, + acrAPI: acrAPI, cacheToken: ®istryTemporaryToken{}, } } @@ -58,27 +51,16 @@ func NewAuth(region, accessKey, secretKey string) Credential { func (a *aliyunAuthCredential) Modify(r *http.Request) (err error) { if !a.isCacheTokenValid() { log.Debugf("[aliyunAuthCredential.Modify.updateToken]Host: %s\n", r.Host) - var client *cr.Client - client, err = cr.NewClientWithAccessKey(a.region, a.accessKey, a.secretKey) - if err != nil { - return - } - - var tokenRequest = cr.CreateGetAuthorizationTokenRequest() - var tokenResponse *cr.GetAuthorizationTokenResponse - tokenRequest.SetDomain(fmt.Sprintf(endpointTpl, a.region)) - tokenResponse, err = client.GetAuthorizationToken(tokenRequest) - if err != nil { - return + if a.acrAPI == nil { + return errors.New("acr api is nil") } - var v authorizationToken - err = json.Unmarshal(tokenResponse.GetHttpContentBytes(), &v) + v, err := a.acrAPI.GetAuthorizationToken() if err != nil { - return + return err } - a.cacheTokenExpiredAt = v.Data.ExpireDate.ToTime() - a.cacheToken.user = v.Data.TempUserName - a.cacheToken.password = v.Data.AuthorizationToken + a.cacheTokenExpiredAt = v.expiresAt + a.cacheToken.user = v.user + a.cacheToken.password = v.password } else { log.Debug("[aliyunAuthCredential] USE CACHE TOKEN!!!") } diff --git a/src/pkg/reg/adapter/aliacr/openapi.go b/src/pkg/reg/adapter/aliacr/openapi.go new file mode 100644 index 00000000000..4a46c8fa087 --- /dev/null +++ b/src/pkg/reg/adapter/aliacr/openapi.go @@ -0,0 +1,315 @@ +// 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 aliacr + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/cr" + "github.com/aliyun/alibaba-cloud-sdk-go/services/cr_ee" +) + +type repository struct { + Name string + Namespace string + ID string +} + +type authToken struct { + user string + password string + expiresAt time.Time +} + +// openapi is an interface that defines methods for interacting with an open API. +type openapi interface { + // ListNamespace returns a list of all namespaces. + ListNamespace() ([]string, error) + // ListRepository returns a list of all repositories for a specified namespace. + ListRepository(namespaceName string) ([]*repository, error) + // ListRepoTag returns a list of all tags for a specified repository. + ListRepoTag(repo *repository) ([]string, error) + // GetAuthorizationToken returns the authorization token for repository. + GetAuthorizationToken() (*authToken, error) +} + +type acrOpenapi struct { + client *cr.Client + domain string +} + +var _ openapi = &acrOpenapi{} + +// newAcrOpenapi creates a new acrOpenapi instance. +func newAcrOpenapi(accessKeyID string, accessKeySecret string, regionID string) (openapi, error) { + client, err := cr.NewClientWithAccessKey(regionID, accessKeyID, accessKeySecret) + if err != nil { + return nil, err + } + return &acrOpenapi{ + client: client, + domain: fmt.Sprintf(endpointTpl, regionID), + }, nil +} + +// ListNamespace returns a list of namespaces +func (acr *acrOpenapi) ListNamespace() ([]string, error) { + var namespaces []string + nsReq := cr.CreateGetNamespaceListRequest() + nsReq.SetDomain(acr.domain) + nsResp, err := acr.client.GetNamespaceList(nsReq) + if err != nil { + return nil, err + } + var resp = &aliACRNamespaceResp{} + err = json.Unmarshal(nsResp.GetHttpContentBytes(), resp) + if err != nil { + return nil, err + } + for _, ns := range resp.Data.Namespaces { + namespaces = append(namespaces, ns.Namespace) + } + return namespaces, nil +} + +// ListRepository returns a list of repositories in the specified namespace +func (acr *acrOpenapi) ListRepository(namespaceName string) ([]*repository, error) { + var repos []*repository + reposReq := cr.CreateGetRepoListByNamespaceRequest() + reposReq.SetDomain(acr.domain) + reposReq.RepoNamespace = namespaceName + var page = 1 + for { + reposReq.Page = requests.NewInteger(page) + reposResp, err := acr.client.GetRepoListByNamespace(reposReq) + if err != nil { + return nil, err + } + var resp = &aliReposResp{} + err = json.Unmarshal(reposResp.GetHttpContentBytes(), resp) + if err != nil { + return nil, err + } + + for _, repo := range resp.Data.Repos { + repos = append(repos, &repository{ + Name: repo.RepoName, + Namespace: repo.RepoNamespace, + }) + } + + if resp.Data.Total <= (resp.Data.Page * resp.Data.PageSize) { + break + } + page++ + } + return repos, nil +} + +// ListRepoTag returns a list of tags in the specified repository +func (acr *acrOpenapi) ListRepoTag(repo *repository) ([]string, error) { + var tags []string + tagsReq := cr.CreateGetRepoTagsRequest() + tagsReq.SetDomain(acr.domain) + tagsReq.RepoNamespace = repo.Namespace + tagsReq.RepoName = repo.Name + var page = 1 + for { + tagsReq.Page = requests.NewInteger(page) + tagsResp, err := acr.client.GetRepoTags(tagsReq) + if err != nil { + return nil, err + } + var resp = &aliTagResp{} + err = json.Unmarshal(tagsResp.GetHttpContentBytes(), resp) + if err != nil { + return nil, err + } + for _, tag := range resp.Data.Tags { + tags = append(tags, tag.Tag) + } + + if resp.Data.Total <= (resp.Data.Page * resp.Data.PageSize) { + break + } + page++ + } + return tags, nil +} + +// GetAuthorizationToken returns the authorization token for repository. +func (acr *acrOpenapi) GetAuthorizationToken() (*authToken, error) { + tokenRequest := cr.CreateGetAuthorizationTokenRequest() + tokenRequest.SetDomain(acr.domain) + tokenResponse, err := acr.client.GetAuthorizationToken(tokenRequest) + if err != nil { + return nil, err + } + var v authorizationToken + err = json.Unmarshal(tokenResponse.GetHttpContentBytes(), &v) + if err != nil { + return nil, err + } + return &authToken{ + user: v.Data.TempUserName, + password: v.Data.AuthorizationToken, + expiresAt: v.Data.ExpireDate.ToTime(), + }, nil +} + +type acreeOpenapi struct { + client *cr_ee.Client + domain string + instanceID string +} + +var _ openapi = &acreeOpenapi{} + +// newAcreeOpenapi creates a new acreeOpenapi instance. +func newAcreeOpenapi(accessKeyID string, accessKeySecret string, regionID string, instanceID string) (openapi, error) { + client, err := cr_ee.NewClientWithAccessKey(regionID, accessKeyID, accessKeySecret) + if err != nil { + return nil, err + } + return &acreeOpenapi{ + client: client, + domain: fmt.Sprintf(endpointTpl, regionID), + instanceID: instanceID, + }, nil +} + +// ListNamespace returns a list of namespaces +func (acree *acreeOpenapi) ListNamespace() ([]string, error) { + var namespaces []string + nsReq := cr_ee.CreateListNamespaceRequest() + nsReq.SetDomain(acree.domain) + nsReq.InstanceId = acree.instanceID + page := 1 + for { + nsReq.PageNo = requests.NewInteger(page) + nsResp, err := acree.client.ListNamespace(nsReq) + if err != nil { + return nil, err + } + for _, ns := range nsResp.Namespaces { + namespaces = append(namespaces, ns.NamespaceName) + } + if !nsResp.ListNamespaceIsSuccess { + return nil, fmt.Errorf("failed to list namespace: %v", nsResp) + } + total, err := strconv.Atoi(nsResp.TotalCount) + if err != nil { + return nil, err + } + if total-(nsResp.PageNo*nsResp.PageSize) <= 0 { + break + } + page++ + } + return namespaces, nil +} + +// ListRepository returns a list of repositories in the specified namespace +func (acree *acreeOpenapi) ListRepository(namespaceName string) ([]*repository, error) { + var repos []*repository + reposReq := cr_ee.CreateListRepositoryRequest() + reposReq.SetDomain(acree.domain) + reposReq.InstanceId = acree.instanceID + reposReq.RepoNamespaceName = namespaceName + reposReq.RepoStatus = "NORMAL" + page := 1 + for { + reposReq.PageNo = requests.NewInteger(page) + reposResp, err := acree.client.ListRepository(reposReq) + if err != nil { + return nil, err + } + if !reposResp.ListRepositoryIsSuccess { + return nil, fmt.Errorf("failed to list repo: %s", reposResp.GetHttpContentString()) + } + for _, repo := range reposResp.Repositories { + repos = append(repos, &repository{ + Name: repo.RepoName, + Namespace: repo.RepoNamespaceName, + ID: repo.RepoId, + }) + } + total, err := strconv.Atoi(reposResp.TotalCount) + if err != nil { + return nil, err + } + if total <= page*reposResp.PageSize { + break + } + page++ + } + return repos, nil +} + +// ListRepoTag returns a list of tags in the specified repository +func (acree *acreeOpenapi) ListRepoTag(repo *repository) ([]string, error) { + var tags []string + tagsReq := cr_ee.CreateListRepoTagRequest() + tagsReq.SetDomain(acree.domain) + tagsReq.InstanceId = acree.instanceID + tagsReq.RepoId = repo.ID + var page = 1 + for { + tagsReq.PageNo = requests.NewInteger(page) + tagsResp, err := acree.client.ListRepoTag(tagsReq) + if err != nil { + return nil, err + } + for _, image := range tagsResp.Images { + tags = append(tags, image.Tag) + } + if !tagsResp.ListRepoTagIsSuccess { + return nil, fmt.Errorf("failed to list repo tag: %s", tagsResp.GetHttpContentString()) + } + total, err := strconv.Atoi(tagsResp.TotalCount) + if err != nil { + return nil, err + } + if total <= page*tagsResp.PageSize { + break + } + page++ + } + return tags, nil +} + +// GetAuthorizationToken returns the authorization token for repository. +func (acree *acreeOpenapi) GetAuthorizationToken() (*authToken, error) { + tokenRequest := cr_ee.CreateGetAuthorizationTokenRequest() + // FIXME: use vpc endpoint if vpc is enabled + tokenRequest.SetDomain(acree.domain) + tokenRequest.InstanceId = acree.instanceID + tokenResponse, err := acree.client.GetAuthorizationToken(tokenRequest) + if err != nil { + return nil, err + } + if !tokenResponse.GetAuthorizationTokenIsSuccess { + return nil, fmt.Errorf("failed to get authorization token: %s", tokenResponse.GetHttpContentString()) + } + return &authToken{ + user: tokenResponse.TempUsername, + password: tokenResponse.AuthorizationToken, + expiresAt: time.Unix(tokenResponse.ExpireTime/1000, 0), + }, nil +} diff --git a/src/pkg/reg/adapter/aliacr/openapi_test.go b/src/pkg/reg/adapter/aliacr/openapi_test.go new file mode 100644 index 00000000000..e70b63ecec4 --- /dev/null +++ b/src/pkg/reg/adapter/aliacr/openapi_test.go @@ -0,0 +1,262 @@ +// 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 aliacr + +import ( + "os" + "reflect" + "testing" + "time" + + "github.com/goharbor/harbor/src/pkg/reg/model" +) + +var acrAdapter *adapter +var acreeAdapter *adapter + +func init() { + accessKey := os.Getenv("ALIYUN_ACCESS_KEY") + accessSecret := os.Getenv("ALIYUN_ACCESS_SECRET") + acrEndpoint := os.Getenv("ALIYUN_ACR_ENDPOINT") + acreeEndpoint := os.Getenv("ALIYUN_ACREE_ENDPOINT") + + if accessKey == "" || accessSecret == "" { + return + } + if acrEndpoint != "" { + acrAdapter, _ = newAdapter(&model.Registry{ + URL: acrEndpoint, + Credential: &model.Credential{ + AccessKey: accessKey, + AccessSecret: accessSecret, + }, + }) + } + + if acreeEndpoint != "" { + acreeAdapter, _ = newAdapter(&model.Registry{ + URL: acreeEndpoint, + Credential: &model.Credential{ + AccessKey: accessKey, + AccessSecret: accessSecret, + }, + }) + } +} + +func skipCheck(adapter *adapter) bool { + return adapter == nil +} + +func Test_ACREE_ListNamespace(t *testing.T) { + if skipCheck(acreeAdapter) { + t.Skip("skip test acree ListNamespace") + } + testcases := []struct { + name string + wantedNamespace []string + }{ + { + name: "test list namespace", + wantedNamespace: []string{"ut_acree_namespace"}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + namespaces, err := acreeAdapter.acrAPI.ListNamespace() + if err != nil { + t.Errorf("ListNamespace error: %v", err) + } + if !reflect.DeepEqual(namespaces, tc.wantedNamespace) { + t.Errorf("ListNamespace error, wants=%v, actual=%v", tc.wantedNamespace, namespaces) + } + }) + } +} + +func Test_ACREE_ListRepositoryAndTag(t *testing.T) { + if skipCheck(acreeAdapter) { + t.Skip("skip test acree ListRepository") + } + testcases := []struct { + name string + namespace string + wantedRepo []string + wantedTag []string + }{ + { + name: "test list repository", + namespace: "ut_acree_namespace", + wantedRepo: []string{"ut_acree_repo"}, + wantedTag: []string{"latest"}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + // List repository + repos, err := acreeAdapter.acrAPI.ListRepository(tc.namespace) + if err != nil { + t.Errorf("ListRepository error: %v", err) + } + var actualRepo []string + for _, repo := range repos { + actualRepo = append(actualRepo, repo.Name) + } + if !reflect.DeepEqual(actualRepo, tc.wantedRepo) { + t.Errorf("ListRepository error, wants=%v, actual=%v", tc.wantedRepo, actualRepo) + } + + // List tag + var actualTag []string + for _, repo := range repos { + tags, err := acreeAdapter.acrAPI.ListRepoTag(repo) + if err != nil { + t.Errorf("ListRepoTag error: %v", err) + } + actualTag = append(actualTag, tags...) + } + if !reflect.DeepEqual(actualTag, tc.wantedTag) { + t.Errorf("ListRepoTag error, wants=%v, actual=%v", tc.wantedTag, actualTag) + } + }) + } +} + +func Test_ACREE_GetAuthorizationToken(t *testing.T) { + if skipCheck(acreeAdapter) { + t.Skip("skip test acree GetAuthorizationToken") + } + testcases := []struct { + name string + }{ + { + name: "test get authorization token", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + token, err := acreeAdapter.acrAPI.GetAuthorizationToken() + if err != nil { + t.Errorf("GetAuthorizationToken error: %v", err) + } + if token == nil { + t.Errorf("GetAuthorizationToken error, token is nil") + } + if !time.Now().Before(token.expiresAt) { + t.Errorf("GetAuthorizationToken error, token expiresAt is not valid") + } + }) + } +} + +func Test_ACR_ListNamespace(t *testing.T) { + if skipCheck(acrAdapter) { + t.Skip("skip test acr ListNamespace") + } + testcases := []struct { + name string + wantedNamespace []string + }{ + { + name: "test list namespace", + wantedNamespace: []string{"ut_acr_namespace"}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + namespaces, err := acrAdapter.acrAPI.ListNamespace() + if err != nil { + t.Errorf("ListNamespace error: %v", err) + } + if !reflect.DeepEqual(namespaces, tc.wantedNamespace) { + t.Errorf("ListNamespace error, wants=%v, actual=%v", tc.wantedNamespace, namespaces) + } + }) + } +} + +func Test_ACR_ListRepositoryAndTag(t *testing.T) { + if skipCheck(acrAdapter) { + t.Skip("skip test acr ListRepository") + } + testcases := []struct { + name string + namespace string + wantedRepo []string + wantedTag []string + }{ + { + name: "test list repository", + namespace: "ut_acr_namespace", + wantedRepo: []string{"ut_acr_repo"}, + wantedTag: []string{"latest"}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + // List repository + repos, err := acrAdapter.acrAPI.ListRepository(tc.namespace) + if err != nil { + t.Errorf("ListRepository error: %v", err) + } + var actualRepo []string + for _, repo := range repos { + actualRepo = append(actualRepo, repo.Name) + } + if !reflect.DeepEqual(actualRepo, tc.wantedRepo) { + t.Errorf("ListRepository error, wants=%v, actual=%v", tc.wantedRepo, actualRepo) + } + // List tag + var actualTag []string + for _, repo := range repos { + tags, err := acrAdapter.acrAPI.ListRepoTag(repo) + if err != nil { + t.Errorf("ListRepoTag error: %v", err) + } + actualTag = append(actualTag, tags...) + } + if !reflect.DeepEqual(actualTag, tc.wantedTag) { + t.Errorf("ListRepoTag error, wants=%v, actual=%v", tc.wantedTag, actualTag) + } + }) + } +} + +func Test_ACR_GetAuthorizationToken(t *testing.T) { + if skipCheck(acrAdapter) { + t.Skip("skip test acr GetAuthorizationToken") + } + testcases := []struct { + name string + }{ + { + name: "test get authorization token", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + token, err := acrAdapter.acrAPI.GetAuthorizationToken() + if err != nil { + t.Errorf("GetAuthorizationToken error: %v", err) + } + if token == nil { + t.Errorf("GetAuthorizationToken error, token is nil") + } + if !time.Now().Before(token.expiresAt) { + t.Errorf("GetAuthorizationToken error, token expiresAt is not valid") + } + }) + } +} diff --git a/src/pkg/reg/adapter/aliacr/types.go b/src/pkg/reg/adapter/aliacr/types.go index 6ff30cca381..7d918282bfe 100644 --- a/src/pkg/reg/adapter/aliacr/types.go +++ b/src/pkg/reg/adapter/aliacr/types.go @@ -17,12 +17,18 @@ package aliacr import "time" const ( - registryEndpointTpl = "https://registry.%s.aliyuncs.com" - registryVPCEndpointTpl = "https://registry-vpc.%s.aliyuncs.com" - registryInternalEndpointTpl = "https://registry-internal.%s.aliyuncs.com" - endpointTpl = "cr.%s.aliyuncs.com" + registryEndpointTpl = "https://registry.%s.aliyuncs.com" + endpointTpl = "cr.%s.aliyuncs.com" + + registryACRService = "registry.aliyuncs.com" ) +type registryServiceInfo struct { + IsACREE bool + RegionID string + InstanceID string +} + type authorizationToken struct { Data struct { ExpireDate timeUnix `json:"expireDate"` diff --git a/src/portal/src/app/base/left-side-nav/projects/create-project/create-project.component.ts b/src/portal/src/app/base/left-side-nav/projects/create-project/create-project.component.ts index bec77040318..1c572ee578f 100644 --- a/src/portal/src/app/base/left-side-nav/projects/create-project/create-project.component.ts +++ b/src/portal/src/app/base/left-side-nav/projects/create-project/create-project.component.ts @@ -107,7 +107,7 @@ export class CreateProjectComponent registries: Registry[] = []; supportedRegistryTypeQueryString: string = - 'type={docker-hub harbor azure-acr aws-ecr google-gcr quay docker-registry github-ghcr jfrog-artifactory}'; + 'type={docker-hub harbor azure-acr ali-acr aws-ecr google-gcr quay docker-registry github-ghcr jfrog-artifactory}'; // **Added property for bandwidth error message** bandwidthError: string | null = null; diff --git a/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts b/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts index f75982a5279..4d571c7d820 100644 --- a/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts @@ -273,6 +273,166 @@ describe('CreateEditEndpointComponent (inline template)', () => { key: 'me-east-1-internal', value: 'https://registry-internal.me-east-1.aliyuncs.com', }, + { + key: 'cn-hangzhou-ee-vpc', + value: `https://instanceName-registry-vpc.cn-hangzhou.cr.aliyuncs.com`, + }, + { + key: 'cn-shanghai-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-shanghai.cr.aliyuncs.com', + }, + { + key: 'cn-qingdao-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-qingdao.cr.aliyuncs.com', + }, + { + key: 'cn-beijing-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-beijing.cr.aliyuncs.com', + }, + { + key: 'cn-zhangjiakou-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-zhangjiakou.cr.aliyuncs.com', + }, + { + key: 'cn-huhehaote-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-huhehaote.cr.aliyuncs.com', + }, + { + key: 'cn-shenzhen-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-shenzhen.cr.aliyuncs.com', + }, + { + key: 'cn-chengdu-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-chengdu.cr.aliyuncs.com', + }, + { + key: 'cn-hongkong-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-hongkong.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-1-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-2-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-2.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-3-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-3.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-5-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-5.aliyuncs.cr.com', + }, + { + key: 'ap-northeast-1-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-northeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-south-1-ee-vpc', + value: 'https:/instanceName-/registry-vpc.ap-south-1.cr.aliyuncs.com', + }, + { + key: 'eu-central-1-ee-vpc', + value: 'https://instanceName-registry-vpc.eu-central-1.cr.aliyuncs.com', + }, + { + key: 'eu-west-1-ee-vpc', + value: 'https://instanceName-registry-vpc.eu-west-1.cr.aliyuncs.com', + }, + { + key: 'us-west-1-ee-vpc', + value: 'https://instanceName-registry-vpc.us-west-1.cr.aliyuncs.com', + }, + { + key: 'us-east-1-ee-vpc', + value: 'https://instanceName-registry-vpc.us-east-1.cr.aliyuncs.com', + }, + { + key: 'me-east-1-ee-vpc', + value: 'https://instanceName-registry-vpc.me-east-1.cr.aliyuncs.com', + }, + { + key: 'cn-hangzhou-ee', + value: `https://instanceName-registry.cn-hangzhou.cr.aliyuncs.com`, + }, + { + key: 'cn-shanghai-ee', + value: 'https://instanceName-registry.cn-shanghai.cr.aliyuncs.com', + }, + { + key: 'cn-qingdao-ee', + value: 'https://instanceName-registry.cn-qingdao.cr.aliyuncs.com', + }, + { + key: 'cn-beijing-ee', + value: 'https://instanceName-registry.cn-beijing.cr.aliyuncs.com', + }, + { + key: 'cn-zhangjiakou-ee', + value: 'https://instanceName-registry.cn-zhangjiakou.cr.aliyuncs.com', + }, + { + key: 'cn-huhehaote-ee', + value: 'https://instanceName-registry.cn-huhehaote.cr.aliyuncs.com', + }, + { + key: 'cn-shenzhen-ee', + value: 'https://instanceName-registry.cn-shenzhen.cr.aliyuncs.com', + }, + { + key: 'cn-chengdu-ee', + value: 'https://instanceName-registry.cn-chengdu.cr.aliyuncs.com', + }, + { + key: 'cn-hongkong-ee', + value: 'https://instanceName-registry.cn-hongkong.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-1-ee', + value: 'https://instanceName-registry.ap-southeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-2-ee', + value: 'https://instanceName-registry.ap-southeast-2.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-3-ee', + value: 'https://instanceName-registry.ap-southeast-3.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-5-ee', + value: 'https://instanceName-registry.ap-southeast-5.aliyuncs.cr.com', + }, + { + key: 'ap-northeast-1-ee', + value: 'https://instanceName-registry.ap-northeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-south-1-ee', + value: 'https:/instanceName-/registry.ap-south-1.cr.aliyuncs.com', + }, + { + key: 'eu-central-1-ee', + value: 'https://instanceName-registry.eu-central-1.cr.aliyuncs.com', + }, + { + key: 'eu-west-1-ee', + value: 'https://instanceName-registry.eu-west-1.cr.aliyuncs.com', + }, + { + key: 'us-west-1-ee', + value: 'https://instanceName-registry.us-west-1.cr.aliyuncs.com', + }, + { + key: 'us-east-1-ee', + value: 'https://instanceName-registry-.us-east-1.cr.aliyuncs.com', + }, + { + key: 'me-east-1-ee', + value: 'https://instanceName-registry.me-east-1.cr.aliyuncs.com', + }, ], }, credential_pattern: null, diff --git a/src/portal/src/app/base/left-side-nav/registries/endpoint.component.spec.ts b/src/portal/src/app/base/left-side-nav/registries/endpoint.component.spec.ts index 473a1de75fe..1b649347c70 100644 --- a/src/portal/src/app/base/left-side-nav/registries/endpoint.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/registries/endpoint.component.spec.ts @@ -263,6 +263,166 @@ describe('EndpointComponent (inline template)', () => { key: 'me-east-1-internal', value: 'https://registry-internal.me-east-1.aliyuncs.com', }, + { + key: 'cn-hangzhou-ee-vpc', + value: `https://instanceName-registry-vpc.cn-hangzhou.cr.aliyuncs.com`, + }, + { + key: 'cn-shanghai-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-shanghai.cr.aliyuncs.com', + }, + { + key: 'cn-qingdao-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-qingdao.cr.aliyuncs.com', + }, + { + key: 'cn-beijing-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-beijing.cr.aliyuncs.com', + }, + { + key: 'cn-zhangjiakou-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-zhangjiakou.cr.aliyuncs.com', + }, + { + key: 'cn-huhehaote-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-huhehaote.cr.aliyuncs.com', + }, + { + key: 'cn-shenzhen-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-shenzhen.cr.aliyuncs.com', + }, + { + key: 'cn-chengdu-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-chengdu.cr.aliyuncs.com', + }, + { + key: 'cn-hongkong-ee-vpc', + value: 'https://instanceName-registry-vpc.cn-hongkong.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-1-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-2-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-2.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-3-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-3.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-5-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-southeast-5.aliyuncs.cr.com', + }, + { + key: 'ap-northeast-1-ee-vpc', + value: 'https://instanceName-registry-vpc.ap-northeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-south-1-ee-vpc', + value: 'https:/instanceName-/registry-vpc.ap-south-1.cr.aliyuncs.com', + }, + { + key: 'eu-central-1-ee-vpc', + value: 'https://instanceName-registry-vpc.eu-central-1.cr.aliyuncs.com', + }, + { + key: 'eu-west-1-ee-vpc', + value: 'https://instanceName-registry-vpc.eu-west-1.cr.aliyuncs.com', + }, + { + key: 'us-west-1-ee-vpc', + value: 'https://instanceName-registry-vpc.us-west-1.cr.aliyuncs.com', + }, + { + key: 'us-east-1-ee-vpc', + value: 'https://instanceName-registry-vpc.us-east-1.cr.aliyuncs.com', + }, + { + key: 'me-east-1-ee-vpc', + value: 'https://instanceName-registry-vpc.me-east-1.cr.aliyuncs.com', + }, + { + key: 'cn-hangzhou-ee', + value: `https://instanceName-registry.cn-hangzhou.cr.aliyuncs.com`, + }, + { + key: 'cn-shanghai-ee', + value: 'https://instanceName-registry.cn-shanghai.cr.aliyuncs.com', + }, + { + key: 'cn-qingdao-ee', + value: 'https://instanceName-registry.cn-qingdao.cr.aliyuncs.com', + }, + { + key: 'cn-beijing-ee', + value: 'https://instanceName-registry.cn-beijing.cr.aliyuncs.com', + }, + { + key: 'cn-zhangjiakou-ee', + value: 'https://instanceName-registry.cn-zhangjiakou.cr.aliyuncs.com', + }, + { + key: 'cn-huhehaote-ee', + value: 'https://instanceName-registry.cn-huhehaote.cr.aliyuncs.com', + }, + { + key: 'cn-shenzhen-ee', + value: 'https://instanceName-registry.cn-shenzhen.cr.aliyuncs.com', + }, + { + key: 'cn-chengdu-ee', + value: 'https://instanceName-registry.cn-chengdu.cr.aliyuncs.com', + }, + { + key: 'cn-hongkong-ee', + value: 'https://instanceName-registry.cn-hongkong.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-1-ee', + value: 'https://instanceName-registry.ap-southeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-2-ee', + value: 'https://instanceName-registry.ap-southeast-2.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-3-ee', + value: 'https://instanceName-registry.ap-southeast-3.cr.aliyuncs.com', + }, + { + key: 'ap-southeast-5-ee', + value: 'https://instanceName-registry.ap-southeast-5.aliyuncs.cr.com', + }, + { + key: 'ap-northeast-1-ee', + value: 'https://instanceName-registry.ap-northeast-1.cr.aliyuncs.com', + }, + { + key: 'ap-south-1-ee', + value: 'https:/instanceName-/registry.ap-south-1.cr.aliyuncs.com', + }, + { + key: 'eu-central-1-ee', + value: 'https://instanceName-registry.eu-central-1.cr.aliyuncs.com', + }, + { + key: 'eu-west-1-ee', + value: 'https://instanceName-registry.eu-west-1.cr.aliyuncs.com', + }, + { + key: 'us-west-1-ee', + value: 'https://instanceName-registry.us-west-1.cr.aliyuncs.com', + }, + { + key: 'us-east-1-ee', + value: 'https://instanceName-registry-.us-east-1.cr.aliyuncs.com', + }, + { + key: 'me-east-1-ee', + value: 'https://instanceName-registry.me-east-1.cr.aliyuncs.com', + }, ], }, credential_pattern: null, diff --git a/src/portal/src/app/shared/services/endpoint.service.ts b/src/portal/src/app/shared/services/endpoint.service.ts index 0b007bca080..ad60f2654c1 100644 --- a/src/portal/src/app/shared/services/endpoint.service.ts +++ b/src/portal/src/app/shared/services/endpoint.service.ts @@ -13,7 +13,7 @@ import { catchError, map } from 'rxjs/operators'; import { ReplicationPolicy } from '../../../../ng-swagger-gen/models/replication-policy'; export const ADAPTERS_MAP = { - 'ali-acr': 'Alibaba ACR', + 'ali-acr': 'Alibaba Cloud ACR', 'aws-ecr': 'Aws ECR', 'azure-acr': 'Azure ACR', 'docker-hub': 'Docker Hub', diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 7523ab2edaf..259acc69e87 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -257,7 +257,7 @@ "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. Für eine unbegrenzte Speicherbeschränkung '-1' eingeben.", "TYPE": "Typ", "PROXY_CACHE": "Proxy Cache", - "PROXY_CACHE_TOOLTIP": "Die Aktivierung der Funktion erlaubt es dem Projekt, als Cache für eine andere Registry Instanz zu dienen. Harbor unterstützt die Proxy Funktion nur für DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, JFrog Artifactory, und Github GHCR", + "PROXY_CACHE_TOOLTIP": "Die Aktivierung der Funktion erlaubt es dem Projekt, als Cache für eine andere Registry Instanz zu dienen. Harbor unterstützt die Proxy Funktion nur für DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Alibaba Cloud ACR, Quay, Google GCR, JFrog Artifactory, und Github GHCR", "ENDPOINT": "Endpunkt", "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpunkt", "NO_PROJECT": "Es konnte kein Projekt gefunden werden" diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index f02db86657f..2b9fadbdf48 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -257,7 +257,7 @@ "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. For unlimited quota enter '-1'.", "TYPE": "Type", "PROXY_CACHE": "Proxy Cache", - "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, Github GHCR, and JFrog Artifactory registries.", + "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Alibaba Cloud ACR, Quay, Google GCR, Github GHCR, and JFrog Artifactory registries.", "ENDPOINT": "Endpoint", "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", "NO_PROJECT": "We couldn't find any projects" diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index d9a244a8966..d9251770584 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -256,7 +256,7 @@ "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. Para não ter limite, utilize -1.", "TYPE": "Tipo", "PROXY_CACHE": "Cache do Proxy", - "PROXY_CACHE_TOOLTIP": "Habilite para fazer deste projeto um cache local de outros repositórios remotos (registries). O Harbor pode servir de cache apenas para outros repositórios Harbor, Docker Hub, AWS ECR, Azure ACR, Quay, Google GCR, Github GHCR, JFrog Artifactory, e repositórios compatíveis com o protocolo Docker Registry", + "PROXY_CACHE_TOOLTIP": "Habilite para fazer deste projeto um cache local de outros repositórios remotos (registries). O Harbor pode servir de cache apenas para outros repositórios Harbor, Docker Hub, AWS ECR, Azure ACR, Alibaba Cloud ACR, Quay, Google GCR, Github GHCR, JFrog Artifactory, e repositórios compatíveis com o protocolo Docker Registry", "ENDPOINT": "Endereço", "PROXY_CACHE_ENDPOINT": "Endereço do Proxy Cache" }, diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index bc6a35d9af2..fa671e35fcf 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -257,7 +257,7 @@ "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. Bu kotayı sınırsız istiyorsanız, lütfen -1 girin.", "TYPE": "Type", "PROXY_CACHE": "Proxy Cache", - "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, JFrog Artifactory, and Github GHCR registries.", + "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Alibaba Cloud ACR, Quay, Google GCR, JFrog Artifactory, and Github GHCR registries.", "ENDPOINT": "Endpoint", "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", "NO_PROJECT": "We couldn't find any projects" diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index cb304a456c8..cd8494279f6 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -256,7 +256,7 @@ "QUOTA_UNLIMIT_TIP": "项目可以使用的最大逻辑空间。如果您想要不设置上限,请输入-1。", "TYPE": "类型", "PROXY_CACHE": "镜像代理", - "PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, JFrog Artifactory, 和 Github GHCR 类型的仓库", + "PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Alibaba Cloud ACR, Quay, Google GCR, JFrog Artifactory, 和 Github GHCR 类型的仓库", "ENDPOINT": "地址", "PROXY_CACHE_ENDPOINT": "镜像代理地址", "NO_PROJECT": "未发现任何项目" diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 9f6031a54ff..e669ea0b214 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -256,7 +256,7 @@ "QUOTA_UNLIMIT_TIP": "專案所能使用的最大邏輯空間。輸入 '-1' 表示無限制配額。", "TYPE": "類型", "PROXY_CACHE": "代理快取", - "PROXY_CACHE_TOOLTIP": "啟用此選項可讓此專案作為特定目標 Registry 實例的拉取快取。Harbor 僅可作為 DockerHub、Docker Registry、Harbor、AWS ECR、Azure ACR、Quay、Google GCR、GitHub GHCR 和 JFrog Artifactory Registry 的代理。", + "PROXY_CACHE_TOOLTIP": "啟用此選項可讓此專案作為特定目標 Registry 實例的拉取快取。Harbor 僅可作為 DockerHub、Docker Registry、Harbor、AWS ECR、Azure ACR、Alibaba Cloud ACR、Quay、Google GCR、GitHub GHCR 和 JFrog Artifactory Registry 的代理。", "ENDPOINT": "端點", "PROXY_CACHE_ENDPOINT": "代理快取端點", "NO_PROJECT": "找不到任何專案"