Skip to content

Commit

Permalink
Merge pull request #49 from doitintl/exit-code
Browse files Browse the repository at this point in the history
Add --exit-error (-e) flag
  • Loading branch information
stepanstipl committed Oct 23, 2020
2 parents 53a9b08 + a1e2d83 commit fc5e8de
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 4 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ $./kubent -h
Usage of ./kubent:
-c, --cluster enable Cluster collector (default true)
-d, --debug enable debug logging
-e, --exit-error exit with non-zero code when issues are found
-f, --filename strings manifests to check
--helm2 enable Helm v2 collector (default true)
--helm3 enable Helm v3 collector (default true)
Expand All @@ -87,10 +88,10 @@ Usage of ./kubent:

### Use in CI

`kubent` will return `0` exit code if the program succeeds, even if it finds
deprecated resources, and non-zero exit code if there is an error during
runtime. Because all info output goes to stderr, it's easy to check in shell if
any issues were found:
`kubent` will by default return `0` exit code if the program succeeds, even if
it finds deprecated resources, and non-zero exit code if there is an error
during runtime. Because all info output goes to stderr, it's easy to check in
shell if any issues were found:

```shell
test -z "$(kubent)" # if stdout output is empty, means no issuse were found
Expand All @@ -108,6 +109,9 @@ elif [ -n "${OUTPUT}" ]; then # check for empty stdout
fi
```

You can also use `--exit-error` (`-e`) flag, which will make kubent to exit
with non-zero return code (`200`) in case any issues are found.

Alternatively, use the json output and smth. like `jq` to check if the result is
empty:

Expand Down
14 changes: 14 additions & 0 deletions cmd/kubent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ var (
gitSha string = "dev"
)

const (
EXIT_CODE_SUCCESS = 0
EXIT_CODE_FAIL_GENERIC = 1
EXIT_CODE_FOUND_ISSUES = 200
)

func getCollectors(collectors []collector.Collector) []map[string]interface{} {
var inputs []map[string]interface{}
for _, c := range collectors {
Expand Down Expand Up @@ -70,6 +76,7 @@ func initCollectors(config *config.Config) []collector.Collector {
}

func main() {
exitCode := EXIT_CODE_FAIL_GENERIC

config, err := config.NewFromFlags()
if err != nil {
Expand Down Expand Up @@ -114,4 +121,11 @@ func main() {
if err != nil {
log.Fatal().Err(err).Msg("Failed to print results")
}

if config.ExitError && len(results) > 0 {
exitCode = EXIT_CODE_FOUND_ISSUES
} else {
exitCode = EXIT_CODE_SUCCESS
}
os.Exit(exitCode)
}
55 changes: 55 additions & 0 deletions cmd/kubent/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package main

import (
"errors"
"os"
"os/exec"
"strconv"
"testing"

"github.com/doitintl/kube-no-trouble/pkg/collector"
Expand Down Expand Up @@ -85,3 +88,55 @@ func TestStoreCollectorError(t *testing.T) {
t.Errorf("Failed to ignore collector with error")
}
}

func TestMainExitCodes(t *testing.T) {
testCases := []struct {
name string
args []string // file list
expected int // number of manifests
}{
{"success", []string{"-c=false", "--helm2=false", "--helm3=false"}, 0},
{"errorBadFlag", []string{"-c=not-boolean"}, 2},
{"successFound", []string{"-c=false", "--helm2=false", "--helm3=false", "-f=../../fixtures/deployment-v1beta1.yaml"}, 0},
{"exitErrorFlagNone", []string{"-c=false", "--helm2=false", "--helm3=false", "-e"}, 0},
{"exitErrorFlagFound", []string{"-c=false", "--helm2=false", "--helm3=false", "-e", "-f=../../fixtures/deployment-v1beta1.yaml"}, 200},
}

if os.Getenv("TEST_EXIT_CODE") == "1" {
tc, err := strconv.Atoi(os.Getenv("TEST_CASE"))
if err != nil {
t.Errorf("failed to determin the test case num (TEST_CASE env var): %v", err)
}

os.Args = []string{os.Args[0]}
os.Args = append(os.Args, testCases[tc].args...)
main()
return
}

for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var ee *exec.ExitError

cmd := exec.Command(os.Args[0], "-test.run=TestMainExitCodes")
cmd.Env = append(os.Environ(), "TEST_EXIT_CODE=1", "TEST_CASE="+strconv.Itoa(i))
err := cmd.Run()

if tc.expected == 0 && err != nil {
t.Fatalf("expected to succeed with exit code %d, failed with %v", tc.expected, err)
}

if tc.expected != 0 && err == nil {
t.Fatalf("expected to get exit code %d, succeeded with 0", tc.expected)
}

if tc.expected != 0 && err != nil {
if errors.As(err, &ee) && ee.ExitCode() != tc.expected {
t.Fatalf("expected to get exit code %d, failed with %v, exit code %d", tc.expected, err, ee.ExitCode())
} else if !errors.As(err, &ee) {
t.Fatalf("expected to get exit code %d, failed with %v", tc.expected, err)
}
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type Config struct {
Cluster bool
Debug bool
ExitError bool
Filenames []string
Helm2 bool
Helm3 bool
Expand All @@ -23,6 +24,7 @@ func NewFromFlags() (*Config, error) {
home := homedir.HomeDir()
flag.BoolVarP(&config.Cluster, "cluster", "c", true, "enable Cluster collector")
flag.BoolVarP(&config.Debug, "debug", "d", false, "enable debug logging")
flag.BoolVarP(&config.ExitError, "exit-error", "e", false, "exit with non-zero code when issues are found")
flag.BoolVar(&config.Helm2, "helm2", true, "enable Helm v2 collector")
flag.BoolVar(&config.Helm3, "helm3", true, "enable Helm v3 collector")
flag.StringSliceVarP(&config.Filenames, "filename", "f", []string{}, "manifests to check, use - for stdin")
Expand Down

0 comments on commit fc5e8de

Please sign in to comment.