feat: Adapt to oci-based helmchart repo (#6200)
* add oci client for registry * add LoadRepoIndexFormOci * feat: Adapt to oci-based helmchart repo * Update the golang base image version in the dockerfile * update oci_test.go Signed-off-by: lingbo <lingbo@lingbohome.com> * fix: Update oci_test.go Signed-off-by: 凌波 <lingbo@lingbohome.com> * Update go imports --------- Signed-off-by: lingbo <lingbo@lingbohome.com> Signed-off-by: 凌波 <lingbo@lingbohome.com> Co-authored-by: hongming <coder.scala@gmail.com>
This commit is contained in:
221
pkg/simple/client/oci/registry.go
Normal file
221
pkg/simple/client/oci/registry.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"oras.land/oras-go/pkg/registry"
|
||||
"oras.land/oras-go/pkg/registry/remote"
|
||||
"oras.land/oras-go/pkg/registry/remote/auth"
|
||||
)
|
||||
|
||||
type RepositoryOptions remote.Repository
|
||||
type RegistryOption func(*Registry)
|
||||
|
||||
// Registry is an HTTP client to a remote registry by oras-go 2.x.
|
||||
// Registry with authentication requires an administrator account.
|
||||
type Registry struct {
|
||||
RepositoryOptions
|
||||
|
||||
RepositoryListPageSize int
|
||||
|
||||
username string
|
||||
password string
|
||||
timeout time.Duration
|
||||
insecureSkipVerifyTLS bool
|
||||
}
|
||||
|
||||
func NewRegistry(name string, options ...RegistryOption) (*Registry, error) {
|
||||
ref := registry.Reference{
|
||||
Registry: name,
|
||||
}
|
||||
if err := ref.ValidateRegistry(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reg := &Registry{RepositoryOptions: RepositoryOptions{
|
||||
Reference: ref,
|
||||
}}
|
||||
for _, option := range options {
|
||||
option(reg)
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Set("User-Agent", "kubesphere.io")
|
||||
reg.Client = &auth.Client{
|
||||
Client: &http.Client{
|
||||
Timeout: reg.timeout,
|
||||
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: reg.insecureSkipVerifyTLS}},
|
||||
},
|
||||
Header: headers,
|
||||
Credential: func(_ context.Context, _ string) (auth.Credential, error) {
|
||||
if reg.username == "" && reg.password == "" {
|
||||
return auth.EmptyCredential, nil
|
||||
}
|
||||
|
||||
return auth.Credential{
|
||||
Username: reg.username,
|
||||
Password: reg.password,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := reg.IsPlainHttp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
func WithBasicAuth(username, password string) RegistryOption {
|
||||
return func(reg *Registry) {
|
||||
reg.username = username
|
||||
reg.password = password
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) RegistryOption {
|
||||
return func(reg *Registry) {
|
||||
reg.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) RegistryOption {
|
||||
return func(reg *Registry) {
|
||||
reg.insecureSkipVerifyTLS = insecureSkipVerifyTLS
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) client() remote.Client {
|
||||
if r.Client == nil {
|
||||
return auth.DefaultClient
|
||||
}
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *Registry) do(req *http.Request) (*http.Response, error) {
|
||||
return r.client().Do(req)
|
||||
}
|
||||
|
||||
func (r *Registry) IsPlainHttp() (bool, error) {
|
||||
schemaProbeList := []bool{false, true}
|
||||
|
||||
var err error
|
||||
for _, probe := range schemaProbeList {
|
||||
r.PlainHTTP = probe
|
||||
err = r.Ping(context.Background())
|
||||
if err == nil {
|
||||
return probe, nil
|
||||
}
|
||||
}
|
||||
|
||||
return r.PlainHTTP, err
|
||||
}
|
||||
|
||||
func (r *Registry) Ping(ctx context.Context) error {
|
||||
url := buildRegistryBaseURL(r.PlainHTTP, r.Reference)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := r.do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
return nil
|
||||
case http.StatusNotFound:
|
||||
return errors.New("not found")
|
||||
default:
|
||||
return ParseErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) Repositories(ctx context.Context, last string, fn func(repos []string) error) error {
|
||||
url := buildRegistryCatalogURL(r.PlainHTTP, r.Reference)
|
||||
var err error
|
||||
for err == nil {
|
||||
url, err = r.repositories(ctx, last, fn, url)
|
||||
// clear `last` for subsequent pages
|
||||
last = ""
|
||||
}
|
||||
if err != errNoLink {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Registry) repositories(ctx context.Context, last string, fn func(repos []string) error, url string) (string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r.RepositoryListPageSize > 0 || last != "" {
|
||||
q := req.URL.Query()
|
||||
if r.RepositoryListPageSize > 0 {
|
||||
q.Set("n", strconv.Itoa(r.RepositoryListPageSize))
|
||||
}
|
||||
if last != "" {
|
||||
q.Set("last", last)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
resp, err := r.do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", ParseErrorResponse(resp)
|
||||
}
|
||||
var page struct {
|
||||
Repositories []string `json:"repositories"`
|
||||
}
|
||||
lr := limitReader(resp.Body, r.MaxMetadataBytes)
|
||||
if err := json.NewDecoder(lr).Decode(&page); err != nil {
|
||||
return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err)
|
||||
}
|
||||
if err := fn(page.Repositories); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return parseLink(resp)
|
||||
}
|
||||
|
||||
func (r *Registry) Repository(ctx context.Context, name string) (registry.Repository, error) {
|
||||
ref := registry.Reference{
|
||||
Registry: r.Reference.Registry,
|
||||
Repository: name,
|
||||
}
|
||||
if err := ref.ValidateRepository(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo := r.repository((*remote.Repository)(&r.RepositoryOptions))
|
||||
repo.Reference = ref
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (r *Registry) repository(repo *remote.Repository) *remote.Repository {
|
||||
return &remote.Repository{
|
||||
Client: repo.Client,
|
||||
Reference: repo.Reference,
|
||||
PlainHTTP: repo.PlainHTTP,
|
||||
ManifestMediaTypes: slices.Clone(repo.ManifestMediaTypes),
|
||||
TagListPageSize: repo.TagListPageSize,
|
||||
ReferrerListPageSize: repo.ReferrerListPageSize,
|
||||
MaxMetadataBytes: repo.MaxMetadataBytes,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user