Merge pull request #359 from helmfile/358-incorrect-exit-code

Fix incorrent exit code issue introduced in v0.145.0
This commit is contained in:
yxxhero 2022-09-14 08:51:16 +08:00 committed by GitHub
commit 3ff98c02a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 30 deletions

View File

@ -6,12 +6,12 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/urfave/cli"
"go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/app"
"github.com/helmfile/helmfile/pkg/app/version"
"github.com/helmfile/helmfile/pkg/config"
"github.com/helmfile/helmfile/pkg/errors"
"github.com/helmfile/helmfile/pkg/helmexec"
)
@ -26,11 +26,11 @@ func toCLIError(g *config.GlobalImpl, err error) error {
if g.AllowNoMatchingRelease {
noMatchingExitCode = 0
}
return cli.NewExitError(e.Error(), noMatchingExitCode)
return errors.NewExitError(e.Error(), noMatchingExitCode)
case *app.MultiError:
return cli.NewExitError(e.Error(), 1)
return errors.NewExitError(e.Error(), 1)
case *app.Error:
return cli.NewExitError(e.Error(), e.Code())
return errors.NewExitError(e.Error(), e.Code())
default:
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
}
@ -41,12 +41,13 @@ func toCLIError(g *config.GlobalImpl, err error) error {
// NewRootCmd creates the root command for the CLI.
func NewRootCmd(globalConfig *config.GlobalOptions, args []string) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "helmfile",
Short: globalUsage,
Long: globalUsage,
Args: cobra.MinimumNArgs(1),
Version: version.GetVersion(),
SilenceUsage: true,
Use: "helmfile",
Short: globalUsage,
Long: globalUsage,
Args: cobra.MinimumNArgs(1),
Version: version.GetVersion(),
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(c *cobra.Command, args []string) error {
// Valid levels:
// https://github.com/uber-go/zap/blob/7e7e266a8dbce911a49554b945538c5b950196b8/zapcore/level.go#L126

3
go.mod
View File

@ -24,7 +24,6 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/urfave/cli v1.22.10
github.com/variantdev/chartify v0.10.2
github.com/variantdev/dag v1.1.0
github.com/variantdev/vals v0.18.0
@ -62,7 +61,6 @@ require (
github.com/aws/aws-sdk-go v1.40.28 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fujiwara/tfstate-lookup v0.4.4 // indirect
@ -107,7 +105,6 @@ require (
github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect

4
go.sum
View File

@ -394,7 +394,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -1144,7 +1143,6 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -1245,8 +1243,6 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/variantdev/chartify v0.10.2 h1:CAMlSE4kBl8ft/Xl4ob+eyFZ2KV5evkiMEI43M4CaKs=
github.com/variantdev/chartify v0.10.2/go.mod h1:A0nQmb+ihiBJrrbgofs1t7QVeit+/llT0vJhvkj7U0Q=
github.com/variantdev/dag v1.1.0 h1:xodYlSng33KWGvIGMpKUyLcIZRXKiNUx612mZJqYrDg=

17
main.go
View File

@ -1,28 +1,19 @@
package main
import (
"fmt"
"os"
"github.com/urfave/cli"
"github.com/helmfile/helmfile/cmd"
"github.com/helmfile/helmfile/pkg/config"
"github.com/helmfile/helmfile/pkg/errors"
)
func warning(format string, v ...interface{}) {
format = fmt.Sprintf("WARNING: %s\n", format)
fmt.Fprintf(os.Stderr, format, v...)
}
func main() {
globalConfig := new(config.GlobalOptions)
rootCmd, err := cmd.NewRootCmd(globalConfig, os.Args[1:])
if err != nil {
warning("%+v", err)
os.Exit(1)
}
errors.HandleExitCoder(err)
if err := rootCmd.Execute(); err != nil {
cli.HandleExitCoder(err)
errors.HandleExitCoder(err)
}
}

97
pkg/errors/errors.go Normal file
View File

@ -0,0 +1,97 @@
/*
* MIT License
*
* Copyright (c) 2022 urfave/cli maintainers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package errors
import (
"fmt"
"io"
"os"
)
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
var OsExiter = os.Exit
// ErrWriter is used to write errors to the user. This can be anything
// implementing the io.Writer interface and defaults to os.Stderr.
var ErrWriter io.Writer = os.Stderr
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface {
error
ExitCode() int
}
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct {
exitCode int
message interface{}
}
// NewExitError makes a new *ExitError
func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{
exitCode: exitCode,
message: message,
}
}
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string {
return fmt.Sprintf("%v", ee.message)
}
// ExitCode returns the exit code, fulfilling the interface required by
// `ExitCoder`
func (ee *ExitError) ExitCode() int {
return ee.exitCode
}
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
// given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice and calls OsExiter with the last exit code.
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" {
fmt.Fprintln(ErrWriter, err)
}
OsExiter(exitErr.ExitCode())
return
}
// unknown error exit with code 3
fmt.Fprintln(ErrWriter, err)
OsExiter(3)
}

87
pkg/errors/errors_test.go Normal file
View File

@ -0,0 +1,87 @@
/*
* MIT License
*
* Copyright (c) 2022 urfave/cli maintainers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package errors
import (
"os"
"reflect"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var (
wd, _ = os.Getwd()
lastExitCode = 0
fakeOsExiter = func(rc int) {
lastExitCode = rc
}
)
func expect(t *testing.T, a interface{}, b interface{}) {
_, fn, line, _ := runtime.Caller(1)
fn = strings.Replace(fn, wd+"/", "", -1)
require.Equalf(t, a, b, "(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
func TestHandleExitCoder_nil(t *testing.T) {
exitCode := 0
called := false
OsExiter = func(rc int) {
if !called {
exitCode = rc
called = true
}
}
defer func() { OsExiter = fakeOsExiter }()
HandleExitCoder(nil)
expect(t, exitCode, 0)
expect(t, called, false)
}
func TestHandleExitCoder_ExitCoder(t *testing.T) {
exitCode := 0
called := false
OsExiter = func(rc int) {
if !called {
exitCode = rc
called = true
}
}
defer func() { OsExiter = fakeOsExiter }()
HandleExitCoder(NewExitError("galactic perimeter breach", 9))
expect(t, exitCode, 9)
expect(t, called, true)
}