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
This commit is contained in:
Andrew Drake 2019-11-13 20:34:17 -05:00 committed by Andrew Drake
parent 43f1188325
commit c099f69d94
6 changed files with 42 additions and 20 deletions

View File

@ -1994,6 +1994,9 @@ func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error {
return nil return nil
} }
func (helm *mockHelmExec) IsHelm3() bool {
return false
}
func TestTemplate_SingleStateFile(t *testing.T) { func TestTemplate_SingleStateFile(t *testing.T) {
files := map[string]string{ files := map[string]string{

View File

@ -157,6 +157,10 @@ func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error {
return nil return nil
} }
func (helm *Helm) IsHelm3() bool {
return false
}
func (helm *Helm) sync(m *sync.Mutex, f func()) { func (helm *Helm) sync(m *sync.Mutex, f func()) {
if m != nil { if m != nil {
m.Lock() m.Lock()

View File

@ -20,6 +20,7 @@ type decryptedSecret struct {
type execer struct { type execer struct {
helmBinary string helmBinary string
isHelm3 bool
runner Runner runner Runner
logger *zap.SugaredLogger logger *zap.SugaredLogger
kubeContext string kubeContext string
@ -45,15 +46,31 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
return zap.New(core).Sugar() 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 // New for running helm commands
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer { func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
return &execer{ return &execer{
helmBinary: helmBinary, helmBinary: helmBinary,
isHelm3: detectHelm3(helmBinary, logger, runner),
logger: logger, logger: logger,
kubeContext: kubeContext, kubeContext: kubeContext,
runner: runner, runner: runner,
decryptedSecrets: make(map[string]*decryptedSecret), decryptedSecrets: make(map[string]*decryptedSecret),
} }
} }
func (helm *execer) SetExtraArgs(args ...string) { 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) preArgs := context.GetTillerlessArgs(helm.helmBinary)
env := context.getTillerlessEnv() env := context.getTillerlessEnv()
var args []string var args []string
if helm.isHelm3() { if helm.IsHelm3() {
args = []string{"list", "--filter", filter} args = []string{"list", "--filter", filter}
} else { } else {
args = []string{"list", filter} args = []string{"list", filter}
@ -139,7 +156,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
// of the release to exist. // 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. // 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 := strings.Split(string(out), "\n")
lines = lines[1:] lines = lines[1:]
out = []byte(strings.Join(lines, "\n")) 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 { func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
helm.logger.Infof("Templating release=%v, chart=%v", name, chart) helm.logger.Infof("Templating release=%v, chart=%v", name, chart)
var args []string var args []string
if helm.isHelm3() { if helm.IsHelm3() {
args = []string{"template", name, chart} args = []string{"template", name, chart}
} else { } else {
args = []string{"template", chart, "--name", name} 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) preArgs := context.GetTillerlessArgs(helm.helmBinary)
env := context.getTillerlessEnv() env := context.getTillerlessEnv()
var args []string var args []string
if helm.isHelm3() { if helm.IsHelm3() {
args = []string{"test", "run", name} args = []string{"test", "run", name}
} else { } else {
args = []string{"test", name} args = []string{"test", name}
@ -323,6 +340,6 @@ func (helm *execer) write(out []byte) {
} }
} }
func (helm *execer) isHelm3() bool { func (helm *execer) IsHelm3() bool {
return os.Getenv("HELMFILE_HELM3") != "" return helm.isHelm3
} }

View File

@ -19,8 +19,10 @@ type Interface interface {
TestRelease(context HelmContext, name string, flags ...string) error TestRelease(context HelmContext, name string, flags ...string) error
List(context HelmContext, filter string, flags ...string) (string, error) List(context HelmContext, filter string, flags ...string) (string, error)
DecryptSecret(context HelmContext, name string, flags ...string) (string, error) DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
IsHelm3() bool
} }
type DependencyUpdater interface { type DependencyUpdater interface {
UpdateDeps(chart string) error UpdateDeps(chart string) error
IsHelm3() bool
} }

View File

@ -280,7 +280,7 @@ func (m *chartDependencyManager) lockFileName() string {
} }
func (m *chartDependencyManager) Update(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) { 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.updateHelm3(shell, wd, unresolved)
} }
return m.updateHelm2(shell, wd, unresolved) return m.updateHelm2(shell, wd, unresolved)
@ -330,7 +330,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre
return nil, err return nil, err
} }
if isHelm3() && originalLockFileContent != nil { if shell.IsHelm3() && originalLockFileContent != nil {
if err := m.writeBytes(filepath.Join(wd, chartLockFile), originalLockFileContent); err != nil { if err := m.writeBytes(filepath.Join(wd, chartLockFile), originalLockFileContent); err != nil {
return nil, err return nil, err
} }
@ -357,7 +357,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre
}) })
// Don't update lock file if no dependency updated. // Don't update lock file if no dependency updated.
if !isHelm3() && originalLockFileContent != nil { if !shell.IsHelm3() && originalLockFileContent != nil {
originalLockedReqs := &ChartLockedRequirements{} originalLockedReqs := &ChartLockedRequirements{}
if err := yaml.Unmarshal(originalLockFileContent, originalLockedReqs); err != nil { if err := yaml.Unmarshal(originalLockFileContent, originalLockedReqs); err != nil {
return nil, err return nil, err

View File

@ -477,7 +477,7 @@ func (st *HelmState) DeleteReleasesForSync(affectedReleases *AffectedReleases, h
relErr = newReleaseError(release, err) relErr = newReleaseError(release, err)
} else { } else {
var args []string var args []string
if isHelm3() { if helm.IsHelm3() {
args = []string{} args = []string{}
if release.Namespace != "" { if release.Namespace != "" {
args = append(args, "--namespace", release.Namespace) args = append(args, "--namespace", release.Namespace)
@ -577,7 +577,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
relErr = newReleaseError(release, err) relErr = newReleaseError(release, err)
} else if installed { } else if installed {
var args []string var args []string
if isHelm3() { if helm.IsHelm3() {
args = []string{} args = []string{}
} else { } else {
args = []string{"--purge"} 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) { func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) {
flags := st.connectionFlags(release) flags := st.connectionFlags(release)
if isHelm3() && release.Namespace != "" { if helm.IsHelm3() && release.Namespace != "" {
flags = append(flags, "--namespace", release.Namespace) flags = append(flags, "--namespace", release.Namespace)
} }
return helm.List(context, "^"+release.Name+"$", flags...) return helm.List(context, "^"+release.Name+"$", flags...)
@ -1161,11 +1161,11 @@ func (st *HelmState) DeleteReleases(affectedReleases *AffectedReleases, helm hel
st.ApplyOverrides(&release) st.ApplyOverrides(&release)
flags := []string{} flags := []string{}
if purge && !isHelm3() { if purge && !helm.IsHelm3() {
flags = append(flags, "--purge") flags = append(flags, "--purge")
} }
flags = st.appendConnectionFlags(flags, &release) flags = st.appendConnectionFlags(flags, &release)
if isHelm3() && release.Namespace != "" { if helm.IsHelm3() && release.Namespace != "" {
flags = append(flags, "--namespace", release.Namespace) flags = append(flags, "--namespace", release.Namespace)
} }
context := st.createHelmContext(&release, workerIndex) context := st.createHelmContext(&release, workerIndex)
@ -1192,7 +1192,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
flags = append(flags, "--cleanup") flags = append(flags, "--cleanup")
} }
duration := strconv.Itoa(timeout) duration := strconv.Itoa(timeout)
if isHelm3() { if helm.IsHelm3() {
duration += "s" duration += "s"
} }
flags = append(flags, "--timeout", duration) 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 // Clean will remove any generated secrets
func (st *HelmState) Clean() []error { func (st *HelmState) Clean() []error {
errs := []error{} errs := []error{}
@ -1532,7 +1528,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
} }
if timeout != 0 { if timeout != 0 {
duration := strconv.Itoa(timeout) duration := strconv.Itoa(timeout)
if isHelm3() { if helm.IsHelm3() {
duration += "s" duration += "s"
} }
flags = append(flags, "--timeout", duration) flags = append(flags, "--timeout", duration)