feat: Optional detailed exitcodes for `helmfile diff`

Adds the `--detailed-exitcode` to the `helmfile diff` command to return `1` on failure, and `2` when no error but diff is seen.

This feature requires the latest `helm-diff` containing https://github.com/databus23/helm-diff/pull/78, and `helm` containing https://github.com/helm/helm/pull/4367.

This is verified to work by manually running commands like the followings:

```bash
./helmfile --helm-binary helm211dev -f ./examples/helmfile.d diff --detailed-exitcode; echo $?
./helmfile --helm-binary helm211dev -f ./examples/helmfile.d diff; echo $?
```

Note that, in above example commands, `helm211dev` is a custom `helm` binary that is built from helm's master branch containing [the necessary enhancement to allow propagate non-zero plugin exit code](https://github.com/helm/helm/pull/4367).
This commit is contained in:
Yusuke KUOKA 2018-07-26 00:16:32 +09:00
parent 92d09a3e92
commit 3b4ce90a5a
2 changed files with 21 additions and 5 deletions

20
main.go
View File

@ -15,6 +15,7 @@ import (
"github.com/urfave/cli"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os/exec"
)
const (
@ -166,6 +167,10 @@ func main() {
Name: "sync-repos",
Usage: "enable a repo sync prior to diffing",
},
cli.BoolFlag{
Name: "detailed-exitcode",
Usage: "return a non-zero exit code when there are changes",
},
cli.IntFlag{
Name: "concurrency",
Value: 0,
@ -190,8 +195,9 @@ func main() {
values := c.StringSlice("values")
workers := c.Int("concurrency")
detailedExitCode := c.Bool("detailed-exitcode")
return state.DiffReleases(helm, values, workers)
return state.DiffReleases(helm, values, workers, detailedExitCode)
})
},
},
@ -367,8 +373,7 @@ func main() {
err := app.Run(os.Args)
if err != nil {
log.Printf("err: %s", err.Error())
os.Exit(1)
log.Panicf("[bug] this code path shouldn't be arrived: helmfile is expected to exit from within the `cleanup` func in main.go: %v", err)
}
}
@ -517,7 +522,14 @@ func clean(state *state.HelmState, errs []error) error {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
switch e := errs[0].(type) {
case *exec.ExitError:
// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
status := e.Sys().(syscall.WaitStatus)
os.Exit(status.ExitStatus())
default:
os.Exit(1)
}
}
return nil
}

View File

@ -254,7 +254,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
}
// DiffReleases wrapper for executing helm diff on the releases
func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int) []error {
func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode bool) []error {
var wgRelease sync.WaitGroup
var wgError sync.WaitGroup
errs := []error{}
@ -289,6 +289,10 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [
flags = append(flags, "--values", valfile)
}
if detailedExitCode {
flags = append(flags, "--detailed-exitcode")
}
if len(errs) == 0 {
if err := helm.DiffRelease(release.Name, normalizeChart(state.BaseChartPath, release.Chart), flags...); err != nil {
errs = append(errs, err)