Stabilize helmfile-diff output (#1619)

`helmfile-diff` sorts multiple and concurrent helm-diff outputs and stabilizes writes to stdout.

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.
This commit is contained in:
Yusuke Kuoka 2020-12-11 09:51:26 +09:00 committed by GitHub
parent 28e7ebb4a4
commit 1c7b872476
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 19 deletions

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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
}