helmfile/pkg/app/init.go

242 lines
5.8 KiB
Go

package app
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
"helm.sh/helm/v3/pkg/cli"
"github.com/helmfile/helmfile/pkg/helmexec"
)
const (
HelmRequiredVersion = "v3.18.6"
HelmDiffRecommendedVersion = "v3.13.1"
HelmRecommendedVersion = "v3.19.0"
HelmSecretsRecommendedVersion = "v4.6.5"
HelmGitRecommendedVersion = "v1.3.0"
HelmS3RecommendedVersion = "v0.16.3"
HelmInstallCommand = "https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3"
)
var (
manuallyInstallCode = 1
windowPackageManagers = map[string]string{
"scoop": fmt.Sprintf("scoop install helm@%s", strings.TrimLeft(HelmRecommendedVersion, "v")),
"choco": fmt.Sprintf("choco install kubernetes-helm --version %s", strings.TrimLeft(HelmRecommendedVersion, "v")),
}
helmPlugins = []helmRecommendedPlugin{
{
name: "diff",
version: HelmDiffRecommendedVersion,
repo: "https://github.com/databus23/helm-diff",
},
{
name: "secrets",
version: HelmSecretsRecommendedVersion,
repo: "https://github.com/jkroepke/helm-secrets",
},
{
name: "s3",
version: HelmS3RecommendedVersion,
repo: "https://github.com/hypnoglow/helm-s3.git",
},
{
name: "helm-git",
version: HelmGitRecommendedVersion,
repo: "https://github.com/aslafy-z/helm-git.git",
},
}
)
type helmRecommendedPlugin struct {
name string
version string
repo string
}
type HelmfileInit struct {
helmBinary string
configProvider InitConfigProvider
logger *zap.SugaredLogger
runner helmexec.Runner
}
func downloadfile(filepath string, url string) error {
file, err := os.Create(filepath)
if err != nil {
return err
}
defer func() { _ = file.Close() }()
resp, err := http.Get(url)
if err != nil {
return err
}
if resp.StatusCode/100 != 2 {
return fmt.Errorf("download %s error, code: %d", url, resp.StatusCode)
}
defer func() { _ = resp.Body.Close() }()
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
return nil
}
func NewHelmfileInit(helmBinary string, c InitConfigProvider, logger *zap.SugaredLogger, runner helmexec.Runner) *HelmfileInit {
return &HelmfileInit{helmBinary: helmBinary, configProvider: c, logger: logger, runner: runner}
}
func (h *HelmfileInit) UpdateHelm() error {
return h.InstallHelm()
}
func (h *HelmfileInit) installHelmOnWindows() error {
for name, command := range windowPackageManagers {
_, err := exec.LookPath(name)
if err != nil {
continue
}
err = h.WhetherContinue(fmt.Sprintf("use: '%s'", command))
if err != nil {
return err
}
_, err = h.runner.Execute("cmd", []string{
"/c",
command,
}, nil, true)
return err
}
return &Error{msg: "windows platform, please install helm manually, installation steps: https://helm.sh/docs/intro/install/", code: &manuallyInstallCode}
}
func (h *HelmfileInit) InstallHelm() error {
if runtime.GOOS == "windows" {
return h.installHelmOnWindows()
}
err := h.WhetherContinue(fmt.Sprintf("use: '%s'", HelmInstallCommand))
if err != nil {
return err
}
getHelmScript, err := os.CreateTemp("", "get-helm-3.sh")
defer func() {
_ = getHelmScript.Close()
_ = os.Remove(getHelmScript.Name())
}()
if err != nil {
return err
}
err = downloadfile(getHelmScript.Name(), HelmInstallCommand)
if err != nil {
return err
}
_, err = h.runner.Execute("bash", []string{
getHelmScript.Name(),
"--version",
HelmRecommendedVersion,
}, nil, true)
if err != nil {
return err
}
h.helmBinary = DefaultHelmBinary
return nil
}
func (h *HelmfileInit) WhetherContinue(ask string) error {
if h.configProvider.Force() {
return nil
}
askYes := AskForConfirmation(ask)
if !askYes {
return &Error{msg: "cancel automatic installation, please install manually", code: &manuallyInstallCode}
}
return nil
}
func (h *HelmfileInit) CheckHelmPlugins() error {
settings := cli.New()
helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner)
if err != nil {
return err
}
for _, p := range helmPlugins {
pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
if err != nil {
if !strings.Contains(err.Error(), "not installed") {
return err
}
err = h.WhetherContinue(fmt.Sprintf("The helm plugin %q is not installed, do you want to install it?", p.name))
if err != nil {
return err
}
err = helm.AddPlugin(p.name, p.repo, p.version)
if err != nil {
return err
}
pluginVersion, _ = helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
}
requiredVersion, _ := semver.NewVersion(p.version)
if pluginVersion.LessThan(requiredVersion) {
err = h.WhetherContinue(fmt.Sprintf("The helm plugin %q version is too low, do you want to update it?", p.name))
if err != nil {
return err
}
err = helm.UpdatePlugin(p.name)
if err != nil {
return err
}
}
}
return nil
}
func (h *HelmfileInit) CheckHelm() error {
helmExits := true
_, err := exec.LookPath(h.helmBinary)
if err != nil {
helmExits = false
}
if !helmExits {
h.logger.Info("helm not found, needs to be installed")
err = h.InstallHelm()
if err != nil {
return err
}
}
helmversion, err := helmexec.GetHelmVersion(h.helmBinary, h.runner)
if err != nil {
return err
}
requiredHelmVersion, _ := semver.NewVersion(HelmRequiredVersion)
if helmversion.LessThan(requiredHelmVersion) {
h.logger.Infof("helm version is too low, the current version is %s, the required version is %s", helmversion, requiredHelmVersion)
err = h.UpdateHelm()
if err != nil {
return err
}
}
return nil
}
func (h *HelmfileInit) Initialize() error {
err := h.CheckHelm()
if err != nil {
return err
}
err = h.CheckHelmPlugins()
if err != nil {
return err
}
h.logger.Info("helmfile initialization completed!")
return nil
}