diff --git a/pkg/helmexec/context.go b/pkg/helmexec/context.go index 4dfa7153..12a48497 100644 --- a/pkg/helmexec/context.go +++ b/pkg/helmexec/context.go @@ -1,6 +1,7 @@ package helmexec import ( + "io" "os" "path/filepath" ) @@ -10,6 +11,7 @@ type HelmContext struct { TillerNamespace string HistoryMax int WorkerIndex int + Writer io.Writer } func (context *HelmContext) GetTillerlessArgs(helm *execer) []string { diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index 31a9fcfd..e7e8b1a8 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -183,7 +183,7 @@ func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags . } out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env) - helm.write(out) + helm.write(nil, out) return err } @@ -192,7 +192,7 @@ func (helm *execer) ReleaseStatus(context HelmContext, name string, flags ...str preArgs := context.GetTillerlessArgs(helm) env := context.getTillerlessEnv() out, err := helm.exec(append(append(preArgs, "status", name), flags...), env) - helm.write(out) + helm.write(nil, out) return err } @@ -219,7 +219,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s lines = lines[1:] out = []byte(strings.Join(lines, "\n")) } - helm.write(out) + helm.write(nil, out) return string(out), err } @@ -317,12 +317,16 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string) } out, err := helm.exec(append(args, flags...), map[string]string{}) - helm.write(out) + helm.write(nil, out) return err } func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppressDiff bool, flags ...string) error { - helm.logger.Infof("Comparing release=%v, chart=%v", name, chart) + if context.Writer != nil { + fmt.Fprintf(context.Writer, "Comparing release=%v, chart=%v\n", name, chart) + } else { + helm.logger.Infof("Comparing release=%v, chart=%v", name, chart) + } preArgs := context.GetTillerlessArgs(helm) env := context.getTillerlessEnv() out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env) @@ -340,13 +344,13 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres case ExitError: if e.ExitStatus() == 2 { if !(suppressDiff) { - helm.write(out) + helm.write(context.Writer, out) } return err } } } else if !(suppressDiff) { - helm.write(out) + helm.write(context.Writer, out) } return err } @@ -354,7 +358,7 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres func (helm *execer) Lint(name, chart string, flags ...string) error { helm.logger.Infof("Linting release=%v, chart=%v", name, chart) out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{}) - helm.write(out) + helm.write(nil, out) return err } @@ -370,7 +374,7 @@ func (helm *execer) DeleteRelease(context HelmContext, name string, flags ...str preArgs := context.GetTillerlessArgs(helm) env := context.getTillerlessEnv() out, err := helm.exec(append(append(preArgs, "delete", name), flags...), env) - helm.write(out) + helm.write(nil, out) return err } @@ -380,7 +384,7 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin env := context.getTillerlessEnv() args := []string{"test", name} out, err := helm.exec(append(append(preArgs, args...), flags...), env) - helm.write(out) + helm.write(nil, out) return err } @@ -413,9 +417,12 @@ func (helm *execer) info(out []byte) { } } -func (helm *execer) write(out []byte) { +func (helm *execer) write(w io.Writer, out []byte) { if len(out) > 0 { - fmt.Printf("%s\n", out) + if w == nil { + w = os.Stdout + } + fmt.Fprintf(w, "%s\n", out) } } diff --git a/pkg/state/state.go b/pkg/state/state.go index 854b5692..77249a53 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1418,7 +1418,9 @@ func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []st } type diffResult struct { - err *ReleaseError + release *ReleaseSpec + err *ReleaseError + buf *bytes.Buffer } type diffPrepareResult struct { @@ -1600,6 +1602,14 @@ func (st *HelmState) createHelmContext(spec *ReleaseSpec, workerIndex int) helme } } +func (st *HelmState) createHelmContextWithWriter(spec *ReleaseSpec, w io.Writer) helmexec.HelmContext { + ctx := st.createHelmContext(spec, 0) + + ctx.Writer = w + + return ctx +} + type DiffOpts struct { Context int NoColor bool @@ -1617,7 +1627,13 @@ func (o *DiffOpts) Apply(opts *DiffOpts) { type DiffOpt interface{ Apply(*DiffOpts) } // DiffReleases wrapper for executing helm diff on the releases -// It returns releases that had any changes +// It returns releases that had any changes, and errors if any. +// +// This function has responsibility to stabilize the order of writes to stdout from multiple concurrent helm-diff runs. +// It's required to use the stdout from helmfile-diff to detect if there was another change(s) between 2 points in time. +// For example, terraform-provider-helmfile runs a helmfile-diff on `terraform plan` and another on `terraform apply`. +// `terraform`, by design, fails when helmfile-diff outputs were not equivalent. +// Stabilized helmfile-diff output rescues that. func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, includeTests, suppressSecrets, suppressDiff, triggerCleanupEvents bool, opt ...DiffOpt) ([]ReleaseSpec, []error) { opts := &DiffOpts{} for _, o := range opt { @@ -1644,6 +1660,7 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st results := make(chan diffResult, len(preps)) rs := []ReleaseSpec{} + outputs := map[string]*bytes.Buffer{} errs := []error{} // The exit code returned by helm-diff when it detected any changes @@ -1662,19 +1679,20 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st for prep := range jobQueue { flags := prep.flags release := prep.release + buf := &bytes.Buffer{} if prep.upgradeDueToSkippedDiff { - results <- diffResult{&ReleaseError{ReleaseSpec: release, err: nil, Code: HelmDiffExitCodeChanged}} - } else if err := helm.DiffRelease(st.createHelmContext(release, workerIndex), release.Name, normalizeChart(st.basePath, release.Chart), suppressDiff, flags...); err != nil { + results <- diffResult{release, &ReleaseError{ReleaseSpec: release, err: nil, Code: HelmDiffExitCodeChanged}, buf} + } else if err := helm.DiffRelease(st.createHelmContextWithWriter(release, buf), release.Name, normalizeChart(st.basePath, release.Chart), suppressDiff, flags...); err != nil { switch e := err.(type) { case helmexec.ExitError: // Propagate any non-zero exit status from the external command like `helm` that is failed under the hood - results <- diffResult{&ReleaseError{release, err, e.ExitStatus()}} + results <- diffResult{release, &ReleaseError{release, err, e.ExitStatus()}, buf} default: - results <- diffResult{&ReleaseError{release, err, 0}} + results <- diffResult{release, &ReleaseError{release, err, 0}, buf} } } else { // diff succeeded, found no changes - results <- diffResult{} + results <- diffResult{release, nil, buf} } if triggerCleanupEvents { @@ -1693,10 +1711,18 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st rs = append(rs, *res.err.ReleaseSpec) } } + + outputs[ReleaseToID(res.release)] = res.buf } }, ) + for _, p := range preps { + if stdout, ok := outputs[ReleaseToID(p.release)]; ok { + fmt.Print(stdout.String()) + } + } + return rs, errs }