harbor/src/pkg/reg/adapter/huawei/huawei_adapter.go

314 lines
8.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 huawei
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
common_http "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/http/modifier"
"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/model"
"github.com/goharbor/harbor/src/pkg/registry/auth/basic"
)
func init() {
err := adp.RegisterFactory(model.RegistryTypeHuawei, new(factory))
if err != nil {
log.Errorf("failed to register factory for Huawei: %v", err)
return
}
log.Infof("the factory of Huawei adapter was registered")
}
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 nil
}
var (
_ adp.Adapter = (*adapter)(nil)
_ adp.ArtifactRegistry = (*adapter)(nil)
)
// Adapter is for images replications between harbor and Huawei image repository(SWR)
type adapter struct {
*native.Adapter
registry *model.Registry
client *common_http.Client
// original http client with no modifer,
// huawei's some api interface with basic authorization,
// some with bearer token authorization.
oriClient *http.Client
}
// Info gets info about Huawei SWR
func (a *adapter) Info() (*model.RegistryInfo, error) {
registryInfo := model.RegistryInfo{
Type: model.RegistryTypeHuawei,
Description: "Adapter for SWR -- The image registry of Huawei Cloud",
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 &registryInfo, nil
}
// ListNamespaces lists namespaces from Huawei SWR with the provided query conditions.
func (a *adapter) ListNamespaces(query *model.NamespaceQuery) ([]*model.Namespace, error) {
var namespaces []*model.Namespace
urls := fmt.Sprintf("%s/dockyard/v2/visible/namespaces", a.registry.URL)
r, err := http.NewRequest("GET", urls, nil)
if err != nil {
return namespaces, err
}
r.Header.Add("content-type", "application/json; charset=utf-8")
resp, err := a.client.Do(r)
if err != nil {
return namespaces, err
}
defer resp.Body.Close()
code := resp.StatusCode
if code >= 300 || code < 200 {
body, _ := io.ReadAll(resp.Body)
return namespaces, fmt.Errorf("[%d][%s]", code, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return namespaces, err
}
var namespacesData hwNamespaceList
err = json.Unmarshal(body, &namespacesData)
if err != nil {
return namespaces, err
}
reg := fmt.Sprintf(".*%s.*", strings.Replace(query.Name, " ", "", -1))
for _, namespaceData := range namespacesData.Namespace {
namespace := model.Namespace{
Name: namespaceData.Name,
Metadata: namespaceData.metadata(),
}
b, err := regexp.MatchString(reg, namespace.Name)
if err != nil {
return namespaces, nil
}
if b {
namespaces = append(namespaces, &namespace)
}
}
return namespaces, nil
}
// ConvertResourceMetadata convert resource metadata for Huawei SWR
func (a *adapter) ConvertResourceMetadata(resourceMetadata *model.ResourceMetadata, _ *model.Namespace) (*model.ResourceMetadata, error) {
metadata := &model.ResourceMetadata{
Repository: resourceMetadata.Repository,
Vtags: resourceMetadata.Vtags,
}
return metadata, nil
}
// PrepareForPush prepare for push to Huawei SWR
func (a *adapter) PrepareForPush(resources []*model.Resource) error {
namespaces := map[string]struct{}{}
for _, resource := range resources {
var namespace string
paths := strings.Split(resource.Metadata.Repository.Name, "/")
if len(paths) > 0 {
namespace = paths[0]
}
ns, err := a.GetNamespace(namespace)
if err != nil {
return err
}
if ns != nil && ns.Name == namespace {
continue
}
namespaces[namespace] = struct{}{}
}
url := fmt.Sprintf("%s/dockyard/v2/namespaces", a.registry.URL)
for namespace := range namespaces {
namespacebyte, err := json.Marshal(struct {
Namespace string `json:"namespace"`
}{
Namespace: namespace,
})
if err != nil {
return err
}
r, err := http.NewRequest("POST", url, strings.NewReader(string(namespacebyte)))
if err != nil {
return err
}
r.Header.Add("content-type", "application/json; charset=utf-8")
resp, err := a.client.Do(r)
if err != nil {
return err
}
defer resp.Body.Close()
code := resp.StatusCode
if code >= 300 || code < 200 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("[%d][%s]", code, string(body))
}
log.Debugf("namespace %s created", namespace)
}
return nil
}
// GetNamespace gets a namespace from Huawei SWR
func (a *adapter) GetNamespace(namespaceStr string) (*model.Namespace, error) {
var namespace = &model.Namespace{
Name: "",
Metadata: make(map[string]interface{}),
}
urls := fmt.Sprintf("%s/dockyard/v2/namespaces/%s", a.registry.URL, namespaceStr)
r, err := http.NewRequest("GET", urls, nil)
if err != nil {
return namespace, err
}
r.Header.Add("content-type", "application/json; charset=utf-8")
resp, err := a.client.Do(r)
if err != nil {
return namespace, err
}
defer resp.Body.Close()
code := resp.StatusCode
if code >= 300 || code < 200 {
body, _ := io.ReadAll(resp.Body)
return namespace, fmt.Errorf("[%d][%s]", code, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return namespace, err
}
var namespaceData hwNamespace
err = json.Unmarshal(body, &namespaceData)
if err != nil {
return namespace, err
}
namespace.Name = namespaceData.Name
namespace.Metadata = namespaceData.metadata()
return namespace, nil
}
// HealthCheck check health for huawei SWR
func (a *adapter) HealthCheck() (string, error) {
return model.Healthy, nil
}
func newAdapter(registry *model.Registry) (adp.Adapter, error) {
var (
modifiers = []modifier.Modifier{}
authorizer modifier.Modifier
)
if registry.Credential != nil {
authorizer = basic.NewAuthorizer(
registry.Credential.AccessKey,
registry.Credential.AccessSecret)
modifiers = append(modifiers, authorizer)
}
transport := common_http.GetHTTPTransport(common_http.WithInsecure(registry.Insecure))
return &adapter{
Adapter: native.NewAdapter(registry),
registry: registry,
client: common_http.NewClient(
&http.Client{
Transport: transport,
},
modifiers...,
),
oriClient: &http.Client{
Transport: transport,
},
}, nil
}
type hwNamespaceList struct {
Namespace []hwNamespace `json:"namespaces"`
}
type hwNamespace struct {
ID int64 `json:"id" orm:"column(id)"`
Name string `json:"name"`
CreatorName string `json:"creator_name,omitempty"`
DomainPublic int `json:"-"`
Auth int `json:"auth"`
DomainName string `json:"-"`
UserCount int64 `json:"user_count"`
ImageCount int64 `json:"image_count"`
}
func (ns hwNamespace) metadata() map[string]interface{} {
var metadata = make(map[string]interface{})
metadata["id"] = ns.ID
metadata["creator_name"] = ns.CreatorName
metadata["domain_public"] = ns.DomainPublic
metadata["auth"] = ns.Auth
metadata["domain_name"] = ns.DomainName
metadata["user_count"] = ns.UserCount
metadata["image_count"] = ns.ImageCount
return metadata
}