mirror of
https://github.com/1Panel-dev/KubePi.git
synced 2025-10-05 15:26:58 +08:00
466 lines
12 KiB
Go
466 lines
12 KiB
Go
package helm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/KubeOperator/kubepi/internal/server"
|
|
"github.com/gofrs/flock"
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v3"
|
|
"helm.sh/helm/v3/cmd/helm/search"
|
|
"helm.sh/helm/v3/pkg/action"
|
|
"helm.sh/helm/v3/pkg/chart"
|
|
"helm.sh/helm/v3/pkg/chart/loader"
|
|
"helm.sh/helm/v3/pkg/cli"
|
|
"helm.sh/helm/v3/pkg/getter"
|
|
"helm.sh/helm/v3/pkg/helmpath"
|
|
"helm.sh/helm/v3/pkg/release"
|
|
"helm.sh/helm/v3/pkg/repo"
|
|
"io/ioutil"
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
"k8s.io/client-go/rest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
helmDriver = "secrets"
|
|
)
|
|
|
|
func nolog(format string, v ...interface{}) {}
|
|
|
|
type Config struct {
|
|
Host string
|
|
BearerToken string
|
|
OldNamespace string
|
|
Namespace string
|
|
Architectures string
|
|
ClusterName string
|
|
KubeConfig *rest.Config
|
|
}
|
|
type Client struct {
|
|
actionConfig *action.Configuration
|
|
Namespace string
|
|
settings *cli.EnvSettings
|
|
Architectures string
|
|
ClusterName string
|
|
}
|
|
|
|
func GetSettings(cluster string) *cli.EnvSettings {
|
|
checkFiles(cluster)
|
|
return &cli.EnvSettings{
|
|
PluginsDirectory: helmpath.DataPath(cluster + "/plugins"),
|
|
RegistryConfig: helmpath.ConfigPath(cluster + "/registry.json"),
|
|
RepositoryConfig: helmpath.ConfigPath(cluster + "/repositories.yaml"),
|
|
RepositoryCache: helmpath.CachePath(cluster + "/repository"),
|
|
}
|
|
}
|
|
|
|
func checkFiles(cluster string) {
|
|
|
|
var dataPath = helmpath.ConfigPath(cluster)
|
|
var repositoryPath = dataPath + "/repositories.yaml"
|
|
var registryPath = dataPath + "/registry.json"
|
|
|
|
if _, err := os.Stat(dataPath); os.IsNotExist(err) {
|
|
os.MkdirAll(dataPath, 0777)
|
|
os.Chmod(dataPath, 0777)
|
|
}
|
|
if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
|
|
os.Create(repositoryPath)
|
|
}
|
|
if _, err := os.Stat(registryPath); os.IsNotExist(err) {
|
|
os.Create(registryPath)
|
|
}
|
|
return
|
|
}
|
|
|
|
func NewClient(config *Config) (*Client, error) {
|
|
client := Client{
|
|
Architectures: config.Architectures,
|
|
}
|
|
client.settings = GetSettings(config.ClusterName)
|
|
cf := genericclioptions.NewConfigFlags(true)
|
|
apiServer := config.Host
|
|
cf.APIServer = &apiServer
|
|
kubeConfig := config.KubeConfig
|
|
cf.WrapConfigFn = func(config *rest.Config) *rest.Config {
|
|
return kubeConfig
|
|
}
|
|
cf.CacheDir = nil
|
|
if config.Namespace != "" {
|
|
client.Namespace = config.Namespace
|
|
cf.Namespace = &client.Namespace
|
|
}
|
|
client.ClusterName = config.ClusterName
|
|
actionConfig := new(action.Configuration)
|
|
if err := actionConfig.Init(cf, config.Namespace, helmDriver, nolog); err != nil {
|
|
return nil, err
|
|
}
|
|
client.actionConfig = actionConfig
|
|
|
|
return &client, nil
|
|
}
|
|
|
|
func (c Client) List(limit, offset int, pattern string) ([]*release.Release, int, error) {
|
|
client := action.NewList(c.actionConfig)
|
|
if c.Namespace == "" {
|
|
client.AllNamespaces = true
|
|
client.All = true
|
|
}
|
|
client.SetStateMask()
|
|
list, err := client.Run()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
client.Limit = limit
|
|
client.Offset = offset - 1
|
|
if pattern != "" {
|
|
client.Filter = pattern
|
|
}
|
|
result, err := client.Run()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return result, len(list), nil
|
|
}
|
|
|
|
func (c Client) GetDetail(name string) (*release.Release, error) {
|
|
client := action.NewGet(c.actionConfig)
|
|
result, err := client.Run(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (c Client) Install(name, repoName, chartName, chartVersion string, values map[string]interface{}) (*release.Release, error) {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rp *repo.Entry
|
|
for _, r := range repos {
|
|
if r.Name == repoName {
|
|
rp = r
|
|
}
|
|
}
|
|
if rp == nil {
|
|
return nil, errors.New("get chart detail failed, repo not found")
|
|
}
|
|
client := action.NewInstall(c.actionConfig)
|
|
client.ReleaseName = name
|
|
client.Namespace = c.Namespace
|
|
client.RepoURL = rp.URL
|
|
client.Username = rp.Username
|
|
client.Password = rp.Password
|
|
client.ChartPathOptions.InsecureSkipTLSverify = true
|
|
if len(chartVersion) != 0 {
|
|
client.ChartPathOptions.Version = chartVersion
|
|
}
|
|
p, err := client.ChartPathOptions.LocateChart(chartName, c.settings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("locate chart %s failed: %v", chartName, err)
|
|
}
|
|
ct, err := loader.Load(p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load chart %s failed: %v", chartName, err)
|
|
}
|
|
re, err := client.Run(ct, values)
|
|
if err != nil {
|
|
return re, errors.Wrap(err, fmt.Sprintf("install %s with chart %s failed: %v", name, chartName, err))
|
|
}
|
|
return re, nil
|
|
}
|
|
|
|
func (c Client) Upgrade(name, repoName, chartName, chartVersion string, values map[string]interface{}) (*release.Release, error) {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rp *repo.Entry
|
|
for _, r := range repos {
|
|
if r.Name == repoName {
|
|
rp = r
|
|
}
|
|
}
|
|
if rp == nil {
|
|
return nil, errors.New("get chart detail failed, repo not found")
|
|
}
|
|
|
|
client := action.NewUpgrade(c.actionConfig)
|
|
client.Namespace = c.Namespace
|
|
client.RepoURL = rp.URL
|
|
client.Username = rp.Username
|
|
client.Password = rp.Password
|
|
client.DryRun = false
|
|
client.ChartPathOptions.InsecureSkipTLSverify = true
|
|
client.ChartPathOptions.Version = chartVersion
|
|
p, err := client.ChartPathOptions.LocateChart(chartName, c.settings)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, fmt.Sprintf("locate chart %s failed: %v", chartName, err))
|
|
}
|
|
ct, err := loader.Load(p)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, fmt.Sprintf("load chart %s failed: %v", chartName, err))
|
|
|
|
}
|
|
|
|
release, err := client.Run(name, ct, values)
|
|
if err != nil {
|
|
return release, errors.Wrap(err, fmt.Sprintf("upgrade tool %s with chart %s failed: %v", name, chartName, err))
|
|
}
|
|
return release, nil
|
|
}
|
|
|
|
func (c Client) Uninstall(name string) (*release.UninstallReleaseResponse, error) {
|
|
client := action.NewUninstall(c.actionConfig)
|
|
res, err := client.Run(name)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, fmt.Sprintf("uninstall tool %s failed: %v", name, err))
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (c Client) ListCharts(repoName, pattern string) ([]*search.Result, error) {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list chart failed: %v", err)
|
|
}
|
|
i := search.NewIndex()
|
|
for _, re := range repos {
|
|
if repoName != "KRepoAll" {
|
|
if repoName != re.Name {
|
|
continue
|
|
}
|
|
}
|
|
settings := GetSettings(c.ClusterName)
|
|
path := filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(re.Name))
|
|
indexFile, err := repo.LoadIndexFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list chart failed: %v", err)
|
|
}
|
|
i.AddRepo(re.Name, indexFile, true)
|
|
}
|
|
var result []*search.Result
|
|
if pattern != "" {
|
|
result = i.SearchLiteral(pattern, 1)
|
|
} else {
|
|
result = i.All()
|
|
}
|
|
search.SortScore(result)
|
|
return result, nil
|
|
}
|
|
|
|
func (c Client) GetCharts(repoName, name string) ([]*search.Result, error) {
|
|
charts, err := c.ListCharts(repoName, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []*search.Result
|
|
for _, chart := range charts {
|
|
if chart.Chart.Name == name {
|
|
result = append(result, chart)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (c Client) GetChartDetail(repoName, name, version string) (*chart.Chart, error) {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var re *repo.Entry
|
|
for _, r := range repos {
|
|
if r.Name == repoName {
|
|
re = r
|
|
}
|
|
}
|
|
if re == nil {
|
|
return nil, errors.New("get chart detail failed, repo not found")
|
|
}
|
|
|
|
cacheFilePath := c.settings.RepositoryCache + "/" + name + "-" + version + ".tgz"
|
|
_, err = os.Stat(cacheFilePath)
|
|
if err != nil {
|
|
client := action.NewShow(action.ShowAll)
|
|
client.Version = version
|
|
client.RepoURL = re.URL
|
|
client.Username = re.Username
|
|
client.Password = re.Password
|
|
cacheFilePath, err = client.LocateChart(name, c.settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
ct, err := loader.Load(cacheFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ct, nil
|
|
}
|
|
|
|
func (c Client) ListRepo() ([]*repo.Entry, error) {
|
|
settings := GetSettings(c.ClusterName)
|
|
var repos []*repo.Entry
|
|
f, err := repo.LoadFile(settings.RepositoryConfig)
|
|
if err != nil {
|
|
return repos, err
|
|
}
|
|
return f.Repositories, nil
|
|
}
|
|
|
|
func (c Client) RemoveRepo(name string) (bool, error) {
|
|
settings := GetSettings(c.ClusterName)
|
|
f, err := repo.LoadFile(settings.RepositoryConfig)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
success := f.Remove(name)
|
|
if !success {
|
|
return false, err
|
|
}
|
|
if err := f.WriteFile(settings.RepositoryConfig, 0644); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (c Client) AddRepo(name string, url string, username string, password string) error {
|
|
settings := GetSettings(c.ClusterName)
|
|
|
|
repoFile := settings.RepositoryConfig
|
|
|
|
err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
|
|
if err != nil && !os.IsExist(err) {
|
|
return err
|
|
}
|
|
|
|
fileLock := flock.New(strings.Replace(repoFile, filepath.Ext(repoFile), ".lock", 1))
|
|
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
locked, err := fileLock.TryLockContext(lockCtx, time.Second)
|
|
if err == nil && locked {
|
|
defer func() {
|
|
if err := fileLock.Unlock(); err != nil {
|
|
server.Logger().Errorf("addRepo fileLock.Unlock failed, error: %s", err.Error())
|
|
}
|
|
}()
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(repoFile)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
var f repo.File
|
|
if err := yaml.Unmarshal(b, &f); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f.Has(name) {
|
|
return errors.Errorf("repository name (%s) already exists, please specify a different name", name)
|
|
}
|
|
|
|
e := repo.Entry{
|
|
Name: name,
|
|
URL: url,
|
|
Username: username,
|
|
Password: password,
|
|
InsecureSkipTLSverify: true,
|
|
}
|
|
|
|
r, err := repo.NewChartRepository(&e, getter.All(settings))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.CachePath = settings.RepositoryCache
|
|
if _, err := r.DownloadIndexFile(); err != nil {
|
|
return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
|
|
}
|
|
f.Update(&e)
|
|
if err := f.WriteFile(repoFile, 0644); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c Client) UpdateRepo(repoName string) error {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var re *repo.Entry
|
|
for _, r := range repos {
|
|
if r.Name == repoName {
|
|
re = r
|
|
}
|
|
}
|
|
if re == nil {
|
|
return errors.New("repo not found")
|
|
}
|
|
|
|
settings := GetSettings(c.ClusterName)
|
|
repoFile := settings.RepositoryConfig
|
|
repoCache := settings.RepositoryCache
|
|
f, err := repo.LoadFile(repoFile)
|
|
if err != nil {
|
|
return fmt.Errorf("load file of repo %s failed: %v", repoFile, err)
|
|
}
|
|
f.Update(re)
|
|
var rps []*repo.ChartRepository
|
|
for _, cfg := range f.Repositories {
|
|
r, err := repo.NewChartRepository(cfg, getter.All(settings))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if repoCache != "" {
|
|
r.CachePath = repoCache
|
|
}
|
|
rps = append(rps, r)
|
|
}
|
|
updateCharts(rps)
|
|
return nil
|
|
}
|
|
|
|
func (c Client) GetRepo(name string) (*repo.Entry, error) {
|
|
repos, err := c.ListRepo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var re *repo.Entry
|
|
for _, r := range repos {
|
|
if r.Name == name {
|
|
re = r
|
|
break
|
|
}
|
|
}
|
|
return re, nil
|
|
}
|
|
|
|
func updateCharts(repos []*repo.ChartRepository) {
|
|
fmt.Printf("Hang tight while we grab the latest from your chart repositories...")
|
|
var wg sync.WaitGroup
|
|
for _, re := range repos {
|
|
wg.Add(1)
|
|
go func(re *repo.ChartRepository) {
|
|
defer wg.Done()
|
|
if _, err := re.DownloadIndexFile(); err != nil {
|
|
fmt.Printf("...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
|
|
} else {
|
|
fmt.Printf("...Successfully got an update from the %q chart repository\n", re.Config.Name)
|
|
}
|
|
}(re)
|
|
}
|
|
wg.Wait()
|
|
fmt.Printf("Update Complete. ⎈ Happy Helming!⎈ ")
|
|
}
|