diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ed4c5..ac876d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased +- Added new flag `--response-code` (`-R`) to check expected http status code of a request. ## [0.7.0] - 2022-04-19 diff --git a/README.md b/README.md index 526815f..425533a 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Flags: -u, --url string URL to test (default "http://localhost:80/") -s, --search-string string String to search for, if not provided do status check only -r, --redirect-ok Allow redirects + -R, --response-code strings check for http response code, if not provided do status check only -T, --timeout int Request timeout in seconds (default 15) -H, --header strings Additional header(s) to send in check request -C, --mtls-cert-file string Certificate file for mutual TLS auth in PEM format @@ -79,6 +80,18 @@ Use "http-check [command] --help" for more information about a command. http-check --url https://sensu.io --search-string Monitoring http-check OK: found "Monitoring" at https://sensu.io +http-check --url https://sensu.io/notfound --response-code 301 +http-check OK: HTTP Status 301 for https://sensu.io/notfound + +http-check --url https://sensu.io --response-code 200 +http-check OK: HTTP Status 200 for https://sensu.io + +http-check --url https://sensu.io/notfound --redirect-ok --response-code 301 +http-check CRITICAL: HTTP Status 200 for https://sensu.io/notfound. Expected [301] + +http-check --url https://sensu.io/notfound --redirect-ok --response-code 301,401,200 +http-check OK: HTTP Status 200 for https://sensu.io/oops + http-check --url https://sensu.io --search-string droids http-check CRITICAL: "droids" not found at https://sensu.io @@ -103,6 +116,7 @@ http-check OK: HTTP Status 200 for http://localhost:8000/health * When using `--redirect-ok` it affects both the string search and status checkfunctionality. - For a string search, if true, it searches for the string in the eventual destination. - For a status check, if false, receiving a redirect will return a `warning` status. If true, it will return an `ok` status. + - When the --response-code option is used in conjunction with --redirect-ok, --response-code will be evaluated for the status of the redirected destination. * Headers should be in the form of "Header-Name: Header value". ### http-perf diff --git a/cmd/http-check/main.go b/cmd/http-check/main.go index 345c422..fa5f812 100644 --- a/cmd/http-check/main.go +++ b/cmd/http-check/main.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net/http" "net/url" + "strconv" "strings" "time" @@ -23,6 +24,7 @@ type Config struct { sensu.PluginConfig URL string SearchString string + ResponseCode []string TrustedCAFile string InsecureSkipVerify bool RedirectOK bool @@ -62,6 +64,15 @@ var ( Usage: "String to search for, if not provided do status check only", Value: &plugin.SearchString, }, + { + Path: "response-code", + Env: "CHECK_RESPONSE_CODE", + Argument: "response-code", + Shorthand: "R", + Default: []string{}, + Usage: "check for http response code, if not provided do status check only", + Value: &plugin.ResponseCode, + }, { Path: "insecure-skip-verify", Env: "", @@ -145,6 +156,16 @@ func checkArgs(event *types.Event) (int, error) { } } } + + if len(plugin.ResponseCode) > 0 { + for _, code := range plugin.ResponseCode { + _, err := strconv.Atoi(code) + if err != nil { + return sensu.CheckStateCritical, fmt.Errorf("--response-code %q value malformed, should be a valid http response code ", code) + } + } + } + if len(plugin.TrustedCAFile) > 0 { caCertPool, err := corev2.LoadCACerts(plugin.TrustedCAFile) if err != nil { @@ -228,6 +249,25 @@ func executeCheck(event *types.Event) (int, error) { return sensu.CheckStateCritical, nil } + // check for response code + if len(plugin.ResponseCode) > 0 { + + ExpectedCodes := make([]int, len(plugin.ResponseCode)) + for i, s := range plugin.ResponseCode { + ExpectedCodes[i], _ = strconv.Atoi(s) + } + + found := contains(ExpectedCodes, resp.StatusCode) + + if found { + fmt.Printf("%s OK: HTTP Status %v for %s\n", plugin.PluginConfig.Name, resp.StatusCode, resp.Request.URL) + return sensu.CheckStateOK, nil + } else { + fmt.Printf("%s CRITICAL: HTTP Status %v for %s. Expected %s\n", plugin.PluginConfig.Name, resp.StatusCode, plugin.URL, plugin.ResponseCode) + return sensu.CheckStateCritical, nil + } + } + switch { case resp.StatusCode >= http.StatusBadRequest: fmt.Printf("%s CRITICAL: HTTP Status %v for %s\n", plugin.PluginConfig.Name, resp.StatusCode, plugin.URL) @@ -255,3 +295,12 @@ func executeCheck(event *types.Event) (int, error) { return sensu.CheckStateOK, nil } } + +func contains(s []int, val int) bool { + for _, v := range s { + if v == val { + return true + } + } + return false +} diff --git a/cmd/http-check/main_test.go b/cmd/http-check/main_test.go index 8a53f8d..ac3bd52 100644 --- a/cmd/http-check/main_test.go +++ b/cmd/http-check/main_test.go @@ -6,8 +6,8 @@ import ( "net/url" "testing" - "github.com/sensu/sensu-plugin-sdk/sensu" corev2 "github.com/sensu/sensu-go/api/core/v2" + "github.com/sensu/sensu-plugin-sdk/sensu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,12 +50,18 @@ func TestExecuteCheck(t *testing.T) { returnStatus int httpStatus int allowRedirect bool + responseCode []string }{ - {sensu.CheckStateOK, http.StatusOK, false}, - {sensu.CheckStateOK, http.StatusOK, true}, - {sensu.CheckStateWarning, http.StatusMovedPermanently, false}, - {sensu.CheckStateCritical, http.StatusBadRequest, false}, - {sensu.CheckStateCritical, http.StatusInternalServerError, false}, + {sensu.CheckStateOK, http.StatusOK, false, nil}, + {sensu.CheckStateOK, http.StatusOK, true, nil}, + {sensu.CheckStateCritical, http.StatusNotFound, true, []string{"301"}}, + {sensu.CheckStateOK, http.StatusMovedPermanently, false, []string{"301"}}, + {sensu.CheckStateOK, http.StatusOK, false, []string{"200"}}, + {sensu.CheckStateOK, http.StatusNotFound, false, []string{"200", "404"}}, + {sensu.CheckStateOK, http.StatusCreated, false, []string{"200", "201"}}, + {sensu.CheckStateWarning, http.StatusMovedPermanently, false, nil}, + {sensu.CheckStateCritical, http.StatusBadRequest, false, nil}, + {sensu.CheckStateCritical, http.StatusInternalServerError, false, nil}, } for _, tc := range testCasesStatus { @@ -77,6 +83,7 @@ func TestExecuteCheck(t *testing.T) { plugin.URL = test.URL plugin.SearchString = "" plugin.RedirectOK = tc.allowRedirect + plugin.ResponseCode = tc.responseCode status, err := executeCheck(event) assert.NoError(err) assert.Equal(tc.returnStatus, status)