mirror of https://github.com/goharbor/harbor.git
359 lines
9.0 KiB
Go
359 lines
9.0 KiB
Go
// 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 (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
commonhttp "github.com/goharbor/harbor/src/common/http"
|
|
"github.com/goharbor/harbor/src/common/utils"
|
|
"github.com/goharbor/harbor/src/lib/log"
|
|
adp "github.com/goharbor/harbor/src/pkg/reg/adapter"
|
|
"github.com/goharbor/harbor/src/pkg/reg/adapter/native"
|
|
"github.com/goharbor/harbor/src/pkg/reg/filter"
|
|
"github.com/goharbor/harbor/src/pkg/reg/model"
|
|
"github.com/goharbor/harbor/src/pkg/reg/util"
|
|
"github.com/goharbor/harbor/src/pkg/registry/auth/bearer"
|
|
)
|
|
|
|
func init() {
|
|
if err := adp.RegisterFactory(model.RegistryTypeAliAcr, new(factory)); err != nil {
|
|
log.Errorf("failed to register factory for %s: %v", model.RegistryTypeAliAcr, err)
|
|
return
|
|
}
|
|
log.Infof("the factory for adapter %s registered", model.RegistryTypeAliAcr)
|
|
}
|
|
|
|
// example:
|
|
// https://cr.%s.aliyuncs.com
|
|
var regACRServiceURL = regexp.MustCompile(`https://cr\.([\w\-]+)\.aliyuncs\.com`)
|
|
|
|
func getURL(url string) (string, error) {
|
|
if url == "" {
|
|
return "", errors.New("empty url")
|
|
}
|
|
|
|
var rs []string
|
|
rs = regACRServiceURL.FindStringSubmatch(url)
|
|
if rs == nil {
|
|
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 <= 0 {
|
|
return nil, errors.New("empty service")
|
|
}
|
|
|
|
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
|
|
} else {
|
|
return ®istryServiceInfo{
|
|
IsACREE: false,
|
|
RegionID: parts[1],
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func newAdapter(registry *model.Registry) (*adapter, error) {
|
|
url, err := getURL(registry.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
registry.URL = url
|
|
|
|
realm, service, err := util.Ping(registry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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{
|
|
acrAPI: acrAPI,
|
|
registry: registry,
|
|
Adapter: native.NewAdapterWithAuthorizer(registry, authorizer),
|
|
}, nil
|
|
}
|
|
|
|
type factory struct {
|
|
}
|
|
|
|
// Create ...
|
|
func (f *factory) Create(r *model.Registry) (adp.Adapter, error) {
|
|
return newAdapter(r)
|
|
}
|
|
|
|
// AdapterPattern ...
|
|
func (f *factory) AdapterPattern() *model.AdapterPattern {
|
|
return getAdapterInfo()
|
|
}
|
|
|
|
var (
|
|
_ adp.Adapter = (*adapter)(nil)
|
|
_ adp.ArtifactRegistry = (*adapter)(nil)
|
|
)
|
|
|
|
// adapter for to aliyun docker registry
|
|
type adapter struct {
|
|
*native.Adapter
|
|
acrAPI openapi
|
|
registry *model.Registry
|
|
}
|
|
|
|
var _ adp.Adapter = &adapter{}
|
|
|
|
// Info ...
|
|
func (a *adapter) Info() (*model.RegistryInfo, error) {
|
|
info := &model.RegistryInfo{
|
|
Type: model.RegistryTypeAliAcr,
|
|
SupportedResourceTypes: []string{
|
|
model.ResourceTypeImage,
|
|
},
|
|
SupportedResourceFilters: []*model.FilterStyle{
|
|
{
|
|
Type: model.FilterTypeName,
|
|
Style: model.FilterStyleTypeText,
|
|
},
|
|
{
|
|
Type: model.FilterTypeTag,
|
|
Style: model.FilterStyleTypeText,
|
|
},
|
|
},
|
|
SupportedTriggers: []string{
|
|
model.TriggerTypeManual,
|
|
model.TriggerTypeScheduled,
|
|
},
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
func getAdapterInfo() *model.AdapterPattern {
|
|
var endpoints []*model.Endpoint
|
|
// https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.11186623.2.7.58683ae5Q4lo1o
|
|
for _, e := range []string{
|
|
"cn-qingdao",
|
|
"cn-beijing",
|
|
"cn-zhangjiakou",
|
|
"cn-huhehaote",
|
|
"cn-wulanchabu",
|
|
"cn-hangzhou",
|
|
"cn-shanghai",
|
|
"cn-shenzhen",
|
|
"cn-heyuan",
|
|
"cn-guangzhou",
|
|
"cn-chengdu",
|
|
"cn-hongkong",
|
|
"ap-southeast-1",
|
|
"ap-southeast-2",
|
|
"ap-southeast-3",
|
|
"ap-southeast-5",
|
|
"ap-south-1",
|
|
"ap-northeast-1",
|
|
"us-west-1",
|
|
"us-east-1",
|
|
"eu-central-1",
|
|
"eu-west-1",
|
|
"me-east-1",
|
|
} {
|
|
endpoints = append(endpoints, &model.Endpoint{
|
|
Key: e,
|
|
Value: fmt.Sprintf("https://registry.%s.aliyuncs.com", e),
|
|
})
|
|
endpoints = append(endpoints, &model.Endpoint{
|
|
Key: e + "-vpc",
|
|
Value: fmt.Sprintf("https://registry-vpc.%s.aliyuncs.com", e),
|
|
})
|
|
endpoints = append(endpoints, &model.Endpoint{
|
|
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{
|
|
EndpointType: model.EndpointPatternTypeList,
|
|
Endpoints: endpoints,
|
|
},
|
|
}
|
|
return info
|
|
}
|
|
|
|
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...)
|
|
}
|
|
if len(namespaces) > 0 {
|
|
log.Debugf("parsed the namespaces %v from pattern %s", namespaces, namespacePattern)
|
|
return namespaces, nil
|
|
}
|
|
}
|
|
|
|
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) ([]*model.Resource, error) {
|
|
log.Debugf("FetchArtifacts.filters: %#v\n", filters)
|
|
|
|
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
|
|
for _, f := range filters {
|
|
if f.Type == model.FilterTypeName {
|
|
repoPattern = f.Value.(string)
|
|
}
|
|
}
|
|
var namespacePattern = strings.Split(repoPattern, "/")[0]
|
|
|
|
log.Debugf("\nrepoPattern=%s tagsPattern=%s\n\n", repoPattern, tagsPattern)
|
|
|
|
// get namespaces
|
|
namespaces, err := a.listCandidateNamespaces(namespacePattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Debugf("got namespaces: %v \n", namespaces)
|
|
|
|
// list repos
|
|
var repositories []*repository
|
|
for _, namespace := range namespaces {
|
|
repos, err := a.acrAPI.ListRepository(namespace)
|
|
if err != nil {
|
|
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.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 nil, err
|
|
}
|
|
if ok {
|
|
repositories = append(repositories, repo)
|
|
}
|
|
}
|
|
}
|
|
log.Debugf("FetchArtifacts.repositories: %#v\n", repositories)
|
|
|
|
var rawResources = make([]*model.Resource, len(repositories))
|
|
runner := utils.NewLimitedConcurrentRunner(adp.MaxConcurrency)
|
|
|
|
for i, r := range repositories {
|
|
index := i
|
|
repo := r
|
|
runner.AddTask(func() error {
|
|
var tags []string
|
|
tags, err = a.acrAPI.ListRepoTag(repo)
|
|
if err != nil {
|
|
return fmt.Errorf("list tags for repo '%s' error: %v", repo.Name, err)
|
|
}
|
|
|
|
var artifacts []*model.Artifact
|
|
for _, tag := range tags {
|
|
artifacts = append(artifacts, &model.Artifact{
|
|
Tags: []string{tag},
|
|
})
|
|
}
|
|
filterArtifacts, err := filter.DoFilterArtifacts(artifacts, filters)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(filterArtifacts) > 0 {
|
|
rawResources[index] = &model.Resource{
|
|
Type: model.ResourceTypeImage,
|
|
Registry: a.registry,
|
|
Metadata: &model.ResourceMetadata{
|
|
Repository: &model.Repository{
|
|
Name: filepath.Join(repo.Namespace, repo.Name),
|
|
},
|
|
Artifacts: filterArtifacts,
|
|
},
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
if err = runner.Wait(); err != nil {
|
|
return nil, fmt.Errorf("failed to fetch artifacts: %v", err)
|
|
}
|
|
for _, r := range rawResources {
|
|
if r != nil {
|
|
resources = append(resources, r)
|
|
}
|
|
}
|
|
|
|
return resources, nil
|
|
}
|