/* Copyright 2021 The KubeVela 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 helm import ( "fmt" "io" "log" "os" "strings" "github.com/pkg/errors" "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/release" "helm.sh/helm/v3/pkg/repo" "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/utils/common" cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" ) // VelaDebugLog defines an ENV to set vela helm install log to be debug const VelaDebugLog = "VELA_DEBUG" var ( settings = cli.New() ) // Install will install helm chart func Install(ioStreams cmdutil.IOStreams, repoName, repoURL, chartName, version, namespace, releaseName string, vals map[string]interface{}) error { if len(namespace) > 0 { args, err := common.InitBaseRestConfig() if err != nil { return err } kubeClient, err := args.GetClient() if err != nil { return err } exist, err := cmdutil.DoesNamespaceExist(kubeClient, namespace) if err != nil { return err } if !exist { if err = cmdutil.NewNamespace(kubeClient, namespace); err != nil { return fmt.Errorf("create namespace (%s) failed for chart %s: %w", namespace, chartName, err) } } } // check release running first before add repo and install chart if IsHelmReleaseRunning(releaseName, chartName, namespace, ioStreams) { return nil } if !IsHelmRepositoryExist(repoName, repoURL) { err := AddHelmRepository(repoName, repoURL, "", "", "", "", "", false, ioStreams.Out) if err != nil { return err } } chartClient, err := NewHelmInstall(version, namespace, releaseName) if err != nil { return err } chartRequested, err := GetChart(chartClient, repoName+"/"+chartName) if err != nil { return err } rel, err := chartClient.Run(chartRequested, vals) if err != nil { return err } ioStreams.Infof("Successfully installed chart (%s) with release name (%s)\n", chartName, rel.Name) return nil } // Uninstall will uninstall helm chart func Uninstall(ioStreams cmdutil.IOStreams, chartName, namespace, releaseName string) error { if !IsHelmReleaseRunning(releaseName, chartName, namespace, ioStreams) { return nil } uninstall, err := NewHelmUninstall(namespace) if err != nil { return err } _, err = uninstall.Run(releaseName) if err != nil { return err } ioStreams.Infof("Successfully removed chart (%s) with release name (%s)\n", chartName, releaseName) return nil } // NewHelmInstall will create a install client for helm install func NewHelmInstall(version, namespace, releaseName string) (*action.Install, error) { actionConfig := new(action.Configuration) if len(namespace) == 0 { namespace = types.DefaultKubeVelaNS } if err := actionConfig.Init( cmdutil.NewRestConfigGetter(namespace), namespace, os.Getenv("HELM_DRIVER"), debug, ); err != nil { return nil, err } client := action.NewInstall(actionConfig) client.ReleaseName = releaseName // MUST set here, client didn't use namespace from configuration client.Namespace = namespace if len(version) > 0 { client.Version = version } else { client.Version = types.DefaultKubeVelaVersion } return client, nil } // NewHelmUninstall will create a helm uninstall client func NewHelmUninstall(namespace string) (*action.Uninstall, error) { actionConfig := new(action.Configuration) if err := actionConfig.Init( cmdutil.NewRestConfigGetter(namespace), namespace, os.Getenv("HELM_DRIVER"), debug, ); err != nil { return nil, err } return action.NewUninstall(actionConfig), nil } // IsHelmRepositoryExist will check help repo exists func IsHelmRepositoryExist(name, url string) bool { repos := GetHelmRepositoryList() for _, repo := range repos { if repo.Name == name && repo.URL == url { return true } } return false } // GetHelmRepositoryList get the helm repo list from default setting func GetHelmRepositoryList() []*repo.Entry { f, err := repo.LoadFile(settings.RepositoryConfig) if err == nil && len(f.Repositories) > 0 { return f.Repositories } return nil } func debug(format string, v ...interface{}) { if settings.Debug { format = fmt.Sprintf("[debug] %s\n", format) _ = log.Output(2, fmt.Sprintf(format, v...)) } } // AddHelmRepository add helm repo func AddHelmRepository(name, url, username, password, certFile, keyFile, caFile string, insecureSkipTLSverify bool, out io.Writer) error { var f repo.File c := repo.Entry{ Name: name, URL: url, Username: username, Password: password, CertFile: certFile, KeyFile: keyFile, CAFile: caFile, InsecureSkipTLSverify: insecureSkipTLSverify, } r, err := repo.NewChartRepository(&c, getter.All(settings)) if err != nil { return err } 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(&c) if err := f.WriteFile(settings.RepositoryConfig, 0644); err != nil { return err } fmt.Fprintf(out, "%q has been added to your repositories\n", name) return nil } // IsHelmReleaseRunning check helm release running func IsHelmReleaseRunning(releaseName, chartName, ns string, streams cmdutil.IOStreams) bool { releases, err := GetHelmRelease(ns) if err != nil { streams.Error("get helm release err", err) return false } for _, r := range releases { if strings.Contains(r.Chart.ChartFullPath(), chartName) && r.Name == releaseName { return true } } return false } // GetHelmRelease will get helm release func GetHelmRelease(ns string) ([]*release.Release, error) { actionConfig := new(action.Configuration) client := action.NewList(actionConfig) if err := actionConfig.Init(settings.RESTClientGetter(), ns, os.Getenv("HELM_DRIVER"), debug); err != nil { return nil, err } results, err := client.Run() if err != nil { return nil, err } return results, nil } // GetChart will locate chart func GetChart(client *action.Install, name string) (*chart.Chart, error) { if os.Getenv(VelaDebugLog) != "" { settings.Debug = true } chartPath, err := client.ChartPathOptions.LocateChart(name, settings) if err != nil { return nil, err } chartRequested, err := loader.Load(chartPath) if err != nil { return nil, err } return chartRequested, nil } // InstallHelmChart will install helm chart from types.Chart func InstallHelmChart(ioStreams cmdutil.IOStreams, c types.Chart) error { return Install(ioStreams, c.Repo, c.URL, c.Name, c.Version, c.Namespace, c.Name, c.Values) }