Repository: roboll/helmfile
Pull request #1284 (2020-05-29 15:00:37)

typo: Fix message for detected diff
This one is pretty self-explanatory (:


Patch:
diff --git a/go.mod b/go.mod
index c15184c..b0d8ae3 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
 	github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
 	github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
 	github.com/urfave/cli v1.20.0
-	github.com/variantdev/chartify v0.3.3
+	github.com/variantdev/chartify v0.3.7
 	github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
 	github.com/variantdev/vals v0.4.1-0.20200501114609-9cebe482281c
 	go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
diff --git a/go.sum b/go.sum
index 63b1758..1768b18 100644
--- a/go.sum
+++ b/go.sum
@@ -801,6 +801,14 @@ github.com/variantdev/chartify v0.3.2 h1:EjwlY/ArgkOhsvbMG+xyeBnsWRV6eaxfHfJj9st
 github.com/variantdev/chartify v0.3.2/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
 github.com/variantdev/chartify v0.3.3 h1:qKi5Rw4A9TfZ2oedQIop4rcHfiToraakWIdn1rZJyLo=
 github.com/variantdev/chartify v0.3.3/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
+github.com/variantdev/chartify v0.3.4 h1:1KAUAtK4AZ1/N+Hy3YNN2w+6Yl/BoDSdyYTGuODhNz0=
+github.com/variantdev/chartify v0.3.4/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
+github.com/variantdev/chartify v0.3.5 h1:5lH6eJXlhfWT7vQ+OGtRlocgRwZriIrge5OAGFMKs4M=
+github.com/variantdev/chartify v0.3.5/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
+github.com/variantdev/chartify v0.3.6 h1:FQOcc0ikcf2yni+75STkKu68Qz2zoyqJSxDDc6xH1X4=
+github.com/variantdev/chartify v0.3.6/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
+github.com/variantdev/chartify v0.3.7 h1:4bnmhnnjqkGAbUAn2ZZkyz+LJfQLbWv2YGmpt/4NfK0=
+github.com/variantdev/chartify v0.3.7/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
 github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 h1:KrfQBEUn+wEOQ/6UIfoqRDvn+Q/wZridQ7t0G1vQqKE=
 github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE=
 github.com/variantdev/vals v0.4.0 h1:O1O7/sWhlvozcY2DjZBzlE1notxwVo6UBT1+w7HsO/k=
diff --git a/pkg/app/app.go b/pkg/app/app.go
index c92c1d2..cf1aef1 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -101,13 +101,11 @@ func Init(app *App) *App {
 
 func (a *App) Deps(c DepsConfigProvider) error {
 	return a.ForEachStateFiltered(func(run *Run) (errs []error) {
-		err := run.withPreparedCharts(false, func() {
+		prepErrs := run.withReposAndPreparedCharts(false, c.SkipRepos(), func() {
 			errs = run.Deps(c)
 		})
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		return
 	})
@@ -155,7 +153,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
 
 		var errs []error
 
-		err := run.withPreparedCharts(false, func() {
+		prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
 			msg, matched, affected, errs = run.Diff(c)
 		})
 
@@ -163,9 +161,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
 			a.Logger.Info(*msg)
 		}
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		affectedAny = affectedAny || affected
 
@@ -197,7 +193,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
 		// See https://github.com/roboll/helmfile/issues/749
 		code := 2
 		e := &Error{
-			msg:  "Identified at least on change",
+			msg:  "Identified at least one change",
 			code: &code,
 		}
 		return e
@@ -208,13 +204,11 @@ func (a *App) Diff(c DiffConfigProvider) error {
 
 func (a *App) Template(c TemplateConfigProvider) error {
 	return a.ForEachState(func(run *Run) (ok bool, errs []error) {
-		err := run.withPreparedCharts(true, func() {
+		prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), func() {
 			ok, errs = a.template(run, c)
 		})
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		return
 	})
@@ -222,13 +216,11 @@ func (a *App) Template(c TemplateConfigProvider) error {
 
 func (a *App) Lint(c LintConfigProvider) error {
 	return a.ForEachStateFiltered(func(run *Run) (errs []error) {
-		err := run.withPreparedCharts(true, func() {
+		prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), func() {
 			errs = run.Lint(c)
 		})
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		return
 	})
@@ -236,13 +228,11 @@ func (a *App) Lint(c LintConfigProvider) error {
 
 func (a *App) Sync(c SyncConfigProvider) error {
 	return a.ForEachState(func(run *Run) (ok bool, errs []error) {
-		err := run.withPreparedCharts(false, func() {
+		prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
 			ok, errs = a.sync(run, c)
 		})
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		return
 	})
@@ -258,7 +248,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
 	opts = append(opts, SetRetainValuesFiles(c.RetainValuesFiles()))
 
 	err := a.ForEachState(func(run *Run) (ok bool, errs []error) {
-		err := run.withPreparedCharts(false, func() {
+		prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
 			matched, updated, es := a.apply(run, c)
 
 			mut.Lock()
@@ -268,9 +258,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
 			ok, errs = matched, es
 		})
 
-		if err != nil {
-			errs = append(errs, err)
-		}
+		errs = append(errs, prepErrs...)
 
 		return
 	}, opts...)
@@ -935,7 +923,6 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri
 func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
 	st := r.state
 	helm := r.helm
-	ctx := r.ctx
 
 	allReleases := st.GetReleasesWithOverrides()
 
@@ -952,9 +939,6 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
 	st.Releases = toApply
 
 	if !c.SkipDeps() {
-		if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
-			return false, false, errs
-		}
 		if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
 			return false, false, errs
 		}
@@ -1126,7 +1110,6 @@ Do you really want to delete?
 func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) {
 	st := r.state
 	helm := r.helm
-	ctx := r.ctx
 
 	allReleases := st.GetReleasesWithOverrides()
 
@@ -1143,9 +1126,6 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) {
 	st.Releases = toSync
 
 	if !c.SkipDeps() {
-		if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
-			return false, errs
-		}
 		if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
 			return false, errs
 		}
@@ -1252,7 +1232,6 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) {
 func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) {
 	st := r.state
 	helm := r.helm
-	ctx := r.ctx
 
 	allReleases := st.GetReleasesWithOverrides()
 
@@ -1269,9 +1248,6 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) {
 	st.Releases = toRender
 
 	if !c.SkipDeps() {
-		if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
-			return false, errs
-		}
 		if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
 			return false, errs
 		}
diff --git a/pkg/app/run.go b/pkg/app/run.go
index b1ed6d4..0895ec0 100644
--- a/pkg/app/run.go
+++ b/pkg/app/run.go
@@ -32,6 +32,21 @@ func (r *Run) askForConfirmation(msg string) bool {
 	return AskForConfirmation(msg)
 }
 
+func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, f func()) []error {
+	if !skipRepos {
+		ctx := r.ctx
+		if errs := ctx.SyncReposOnce(r.state, r.helm); errs != nil && len(errs) > 0 {
+			return errs
+		}
+	}
+
+	if err := r.withPreparedCharts(forceDownload, f); err != nil {
+		return []error{err}
+	}
+
+	return nil
+}
+
 func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
 	if r.ReleaseToChart != nil {
 		panic("Run.PrepareCharts can be called only once")
@@ -66,12 +81,6 @@ func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
 func (r *Run) Deps(c DepsConfigProvider) []error {
 	r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
 
-	if !c.SkipRepos() {
-		if errs := r.ctx.SyncReposOnce(r.state, r.helm); errs != nil && len(errs) > 0 {
-			return errs
-		}
-	}
-
 	return r.state.UpdateDeps(r.helm)
 }
 
@@ -102,7 +111,6 @@ func (r *Run) Status(c StatusesConfigProvider) []error {
 func (r *Run) Diff(c DiffConfigProvider) (*string, bool, bool, []error) {
 	st := r.state
 	helm := r.helm
-	ctx := r.ctx
 
 	allReleases := st.GetReleasesWithOverrides()
 
@@ -120,9 +128,6 @@ func (r *Run) Diff(c DiffConfigProvider) (*string, bool, bool, []error) {
 	st.Releases = toDiff
 
 	if !c.SkipDeps() {
-		if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
-			return nil, false, false, errs
-		}
 		if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
 			return nil, false, false, errs
 		}
@@ -175,15 +180,11 @@ func (r *Run) Test(c TestConfigProvider) []error {
 func (r *Run) Lint(c LintConfigProvider) []error {
 	st := r.state
 	helm := r.helm
-	ctx := r.ctx
 
 	values := c.Values()
 	args := argparser.GetArgs(c.Args(), st)
 	workers := c.Concurrency()
 	if !c.SkipDeps() {
-		if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
-			return errs
-		}
 		if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
 			return errs
 		}
diff --git a/pkg/helmexec/exit_error.go b/pkg/helmexec/exit_error.go
index 9419cf4..31eaf7c 100644
--- a/pkg/helmexec/exit_error.go
+++ b/pkg/helmexec/exit_error.go
@@ -5,19 +5,55 @@ import (
 	"strings"
 )
 
-func newExitError(helmCmdPath string, exitStatus int, errorMessage string) ExitError {
+func newExitError(path string, args []string, exitStatus int, err error, stderr, combined string) ExitError {
+	var out string
+
+	out += fmt.Sprintf("PATH:\n%s", Indent(path, "  "))
+
+	out += "\n\nARGS:"
+	for i, a := range args {
+		out += fmt.Sprintf("\n%s", Indent(fmt.Sprintf("%d: %s (%d bytes)", i, a, len(a)), "  "))
+	}
+
+	out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), "  "))
+
+	out += fmt.Sprintf("\n\nEXIT STATUS\n%s", Indent(fmt.Sprintf("%d", exitStatus), "  "))
+
+	if len(stderr) > 0 {
+		out += fmt.Sprintf("\n\nSTDERR:\n%s", Indent(stderr, "  "))
+	}
+
+	if len(combined) > 0 {
+		out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(combined, "  "))
+	}
+
 	return ExitError{
-		Message: fmt.Sprintf("the following cmd exited with status %d:\n%s\n\n%s", exitStatus, indent(strings.TrimSpace(helmCmdPath)), indent(strings.TrimSpace(errorMessage))),
+		Message: fmt.Sprintf("command %q exited with non-zero status:\n\n%s", path, out),
 		Code:    exitStatus,
 	}
 }
 
-func indent(text string) string {
+// indents a block of text with an indent string
+func Indent(text, indent string) string {
+	var b strings.Builder
+
+	b.Grow(len(text) * 2)
+
 	lines := strings.Split(text, "\n")
-	for i := range lines {
-		lines[i] = "  " + lines[i]
+
+	last := len(lines) - 1
+
+	for i, j := range lines {
+		if i > 0 && i < last && j != "" {
+			b.WriteString("\n")
+		}
+
+		if j != "" {
+			b.WriteString(indent + j)
+		}
 	}
-	return strings.Join(lines, "\n")
+
+	return b.String()
 }
 
 // ExitError is created whenever your shell command exits with a non-zero exit status
diff --git a/pkg/helmexec/runner.go b/pkg/helmexec/runner.go
index 179c019..8762c11 100644
--- a/pkg/helmexec/runner.go
+++ b/pkg/helmexec/runner.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"go.uber.org/zap"
+	"io"
 	"os"
 	"os/exec"
 	"strings"
@@ -33,10 +34,10 @@ func (shell ShellRunner) Execute(cmd string, args []string, env map[string]strin
 	preparedCmd := exec.Command(cmd, args...)
 	preparedCmd.Dir = shell.Dir
 	preparedCmd.Env = mergeEnv(os.Environ(), env)
-	return combinedOutput(preparedCmd, shell.Logger)
+	return Output(preparedCmd)
 }
 
-func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
+func Output(c *exec.Cmd) ([]byte, error) {
 	if c.Stdout != nil {
 		return nil, errors.New("exec: Stdout already set")
 	}
@@ -45,13 +46,11 @@ func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
 	}
 	var stdout bytes.Buffer
 	var stderr bytes.Buffer
-	c.Stdout = &stdout
-	c.Stderr = &stderr
+	var combined bytes.Buffer
+	c.Stdout = io.MultiWriter(&stdout, &combined)
+	c.Stderr = io.MultiWriter(&stderr, &combined)
 	err := c.Run()
 
-	o := stdout.Bytes()
-	e := stderr.Bytes()
-
 	if err != nil {
 		// TrimSpace is necessary, because otherwise helmfile prints the redundant new-lines after each error like:
 		//
@@ -64,14 +63,13 @@ func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
 			// so that helmfile could return its own exit code accordingly
 			waitStatus := ee.Sys().(syscall.WaitStatus)
 			exitStatus := waitStatus.ExitStatus()
-			cmd := fmt.Sprintf("%s %s", c.Path, strings.Join(c.Args, " "))
-			err = newExitError(cmd, exitStatus, string(e))
+			err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String())
 		default:
 			panic(fmt.Sprintf("unexpected error: %v", err))
 		}
 	}
 
-	return o, err
+	return stdout.Bytes(), err
 }
 
 func mergeEnv(orig []string, new map[string]string) []string {
diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go
index 23989ab..fe4b36b 100644
--- a/pkg/state/helmx.go
+++ b/pkg/state/helmx.go
@@ -25,6 +25,8 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) ([]s
 func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) (bool, *chartify.ChartifyOpts, error) {
 	var opts chartify.ChartifyOpts
 
+	opts.WorkaroundOutputDirIssue = true
+
 	var shouldRun bool
 
 	opts.EnableKustomizeAlphaPlugins = true
diff --git a/pkg/tmpl/context_funcs.go b/pkg/tmpl/context_funcs.go
index fac7c8c..5d65e58 100644
--- a/pkg/tmpl/context_funcs.go
+++ b/pkg/tmpl/context_funcs.go
@@ -2,6 +2,7 @@ package tmpl
 
 import (
 	"fmt"
+	"github.com/roboll/helmfile/pkg/helmexec"
 	"golang.org/x/sync/errgroup"
 	"gopkg.in/yaml.v2"
 	"io"
@@ -97,25 +98,9 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
 	g.Go(func() error {
 		// We use CombinedOutput to produce helpful error messages
 		// See https://github.com/roboll/helmfile/issues/1158
-		bs, err := cmd.CombinedOutput()
+		bs, err := helmexec.Output(cmd)
 		if err != nil {
-			args := strings.Join(strArgs, ", ")
-			shownCmd := []string{command}
-			if len(args) > 0 {
-				shownCmd = append(shownCmd, args)
-			}
-
-			var out string
-
-			out += fmt.Sprintf("\n\nCOMMAND:\n%s", Indent(strings.Join(shownCmd, " "), "  "))
-
-			out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), "  "))
-
-			if len(bs) > 0 {
-				out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(string(bs), "  "))
-			}
-
-			return fmt.Errorf("%v%s", err, out)
+			return err
 		}
 
 		bytes = bs
@@ -130,29 +115,6 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
 	return string(bytes), nil
 }
 
-// indents a block of text with an indent string
-func Indent(text, indent string) string {
-	var b strings.Builder
-
-	b.Grow(len(text) * 2)
-
-	lines := strings.Split(text, "\n")
-
-	last := len(lines) - 1
-
-	for i, j := range lines {
-		if i > 0 && i < last && j != "" {
-			b.WriteString("\n")
-		}
-
-		if j != "" {
-			b.WriteString(indent + j)
-		}
-	}
-
-	return b.String()
-}
-
 func (c *Context) ReadFile(filename string) (string, error) {
 	var path string
 	if filepath.IsAbs(filename) {


Test patch:
diff --git a/pkg/app/diff_test.go b/pkg/app/diff_test.go
index 2069fdd..8b5127e 100644
--- a/pkg/app/diff_test.go
+++ b/pkg/app/diff_test.go
@@ -172,7 +172,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				// noop on frontend-v2
 				exectest.DiffKey{Name: "frontend-v2", Chart: "charts/frontend", Flags: "--kube-contextdefault--detailed-exitcode"}: nil,
@@ -389,7 +389,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -461,7 +461,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -483,7 +483,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -506,7 +506,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -529,7 +529,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -553,7 +553,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -577,7 +577,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -604,7 +604,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -630,7 +630,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -705,7 +705,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -737,7 +737,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -771,7 +771,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -803,7 +803,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -835,7 +835,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -867,7 +867,7 @@ releases:
 `,
 			},
 			detailedExitcode: true,
-			error:            "Identified at least on change",
+			error:            "Identified at least one change",
 			diffs: map[exectest.DiffKey]error{
 				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
 				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -925,7 +925,7 @@ releases:
 			upgraded: []exectest.Release{},
 			// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
 			concurrency: 1,
-			error:       "Identified at least on change",
+			error:       "Identified at least one change",
 			log: `processing file "helmfile.yaml" in directory "."
 first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
 first-pass uses: &{default map[] map[]}