feat: --detailed-exitcode for `helmfile apply` (#1120)

Resolves #1113
This commit is contained in:
KUOKA Yusuke 2020-02-26 21:09:05 +09:00 committed by GitHub
parent 8acbbc596d
commit 0186254e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 37 deletions

View File

@ -329,6 +329,10 @@ func main() {
Value: 0,
Usage: "output NUM lines of context around changes",
},
cli.BoolFlag{
Name: "detailed-exitcode",
Usage: "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully",
},
cli.StringFlag{
Name: "args",
Value: "",
@ -625,11 +629,11 @@ func action(do func(*app.App, configImpl) error) func(*cli.Context) error {
a := app.New(conf)
a.ErrorHandler = func(err error) error {
if err := do(a, conf); err != nil {
return toCliError(implCtx, err)
}
return do(a, conf)
return nil
}
}

View File

@ -10,6 +10,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"
"syscall"
"text/tabwriter"
@ -40,8 +41,6 @@ type App struct {
FileOrDir string
ErrorHandler func(error) error
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
glob func(string) ([]string, error)
@ -144,9 +143,31 @@ func (a *App) Sync(c SyncConfigProvider) error {
}
func (a *App) Apply(c ApplyConfigProvider) error {
return a.ForEachState(func(run *Run) (bool, []error) {
return a.apply(run, c)
var any bool
mut := &sync.Mutex{}
err := a.ForEachState(func(run *Run) (bool, []error) {
matched, updated, errs := a.apply(run, c)
mut.Lock()
any = any || updated
mut.Unlock()
return matched, errs
})
if err != nil {
return err
}
if c.DetailedExitcode() && any {
code := 2
return &Error{msg: "", Errors: nil, code: &code}
}
return nil
}
func (a *App) Status(c StatusesConfigProvider) error {
@ -410,10 +431,6 @@ func (a *App) ForEachStateFiltered(do func(*Run) []error) error {
return do(run)
})
if err != nil && a.ErrorHandler != nil {
return a.ErrorHandler(err)
}
return err
}
@ -424,10 +441,6 @@ func (a *App) ForEachState(do func(*Run) (bool, []error)) error {
return do(run)
})
if err != nil && a.ErrorHandler != nil {
return a.ErrorHandler(err)
}
return err
}
@ -661,7 +674,7 @@ func (a *App) findDesiredStateFiles(specifiedPath string) ([]string, error) {
return files, nil
}
func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, []error) {
func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
st := r.state
helm := r.helm
ctx := r.ctx
@ -670,10 +683,10 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, []error) {
toApply, err := st.GetSelectedReleasesWithOverrides()
if err != nil {
return false, []error{err}
return false, false, []error{err}
}
if len(toApply) == 0 {
return false, nil
return false, false, nil
}
// Do build deps and prepare only on selected releases so that we won't waste time
@ -682,14 +695,14 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, []error) {
if !c.SkipDeps() {
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
return false, errs
return false, false, errs
}
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
return false, errs
return false, false, errs
}
}
if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 {
return false, errs
return false, false, errs
}
// helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly
@ -730,7 +743,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, []error) {
}
if len(fatalErrs) > 0 {
return false, fatalErrs
return false, false, fatalErrs
}
releasesToBeDeleted := map[string]state.ReleaseSpec{}
@ -755,7 +768,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, []error) {
logger := c.Logger()
logger.Infof("")
logger.Infof("No affected releases")
return true, nil
return true, false, nil
}
names := []string{}
@ -838,7 +851,7 @@ Do you really want to apply?
}
affectedReleases.DisplayAffectedReleases(c.Logger())
return true, syncErrs
return true, true, syncErrs
}
func (a *App) delete(r *Run, purge bool, c DestroyConfigProvider) (bool, []error) {
@ -1133,6 +1146,8 @@ type Error struct {
msg string
Errors []error
code *int
}
func (e *Error) Error() string {
@ -1165,6 +1180,10 @@ func (e *Error) Error() string {
}
func (e *Error) Code() int {
if e.code != nil {
return *e.code
}
allDiff := false
anyNonZero := false
for _, err := range e.Errors {
@ -1195,7 +1214,7 @@ func (e *Error) Code() int {
}
func appError(msg string, err error) error {
return &Error{msg, []error{err}}
return &Error{msg: msg, Errors: []error{err}}
}
func (c context) clean(errs []error) error {

View File

@ -1890,17 +1890,18 @@ func (c configImpl) Concurrency() int {
}
type applyConfig struct {
args string
values []string
set []string
skipDeps bool
suppressSecrets bool
suppressDiff bool
noColor bool
context int
concurrency int
interactive bool
logger *zap.SugaredLogger
args string
values []string
set []string
skipDeps bool
suppressSecrets bool
suppressDiff bool
noColor bool
context int
concurrency int
detailedExitcode bool
interactive bool
logger *zap.SugaredLogger
}
func (a applyConfig) Args() string {
@ -1939,6 +1940,10 @@ func (a applyConfig) Concurrency() int {
return a.concurrency
}
func (a applyConfig) DetailedExitcode() bool {
return a.detailedExitcode
}
func (a applyConfig) Interactive() bool {
return a.interactive
}

View File

@ -43,6 +43,8 @@ type ApplyConfigProvider interface {
SuppressSecrets() bool
SuppressDiff() bool
DetailedExitcode() bool
NoColor() bool
Context() int

View File

@ -77,6 +77,9 @@ ${helmfile} -f ${dir}/happypath.yaml template
code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile template: ${code}"
info "Applying ${dir}/happypath.yaml"
bash -c "${helmfile} -f ${dir}/happypath.yaml apply --detailed-exitcode; code="'$?'"; echo Code: "'$code'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile apply"
info "Syncing ${dir}/happypath.yaml"
${helmfile} -f ${dir}/happypath.yaml sync
wait_deploy_ready httpbin-httpbin
@ -84,9 +87,9 @@ retry 5 "curl --fail $(minikube service --url --namespace=${test_ns} httpbin-htt
[ ${retry_result} -eq 0 ] || fail "httpbin failed to return 200 OK"
info "Applying ${dir}/happypath.yaml"
${helmfile} -f ${dir}/happypath.yaml apply
${helmfile} -f ${dir}/happypath.yaml apply --detailed-exitcode
code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: ${code}"
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}"
info "Locking dependencies"
${helmfile} -f ${dir}/happypath.yaml deps