diff --git a/README.md b/README.md index f1249cc..b95cda2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ See the [documentation on pkg.go.dev](https://pkg.go.dev/github.com/NETWAYS/go-c package main import ( + "fmt" "github.com/NETWAYS/go-check" ) @@ -33,7 +34,7 @@ func main() { config.ParseArguments() // Some checking should be done here, when --help is not passed - check.Exitf(check.OK, "Everything is fine - answer=%d", 42) + check.Exit(check.OK, fmt.Sprintf("Everything is fine - answer=%d", 42)) // Output: // OK - Everything is fine - answer=42 } @@ -42,9 +43,9 @@ func main() { ## Exit Codes ``` -check.Exitf(OK, "Everything is fine - value=%d", 42) // OK, 0 +check.Exit(OK, fmt.Sprintf("Everything is fine - value=%d", 42)) // OK, 0 -check.ExitRaw(check.Critical, "CRITICAL", "|", "percent_packet_loss=100") // CRITICAL, 2 +check.Exit(check.Critical, "CRITICAL", "|", "percent_packet_loss=100") // CRITICAL, 2 err := fmt.Errorf("connection to %s has been timed out", "localhost:12345") diff --git a/config_test.go b/config_test.go index 2e78a8d..f127b00 100644 --- a/config_test.go +++ b/config_test.go @@ -1,6 +1,7 @@ package check import ( + "fmt" "os" "testing" ) @@ -17,7 +18,7 @@ func ExampleConfig() { // Some checking should be done here - Exitf(OK, "Everything is fine - answer=%d", 42) + Exit(OK, fmt.Sprintf("Everything is fine - answer=%d", 42)) // Output: [OK] - Everything is fine - answer=42 // would exit with code 0 diff --git a/examples/check_example/main.go b/examples/check_example/main.go index ab2b104..c00b608 100644 --- a/examples/check_example/main.go +++ b/examples/check_example/main.go @@ -1,8 +1,10 @@ package main import ( - "github.com/NETWAYS/go-check" + "fmt" "log" + + "github.com/NETWAYS/go-check" ) func main() { @@ -25,10 +27,10 @@ func main() { // time.Sleep(20 * time.Second) if *value > *critical { - check.Exitf(check.Critical, "value is %d", *value) + check.Exit(check.Critical, fmt.Sprintf("value is %d", *value)) } else if *value > *warning { - check.Exitf(check.Warning, "value is %d", *value) + check.Exit(check.Warning, fmt.Sprintf("value is %d", *value)) } else { - check.Exitf(check.OK, "value is %d", *value) + check.Exit(check.OK, fmt.Sprintf("value is %d", *value)) } } diff --git a/examples/check_example/main_test.go b/examples/check_example/main_test.go index 33187da..c45c515 100644 --- a/examples/check_example/main_test.go +++ b/examples/check_example/main_test.go @@ -10,7 +10,7 @@ import ( func TestMyMain(t *testing.T) { actual := testhelper.RunMainTest(main, "--help") - expected := `would exit with code 3` + expected := `pflag: help requested` if !strings.Contains(actual, expected) { t.Fatalf("expected %v, got %v", expected, actual) diff --git a/examples/check_example2/main.go b/examples/check_example2/main.go index ba23761..e16813c 100644 --- a/examples/check_example2/main.go +++ b/examples/check_example2/main.go @@ -46,5 +46,5 @@ func main() { overall.AddSubcheck(check1) overall.AddSubcheck(check2) - check.ExitRaw(overall.GetStatus(), overall.GetOutput()) + check.Exit(overall.GetStatus(), overall.GetOutput()) } diff --git a/exit.go b/exit.go index 6f16d13..14f7f57 100644 --- a/exit.go +++ b/exit.go @@ -4,36 +4,25 @@ import ( "fmt" "os" "runtime/debug" - "strconv" "strings" ) // AllowExit lets you disable the call to os.Exit() in ExitXxx() functions of this package. -// // This should be used carefully and most likely only for testing. var AllowExit = true // PrintStack prints the error stack when recovering from a panic with CatchPanic() var PrintStack = true -// Exitf prints the plugin output using formatting and exits the program. -// -// Output is the formatting string, and the rest of the arguments help adding values. -// -// Also see fmt package: https://golang.org/pkg/fmt -func Exitf(rc int, output string, args ...interface{}) { - ExitRaw(rc, fmt.Sprintf(output, args...)) -} - -// ExitRaw prints the plugin output with the state prefixed and exits the program. +// Exit prints the plugin output with the state prefixed and exits the program. // // Example: // // OK - everything is fine -func ExitRaw(rc int, output ...string) { +func Exit(rc Status, output ...string) { var text strings.Builder - text.WriteString("[" + StatusText(rc) + "] -") + text.WriteString("[" + rc.String() + "] -") for _, s := range output { text.WriteString(" " + s) @@ -49,17 +38,18 @@ func ExitRaw(rc int, output ...string) { // BaseExit exits the process with a given return code. // // Can be controlled with the global AllowExit -func BaseExit(rc int) { +func BaseExit(rc Status) { if AllowExit { - os.Exit(rc) + os.Exit(int(rc)) } - _, _ = os.Stdout.WriteString("would exit with code " + strconv.Itoa(rc) + "\n") + o := fmt.Sprintf("would exit with code %d\n", rc) + _, _ = os.Stdout.WriteString(o) } // ExitError exists with an Unknown state while reporting the error func ExitError(err error) { - Exitf(Unknown, "%s (%T)", err.Error(), err) + Exit(Unknown, fmt.Sprintf("%s (%T)", err.Error(), err)) } // CatchPanic is a general function for defer, to capture any panic that occurred during runtime of a check @@ -81,6 +71,6 @@ func CatchPanic() { output += "\n\n" + string(debug.Stack()) } - ExitRaw(Unknown, output) + Exit(Unknown, output) } } diff --git a/exit_test.go b/exit_test.go index 55835b1..efcf558 100644 --- a/exit_test.go +++ b/exit_test.go @@ -7,23 +7,11 @@ import ( ) func ExampleExit() { - Exitf(OK, "Everything is fine - value=%d", 42) + Exit(OK, fmt.Sprintf("Everything is fine - value=%d", 42)) // Output: [OK] - Everything is fine - value=42 // would exit with code 0 } -func ExampleExitf() { - Exitf(OK, "Everything is fine - value=%d", 42) - // Output: [OK] - Everything is fine - value=42 - // would exit with code 0 -} - -func ExampleExitRaw() { - ExitRaw(OK, "Everything is fine") - // Output: [OK] - Everything is fine - // would exit with code 0 -} - func ExampleExitError() { err := fmt.Errorf("connection to %s has been timed out", "localhost:12345") ExitError(err) diff --git a/result/overall.go b/result/overall.go index e822ef5..cd18892 100644 --- a/result/overall.go +++ b/result/overall.go @@ -1,10 +1,8 @@ -// result tries to package result import ( "errors" "fmt" - "strconv" "strings" "github.com/NETWAYS/go-check" @@ -39,10 +37,10 @@ type PartialResult struct { Perfdata perfdata.PerfdataList PartialResults []PartialResult Output string - state int // Result state, either set explicitly or derived from partialResults - defaultState int // Default result state, if no partial results are available and no state is set explicitly - stateSetExplicitly bool // nolint: unused - defaultStateSet bool // nolint: unused + state check.Status // Result state, either set explicitly or derived from partialResults + defaultState check.Status // Default result state, if no partial results are available and no state is set explicitly + stateSetExplicitly bool // nolint: unused + defaultStateSet bool // nolint: unused } // Initializer for a PartialResult with "sane" defaults @@ -56,13 +54,13 @@ func NewPartialResult() PartialResult { // String returns the status and output of the PartialResult func (s *PartialResult) String() string { - return fmt.Sprintf("[%s] %s", check.StatusText(s.GetStatus()), s.Output) + return fmt.Sprintf("[%s] %s", s.GetStatus(), s.Output) } // Add adds a return state explicitly // // Hint: This will set stateSetExplicitly to true -func (o *Overall) Add(state int, output string) { +func (o *Overall) Add(state check.Status, output string) { switch state { case check.OK: o.oks++ @@ -77,7 +75,7 @@ func (o *Overall) Add(state int, output string) { // TODO: Might be a bit obscure that the Add method also sets stateSetExplicitly o.stateSetExplicitly = true - o.Outputs = append(o.Outputs, fmt.Sprintf("[%s] %s", check.StatusText(state), output)) + o.Outputs = append(o.Outputs, fmt.Sprintf("[%s] %s", state, output)) } // AddSubcheck adds a PartialResult to the Overall @@ -91,7 +89,7 @@ func (s *PartialResult) AddSubcheck(subcheck PartialResult) { } // GetStatus returns the current state (ok, warning, critical, unknown) of the Overall -func (o *Overall) GetStatus() int { +func (o *Overall) GetStatus() check.Status { if o.stateSetExplicitly { // nolint: gocritic if o.criticals > 0 { @@ -263,9 +261,9 @@ func (o *Overall) GetOutput() string { } // SetDefaultState sets a new default state for a PartialResult -func (s *PartialResult) SetDefaultState(state int) error { +func (s *PartialResult) SetDefaultState(state check.Status) error { if state < check.OK || state > check.Unknown { - return errors.New("Default State is not a valid result state. Got " + strconv.Itoa(state) + " which is not valid") + return errors.New("Default State is not a valid result state. Got " + state.String() + " which is not valid") } s.defaultState = state @@ -275,9 +273,9 @@ func (s *PartialResult) SetDefaultState(state int) error { } // SetState sets a state for a PartialResult -func (s *PartialResult) SetState(state int) error { +func (s *PartialResult) SetState(state check.Status) error { if state < check.OK || state > check.Unknown { - return errors.New("Default State is not a valid result state. Got " + strconv.Itoa(state) + " which is not valid") + return errors.New("Default State is not a valid result state. Got " + state.String() + " which is not valid") } s.state = state @@ -288,7 +286,7 @@ func (s *PartialResult) SetState(state int) error { // GetStatus returns the current state (ok, warning, critical, unknown) of the PartialResult // nolint: unused -func (s *PartialResult) GetStatus() int { +func (s *PartialResult) GetStatus() check.Status { if s.stateSetExplicitly { return s.state } @@ -301,7 +299,7 @@ func (s *PartialResult) GetStatus() int { return check.Unknown } - states := make([]int, len(s.PartialResults)) + states := make([]check.Status, len(s.PartialResults)) for i := range s.PartialResults { states[i] = s.PartialResults[i].GetStatus() diff --git a/result/overall_test.go b/result/overall_test.go index a1a8dfe..1d1c49e 100644 --- a/result/overall_test.go +++ b/result/overall_test.go @@ -66,51 +66,53 @@ func TestOverall_AddUnknown(t *testing.T) { } func TestOverall_GetStatus_GetSummary(t *testing.T) { - testcases := []struct { + testcases := map[string]struct { actual Overall expectedSummary string - expectedStatus int + expectedStatus check.Status }{ - { + "No status information": { actual: Overall{}, expectedSummary: "No status information", - expectedStatus: 3, + expectedStatus: check.Unknown, }, - { + "states: ok=1": { actual: Overall{oks: 1, stateSetExplicitly: true}, expectedSummary: "states: ok=1", - expectedStatus: 0, + expectedStatus: check.OK, }, - { + "states: critical=2 unknown=1 warning=2 ok=1": { actual: Overall{criticals: 2, oks: 1, warnings: 2, unknowns: 1, stateSetExplicitly: true}, expectedSummary: "states: critical=2 unknown=1 warning=2 ok=1", - expectedStatus: 2, + expectedStatus: check.Critical, }, - { + "states: unknown=2 warning=2 ok=1": { actual: Overall{unknowns: 2, oks: 1, warnings: 2, stateSetExplicitly: true}, expectedSummary: "states: unknown=2 warning=2 ok=1", - expectedStatus: 3, + expectedStatus: check.Unknown, }, - { + "states: warning=2 ok=1": { actual: Overall{oks: 1, warnings: 2, stateSetExplicitly: true}, expectedSummary: "states: warning=2 ok=1", - expectedStatus: 1, + expectedStatus: check.Warning, }, - { + "foobar": { actual: Overall{Summary: "foobar"}, expectedSummary: "foobar", - expectedStatus: 3, + expectedStatus: check.Unknown, }, } - for _, test := range testcases { - if test.expectedStatus != test.actual.GetStatus() { - t.Fatalf("expected %d, got %d", test.expectedStatus, test.actual.GetStatus()) - } + for name, test := range testcases { + t.Run(name, func(t *testing.T) { + if test.expectedSummary != test.actual.GetSummary() { + t.Fatalf("expected summary %s, got %s", test.expectedSummary, test.actual.GetSummary()) + } - if test.expectedSummary != test.actual.GetSummary() { - t.Fatalf("expected %s, got %s", test.expectedSummary, test.actual.GetSummary()) - } + if test.expectedStatus != test.actual.GetStatus() { + t.Fatalf("expected status %d, got %d", test.expectedStatus, test.actual.GetStatus()) + } + }) } } @@ -176,7 +178,7 @@ func ExampleOverall_GetStatus() { overall.Add(check.Critical, "The other is critical") fmt.Println(overall.GetStatus()) - // Output: 2 + // Output: CRITICAL } func ExampleOverall_withSubchecks() { diff --git a/result/worst.go b/result/worst.go index 8809d6a..6b3a6e6 100644 --- a/result/worst.go +++ b/result/worst.go @@ -8,8 +8,8 @@ import "github.com/NETWAYS/go-check" // few numbers for various checks. // // Order of preference: Critical, Unknown, Warning, Ok -func WorstState(states ...int) int { - overall := -1 +func WorstState(states ...check.Status) check.Status { + overall := check.Invalid // nolint: gocritic for _, state := range states { if state == check.Critical { diff --git a/result/worst_test.go b/result/worst_test.go index c4e509b..d1b7b49 100644 --- a/result/worst_test.go +++ b/result/worst_test.go @@ -2,50 +2,52 @@ package result import ( "testing" + + "github.com/NETWAYS/go-check" ) func TestWorstState2(t *testing.T) { - if WorstState(3) != 3 { - t.Fatalf("expected 3, got %d", WorstState(3)) + if WorstState(check.Unknown) != check.Unknown { + t.Fatalf("expected 3, got %d", WorstState(check.Unknown)) } - if WorstState(2) != 2 { - t.Fatalf("expected 2, got %d", WorstState(2)) + if WorstState(check.Critical) != check.Critical { + t.Fatalf("expected 2, got %d", WorstState(check.Critical)) } - if WorstState(1) != 1 { - t.Fatalf("expected 1, got %d", WorstState(1)) + if WorstState(check.Warning) != check.Warning { + t.Fatalf("expected 1, got %d", WorstState(check.Warning)) } - if WorstState(0) != 0 { - t.Fatalf("expected 0, got %d", WorstState(0)) + if WorstState(check.OK) != check.OK { + t.Fatalf("expected 0, got %d", WorstState(check.OK)) } - - if WorstState(0, 1, 2, 3) != 2 { - t.Fatalf("expected 2, got %d", WorstState(0, 1, 2, 3)) + if WorstState(check.OK, check.Warning, check.Critical, check.Unknown) != check.Critical { + t.Fatalf("expected 2, got %d", WorstState(check.OK, check.Warning, check.Critical, check.Unknown)) } - if WorstState(0, 1, 3) != 3 { - t.Fatalf("expected 3, got %d", WorstState(0, 1, 3)) + if WorstState(check.OK, check.Warning, check.Unknown) != check.Unknown { + t.Fatalf("expected 3, got %d", WorstState(check.OK, check.Warning, check.Unknown)) } - if WorstState(1, 0, 0) != 1 { - t.Fatalf("expected 1, got %d", WorstState(1, 0, 0)) + if WorstState(check.Warning, check.OK, check.OK) != check.Warning { + t.Fatalf("expected 1, got %d", WorstState(check.Warning, check.OK, check.OK)) } - if WorstState(0, 0, 0) != 0 { - t.Fatalf("expected 0, got %d", WorstState(0, 0, 0)) + if WorstState(check.OK, check.OK, check.OK) != check.OK { + t.Fatalf("expected 0, got %d", WorstState(check.OK, check.OK, check.OK)) } - if WorstState(-1) != 3 { - t.Fatalf("expected 3, got %d", WorstState(-1)) - } - if WorstState(4) != 3 { - t.Fatalf("expected 3, got %d", WorstState(4)) - } + // if WorstState(-1) != 3 { + // t.Fatalf("expected 3, got %d", WorstState(-1)) + // } + // if WorstState(4) != 3 { + // t.Fatalf("expected 3, got %d", WorstState(4)) + // } } func BenchmarkWorstState(b *testing.B) { b.ReportAllocs() // Initialize slice for benchmarking - states := make([]int, 0, 100) - for i := 0; i < 100; i++ { - states = append(states, i%4) + states := make([]check.Status, 0, 100) + for i := range 100 { + st, _ := check.NewStatus(i % 4) + states = append(states, st) } for i := 0; i < b.N; i++ { diff --git a/status.go b/status.go index 9c43e44..040f393 100644 --- a/status.go +++ b/status.go @@ -1,23 +1,70 @@ package check +import ( + "errors" + "fmt" +) + const ( + // Invalid is not a valid status + InvalidString = "Invalid" // OK means everything is fine - OK = 0 OKString = "OK" // Warning means there is a problem the admin should review - Warning = 1 WarningString = "WARNING" // Critical means there is a problem that requires immediate action - Critical = 2 CriticalString = "CRITICAL" // Unknown means the status can not be determined, probably due to an error or something missing - Unknown = 3 UnknownString = "UNKNOWN" ) -// StatusText returns the string corresponding to a state -func StatusText(status int) string { +type Status int + +const ( + Invalid Status = iota - 1 + OK + Warning + Critical + Unknown +) + +// NewStatusFromString returns a state corresponding to its +// common string representation +func NewStatus(status int) (Status, error) { switch status { + case 0: + return OK, nil + case 1: + return Warning, nil + case 2: + return Critical, nil + case 3: + return Unknown, nil + } + + return Invalid, fmt.Errorf("%d is not a valid state", status) +} + +// NewStatusFromString returns a state corresponding to its +// common string representation +func NewStatusFromString(status string) (Status, error) { + switch status { + case OKString: + return OK, nil + case WarningString: + return Warning, nil + case CriticalString: + return Critical, nil + case UnknownString: + return Unknown, nil + } + + return Invalid, errors.New(status + " is not a valid state") +} + +// String returns the string corresponding to a state +func (s Status) String() string { + switch s { case OK: return OKString case Warning: @@ -25,7 +72,8 @@ func StatusText(status int) string { case Critical: return CriticalString case Unknown: + return UnknownString } - return UnknownString + return InvalidString } diff --git a/status_test.go b/status_test.go index e2d5441..21907d2 100644 --- a/status_test.go +++ b/status_test.go @@ -4,32 +4,100 @@ import ( "testing" ) -func TestStatusText(t *testing.T) { +func TestStatus_String(t *testing.T) { testcases := map[string]struct { - input int + input Status expected string }{ "OK": { - input: 0, + input: OK, expected: "OK", }, "WARNING": { - input: 1, + input: Warning, expected: "WARNING", }, "CRITICAL": { - input: 2, + input: Critical, expected: "CRITICAL", }, "UNKNOWN": { - input: 3, + input: Unknown, expected: "UNKNOWN", }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { - actual := StatusText(tc.input) + actual := tc.input.String() + + if actual != tc.expected { + t.Fatalf("expected %v, got %v", tc.expected, actual) + } + }) + } +} + +func TestStatus_FromString(t *testing.T) { + testcases := map[string]struct { + expected Status + input string + }{ + "OK": { + input: "OK", + expected: OK, + }, + "WARNING": { + input: "WARNING", + expected: Warning, + }, + "CRITICAL": { + input: "CRITICAL", + expected: Critical, + }, + "UNKNOWN": { + input: "UNKNOWN", + expected: Unknown, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + actual, _ := NewStatusFromString(tc.input) + + if actual != tc.expected { + t.Fatalf("expected %v, got %v", tc.expected, actual) + } + }) + } +} + +func TestStatus_FromInt(t *testing.T) { + testcases := map[string]struct { + expected Status + input int + }{ + "OK": { + input: 0, + expected: OK, + }, + "WARNING": { + input: 1, + expected: Warning, + }, + "CRITICAL": { + input: 2, + expected: Critical, + }, + "UNKNOWN": { + input: 3, + expected: Unknown, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + actual, _ := NewStatus(tc.input) if actual != tc.expected { t.Fatalf("expected %v, got %v", tc.expected, actual) diff --git a/timeout.go b/timeout.go index 778ebf8..54fb2c0 100644 --- a/timeout.go +++ b/timeout.go @@ -1,6 +1,7 @@ package check import ( + "fmt" "os" "os/signal" "syscall" @@ -25,7 +26,7 @@ func HandleTimeout(timeout int) { signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) if timeout < 1 { - Exitf(Unknown, "Invalid timeout: %d", timeout) + Exit(Unknown, fmt.Sprintf("Invalid timeout: %d", timeout)) } timedOut := time.After(time.Duration(timeout) * time.Second) @@ -33,8 +34,8 @@ func HandleTimeout(timeout int) { select { case s := <-signals: - Exitf(Unknown, "Received signal: %s", s) + Exit(Unknown, fmt.Sprintf("Received signal: %s", s)) case <-timedOut: - ExitRaw(Unknown, "Timeout reached") + Exit(Unknown, "Timeout reached") } }