206 lines
5.0 KiB
Go
206 lines
5.0 KiB
Go
/*
|
|
* Copyright 2024 the KubeSphere Authors.
|
|
* Please refer to the LICENSE file in the root directory of the project.
|
|
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
|
*/
|
|
|
|
package application
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"helm.sh/helm/v3/pkg/registry"
|
|
helmrepo "helm.sh/helm/v3/pkg/repo"
|
|
"k8s.io/klog/v2"
|
|
appv2 "kubesphere.io/api/application/v2"
|
|
|
|
"kubesphere.io/kubesphere/pkg/simple/client/oci"
|
|
)
|
|
|
|
func HelmPullFromOci(u string, cred appv2.RepoCredential) ([]byte, error) {
|
|
if !registry.IsOCI(u) {
|
|
return nil, fmt.Errorf("invalid oci URL format: %s", u)
|
|
}
|
|
_, err := url.Parse(u)
|
|
if err != nil {
|
|
klog.Errorf("invalid oci chart URL format: %s, err:%v", u, err)
|
|
return nil, err
|
|
}
|
|
|
|
client, err := newOCIRegistryClient(u, cred)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pullRef := strings.TrimPrefix(u, fmt.Sprintf("%s://", registry.OCIScheme))
|
|
pullResult, err := client.Pull(pullRef)
|
|
if err != nil {
|
|
klog.Errorf("An error occurred to pull chart from repository: %s,err:%v", pullRef, err)
|
|
return nil, err
|
|
}
|
|
|
|
return pullResult.Chart.Data, nil
|
|
}
|
|
|
|
func LoadRepoIndexFromOci(u string, cred appv2.RepoCredential) (idx helmrepo.IndexFile, err error) {
|
|
if !registry.IsOCI(u) {
|
|
return idx, fmt.Errorf("invalid oci URL format: %s", u)
|
|
}
|
|
|
|
parsedURL, err := url.Parse(u)
|
|
if err != nil {
|
|
klog.Errorf("invalid repo URL format: %s, err:%v", u, err)
|
|
return idx, err
|
|
}
|
|
|
|
repoCharts, err := GetRepoChartsFromOci(parsedURL, cred)
|
|
if err != nil {
|
|
return idx, err
|
|
}
|
|
if len(repoCharts) == 0 {
|
|
return idx, nil
|
|
}
|
|
|
|
client, err := newOCIRegistryClient(u, cred)
|
|
if err != nil {
|
|
return idx, err
|
|
}
|
|
|
|
index := helmrepo.NewIndexFile()
|
|
for _, repoChart := range repoCharts {
|
|
tags, err := client.Tags(fmt.Sprintf("%s/%s", parsedURL.Host, repoChart))
|
|
if err != nil {
|
|
klog.Errorf("An error occurred to load tags from repository: %s/%s,err:%v", parsedURL.Host, repoChart, err)
|
|
continue
|
|
}
|
|
if len(tags) == 0 {
|
|
klog.Errorf("Unable to locate any tags in provided repository: %s/%s,err:%v", parsedURL.Host, repoChart, err)
|
|
continue
|
|
}
|
|
|
|
for _, tag := range tags {
|
|
pullRef := fmt.Sprintf("%s/%s:%s", parsedURL.Host, repoChart, tag)
|
|
pullResult, err := client.Pull(pullRef)
|
|
if err != nil {
|
|
klog.Errorf("An error occurred to pull chart from repository: %s,err:%v", pullRef, err)
|
|
continue
|
|
}
|
|
|
|
baseUrl := fmt.Sprintf("%s://%s", registry.OCIScheme, pullRef)
|
|
hash := strings.TrimPrefix(pullResult.Chart.Digest, "sha256:")
|
|
if err := index.MustAdd(pullResult.Chart.Meta, "", baseUrl, hash); err != nil {
|
|
klog.Errorf("failed adding chart metadata to index with repository: %s,err:%v", pullRef, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
index.SortEntries()
|
|
|
|
return *index, nil
|
|
}
|
|
|
|
func GetRepoChartsFromOci(parsedURL *url.URL, cred appv2.RepoCredential) ([]string, error) {
|
|
if parsedURL == nil {
|
|
return nil, errors.New("missing parsedURL")
|
|
}
|
|
|
|
skipTLS := true
|
|
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
|
skipTLS = false
|
|
}
|
|
|
|
reg, err := oci.NewRegistry(parsedURL.Host,
|
|
oci.WithTimeout(5*time.Second),
|
|
oci.WithBasicAuth(cred.Username, cred.Password),
|
|
oci.WithInsecureSkipVerifyTLS(skipTLS))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
repoPath := strings.TrimSuffix(parsedURL.Path, "/")
|
|
repoPath = strings.TrimPrefix(repoPath, "/")
|
|
var repoCharts []string
|
|
err = reg.Repositories(ctx, "", func(repos []string) error {
|
|
cutPrefix := repoPath
|
|
if cutPrefix != "" {
|
|
cutPrefix = cutPrefix + "/"
|
|
}
|
|
for _, repo := range repos {
|
|
if subRepo, found := strings.CutPrefix(repo, cutPrefix); found && subRepo != "" {
|
|
if !strings.Contains(subRepo, "/") {
|
|
repoCharts = append(repoCharts, fmt.Sprintf("%s/%s", repoPath, subRepo))
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return repoCharts, nil
|
|
}
|
|
|
|
func newOCIRegistryClient(u string, cred appv2.RepoCredential) (*registry.Client, error) {
|
|
parsedURL, err := url.Parse(u)
|
|
if err != nil {
|
|
klog.Errorf("invalid oci repo URL format: %s, err:%v", u, err)
|
|
return nil, err
|
|
}
|
|
|
|
skipTLS := true
|
|
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
|
skipTLS = false
|
|
}
|
|
|
|
reg, err := oci.NewRegistry(parsedURL.Host,
|
|
oci.WithTimeout(5*time.Second),
|
|
oci.WithBasicAuth(cred.Username, cred.Password),
|
|
oci.WithInsecureSkipVerifyTLS(skipTLS))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
transport := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLS},
|
|
Proxy: http.ProxyFromEnvironment,
|
|
}
|
|
|
|
opts := []registry.ClientOption{registry.ClientOptHTTPClient(&http.Client{
|
|
Transport: transport,
|
|
Timeout: 5 * time.Second,
|
|
})}
|
|
|
|
if reg.PlainHTTP {
|
|
opts = append(opts, registry.ClientOptPlainHTTP())
|
|
}
|
|
|
|
client, err := registry.NewClient(opts...)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cred.Username != "" || cred.Password != "" {
|
|
err = client.Login(parsedURL.Host,
|
|
registry.LoginOptBasicAuth(cred.Username, cred.Password),
|
|
registry.LoginOptInsecure(reg.PlainHTTP))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return client, nil
|
|
}
|