Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add acr ee support #19658

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/go.mod
Expand Up @@ -5,7 +5,7 @@ go 1.21
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.50.5
github.com/beego/beego/v2 v2.0.6
Expand Down
2 changes: 2 additions & 0 deletions src/go.sum
Expand Up @@ -66,6 +66,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/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
Expand Down
230 changes: 94 additions & 136 deletions src/pkg/reg/adapter/aliacr/adapter.go
Expand Up @@ -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"
Expand All @@ -45,48 +41,83 @@ 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 getURL(url string) (string, error) {
if url == "" {
return "", errors.New("empty url")
}
rs := regRegion.FindStringSubmatch(url)

var rs []string
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 <= 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 &registryServiceInfo{
IsACREE: true,
RegionID: parts[1],
InstanceID: parts[length-1],
}, nil
} else {
return &registryServiceInfo{
IsACREE: false,
RegionID: parts[1],
}, nil
}
// fmt.Println(rs)
return rs[2], nil
}

func newAdapter(registry *model.Registry) (*adapter, error) {
region, err := getRegion(registry.URL)
url, err := getURL(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
}
Expand All @@ -112,16 +143,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,
Expand All @@ -141,7 +171,7 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) {
model.TriggerTypeScheduled,
},
}
return
return info, nil
}

func getAdapterInfo() *model.AdapterPattern {
Expand Down Expand Up @@ -184,6 +214,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{
Expand All @@ -194,30 +234,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...)
Expand All @@ -228,19 +246,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
Expand All @@ -254,31 +275,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)
Expand All @@ -295,9 +314,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
Expand All @@ -317,13 +336,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
})
}
Expand All @@ -336,65 +354,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
}