From c099f69d94113299f8e2a53efa90eb5279d34067 Mon Sep 17 00:00:00 2001 From: Andrew Drake Date: Wed, 13 Nov 2019 20:34:17 -0500 Subject: [PATCH] feat: Automatically enable Helm v3 mode Runs `helm version` in helmexec.New, and exposes a method on Interface to allow other packages to use the detected version. Preserves compatibility with previous HELMFILE_HELM3 mechanism. Resolves #923 --- pkg/app/app_test.go | 3 +++ pkg/exectest/helm.go | 4 ++++ pkg/helmexec/exec.go | 29 +++++++++++++++++++++++------ pkg/helmexec/helmexec.go | 2 ++ pkg/state/chart_dependency.go | 6 +++--- pkg/state/state.go | 18 +++++++----------- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index fbf94adf..add8351c 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -1994,6 +1994,9 @@ func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { return nil } +func (helm *mockHelmExec) IsHelm3() bool { + return false +} func TestTemplate_SingleStateFile(t *testing.T) { files := map[string]string{ diff --git a/pkg/exectest/helm.go b/pkg/exectest/helm.go index ebcf1fa2..3a759c50 100644 --- a/pkg/exectest/helm.go +++ b/pkg/exectest/helm.go @@ -157,6 +157,10 @@ func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error { return nil } +func (helm *Helm) IsHelm3() bool { + return false +} + func (helm *Helm) sync(m *sync.Mutex, f func()) { if m != nil { m.Lock() diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index 79b41208..67d10290 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -20,6 +20,7 @@ type decryptedSecret struct { type execer struct { helmBinary string + isHelm3 bool runner Runner logger *zap.SugaredLogger kubeContext string @@ -45,15 +46,31 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { return zap.New(core).Sugar() } +func detectHelm3(helmBinary string, logger *zap.SugaredLogger, runner Runner) bool { + // Support explicit opt-in via environment variable + if os.Getenv("HELMFILE_HELM3") != "" { + return true + } + + // Autodetect from `helm verison` + bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil) + if err != nil { + panic(err) + } + return strings.HasPrefix(string(bytes), "v3.") +} + // New for running helm commands func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer { return &execer{ helmBinary: helmBinary, + isHelm3: detectHelm3(helmBinary, logger, runner), logger: logger, kubeContext: kubeContext, runner: runner, decryptedSecrets: make(map[string]*decryptedSecret), } + } func (helm *execer) SetExtraArgs(args ...string) { @@ -126,7 +143,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s preArgs := context.GetTillerlessArgs(helm.helmBinary) env := context.getTillerlessEnv() var args []string - if helm.isHelm3() { + if helm.IsHelm3() { args = []string{"list", "--filter", filter} } else { args = []string{"list", filter} @@ -139,7 +156,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s // of the release to exist. // // This fixes it by removing the header from the v3 output, so that the output is formatted the same as that of v2. - if helm.isHelm3() { + if helm.IsHelm3() { lines := strings.Split(string(out), "\n") lines = lines[1:] out = []byte(strings.Join(lines, "\n")) @@ -219,7 +236,7 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error { helm.logger.Infof("Templating release=%v, chart=%v", name, chart) var args []string - if helm.isHelm3() { + if helm.IsHelm3() { args = []string{"template", name, chart} } else { args = []string{"template", chart, "--name", name} @@ -286,7 +303,7 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin preArgs := context.GetTillerlessArgs(helm.helmBinary) env := context.getTillerlessEnv() var args []string - if helm.isHelm3() { + if helm.IsHelm3() { args = []string{"test", "run", name} } else { args = []string{"test", name} @@ -323,6 +340,6 @@ func (helm *execer) write(out []byte) { } } -func (helm *execer) isHelm3() bool { - return os.Getenv("HELMFILE_HELM3") != "" +func (helm *execer) IsHelm3() bool { + return helm.isHelm3 } diff --git a/pkg/helmexec/helmexec.go b/pkg/helmexec/helmexec.go index 54c3feae..74a40d3b 100644 --- a/pkg/helmexec/helmexec.go +++ b/pkg/helmexec/helmexec.go @@ -19,8 +19,10 @@ type Interface interface { TestRelease(context HelmContext, name string, flags ...string) error List(context HelmContext, filter string, flags ...string) (string, error) DecryptSecret(context HelmContext, name string, flags ...string) (string, error) + IsHelm3() bool } type DependencyUpdater interface { UpdateDeps(chart string) error + IsHelm3() bool } diff --git a/pkg/state/chart_dependency.go b/pkg/state/chart_dependency.go index 467e4795..ccd649a6 100644 --- a/pkg/state/chart_dependency.go +++ b/pkg/state/chart_dependency.go @@ -280,7 +280,7 @@ func (m *chartDependencyManager) lockFileName() string { } func (m *chartDependencyManager) Update(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) { - if isHelm3() { + if shell.IsHelm3() { return m.updateHelm3(shell, wd, unresolved) } return m.updateHelm2(shell, wd, unresolved) @@ -330,7 +330,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre return nil, err } - if isHelm3() && originalLockFileContent != nil { + if shell.IsHelm3() && originalLockFileContent != nil { if err := m.writeBytes(filepath.Join(wd, chartLockFile), originalLockFileContent); err != nil { return nil, err } @@ -357,7 +357,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre }) // Don't update lock file if no dependency updated. - if !isHelm3() && originalLockFileContent != nil { + if !shell.IsHelm3() && originalLockFileContent != nil { originalLockedReqs := &ChartLockedRequirements{} if err := yaml.Unmarshal(originalLockFileContent, originalLockedReqs); err != nil { return nil, err diff --git a/pkg/state/state.go b/pkg/state/state.go index f6dd690f..fbc22584 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -477,7 +477,7 @@ func (st *HelmState) DeleteReleasesForSync(affectedReleases *AffectedReleases, h relErr = newReleaseError(release, err) } else { var args []string - if isHelm3() { + if helm.IsHelm3() { args = []string{} if release.Namespace != "" { args = append(args, "--namespace", release.Namespace) @@ -577,7 +577,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme relErr = newReleaseError(release, err) } else if installed { var args []string - if isHelm3() { + if helm.IsHelm3() { args = []string{} } else { args = []string{"--purge"} @@ -646,7 +646,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) { flags := st.connectionFlags(release) - if isHelm3() && release.Namespace != "" { + if helm.IsHelm3() && release.Namespace != "" { flags = append(flags, "--namespace", release.Namespace) } return helm.List(context, "^"+release.Name+"$", flags...) @@ -1161,11 +1161,11 @@ func (st *HelmState) DeleteReleases(affectedReleases *AffectedReleases, helm hel st.ApplyOverrides(&release) flags := []string{} - if purge && !isHelm3() { + if purge && !helm.IsHelm3() { flags = append(flags, "--purge") } flags = st.appendConnectionFlags(flags, &release) - if isHelm3() && release.Namespace != "" { + if helm.IsHelm3() && release.Namespace != "" { flags = append(flags, "--namespace", release.Namespace) } context := st.createHelmContext(&release, workerIndex) @@ -1192,7 +1192,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout flags = append(flags, "--cleanup") } duration := strconv.Itoa(timeout) - if isHelm3() { + if helm.IsHelm3() { duration += "s" } flags = append(flags, "--timeout", duration) @@ -1202,10 +1202,6 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout }) } -func isHelm3() bool { - return os.Getenv("HELMFILE_HELM3") != "" -} - // Clean will remove any generated secrets func (st *HelmState) Clean() []error { errs := []error{} @@ -1532,7 +1528,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp } if timeout != 0 { duration := strconv.Itoa(timeout) - if isHelm3() { + if helm.IsHelm3() { duration += "s" } flags = append(flags, "--timeout", duration)