From dc983c03bd3c83d722fbd77d760b9777dcd34160 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Wed, 17 Dec 2025 15:12:41 +0100 Subject: [PATCH 01/11] update to go 1.25 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 64727b7..116aa5e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/consol-monitoring/check_prometheus -go 1.19 +go 1.25 replace internal/helper => ./internal/helper From 7854afced8e074bed77cc30e694ebe557f3a2270 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Wed, 17 Dec 2025 15:47:02 +0100 Subject: [PATCH 02/11] fix module paths, now uses the github.com/consol-monitoring/check_prometheus/ paths instead --- go.mod | 14 ++++---- go.sum | 61 ++++++++++++++++++++++++---------- internal/helper/go.mod | 3 -- internal/mode/go.mod | 3 -- internal/mode/ping.go | 3 +- internal/mode/query.go | 2 +- internal/mode/targetsHealth.go | 2 +- main.go | 4 +-- 8 files changed, 55 insertions(+), 37 deletions(-) delete mode 100644 internal/helper/go.mod delete mode 100644 internal/mode/go.mod diff --git a/go.mod b/go.mod index 116aa5e..f01bc06 100644 --- a/go.mod +++ b/go.mod @@ -2,23 +2,21 @@ module github.com/consol-monitoring/check_prometheus go 1.25 -replace internal/helper => ./internal/helper - -replace internal/mode => ./internal/mode - require ( github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478 + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/common v0.67.4 github.com/urfave/cli v1.22.14 - internal/helper v0.0.0-00010101000000-000000000000 - internal/mode v0.0.0-00010101000000-000000000000 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/go.sum b/go.sum index 443cc8d..8050767 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,53 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478 h1:1Gkwxgpji7m40MWYhS2ur/9nNQJTL9SO8hMBIFuzLPs= github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478/go.mod h1:NVETti6i+ZU5nH7slbTo6Muugvml+0heXX1oswt1FHE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -37,18 +56,26 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/helper/go.mod b/internal/helper/go.mod deleted file mode 100644 index f7981f2..0000000 --- a/internal/helper/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module helper - -go 1.19 diff --git a/internal/mode/go.mod b/internal/mode/go.mod deleted file mode 100644 index 8072c95..0000000 --- a/internal/mode/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module mode - -go 1.19 diff --git a/internal/mode/ping.go b/internal/mode/ping.go index 314e6db..bc99b54 100644 --- a/internal/mode/ping.go +++ b/internal/mode/ping.go @@ -6,8 +6,7 @@ import ( "fmt" "time" - "internal/helper" - + "github.com/consol-monitoring/check_prometheus/internal/helper" "github.com/consol-monitoring/check_x" "github.com/prometheus/common/model" ) diff --git a/internal/mode/query.go b/internal/mode/query.go index aedd369..8c70791 100644 --- a/internal/mode/query.go +++ b/internal/mode/query.go @@ -9,7 +9,7 @@ import ( "text/template" "time" - "internal/helper" + "github.com/consol-monitoring/check_prometheus/internal/helper" "github.com/consol-monitoring/check_x" "github.com/prometheus/common/model" diff --git a/internal/mode/targetsHealth.go b/internal/mode/targetsHealth.go index 25b0c7f..86f3b7e 100644 --- a/internal/mode/targetsHealth.go +++ b/internal/mode/targetsHealth.go @@ -7,7 +7,7 @@ import ( "path" "time" - "internal/helper" + "github.com/consol-monitoring/check_prometheus/internal/helper" "github.com/consol-monitoring/check_x" ) diff --git a/main.go b/main.go index 27129de..55d5cdc 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,8 @@ import ( "os" "time" - "internal/helper" - "internal/mode" + "github.com/consol-monitoring/check_prometheus/internal/helper" + "github.com/consol-monitoring/check_prometheus/internal/mode" "github.com/consol-monitoring/check_x" "github.com/urfave/cli" From 7ad476ee66103bcb0a00773b9c7c7e9c1b1033a0 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Wed, 17 Dec 2025 16:10:05 +0100 Subject: [PATCH 03/11] add linting, fix linter issues --- .golangci.yml | 24 ++++++++++++++++++++++++ internal/helper/prometheus.go | 6 +++--- internal/mode/ping.go | 3 ++- internal/mode/query.go | 28 +++++++++++++++++----------- internal/mode/targetsHealth.go | 12 +++++++----- 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..67fe67c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,24 @@ +version: "2" +linters: + default: standard + settings: + gocritic: + enabled-tags: + - performance + - style + - experimental + gomoddirectives: + replace-local: true + lll: + line-length: 200 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling +formatters: + enable: + - gofmt + - goimports diff --git a/internal/helper/prometheus.go b/internal/helper/prometheus.go index 4ea2986..59498ee 100644 --- a/internal/helper/prometheus.go +++ b/internal/helper/prometheus.go @@ -2,7 +2,7 @@ package helper import ( "fmt" - "io/ioutil" + "io" "net/http" "time" @@ -29,7 +29,7 @@ func DoAPIRequest(address string) ([]byte, error) { if err != nil { return nil, err } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func CheckTimeFreshness(timestamp time.Time) { if TimestampFreshness == 0 { return } - timeDiff := time.Now().Sub(timestamp) + timeDiff := time.Since(timestamp) if int(timeDiff.Seconds()) > TimestampFreshness { check_x.Exit(check_x.Unknown, fmt.Sprintf("One of the scraped data exceed the freshness by %ds", int(timeDiff.Seconds())-TimestampFreshness)) } diff --git a/internal/mode/ping.go b/internal/mode/ping.go index bc99b54..68d3927 100644 --- a/internal/mode/ping.go +++ b/internal/mode/ping.go @@ -39,7 +39,7 @@ func Ping(address string) (err error) { } vector := result.(model.Vector) if len(vector) != 1 { - return fmt.Errorf("The query '%s' did not return a vector with a single entry", query) + return fmt.Errorf("the query '%s' did not return a vector with a single entry", query) } sample := vector[0] helper.CheckTimestampFreshness(sample.Timestamp) @@ -53,5 +53,6 @@ func Ping(address string) (err error) { } check_x.NewPerformanceData("duration", endTime.Sub(startTime).Seconds()).Unit("s").Min(0) check_x.Exit(check_x.OK, fmt.Sprintf("Version: %s, Instance %s", dat.Metric.Version, dat.Metric.Instance)) + return err } diff --git a/internal/mode/query.go b/internal/mode/query.go index 8c70791..231ac7c 100644 --- a/internal/mode/query.go +++ b/internal/mode/query.go @@ -19,29 +19,29 @@ import ( func Query(address, query, warning, critical, alias, search, replace, emptyQueryMessage string, emptyQueryStatus check_x.State) (err error) { warn, err := check_x.NewThreshold(warning) if err != nil { - return + return err } crit, err := check_x.NewThreshold(critical) if err != nil { - return + return err } var re *regexp.Regexp if search != "" { re, err = regexp.Compile(search) if err != nil { - return + return err } } apiClient, err := helper.NewAPIClientV1(address) if err != nil { - return + return err } result, _, err := apiClient.Query(context.TODO(), query, time.Now()) if err != nil { - return + return err } switch result.Type() { @@ -64,7 +64,7 @@ func Query(address, query, warning, critical, alias, search, replace, emptyQuery states := check_x.States{} var output string if len(vector) == 0 && emptyQueryMessage != "" { - output = fmt.Sprintf("%s", emptyQueryMessage) + output = emptyQueryMessage } else if len(vector) == 0 { output = fmt.Sprintf("Query '%s' returned no data.", query) } @@ -79,6 +79,7 @@ func Query(address, query, warning, critical, alias, search, replace, emptyQuery states = append(states, check_x.Evaluator{Warning: warn, Critical: crit}.Evaluate(sampleValue)) output += expandAlias(alias, sample.Metric, sampleValue) } + return evalStates(states, output, query) case model.ValMatrix: matrix := result.(model.Matrix) @@ -89,31 +90,34 @@ func Query(address, query, warning, critical, alias, search, replace, emptyQuery states = append(states, check_x.Evaluator{Warning: warn, Critical: crit}.Evaluate(float64(value.Value))) } } + return evalStates(states, alias, query) default: - check_x.Exit(check_x.Unknown, fmt.Sprintf("The query did not return a suppoted type(scalar, vector, matrix), instead: '%s'. Query: '%s'", result.Type().String(), query)) + check_x.Exit(check_x.Unknown, fmt.Sprintf("The query did not return a supported type(scalar, vector, matrix), instead: '%s'. Query: '%s'", result.Type().String(), query)) + return nil } + return err } func expandAlias(alias string, labels model.Metric, value float64) string { - tmpl, err := template.New("Output").Parse(alias) + _, err := template.New("Output").Parse(alias) var output string if err != nil { output = alias } else { labelMap := make(map[string]string) for label, value := range labels { - var l string = fmt.Sprintf("%v", label) - var v string = fmt.Sprintf("%v", value) + var l = fmt.Sprintf("%v", label) + var v = fmt.Sprintf("%v", value) labelMap[l] = v } labelMap["xvalue"] = fmt.Sprintf("%v", value) var rendered bytes.Buffer - err = tmpl.Execute(&rendered, labelMap) output = rendered.String() } + return output } @@ -121,6 +125,7 @@ func replaceLabel(label string, re *regexp.Regexp, replace string) string { if re != nil { label = re.ReplaceAllString(label, replace) } + return label } @@ -134,5 +139,6 @@ func evalStates(states check_x.States, alias, query string) error { } else { check_x.Exit(*state, alias) } + return nil } diff --git a/internal/mode/targetsHealth.go b/internal/mode/targetsHealth.go index 86f3b7e..17596f5 100644 --- a/internal/mode/targetsHealth.go +++ b/internal/mode/targetsHealth.go @@ -50,6 +50,7 @@ func getTargets(address string) (*targets, error) { if err = json.Unmarshal(jsonBytes, &dat); err != nil { return nil, err } + return &dat, nil } @@ -57,20 +58,20 @@ func getTargets(address string) (*targets, error) { func TargetsHealth(address, label, warning, critical string) (err error) { warn, err := check_x.NewThreshold(warning) if err != nil { - return + return err } crit, err := check_x.NewThreshold(critical) if err != nil { - return + return err } targets, err := getTargets(address) if err != nil { - return + return err } if (*targets).Status != "success" { - return fmt.Errorf("The API target returnstatus was %s", (*targets).Status) + return fmt.Errorf("the API target returnstatus was %s", (*targets).Status) } msg := "" healthy := 0 @@ -101,5 +102,6 @@ func TargetsHealth(address, label, warning, critical string) (err error) { check_x.NewPerformanceData("targets", sumTargets).Min(0) state := check_x.Evaluator{Warning: warn, Critical: crit}.Evaluate(healthRate) check_x.LongExit(state, fmt.Sprintf("There are %d healthy and %d unhealthy targets", healthy, unhealthy), msg) - return + + return err } From 03d7c5fcf6656f70960860be060c077368bb8c25 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Thu, 18 Dec 2025 00:22:51 +0100 Subject: [PATCH 04/11] prepare for query encoding changes - update to urfave/cli/v3, supports multiple copies of the same argument - add insecure argument, for ignoring TLS errors - add cookie argument, for connecting with authentication cookie - add validators to the address and cookie - revise the prometheus client constructor to use cookies and custom TLS setting - add custom middleware function during http requests, useful for debugging and verbose output --- .golangci.yml | 5 +- .vscode/launch.json | 114 +++++++++ .vscode/settings.json | 3 + go.mod | 4 +- go.sum | 17 +- internal/helper/prometheus.go | 83 ++++++- internal/mode/ping.go | 3 +- internal/mode/query.go | 3 +- internal/mode/targetsHealth.go | 8 +- main.go | 431 ++++++++++++++++++++------------- 10 files changed, 470 insertions(+), 201 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.golangci.yml b/.golangci.yml index 67fe67c..42551bb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,8 +7,9 @@ linters: - performance - style - experimental - gomoddirectives: - replace-local: true + - security + - opinionated + - diagnostic lll: line-length: 200 exclusions: diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8f34996 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,114 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package 1", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "query", + "--help" + ] + }, + { + "name": "Launch Package 2", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "mode", + "query", + "--address", + "http://192.168.122.21/demosite/prometheus", + "-q", + "node_memory_Active_bytes", + ] + }, + { + "name": "Launch Package 3", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "mode", + "query", + "--address", + "http://192.168.122.21/demosite/prometheus", + "-q", + "node_memory_Active_bytes", + "--insecure" + ] + }, + { + "name": "Launch Package 4", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "mode", + "query", + "--cookie", + "thruk_auth=7ab687a17cdb58dedccc8dd920587ec9f2c4be9894287cfba7266b504b431afd_1", + "--cookie", + "thruk_message=warn_message~~There%20had%20been%204%20failed%20login%20attempts.%20%28Date%3A%2012%3A41%3A21%20-%20IP%3A%20192.168.178.20%20%28192.168.178.20%29", + "--address", + "http://192.168.122.21/demosite/prometheus", + "-q", + "node_memory_Active_bytes", + "--insecure" + ] + }, + { + "name": "Launch Package 5", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "mode", + "query", + "--cookie", + "thruk_auth=7ab687a17cdb58dedccc8dd920587ec9f2c4be9894287cfba7266b504b431afd_1", + "--address", + "http://192.168.122.21/demosite/prometheus", + "-q", + "node_memory_Active_bytes", + "--insecure" + ] + }, + { + "name": "Launch Package 6", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "mode", + "query", + "--address", + "http://localhost:9090/", + "-q", + "up", + "--insecure" + ] + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b31e1b8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "go.lintTool": "golangci-lint-v2" +} \ No newline at end of file diff --git a/go.mod b/go.mod index f01bc06..45f1b79 100644 --- a/go.mod +++ b/go.mod @@ -6,17 +6,15 @@ require ( github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/common v0.67.4 - github.com/urfave/cli v1.22.14 + github.com/urfave/cli/v3 v3.6.1 ) require ( - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/go.sum b/go.sum index 8050767..50c8b12 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,9 @@ -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478 h1:1Gkwxgpji7m40MWYhS2ur/9nNQJTL9SO8hMBIFuzLPs= github.com/consol-monitoring/check_x v0.0.0-20230423195421-be7cfdc8c478/go.mod h1:NVETti6i+ZU5nH7slbTo6Muugvml+0heXX1oswt1FHE= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -48,19 +44,12 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= +github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= @@ -76,7 +65,5 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/helper/prometheus.go b/internal/helper/prometheus.go index 59498ee..20640c5 100644 --- a/internal/helper/prometheus.go +++ b/internal/helper/prometheus.go @@ -1,9 +1,12 @@ package helper import ( + "crypto/tls" "fmt" "io" "net/http" + "net/http/cookiejar" + "net/url" "time" "github.com/consol-monitoring/check_x" @@ -12,33 +15,95 @@ import ( "github.com/prometheus/common/model" ) +// TimestampFreshness is the amount of second a result is treated as valid +var TimestampFreshness int + +// InsecureSkipVerify will skip TLS certificate verification when set to true. It will be used when constructing http Transport +var InsecureSkipVerify bool + +// Cookies parsed into []*http.Cookie +var Cookies []*http.Cookie + +type prometheusInterceptor struct { + next http.RoundTripper +} + +func (i *prometheusInterceptor) RoundTrip(req *http.Request) (*http.Response, error) { + // 1. You can log the body here to see exactly what is being sent + fmt.Printf("Sending %s request to %s\n", req.Method, req.URL.String()) + + // 2. Ensure the Content-Type is definitely set + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // 3. Fix potential Idempotency-Key issues by removing it if it's nil + // if val, ok := req.Header["Idempotency-Key"]; ok && val == nil { + // delete(req.Header, "Idempotency-Key") + // } + + return i.next.RoundTrip(req) +} + // NewAPIClientV1 will create an prometheus api client v1 -func NewAPIClientV1(address string) (v1.API, error) { - client, err := api.NewClient(api.Config{ - Address: address, +func NewAPIClientV1(address *url.URL) (v1.API, error) { + baseTransport := http.DefaultTransport.(*http.Transport).Clone() + baseTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: InsecureSkipVerify} + + interceptedTransport := &prometheusInterceptor{ + next: baseTransport, + } + + httpClient := &http.Client{ + Transport: interceptedTransport, + } + + // Initialize cookie jar only when Cookies are provided + if len(Cookies) > 0 { + jar, _ := cookiejar.New(nil) + httpClient.Jar = jar + httpClient.Jar.SetCookies(address, Cookies) + } + + prometheusClient, err := api.NewClient(api.Config{ + Address: address.String(), + Client: httpClient, }) + if err != nil { return nil, err } - return v1.NewAPI(client), nil + + return v1.NewAPI(prometheusClient), nil } // DoAPIRequest does the http handling for an api request -func DoAPIRequest(address string) ([]byte, error) { - resp, err := http.DefaultClient.Get(address) +func DoAPIRequest(url *url.URL) ([]byte, error) { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: InsecureSkipVerify} + + httpClient := &http.Client{ + Transport: transport, + } + + if len(Cookies) > 0 { + jar, _ := cookiejar.New(nil) + httpClient.Jar = jar + httpClient.Jar.SetCookies(url, Cookies) + } + + resp, err := httpClient.Get(url.String()) if err != nil { return nil, err } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } + return body, nil } -// TimestampFreshness is the amount of second a result is treated as valid -var TimestampFreshness int - // CheckTimestampFreshness tests if the data is still valid func CheckTimestampFreshness(timestamp model.Time) { CheckTimeFreshness(time.Unix(int64(timestamp), 0)) diff --git a/internal/mode/ping.go b/internal/mode/ping.go index 68d3927..0dc2587 100644 --- a/internal/mode/ping.go +++ b/internal/mode/ping.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "net/url" "time" "github.com/consol-monitoring/check_prometheus/internal/helper" @@ -25,7 +26,7 @@ type buildInfo struct { } // Ping will fetch build information from the prometheus server -func Ping(address string) (err error) { +func Ping(address *url.URL) (err error) { apiClient, err := helper.NewAPIClientV1(address) if err != nil { return diff --git a/internal/mode/query.go b/internal/mode/query.go index 231ac7c..3c96dd6 100644 --- a/internal/mode/query.go +++ b/internal/mode/query.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "net/url" "regexp" "strconv" "text/template" @@ -16,7 +17,7 @@ import ( ) // Query allows the user to test data in the prometheus server -func Query(address, query, warning, critical, alias, search, replace, emptyQueryMessage string, emptyQueryStatus check_x.State) (err error) { +func Query(address *url.URL, query, warning, critical, alias, search, replace, emptyQueryMessage string, emptyQueryStatus check_x.State) (err error) { warn, err := check_x.NewThreshold(warning) if err != nil { return err diff --git a/internal/mode/targetsHealth.go b/internal/mode/targetsHealth.go index 17596f5..7d6ab4b 100644 --- a/internal/mode/targetsHealth.go +++ b/internal/mode/targetsHealth.go @@ -36,13 +36,13 @@ type targets struct { } `json:"data"` } -func getTargets(address string) (*targets, error) { - u, err := url.Parse(address) +func getTargets(address *url.URL) (*targets, error) { + u, err := url.Parse(address.String()) if err != nil { return nil, err } u.Path = path.Join(u.Path, "/api/v1/targets") - jsonBytes, err := helper.DoAPIRequest(u.String()) + jsonBytes, err := helper.DoAPIRequest(u) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func getTargets(address string) (*targets, error) { } // TargetsHealth tests the health of the targets -func TargetsHealth(address, label, warning, critical string) (err error) { +func TargetsHealth(address *url.URL, label, warning, critical string) (err error) { warn, err := check_x.NewThreshold(warning) if err != nil { return err diff --git a/main.go b/main.go index 55d5cdc..360888e 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,24 @@ package main import ( + "context" + "fmt" + "net/http" + "net/url" "os" + "strings" "time" "github.com/consol-monitoring/check_prometheus/internal/helper" "github.com/consol-monitoring/check_prometheus/internal/mode" "github.com/consol-monitoring/check_x" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) var ( - address string - timeout int + address *url.URL + timeout int64 warning string critical string query string @@ -45,179 +50,273 @@ func getStatus(state string) check_x.State { } func main() { - app := cli.NewApp() - app.Name = "check_prometheus" - app.Usage = "Checks different prometheus stats as well the data itself" - app.Version = "0.0.3" - flagAddress := cli.StringFlag{ - Name: "address", - Usage: "Prometheus address: Protocol + IP + Port.", - Destination: &address, - Value: "http://localhost:9100", - } - flagWarning := cli.StringFlag{ - Name: "w", - Usage: "Warning value. Use nagios-plugin syntax here.", - Destination: &warning, - } - flagCritical := cli.StringFlag{ - Name: "c", - Usage: "Critical value. Use nagios-plugin syntax here.", - Destination: &critical, - } - flagQuery := cli.StringFlag{ - Name: "q", - Usage: "Query to be executed", - Destination: &query, - } - flagAlias := cli.StringFlag{ - Name: "a", - Usage: "Alias, will replace the query within the output, if set. You can use go text/template syntax to output label values (only for vector results).", - Destination: &alias, - } - flagEmptyQueryMessage := cli.StringFlag{ - Name: "eqm", - Usage: "Message if the query returns no data.", - Destination: &emptyQueryMessage, - } - flagEmptyQueryStatus := cli.StringFlag{ - Name: "eqs", - Usage: "Status if the query returns no data.", - Destination: &emptyQueryStatus, - } - flagLabel := cli.StringFlag{ - Name: "l", - Usage: "Prometheus-Label, which will be used for the performance data label. By default job and instance should be available.", - Destination: &label, - Value: mode.DefaultLabel, - } - app.Commands = []cli.Command{ - { - Name: "mode", - Aliases: []string{"m"}, - Usage: "check mode", - Subcommands: []cli.Command{ - { - Name: "ping", - Aliases: []string{"p"}, - Usage: "Returns the build informations", - Description: `This check requires that the prometheus server itself is listetd as target. Following query will be used: 'prometheus_build_info{job="prometheus"}'`, - Action: func(c *cli.Context) error { - startTimeout() - return mode.Ping(address) - }, - Flags: []cli.Flag{ - flagAddress, - }, - }, { - Name: "query", - Aliases: []string{"q"}, - Usage: "Checks collected data", - Description: `Your Promqlquery has to return a vector / scalar / matrix result. The warning and critical values are applied to every value. - Examples: - Vector: - check_prometheus mode query -q 'up' - --> OK - Query: 'up'|'up{instance="192.168.99.101:9245", job="iapetos"}'=1;;;; 'up{instance="0.0.0.0:9091", job="prometheus"}'=1;;;; - - Scalar: - check_prometheus mode query -q 'scalar(up{job="prometheus"})' - --> OK - OK - Query: 'scalar(up{job="prometheus"})' returned: '1'|'scalar'=1;;;; - - Matrix: - check_prometheus mode query -q 'http_requests_total{job="prometheus"}[5m]' - --> OK - Query: 'http_requests_total{job="prometheus"}[5m]' - - Search and Replace: - check_prometheus m query -q 'up' --search '.*job=\"(.*?)\".*' --replace '$1' - --> OK - Query: 'up'|'prometheus'=1;;;; 'iapetos'=0;;;; - - check_prometheus m q -q '{handler="prometheus",quantile="0.99",job="prometheus",__name__=~"http_.*bytes"}' --search '.*__name__=\"(.*?)\".*' --replace '$1' -a 'http_in_out' - --> OK - Alias: 'http_in_out'|'http_request_size_bytes'=296;;;; 'http_response_size_bytes'=5554;;;; - - Use Alias to generate output with label values: - Assumption that your query returns a label "hostname" and "details". - IMPORTANT: To be able to use the value in more advanced output formatting, we just add a label "value" with the current value to the list of labels. - If the specified Alias string cannot be processed by the text/template engine, the Alias string will be printed 1:1. - check_prometheus m q -a 'Hostname: {{.hostname}} - Details: {{.details}}' --search '.*' --replace 'error_state' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> Critical - Hostname: Server01 - Details: Error404|'error_state'=1;0;0;; - - Use Alias with an if/else clause and the use of xvalue: - If xvalue is 1, we output UP, else we output DOWN - check_prometheus m q --search '.*' --replace 'up' -q 'up{instance="SUPERHOST"}' -a 'Hostname: {{.hostname}} Is {{if eq .xvalue "1"}}UP{{else}}DOWN{{end}}.\n' -w 1: -c 1: - --> OK - Hostname: SUPERHOST Is UP.\n|'up'=1;1:;1:;; - - List all available labels to be used with Alias: - Just use -a '{{.}}' and the whole map with all labels will be printed. - check_prometheus m q -q 'up{instance="SUPERHOST"}' -a '{{.}}' - --> OK - map[__name__:up hostname:SUPERHOST instance:SUPERHOST job:snmp mib:RittalCMC xvalue:1]|'{__name__="up", hostname="SUPERHOST", instance="SUPERHOST", job="snmp", mib="RittalCMC"}'=1;;;; - - Use Different Message and Status code for queries that return no data. - If you have a query that only returns data in an error condition you can use this flags to return a custom message and status code. - check_prometheus m q -eqm 'All OK' -eqs 'OK' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> OK - All OK - Without -eqm, -eqs - check_prometheus m q -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> UNKNOWN - The given States do not contain an State - - `, - - Action: func(c *cli.Context) error { - startTimeout() - return mode.Query(address, query, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) - }, - Flags: []cli.Flag{ - flagAddress, - flagQuery, - flagAlias, - flagWarning, - flagCritical, - cli.StringFlag{ - Name: "search", - Usage: "If this variable is set, the given Golang regex will be used to search and replace the result with the 'replace' flag content. This will be appied on the perflabels.", - Destination: &search, + cmd := &cli.Command{ + Name: "check_prometheus", + Usage: "Checks different prometheus stats as well the data itself", + Version: "0.0.3", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "timeout", + Aliases: []string{"t"}, + Usage: "Seconds till check returns unknown, 0 to disable", + Value: 10, + Destination: &timeout, + }, + &cli.IntFlag{ + Name: "data-age", + Aliases: []string{"f"}, + Usage: "If the checked data is older then this in seconds, unknown will be returned. Set to 0 to disable.", + Value: 300, + Destination: &helper.TimestampFreshness, + }, + }, + Commands: []*cli.Command{ + { + Name: "mode", + Aliases: []string{"m"}, + Usage: "check mode", + Commands: []*cli.Command{ + { + Name: "ping", + Aliases: []string{"p"}, + HideHelp: false, + Usage: "Returns the build informations", + Description: `This check requires that the prometheus server itself is listed as target. Following query will be used: 'prometheus_build_info{job="prometheus"}'`, + Action: func(ctx context.Context, cmd *cli.Command) error { + startTimeout() + return mode.Ping(address) }, - cli.StringFlag{ - Name: "replace", - Usage: "See search flag. If the 'search' flag is empty this flag will be ignored.", - Destination: &replace, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err != nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, }, - flagEmptyQueryMessage, - flagEmptyQueryStatus, }, - }, { - Name: "targets_health", - Usage: "Returns the health of the targets", - Description: `The warning and critical thresholds are appied on the health_rate. The health_rate is calculted: sum(healthy) / sum(targets).`, - Action: func(c *cli.Context) error { - startTimeout() - return mode.TargetsHealth(address, label, warning, critical) + + { + Name: "query", + Aliases: []string{"q"}, + HideHelp: false, + Usage: "Checks collected data", + Description: `Your Promqlquery has to return a vector / scalar / matrix result. The warning and critical values are applied to every value. + Examples: + Vector: + check_prometheus mode query -q 'up' + --> OK - Query: 'up'|'up{instance="192.168.99.101:9245", job="iapetos"}'=1;;;; 'up{instance="0.0.0.0:9091", job="prometheus"}'=1;;;; + + Scalar: + check_prometheus mode query -q 'scalar(up{job="prometheus"})' + --> OK - OK - Query: 'scalar(up{job="prometheus"})' returned: '1'|'scalar'=1;;;; + + Matrix: + check_prometheus mode query -q 'http_requests_total{job="prometheus"}[5m]' + --> OK - Query: 'http_requests_total{job="prometheus"}[5m]' + + Search and Replace: + check_prometheus m query -q 'up' --search '.*job=\"(.*?)\".*' --replace '$1' + --> OK - Query: 'up'|'prometheus'=1;;;; 'iapetos'=0;;;; + + check_prometheus m q -q '{handler="prometheus",quantile="0.99",job="prometheus",__name__=~"http_.*bytes"}' --search '.*__name__=\"(.*?)\".*' --replace '$1' -a 'http_in_out' + --> OK - Alias: 'http_in_out'|'http_request_size_bytes'=296;;;; 'http_response_size_bytes'=5554;;;; + + Use Alias to generate output with label values: + Assumption that your query returns a label "hostname" and "details". + IMPORTANT: To be able to use the value in more advanced output formatting, we just add a label "value" with the current value to the list of labels. + If the specified Alias string cannot be processed by the text/template engine, the Alias string will be printed 1:1. + check_prometheus m q -a 'Hostname: {{.hostname}} - Details: {{.details}}' --search '.*' --replace 'error_state' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> Critical - Hostname: Server01 - Details: Error404|'error_state'=1;0;0;; + + Use Alias with an if/else clause and the use of xvalue: + If xvalue is 1, we output UP, else we output DOWN + check_prometheus m q --search '.*' --replace 'up' -q 'up{instance="SUPERHOST"}' -a 'Hostname: {{.hostname}} Is {{if eq .xvalue "1"}}UP{{else}}DOWN{{end}}.\n' -w 1: -c 1: + --> OK - Hostname: SUPERHOST Is UP.\n|'up'=1;1:;1:;; + + List all available labels to be used with Alias: + Just use -a '{{.}}' and the whole map with all labels will be printed. + check_prometheus m q -q 'up{instance="SUPERHOST"}' -a '{{.}}' + --> OK - map[__name__:up hostname:SUPERHOST instance:SUPERHOST job:snmp mib:RittalCMC xvalue:1]|'{__name__="up", hostname="SUPERHOST", instance="SUPERHOST", job="snmp", mib="RittalCMC"}'=1;;;; + + Use Different Message and Status code for queries that return no data. + If you have a query that only returns data in an error condition you can use this flags to return a custom message and status code. + check_prometheus m q -eqm 'All OK' -eqs 'OK' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> OK - All OK + Without -eqm, -eqs + check_prometheus m q -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> UNKNOWN - The given States do not contain an State + + `, + Action: func(c context.Context, cmd *cli.Command) error { + startTimeout() + return mode.Query(address, query, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err == nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, + &cli.StringFlag{ + Name: "q", + Usage: "Query to be executed", + Destination: &query, + }, + &cli.StringFlag{ + Name: "a", + Usage: "Alias, will replace the query within the output, if set. You can use go text/template syntax to output label values (only for vector results).", + Destination: &alias, + }, + &cli.StringFlag{ + Name: "w", + Usage: "Warning value. Use nagios-plugin syntax here.", + Destination: &warning, + }, + &cli.StringFlag{ + Name: "c", + Usage: "Critical value. Use nagios-plugin syntax here.", + Destination: &critical, + }, + &cli.StringFlag{ + Name: "search", + Usage: "If this variable is set, the given Golang regex will be used to search and replace the result with the 'replace' flag content. This will be appied on the perflabels.", + Destination: &search, + }, + &cli.StringFlag{ + Name: "replace", + Usage: "See search flag. If the 'search' flag is empty this flag will be ignored.", + Destination: &replace, + }, + &cli.BoolFlag{ + Name: "insecure, k", + Usage: "Skip TLS certificate verification (insecure)", + Destination: &helper.InsecureSkipVerify, + }, + &cli.StringFlag{ + Name: "cookie", + Usage: "Cookie to send during the api request, in form '=' ", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + cookieKey := value[:strings.IndexRune(value, '=')] + cookieValue := value[strings.IndexRune(value, '=')+1:] + cookie := &http.Cookie{ + Name: cookieKey, + Value: cookieValue, + Path: "/", + SameSite: http.SameSiteLaxMode, + MaxAge: 3600, + Expires: time.Now().Add(time.Hour), + } + helper.Cookies = append(helper.Cookies, cookie) + return nil + }, + Validator: func(value string) error { + strings.Count(value, "=") + if strings.Count(value, "=") != 1 { + return fmt.Errorf("there should be exactly one '=' in the cookie definition") + } + cookieKey := value[:strings.IndexRune(value, '=')] + cookieValue := value[strings.IndexRune(value, '=')+1:] + if cookieKey == "" { + return fmt.Errorf("cookie key cannot be empty") + } + if cookieValue == "" { + return fmt.Errorf("cookie value cannot be empty") + } + if len(cookieValue) > 4096 { + return fmt.Errorf("cookie value cannot be longer than 4096 characters") + } + + return nil + }, + }, + &cli.StringFlag{ + Name: "eqm", + Usage: "Message if the query returns no data.", + Destination: &emptyQueryMessage, + }, + &cli.StringFlag{ + Name: "eqs", + Usage: "Status if the query returns no data.", + Destination: &emptyQueryStatus, + }, + }, }, - Flags: []cli.Flag{ - flagAddress, - flagWarning, - flagCritical, - flagLabel, + + { + Name: "targets_health", + HideHelp: false, + Usage: "Returns the health of the targets", + Description: `The warning and critical thresholds are appied on the health_rate. The health_rate is calculted: sum(healthy) / sum(targets).`, + Action: func(c context.Context, cmd *cli.Command) error { + startTimeout() + return mode.TargetsHealth(address, label, warning, critical) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err != nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, + &cli.StringFlag{ + Name: "w", + Usage: "Warning value. Use nagios-plugin syntax here.", + Destination: &warning, + }, + &cli.StringFlag{ + Name: "c", + Usage: "Critical value. Use nagios-plugin syntax here.", + Destination: &critical, + }, + &cli.StringFlag{ + Name: "l", + Usage: "Prometheus-Label, which will be used for the performance data label. By default job and instance should be available.", + Destination: &label, + Value: mode.DefaultLabel, + }, + }, }, }, }, }, } - app.Flags = []cli.Flag{ - cli.IntFlag{ - Name: "t", - Usage: "Seconds till check returns unknown, 0 to disable", - Value: 10, - Destination: &timeout, - }, - cli.IntFlag{ - Name: "f", - Usage: "If the checked data is older then this in seconds, unknown will be returned. Set to 0 to disable.", - Value: 300, - Destination: &helper.TimestampFreshness, - }, - } - if err := app.Run(os.Args); err != nil { + if err := cmd.Run(context.Background(), os.Args); err != nil { check_x.ErrorExit(err) } } From cafb78269cda5ab4bc6cb4f68e8b6b055f5dd6e0 Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 18 Dec 2025 15:51:51 +0100 Subject: [PATCH 05/11] add --verbose flag --- .vscode/launch.json | 19 +++++++++++++++++++ internal/helper/prometheus.go | 10 ++++++++-- main.go | 9 +++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f34996..e49e45f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -110,5 +110,24 @@ "--insecure" ] }, + { + "name": "Launch Package 7", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "--verbose", + "mode", + "query", + "--address", + "http://localhost:9090/", + "-q", + "up", + "--insecure" + ] + }, ] } diff --git a/internal/helper/prometheus.go b/internal/helper/prometheus.go index 20640c5..db69ddc 100644 --- a/internal/helper/prometheus.go +++ b/internal/helper/prometheus.go @@ -24,13 +24,19 @@ var InsecureSkipVerify bool // Cookies parsed into []*http.Cookie var Cookies []*http.Cookie +// Verbose flag writes to here +var Verbose bool + type prometheusInterceptor struct { next http.RoundTripper } func (i *prometheusInterceptor) RoundTrip(req *http.Request) (*http.Response, error) { - // 1. You can log the body here to see exactly what is being sent - fmt.Printf("Sending %s request to %s\n", req.Method, req.URL.String()) + if Verbose { + fmt.Printf("Sending %s request to %s\n", req.Method, req.URL.String()) + fmt.Printf("Request:\n%+v\n", req) + fmt.Printf("Url:\n%+v\n", req.URL) + } // 2. Ensure the Content-Type is definitely set req.Header.Set("Content-Type", "application/x-www-form-urlencoded") diff --git a/main.go b/main.go index 360888e..0dab2c6 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,15 @@ func main() { Value: 300, Destination: &helper.TimestampFreshness, }, + &cli.BoolFlag{ + Name: "verbose", + Usage: "Turn the verbose mode on.", + Value: false, + Action: func(ctx context.Context, cmd *cli.Command, value bool) error { + helper.Verbose = value + return nil + }, + }, }, Commands: []*cli.Command{ { From dcd953a44c30fbbe6426b7eb0ddc95c983792fbb Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 18 Dec 2025 16:05:01 +0100 Subject: [PATCH 06/11] verbose mode, print request body and headers --- internal/helper/prometheus.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal/helper/prometheus.go b/internal/helper/prometheus.go index db69ddc..af0010e 100644 --- a/internal/helper/prometheus.go +++ b/internal/helper/prometheus.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "strings" "time" "github.com/consol-monitoring/check_x" @@ -36,6 +37,21 @@ func (i *prometheusInterceptor) RoundTrip(req *http.Request) (*http.Response, er fmt.Printf("Sending %s request to %s\n", req.Method, req.URL.String()) fmt.Printf("Request:\n%+v\n", req) fmt.Printf("Url:\n%+v\n", req.URL) + fmt.Printf("Header:\n%+v\n", req.Header) + + // Read and print the body content + if req.Body != nil { + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + fmt.Printf("Error reading body: %v\n", err) + } else { + fmt.Printf("Body:\n%s\n", string(bodyBytes)) + // Restore the body for further processing + req.Body = io.NopCloser(strings.NewReader(string(bodyBytes))) + } + } else { + fmt.Printf("Body is empty\n") + } } // 2. Ensure the Content-Type is definitely set From 6cba1702078a652cb32464ccc76038343a4632ca Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 18 Dec 2025 16:40:33 +0100 Subject: [PATCH 07/11] add--query-encoding option, supports base64 and url encoded queries --- .vscode/launch.json | 42 +++++++++++++++++++++++++++++ main.go | 65 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e49e45f..beab5dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -129,5 +129,47 @@ "--insecure" ] }, + { + "name": "Launch Package 8", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "--verbose", + "mode", + "query", + "--address", + "http://localhost:9090/", + "-q", + "aW5jcmVhc2UoYXBwbGljYXRpb25fYXVmdHJhZ19zdGF0dXNfY291bnR7ZW52aXJvbm1lbnQ9InByb2QiLHN1YnR5cGU9IjIwMjIiLHN0YXR1c0lkIT0iMyJ9WzI0aF0p", + "--query-encoding", + "base64", + "--insecure" + ] + }, + { + "name": "Launch Package 9", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "-t", + "99999", + "--verbose", + "mode", + "query", + "--address", + "http://localhost:9090/", + "-q", + "increase%28application_auftrag_status_count%7Benvironment%3D%22prod%22%2Csubtype%3D%222022%22%2CstatusId%21%3D%223%22%7D%5B24h%5D%29", + "--query-encoding", + "url", + "--insecure" + ] + }, ] } diff --git a/main.go b/main.go index 0dab2c6..25c7d00 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/base64" "fmt" "net/http" "net/url" @@ -16,12 +17,22 @@ import ( "github.com/urfave/cli/v3" ) +type QueryEncoding int + +const ( + Raw QueryEncoding = iota + Base64 + Url +) + var ( address *url.URL timeout int64 warning string critical string query string + queryDecoded string + queryEncoding QueryEncoding alias string search string replace string @@ -170,7 +181,7 @@ func main() { `, Action: func(c context.Context, cmd *cli.Command) error { startTimeout() - return mode.Query(address, query, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) + return mode.Query(address, queryDecoded, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) }, Flags: []cli.Flag{ &cli.StringFlag{ @@ -194,6 +205,11 @@ func main() { Name: "q", Usage: "Query to be executed", Destination: &query, + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + query = value + queryDecoded = value + return nil + }, }, &cli.StringFlag{ Name: "a", @@ -272,6 +288,53 @@ func main() { Usage: "Status if the query returns no data.", Destination: &emptyQueryStatus, }, + &cli.StringFlag{ + Name: "query-encoding", + Value: "raw", + Usage: "Query encoding if query is given in encoded form. Supports 'raw', 'base64' and 'url' type encodings. Specify this parameter after the query.", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + if query == "" { + return fmt.Errorf("query argument is empty, specify query-encoding after specifying query") + } + switch strings.ToLower(value) { + case "raw": + queryEncoding = Raw + queryDecoded = query + case "base64": + queryEncoding = Base64 + bytes, err := base64.StdEncoding.DecodeString(query) + if err != nil { + return fmt.Errorf("base64 query decoding failed with error: %s", err.Error()) + } + queryDecoded = string(bytes) + case "url": + queryEncoding = Url + var err error + queryDecoded, err = url.QueryUnescape(query) + if err != nil { + return fmt.Errorf("url query decoding failed with error: %s", err.Error()) + } + return nil + default: + return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") + } + return nil + }, + Validator: func(value string) error { + switch strings.ToLower(value) { + case "raw": + queryEncoding = Raw + case "base64": + queryEncoding = Base64 + case "url": + queryEncoding = Url + default: + return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") + } + return nil + }, + ValidateDefaults: true, + }, }, }, From 1cd2f1c76d4b391272d777ae75a9d64128b88f75 Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 18 Dec 2025 16:55:42 +0100 Subject: [PATCH 08/11] bump version to 0.0.4 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 25c7d00..ca35a52 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,7 @@ func main() { cmd := &cli.Command{ Name: "check_prometheus", Usage: "Checks different prometheus stats as well the data itself", - Version: "0.0.3", + Version: "0.0.4", Flags: []cli.Flag{ &cli.Int64Flag{ Name: "timeout", From 7beb7336f6afa3433baf6554bf42da5c34942973 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Tue, 23 Dec 2025 15:11:49 +0100 Subject: [PATCH 09/11] move the check cli into its own package and expose it --- internal/checker/checker.go | 395 ++++++++++++++++++++++++++++++++++++ main.go | 386 +---------------------------------- 2 files changed, 397 insertions(+), 384 deletions(-) create mode 100644 internal/checker/checker.go diff --git a/internal/checker/checker.go b/internal/checker/checker.go new file mode 100644 index 0000000..25e4fb3 --- /dev/null +++ b/internal/checker/checker.go @@ -0,0 +1,395 @@ +package checker + +import ( + "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/consol-monitoring/check_prometheus/internal/helper" + "github.com/consol-monitoring/check_prometheus/internal/mode" + + "github.com/consol-monitoring/check_x" + "github.com/urfave/cli/v3" +) + +type QueryEncoding int + +const ( + Raw QueryEncoding = iota + Base64 + Url +) + +var ( + address *url.URL + timeout int64 + warning string + critical string + query string + queryDecoded string + queryEncoding QueryEncoding + alias string + search string + replace string + label string + emptyQueryMessage string + emptyQueryStatus string +) + +func startTimeout() { + if timeout != 0 { + check_x.StartTimeout(time.Duration(timeout) * time.Second) + } +} + +func getStatus(state string) check_x.State { + switch state { + case "OK": + return check_x.OK + case "WARNING": + return check_x.Warning + case "CRITICAL": + return check_x.Critical + default: + return check_x.Unknown + } +} + +func Check(args []string) int { + cmd := &cli.Command{ + Name: "check_prometheus", + Usage: "Checks different prometheus stats as well the data itself", + Version: "0.0.4", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "timeout", + Aliases: []string{"t"}, + Usage: "Seconds till check returns unknown, 0 to disable", + Value: 10, + Destination: &timeout, + }, + &cli.IntFlag{ + Name: "data-age", + Aliases: []string{"f"}, + Usage: "If the checked data is older then this in seconds, unknown will be returned. Set to 0 to disable.", + Value: 300, + Destination: &helper.TimestampFreshness, + }, + &cli.BoolFlag{ + Name: "verbose", + Usage: "Turn the verbose mode on.", + Value: false, + Action: func(ctx context.Context, cmd *cli.Command, value bool) error { + helper.Verbose = value + return nil + }, + }, + }, + Commands: []*cli.Command{ + { + Name: "mode", + Aliases: []string{"m"}, + Usage: "check mode", + Commands: []*cli.Command{ + { + Name: "ping", + Aliases: []string{"p"}, + HideHelp: false, + Usage: "Returns the build informations", + Description: `This check requires that the prometheus server itself is listed as target. Following query will be used: 'prometheus_build_info{job="prometheus"}'`, + Action: func(ctx context.Context, cmd *cli.Command) error { + startTimeout() + return mode.Ping(address) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err != nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, + }, + }, + + { + Name: "query", + Aliases: []string{"q"}, + HideHelp: false, + Usage: "Checks collected data", + Description: `Your Promqlquery has to return a vector / scalar / matrix result. The warning and critical values are applied to every value. + Examples: + Vector: + check_prometheus mode query -q 'up' + --> OK - Query: 'up'|'up{instance="192.168.99.101:9245", job="iapetos"}'=1;;;; 'up{instance="0.0.0.0:9091", job="prometheus"}'=1;;;; + + Scalar: + check_prometheus mode query -q 'scalar(up{job="prometheus"})' + --> OK - OK - Query: 'scalar(up{job="prometheus"})' returned: '1'|'scalar'=1;;;; + + Matrix: + check_prometheus mode query -q 'http_requests_total{job="prometheus"}[5m]' + --> OK - Query: 'http_requests_total{job="prometheus"}[5m]' + + Search and Replace: + check_prometheus m query -q 'up' --search '.*job=\"(.*?)\".*' --replace '$1' + --> OK - Query: 'up'|'prometheus'=1;;;; 'iapetos'=0;;;; + + check_prometheus m q -q '{handler="prometheus",quantile="0.99",job="prometheus",__name__=~"http_.*bytes"}' --search '.*__name__=\"(.*?)\".*' --replace '$1' -a 'http_in_out' + --> OK - Alias: 'http_in_out'|'http_request_size_bytes'=296;;;; 'http_response_size_bytes'=5554;;;; + + Use Alias to generate output with label values: + Assumption that your query returns a label "hostname" and "details". + IMPORTANT: To be able to use the value in more advanced output formatting, we just add a label "value" with the current value to the list of labels. + If the specified Alias string cannot be processed by the text/template engine, the Alias string will be printed 1:1. + check_prometheus m q -a 'Hostname: {{.hostname}} - Details: {{.details}}' --search '.*' --replace 'error_state' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> Critical - Hostname: Server01 - Details: Error404|'error_state'=1;0;0;; + + Use Alias with an if/else clause and the use of xvalue: + If xvalue is 1, we output UP, else we output DOWN + check_prometheus m q --search '.*' --replace 'up' -q 'up{instance="SUPERHOST"}' -a 'Hostname: {{.hostname}} Is {{if eq .xvalue "1"}}UP{{else}}DOWN{{end}}.\n' -w 1: -c 1: + --> OK - Hostname: SUPERHOST Is UP.\n|'up'=1;1:;1:;; + + List all available labels to be used with Alias: + Just use -a '{{.}}' and the whole map with all labels will be printed. + check_prometheus m q -q 'up{instance="SUPERHOST"}' -a '{{.}}' + --> OK - map[__name__:up hostname:SUPERHOST instance:SUPERHOST job:snmp mib:RittalCMC xvalue:1]|'{__name__="up", hostname="SUPERHOST", instance="SUPERHOST", job="snmp", mib="RittalCMC"}'=1;;;; + + Use Different Message and Status code for queries that return no data. + If you have a query that only returns data in an error condition you can use this flags to return a custom message and status code. + check_prometheus m q -eqm 'All OK' -eqs 'OK' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> OK - All OK + Without -eqm, -eqs + check_prometheus m q -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 + --> UNKNOWN - The given States do not contain an State + + `, + Action: func(c context.Context, cmd *cli.Command) error { + startTimeout() + return mode.Query(address, queryDecoded, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err == nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, + &cli.StringFlag{ + Name: "q", + Usage: "Query to be executed", + Destination: &query, + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + query = value + queryDecoded = value + return nil + }, + }, + &cli.StringFlag{ + Name: "a", + Usage: "Alias, will replace the query within the output, if set. You can use go text/template syntax to output label values (only for vector results).", + Destination: &alias, + }, + &cli.StringFlag{ + Name: "w", + Usage: "Warning value. Use nagios-plugin syntax here.", + Destination: &warning, + }, + &cli.StringFlag{ + Name: "c", + Usage: "Critical value. Use nagios-plugin syntax here.", + Destination: &critical, + }, + &cli.StringFlag{ + Name: "search", + Usage: "If this variable is set, the given Golang regex will be used to search and replace the result with the 'replace' flag content. This will be appied on the perflabels.", + Destination: &search, + }, + &cli.StringFlag{ + Name: "replace", + Usage: "See search flag. If the 'search' flag is empty this flag will be ignored.", + Destination: &replace, + }, + &cli.BoolFlag{ + Name: "insecure, k", + Usage: "Skip TLS certificate verification (insecure)", + Destination: &helper.InsecureSkipVerify, + }, + &cli.StringFlag{ + Name: "cookie", + Usage: "Cookie to send during the api request, in form '=' ", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + cookieKey := value[:strings.IndexRune(value, '=')] + cookieValue := value[strings.IndexRune(value, '=')+1:] + cookie := &http.Cookie{ + Name: cookieKey, + Value: cookieValue, + Path: "/", + SameSite: http.SameSiteLaxMode, + MaxAge: 3600, + Expires: time.Now().Add(time.Hour), + } + helper.Cookies = append(helper.Cookies, cookie) + return nil + }, + Validator: func(value string) error { + strings.Count(value, "=") + if strings.Count(value, "=") != 1 { + return fmt.Errorf("there should be exactly one '=' in the cookie definition") + } + cookieKey := value[:strings.IndexRune(value, '=')] + cookieValue := value[strings.IndexRune(value, '=')+1:] + if cookieKey == "" { + return fmt.Errorf("cookie key cannot be empty") + } + if cookieValue == "" { + return fmt.Errorf("cookie value cannot be empty") + } + if len(cookieValue) > 4096 { + return fmt.Errorf("cookie value cannot be longer than 4096 characters") + } + + return nil + }, + }, + &cli.StringFlag{ + Name: "eqm", + Usage: "Message if the query returns no data.", + Destination: &emptyQueryMessage, + }, + &cli.StringFlag{ + Name: "eqs", + Usage: "Status if the query returns no data.", + Destination: &emptyQueryStatus, + }, + &cli.StringFlag{ + Name: "query-encoding", + Value: "raw", + Usage: "Query encoding if query is given in encoded form. Supports 'raw', 'base64' and 'url' type encodings. Specify this parameter after the query.", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + if query == "" { + return fmt.Errorf("query argument is empty, specify query-encoding after specifying query") + } + switch strings.ToLower(value) { + case "raw": + queryEncoding = Raw + queryDecoded = query + case "base64": + queryEncoding = Base64 + bytes, err := base64.StdEncoding.DecodeString(query) + if err != nil { + return fmt.Errorf("base64 query decoding failed with error: %s", err.Error()) + } + queryDecoded = string(bytes) + case "url": + queryEncoding = Url + var err error + queryDecoded, err = url.QueryUnescape(query) + if err != nil { + return fmt.Errorf("url query decoding failed with error: %s", err.Error()) + } + return nil + default: + return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") + } + return nil + }, + Validator: func(value string) error { + switch strings.ToLower(value) { + case "raw": + queryEncoding = Raw + case "base64": + queryEncoding = Base64 + case "url": + queryEncoding = Url + default: + return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") + } + return nil + }, + ValidateDefaults: true, + }, + }, + }, + + { + Name: "targets_health", + HideHelp: false, + Usage: "Returns the health of the targets", + Description: `The warning and critical thresholds are appied on the health_rate. The health_rate is calculted: sum(healthy) / sum(targets).`, + Action: func(c context.Context, cmd *cli.Command) error { + startTimeout() + return mode.TargetsHealth(address, label, warning, critical) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "address", + Usage: "Prometheus address: Protocol + IP + Port.", + Value: "http://localhost:9100", + Action: func(ctx context.Context, cmd *cli.Command, value string) error { + url, err := url.Parse(value) + if err != nil { + address = url + } + return err + }, + Validator: func(value string) error { + _, err := url.Parse(value) + return err + }, + ValidateDefaults: true, + }, + &cli.StringFlag{ + Name: "w", + Usage: "Warning value. Use nagios-plugin syntax here.", + Destination: &warning, + }, + &cli.StringFlag{ + Name: "c", + Usage: "Critical value. Use nagios-plugin syntax here.", + Destination: &critical, + }, + &cli.StringFlag{ + Name: "l", + Usage: "Prometheus-Label, which will be used for the performance data label. By default job and instance should be available.", + Destination: &label, + Value: mode.DefaultLabel, + }, + }, + }, + }, + }, + }, + } + + if err := cmd.Run(context.Background(), args); err != nil { + check_x.ErrorExit(err) + } + + return 0 +} diff --git a/main.go b/main.go index ca35a52..88f6dbb 100644 --- a/main.go +++ b/main.go @@ -1,394 +1,12 @@ package main import ( - "context" - "encoding/base64" - "fmt" - "net/http" - "net/url" "os" - "strings" - "time" - "github.com/consol-monitoring/check_prometheus/internal/helper" - "github.com/consol-monitoring/check_prometheus/internal/mode" - - "github.com/consol-monitoring/check_x" - "github.com/urfave/cli/v3" -) - -type QueryEncoding int - -const ( - Raw QueryEncoding = iota - Base64 - Url -) - -var ( - address *url.URL - timeout int64 - warning string - critical string - query string - queryDecoded string - queryEncoding QueryEncoding - alias string - search string - replace string - label string - emptyQueryMessage string - emptyQueryStatus string + "github.com/consol-monitoring/check_prometheus/internal/checker" ) -func startTimeout() { - if timeout != 0 { - check_x.StartTimeout(time.Duration(timeout) * time.Second) - } -} - -func getStatus(state string) check_x.State { - switch state { - case "OK": - return check_x.OK - case "WARNING": - return check_x.Warning - case "CRITICAL": - return check_x.Critical - default: - return check_x.Unknown - } -} - func main() { - cmd := &cli.Command{ - Name: "check_prometheus", - Usage: "Checks different prometheus stats as well the data itself", - Version: "0.0.4", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "timeout", - Aliases: []string{"t"}, - Usage: "Seconds till check returns unknown, 0 to disable", - Value: 10, - Destination: &timeout, - }, - &cli.IntFlag{ - Name: "data-age", - Aliases: []string{"f"}, - Usage: "If the checked data is older then this in seconds, unknown will be returned. Set to 0 to disable.", - Value: 300, - Destination: &helper.TimestampFreshness, - }, - &cli.BoolFlag{ - Name: "verbose", - Usage: "Turn the verbose mode on.", - Value: false, - Action: func(ctx context.Context, cmd *cli.Command, value bool) error { - helper.Verbose = value - return nil - }, - }, - }, - Commands: []*cli.Command{ - { - Name: "mode", - Aliases: []string{"m"}, - Usage: "check mode", - Commands: []*cli.Command{ - { - Name: "ping", - Aliases: []string{"p"}, - HideHelp: false, - Usage: "Returns the build informations", - Description: `This check requires that the prometheus server itself is listed as target. Following query will be used: 'prometheus_build_info{job="prometheus"}'`, - Action: func(ctx context.Context, cmd *cli.Command) error { - startTimeout() - return mode.Ping(address) - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "address", - Usage: "Prometheus address: Protocol + IP + Port.", - Value: "http://localhost:9100", - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - url, err := url.Parse(value) - if err != nil { - address = url - } - return err - }, - Validator: func(value string) error { - _, err := url.Parse(value) - return err - }, - ValidateDefaults: true, - }, - }, - }, - - { - Name: "query", - Aliases: []string{"q"}, - HideHelp: false, - Usage: "Checks collected data", - Description: `Your Promqlquery has to return a vector / scalar / matrix result. The warning and critical values are applied to every value. - Examples: - Vector: - check_prometheus mode query -q 'up' - --> OK - Query: 'up'|'up{instance="192.168.99.101:9245", job="iapetos"}'=1;;;; 'up{instance="0.0.0.0:9091", job="prometheus"}'=1;;;; - - Scalar: - check_prometheus mode query -q 'scalar(up{job="prometheus"})' - --> OK - OK - Query: 'scalar(up{job="prometheus"})' returned: '1'|'scalar'=1;;;; - - Matrix: - check_prometheus mode query -q 'http_requests_total{job="prometheus"}[5m]' - --> OK - Query: 'http_requests_total{job="prometheus"}[5m]' - - Search and Replace: - check_prometheus m query -q 'up' --search '.*job=\"(.*?)\".*' --replace '$1' - --> OK - Query: 'up'|'prometheus'=1;;;; 'iapetos'=0;;;; - - check_prometheus m q -q '{handler="prometheus",quantile="0.99",job="prometheus",__name__=~"http_.*bytes"}' --search '.*__name__=\"(.*?)\".*' --replace '$1' -a 'http_in_out' - --> OK - Alias: 'http_in_out'|'http_request_size_bytes'=296;;;; 'http_response_size_bytes'=5554;;;; - - Use Alias to generate output with label values: - Assumption that your query returns a label "hostname" and "details". - IMPORTANT: To be able to use the value in more advanced output formatting, we just add a label "value" with the current value to the list of labels. - If the specified Alias string cannot be processed by the text/template engine, the Alias string will be printed 1:1. - check_prometheus m q -a 'Hostname: {{.hostname}} - Details: {{.details}}' --search '.*' --replace 'error_state' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> Critical - Hostname: Server01 - Details: Error404|'error_state'=1;0;0;; - - Use Alias with an if/else clause and the use of xvalue: - If xvalue is 1, we output UP, else we output DOWN - check_prometheus m q --search '.*' --replace 'up' -q 'up{instance="SUPERHOST"}' -a 'Hostname: {{.hostname}} Is {{if eq .xvalue "1"}}UP{{else}}DOWN{{end}}.\n' -w 1: -c 1: - --> OK - Hostname: SUPERHOST Is UP.\n|'up'=1;1:;1:;; - - List all available labels to be used with Alias: - Just use -a '{{.}}' and the whole map with all labels will be printed. - check_prometheus m q -q 'up{instance="SUPERHOST"}' -a '{{.}}' - --> OK - map[__name__:up hostname:SUPERHOST instance:SUPERHOST job:snmp mib:RittalCMC xvalue:1]|'{__name__="up", hostname="SUPERHOST", instance="SUPERHOST", job="snmp", mib="RittalCMC"}'=1;;;; - - Use Different Message and Status code for queries that return no data. - If you have a query that only returns data in an error condition you can use this flags to return a custom message and status code. - check_prometheus m q -eqm 'All OK' -eqs 'OK' -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> OK - All OK - Without -eqm, -eqs - check_prometheus m q -q 'http_requests_total{job="prometheus"}' -w 0 -c 0 - --> UNKNOWN - The given States do not contain an State - - `, - Action: func(c context.Context, cmd *cli.Command) error { - startTimeout() - return mode.Query(address, queryDecoded, warning, critical, alias, search, replace, emptyQueryMessage, getStatus(emptyQueryStatus)) - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "address", - Usage: "Prometheus address: Protocol + IP + Port.", - Value: "http://localhost:9100", - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - url, err := url.Parse(value) - if err == nil { - address = url - } - return err - }, - Validator: func(value string) error { - _, err := url.Parse(value) - return err - }, - ValidateDefaults: true, - }, - &cli.StringFlag{ - Name: "q", - Usage: "Query to be executed", - Destination: &query, - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - query = value - queryDecoded = value - return nil - }, - }, - &cli.StringFlag{ - Name: "a", - Usage: "Alias, will replace the query within the output, if set. You can use go text/template syntax to output label values (only for vector results).", - Destination: &alias, - }, - &cli.StringFlag{ - Name: "w", - Usage: "Warning value. Use nagios-plugin syntax here.", - Destination: &warning, - }, - &cli.StringFlag{ - Name: "c", - Usage: "Critical value. Use nagios-plugin syntax here.", - Destination: &critical, - }, - &cli.StringFlag{ - Name: "search", - Usage: "If this variable is set, the given Golang regex will be used to search and replace the result with the 'replace' flag content. This will be appied on the perflabels.", - Destination: &search, - }, - &cli.StringFlag{ - Name: "replace", - Usage: "See search flag. If the 'search' flag is empty this flag will be ignored.", - Destination: &replace, - }, - &cli.BoolFlag{ - Name: "insecure, k", - Usage: "Skip TLS certificate verification (insecure)", - Destination: &helper.InsecureSkipVerify, - }, - &cli.StringFlag{ - Name: "cookie", - Usage: "Cookie to send during the api request, in form '=' ", - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - cookieKey := value[:strings.IndexRune(value, '=')] - cookieValue := value[strings.IndexRune(value, '=')+1:] - cookie := &http.Cookie{ - Name: cookieKey, - Value: cookieValue, - Path: "/", - SameSite: http.SameSiteLaxMode, - MaxAge: 3600, - Expires: time.Now().Add(time.Hour), - } - helper.Cookies = append(helper.Cookies, cookie) - return nil - }, - Validator: func(value string) error { - strings.Count(value, "=") - if strings.Count(value, "=") != 1 { - return fmt.Errorf("there should be exactly one '=' in the cookie definition") - } - cookieKey := value[:strings.IndexRune(value, '=')] - cookieValue := value[strings.IndexRune(value, '=')+1:] - if cookieKey == "" { - return fmt.Errorf("cookie key cannot be empty") - } - if cookieValue == "" { - return fmt.Errorf("cookie value cannot be empty") - } - if len(cookieValue) > 4096 { - return fmt.Errorf("cookie value cannot be longer than 4096 characters") - } - - return nil - }, - }, - &cli.StringFlag{ - Name: "eqm", - Usage: "Message if the query returns no data.", - Destination: &emptyQueryMessage, - }, - &cli.StringFlag{ - Name: "eqs", - Usage: "Status if the query returns no data.", - Destination: &emptyQueryStatus, - }, - &cli.StringFlag{ - Name: "query-encoding", - Value: "raw", - Usage: "Query encoding if query is given in encoded form. Supports 'raw', 'base64' and 'url' type encodings. Specify this parameter after the query.", - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - if query == "" { - return fmt.Errorf("query argument is empty, specify query-encoding after specifying query") - } - switch strings.ToLower(value) { - case "raw": - queryEncoding = Raw - queryDecoded = query - case "base64": - queryEncoding = Base64 - bytes, err := base64.StdEncoding.DecodeString(query) - if err != nil { - return fmt.Errorf("base64 query decoding failed with error: %s", err.Error()) - } - queryDecoded = string(bytes) - case "url": - queryEncoding = Url - var err error - queryDecoded, err = url.QueryUnescape(query) - if err != nil { - return fmt.Errorf("url query decoding failed with error: %s", err.Error()) - } - return nil - default: - return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") - } - return nil - }, - Validator: func(value string) error { - switch strings.ToLower(value) { - case "raw": - queryEncoding = Raw - case "base64": - queryEncoding = Base64 - case "url": - queryEncoding = Url - default: - return fmt.Errorf("unknown query encoding, available values are 'raw', 'base64', 'url'") - } - return nil - }, - ValidateDefaults: true, - }, - }, - }, - - { - Name: "targets_health", - HideHelp: false, - Usage: "Returns the health of the targets", - Description: `The warning and critical thresholds are appied on the health_rate. The health_rate is calculted: sum(healthy) / sum(targets).`, - Action: func(c context.Context, cmd *cli.Command) error { - startTimeout() - return mode.TargetsHealth(address, label, warning, critical) - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "address", - Usage: "Prometheus address: Protocol + IP + Port.", - Value: "http://localhost:9100", - Action: func(ctx context.Context, cmd *cli.Command, value string) error { - url, err := url.Parse(value) - if err != nil { - address = url - } - return err - }, - Validator: func(value string) error { - _, err := url.Parse(value) - return err - }, - ValidateDefaults: true, - }, - &cli.StringFlag{ - Name: "w", - Usage: "Warning value. Use nagios-plugin syntax here.", - Destination: &warning, - }, - &cli.StringFlag{ - Name: "c", - Usage: "Critical value. Use nagios-plugin syntax here.", - Destination: &critical, - }, - &cli.StringFlag{ - Name: "l", - Usage: "Prometheus-Label, which will be used for the performance data label. By default job and instance should be available.", - Destination: &label, - Value: mode.DefaultLabel, - }, - }, - }, - }, - }, - }, - } - if err := cmd.Run(context.Background(), os.Args); err != nil { - check_x.ErrorExit(err) - } + checker.Check(os.Args) } From 8371ebcb7c557f41ca08a3afcb3874c6288296a8 Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Tue, 23 Dec 2025 15:36:50 +0100 Subject: [PATCH 10/11] move checker package out of internal and expose it --- {internal/checker => checker}/checker.go | 0 main.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {internal/checker => checker}/checker.go (100%) diff --git a/internal/checker/checker.go b/checker/checker.go similarity index 100% rename from internal/checker/checker.go rename to checker/checker.go diff --git a/main.go b/main.go index 88f6dbb..74b8ebf 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/consol-monitoring/check_prometheus/internal/checker" + "github.com/consol-monitoring/check_prometheus/checker" ) func main() { From e71de8bc5d885360e180d1ba805a49a472ef22cf Mon Sep 17 00:00:00 2001 From: Ahmet Ozturk Date: Tue, 23 Dec 2025 16:14:20 +0100 Subject: [PATCH 11/11] move checker under pkg/ , the convention is so --- main.go | 2 +- {checker => pkg/checker}/checker.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {checker => pkg/checker}/checker.go (100%) diff --git a/main.go b/main.go index 74b8ebf..c3d3f54 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/consol-monitoring/check_prometheus/checker" + "github.com/consol-monitoring/check_prometheus/pkg/checker" ) func main() { diff --git a/checker/checker.go b/pkg/checker/checker.go similarity index 100% rename from checker/checker.go rename to pkg/checker/checker.go