From aeba93d0f7c7efce75282c0ec50215bee788542f Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Wed, 1 Mar 2023 09:24:46 -0500 Subject: [PATCH 01/14] feat: add NagiosXI update metric This adds an optional metric (`nagios_update_available_info`) to determine whether a NagiosXI update is available. We get the version from the NagiosXI API, so it's just a matter of the exporter reaching out to `https://assets.nagios.com/downloads/nagiosxi/versions.php`, fetching the latest version, and comparing it to the current version. A metric value of `1` indicates an update is available. If the flag `--nagios.check-updates` is not provided, then the metric value will always be `0`. The metric collection is gated behind a flag because users may not want their exporter reaching out to the public internet repeatedly. --- get_nagios_version/get_nagios_version.go | 50 ++++++++++++++++++ nagios_exporter.go | 64 ++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100755 get_nagios_version/get_nagios_version.go diff --git a/get_nagios_version/get_nagios_version.go b/get_nagios_version/get_nagios_version.go new file mode 100755 index 0000000..9d8c6a1 --- /dev/null +++ b/get_nagios_version/get_nagios_version.go @@ -0,0 +1,50 @@ +package get_nagios_version + +import ( + "fmt" + "io/ioutil" // TODO: ioutil outdated + "net/http" + "strings" + + "golang.org/x/net/html" + log "github.com/sirupsen/logrus" +) + +func GetLatestNagiosXIVersion(NagiosXIURL string) (version string, err error) { + + // Fetch the HTML source data from the URL + resp, err := http.Get(NagiosXIURL) + if err != nil { + fmt.Println("Error fetching HTML:", err) + return + } + defer resp.Body.Close() + + // Read the HTML data + htmlData, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + return "", err + } + + // Convert HTML data to a tokenizer + r := strings.NewReader(string(htmlData)) + tokenizer := html.NewTokenizer(r) + + // Iterate through all tokens + for { + tokenType := tokenizer.Next() + + if tokenType == html.ErrorToken { + break + } else if tokenType == html.TextToken { + text := tokenizer.Token().Data + if strings.HasPrefix(text, "xi-") { + // the first `xi-` string is the latest NagiosXI version in semver + return text, nil + } + } + + } + return "", err +} diff --git a/nagios_exporter.go b/nagios_exporter.go index 1f177a9..81f5c2a 100644 --- a/nagios_exporter.go +++ b/nagios_exporter.go @@ -14,7 +14,10 @@ import ( "strings" "time" + "github.com/wbollock/nagiosxi_exporter/get_nagios_version" + "github.com/BurntSushi/toml" + "github.com/hashicorp/go-version" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" @@ -183,6 +186,11 @@ var ( usersTotal = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "users_total"), "Amount of users present on the system", nil, nil) usersPrivileges = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "users_privileges_total"), "Amount of admin or regular users", []string{"privileges"}, nil) usersStatus = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "users_status_total"), "Amount of disabled or enabled users", []string{"status"}, nil) + + // Optional metric + updateAvailable = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "update_available_info"), "NagiosXI update is available", nil, nil) + + NagiosXIURL string = "https://assets.nagios.com/downloads/nagiosxi/versions.php" ) type Exporter struct { @@ -191,9 +199,10 @@ type Exporter struct { nagiosAPITimeout time.Duration nagiostatsPath string nagiosconfigPath string + checkUpdates bool } -func NewExporter(nagiosEndpoint, nagiosAPIKey string, sslVerify bool, nagiosAPITimeout time.Duration, nagiostatsPath string, nagiosconfigPath string) *Exporter { +func NewExporter(nagiosEndpoint, nagiosAPIKey string, sslVerify bool, nagiosAPITimeout time.Duration, nagiostatsPath string, nagiosconfigPath string, checkUpdates bool) *Exporter { return &Exporter{ nagiosEndpoint: nagiosEndpoint, nagiosAPIKey: nagiosAPIKey, @@ -201,6 +210,7 @@ func NewExporter(nagiosEndpoint, nagiosAPIKey string, sslVerify bool, nagiosAPIT nagiosAPITimeout: nagiosAPITimeout, nagiostatsPath: nagiostatsPath, nagiosconfigPath: nagiosconfigPath, + checkUpdates: checkUpdates, } } @@ -243,6 +253,8 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { ch <- usersPrivileges ch <- usersStatus } + // Optional metric + ch <- updateAvailable } func (e *Exporter) TestNagiosConnectivity(sslVerify bool, nagiosAPITimeout time.Duration) float64 { @@ -288,7 +300,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { up, prometheus.GaugeValue, nagiosStatus, ) - e.QueryAPIsAndUpdateMetrics(ch, e.sslVerify, e.nagiosAPITimeout) + e.QueryAPIsAndUpdateMetrics(ch, e.sslVerify, e.nagiosAPITimeout, e.checkUpdates) } else { nagiosStatus := e.TestNagiosstatsBinary(e.nagiostatsPath, e.nagiosconfigPath) if nagiosStatus == 0 { @@ -427,7 +439,7 @@ func histogramProducer(bucket1, bucket2, bucket3, bucket4, bucket5, bucket6, buc } return bucket1, bucket2, bucket3, bucket4, bucket5, bucket6, bucket7, bucket8, bucket9, bucket10 } -func (e *Exporter) QueryAPIsAndUpdateMetrics(ch chan<- prometheus.Metric, sslVerify bool, nagiosAPITimeout time.Duration) { +func (e *Exporter) QueryAPIsAndUpdateMetrics(ch chan<- prometheus.Metric, sslVerify bool, nagiosAPITimeout time.Duration, checkUpdates bool) { // get system status systeminfoURL := e.nagiosEndpoint + systeminfoAPI + "?apikey=" + e.nagiosAPIKey @@ -445,6 +457,24 @@ func (e *Exporter) QueryAPIsAndUpdateMetrics(ch chan<- prometheus.Metric, sslVer versionInfo, prometheus.GaugeValue, 1, systemInfoObject.Version, ) + // optional cmdline flag to expose this metric + if checkUpdates { + nagiosVersion, err := get_nagios_version.GetLatestNagiosXIVersion(NagiosXIURL) + if err != nil { + log.Fatal(err) + } + + updateMetric := CompareNagiosVersions(nagiosVersion, systemInfoObject.Version) + ch <- prometheus.MustNewConstMetric( + updateAvailable, prometheus.GaugeValue, updateMetric, + // updateMetric 0 = no update, updateMetric 1 = update available + ) + } else { // user did not want to compare nagios versions externally so just say there aren't any updates (0) + ch <- prometheus.MustNewConstMetric( + updateAvailable, prometheus.GaugeValue, 0, + ) + } + // host status hoststatusURL := e.nagiosEndpoint + hoststatusAPI + "?apikey=" + e.nagiosAPIKey @@ -1020,6 +1050,30 @@ func (e *Exporter) QueryNagiostatsAndUpdateMetrics(ch chan<- prometheus.Metric, log.Info("Nagiostats scraped and metrics updated") } +func CompareNagiosVersions(latestVersion string, currentVersion string) float64 { + xiPrefix := "xi-" // NagiosXI versions always start with this + cleanLatestVersion := strings.Trim(latestVersion, xiPrefix) + cleanCurrentVersion := strings.Trim(currentVersion, xiPrefix) + + log.Debug("NagiosXI latest version: ", cleanLatestVersion) + log.Debug("NagiosXI current version: ", cleanCurrentVersion) + + // convert into version object for semver comparison + semVerLatest, err := version.NewVersion(cleanLatestVersion) + if err != nil { + log.Fatal(err) + } + semVerCurrent, err := version.NewVersion(cleanCurrentVersion) + if err != nil { + log.Fatal(err) + } + + if semVerCurrent.LessThan(semVerLatest) { + return 1 // 1 = nagios update is available + } + return 0 // 0 = no nagios update available +} + // custom formatter modified from https://github.com/sirupsen/logrus/issues/719#issuecomment-536459432 // https://stackoverflow.com/questions/48971780/how-to-change-the-format-of-log-output-in-logrus/48972299#48972299 // required as Nagios XI API only supports giving the API token as a URL parameter, and thus can be leaked in the logs @@ -1063,6 +1117,8 @@ func main() { "Path of nagiostats binary and configuration (e.g /usr/local/nagios/bin/nagiostats -c /usr/local/nagios/etc/nagios.cfg)") nagiosConfigPath = flag.String("nagios.config_path", "", "Nagios configuration path for use with nagiostats binary (e.g /usr/local/nagios/etc/nagios.cfg)") + checkUpdates = flag.Bool("nagios.check-updates", false, + "Provides a metric on whether a NagiosXI update is available") ) flag.Parse() @@ -1092,7 +1148,7 @@ func main() { } // convert timeout flag to seconds - exporter := NewExporter(nagiosURL, conf.APIKey, *sslVerify, time.Duration(*nagiosAPITimeout)*time.Second, *statsBinary, *nagiosConfigPath) + exporter := NewExporter(nagiosURL, conf.APIKey, *sslVerify, time.Duration(*nagiosAPITimeout)*time.Second, *statsBinary, *nagiosConfigPath, *checkUpdates) prometheus.MustRegister(exporter) if *statsBinary == "" { From df69c1df3caaa8b0b7e2f642a3652ad23c09bdad Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Wed, 1 Mar 2023 10:10:57 -0500 Subject: [PATCH 02/14] chore: go mod/sum --- go.mod | 15 ++++++++------- go.sum | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 0acd204..ed569aa 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,23 @@ -module github.com/wbollock/nagios_exporter +module github.com/wbollock/nagiosxi_exporter go 1.19 require ( - github.com/BurntSushi/toml v1.2.0 // direct - github.com/prometheus/client_golang v1.13.0 // direct + github.com/BurntSushi/toml v1.2.1 + github.com/hashicorp/go-version v1.6.0 + github.com/prometheus/client_golang v1.14.0 + github.com/sirupsen/logrus v1.9.0 + golang.org/x/net v0.7.0 ) -require github.com/sirupsen/logrus v1.9.0 - require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/sys v0.5.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/go.sum b/go.sum index 6da83e9..5edfa8f 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -126,6 +126,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -164,13 +166,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -272,6 +275,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -326,8 +331,9 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 48a141eb3cf391b6471916985a892428a3a4314f Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 16:37:48 -0500 Subject: [PATCH 03/14] feat: add tests for GetNagiosVersion Just the HTML source from $(date) --- tests/get_nagios_version_test.go | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/get_nagios_version_test.go diff --git a/tests/get_nagios_version_test.go b/tests/get_nagios_version_test.go new file mode 100644 index 0000000..4db407d --- /dev/null +++ b/tests/get_nagios_version_test.go @@ -0,0 +1,57 @@ +package test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/wbollock/nagiosxi_exporter/get_nagios_version" +) + +func TestGetStringFromWebpage(t *testing.T) { + // Create a test server that will return a specific string when called + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` + + + Nagios XI · Previous Versions + + +
+
+
+

Nagios XI - Previous Versions

+ + + + + + + + + + + + + + + + + + + "`)) + })) + defer testServer.Close() + + // Call the function with the URL of the test server + result, err := get_nagios_version.GetLatestNagiosXIVersion(testServer.URL) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Check that the result matches the expected string + expected := "xi-5.9.3" + if result != expected { + t.Errorf("Expected %q, but got %q", expected, result) + } +} From d415ccd3c2afb96718dd73855c74009165f5f73f Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 16:42:14 -0500 Subject: [PATCH 04/14] fix: check err value of Write --- tests/get_nagios_version_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/get_nagios_version_test.go b/tests/get_nagios_version_test.go index 4db407d..55e077a 100644 --- a/tests/get_nagios_version_test.go +++ b/tests/get_nagios_version_test.go @@ -5,13 +5,14 @@ import ( "net/http/httptest" "testing" + log "github.com/sirupsen/logrus" "github.com/wbollock/nagiosxi_exporter/get_nagios_version" ) func TestGetStringFromWebpage(t *testing.T) { // Create a test server that will return a specific string when called testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(` + _, err := w.Write([]byte(` Nagios XI · Previous Versions @@ -40,6 +41,10 @@ func TestGetStringFromWebpage(t *testing.T) { "`)) + if err != nil { + // handle the error here + log.Fatal(err) + } })) defer testServer.Close() From f77b792063a09fb75f5b6872ef7667e0393d3012 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 16:47:13 -0500 Subject: [PATCH 05/14] fix: don't use deprecated io/ioutil --- get_nagios_version/get_nagios_version.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/get_nagios_version/get_nagios_version.go b/get_nagios_version/get_nagios_version.go index 9d8c6a1..23d845b 100755 --- a/get_nagios_version/get_nagios_version.go +++ b/get_nagios_version/get_nagios_version.go @@ -2,12 +2,12 @@ package get_nagios_version import ( "fmt" - "io/ioutil" // TODO: ioutil outdated + "io" "net/http" "strings" - "golang.org/x/net/html" log "github.com/sirupsen/logrus" + "golang.org/x/net/html" ) func GetLatestNagiosXIVersion(NagiosXIURL string) (version string, err error) { @@ -21,7 +21,7 @@ func GetLatestNagiosXIVersion(NagiosXIURL string) (version string, err error) { defer resp.Body.Close() // Read the HTML data - htmlData, err := ioutil.ReadAll(resp.Body) + htmlData, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) return "", err From 268ca8bfac0371d4c60b90550f259588fdd5e3f3 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 16:48:36 -0500 Subject: [PATCH 06/14] chore: run tests in CI --- .github/workflows/golang-tests.yaml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/golang-tests.yaml diff --git a/.github/workflows/golang-tests.yaml b/.github/workflows/golang-tests.yaml new file mode 100644 index 0000000..b18521b --- /dev/null +++ b/.github/workflows/golang-tests.yaml @@ -0,0 +1,30 @@ +name: Go Test + +on: + push: + tags: + - v* + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: '1.19' + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: go test ./tests From 1821cf1bdf02b330b05002724960d62ed867d450 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 16:48:46 -0500 Subject: [PATCH 07/14] chore: bump go version in lint --- .github/workflows/golangci-lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index 60cd6f0..376a729 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 From e3e6756b3b8b4709f9738f40ff15f6bad48e4eee Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 17:07:45 -0500 Subject: [PATCH 08/14] docs: add contributing/releasing instructions --- README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7df9964..bf3cb8a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This exporter does not output Nagios check results as Prometheus metrics; it is ## Table of Contents -- [nagios_exporter](#nagios_exporter) +- [nagios\_exporter](#nagios_exporter) - [Table of Contents](#table-of-contents) - [Configuration](#configuration) - [Nagios Core 3/4 support](#nagios-core-34-support) @@ -39,6 +39,9 @@ This exporter does not output Nagios check results as Prometheus metrics; it is - [NagiosXI](#nagiosxi) - [Nagios Core 3/4, CheckMK](#nagios-core-34-checkmk) - [Resources Used](#resources-used) + - [Contributing](#contributing) + - [Releasing](#releasing) + - [Contributors ✨](#contributors-) ## Configuration @@ -153,6 +156,32 @@ sudo su -s /bin/bash -c "/usr/local/nagios/bin/nagiostats -c / * [goreleaser](https://github.com/goreleaser/goreleaser) * [nfpm](https://github.com/goreleaser/nfpm) +## Contributing + +To build and run the Debian package, install go-releaser and run: + +```bash +goreleaser release --clean --snapshot +# currently I develop on a VM running NagiosXI, but a container would be cool too +scp dist/prometheus-nagios-exporter_1.2.2-next_linux_386.deb root@:/root/ +dpkg -i prometheus-nagios-exporter_1.2.2-next_linux_amd64.deb +# examine metrics +ssh root@ +curl -s localhost:9927/metrics | grep "^nagios" +``` + +## Releasing + +Follow goreleaser's [quick start](https://goreleaser.com/quick-start/) instructions. + +```bash +# make changes, merge into main +export GITHUB_TOKEN="YOUR_GH_TOKEN" +git tag -a v -m "Release summary" +git push origin v +goreleaser release +``` + ## Contributors ✨ From 754c0287b5590ed56e1b0936a84795552dcf9a58 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Fri, 3 Mar 2023 17:08:33 -0500 Subject: [PATCH 09/14] fix: use nagios_exporter as project name --- go.mod | 2 +- nagios_exporter.go | 2 +- tests/get_nagios_version_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ed569aa..6968234 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/wbollock/nagiosxi_exporter +module github.com/wbollock/nagios_exporter go 1.19 diff --git a/nagios_exporter.go b/nagios_exporter.go index 81f5c2a..e9c60ab 100644 --- a/nagios_exporter.go +++ b/nagios_exporter.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/wbollock/nagiosxi_exporter/get_nagios_version" + "github.com/wbollock/nagios_exporter/get_nagios_version" "github.com/BurntSushi/toml" "github.com/hashicorp/go-version" diff --git a/tests/get_nagios_version_test.go b/tests/get_nagios_version_test.go index 55e077a..e8feb48 100644 --- a/tests/get_nagios_version_test.go +++ b/tests/get_nagios_version_test.go @@ -6,7 +6,7 @@ import ( "testing" log "github.com/sirupsen/logrus" - "github.com/wbollock/nagiosxi_exporter/get_nagios_version" + "github.com/wbollock/nagios_exporter/get_nagios_version" ) func TestGetStringFromWebpage(t *testing.T) { From fd05dd7aa900ae6294dfc03668c609d802e8444c Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Mon, 6 Mar 2023 12:25:12 -0500 Subject: [PATCH 10/14] fix: use log.Info for version checking functions We don't need to kill the exporter process if we have issues comparing the current NagiosXI version to upstream. Rather we should just log the error and continue. --- get_nagios_version/get_nagios_version.go | 2 +- nagios_exporter.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/get_nagios_version/get_nagios_version.go b/get_nagios_version/get_nagios_version.go index 23d845b..56d3a90 100755 --- a/get_nagios_version/get_nagios_version.go +++ b/get_nagios_version/get_nagios_version.go @@ -23,7 +23,7 @@ func GetLatestNagiosXIVersion(NagiosXIURL string) (version string, err error) { // Read the HTML data htmlData, err := io.ReadAll(resp.Body) if err != nil { - log.Fatal(err) + log.Info(err) return "", err } diff --git a/nagios_exporter.go b/nagios_exporter.go index e9c60ab..48e2eab 100644 --- a/nagios_exporter.go +++ b/nagios_exporter.go @@ -1061,11 +1061,11 @@ func CompareNagiosVersions(latestVersion string, currentVersion string) float64 // convert into version object for semver comparison semVerLatest, err := version.NewVersion(cleanLatestVersion) if err != nil { - log.Fatal(err) + log.Info(err) } semVerCurrent, err := version.NewVersion(cleanCurrentVersion) if err != nil { - log.Fatal(err) + log.Info(err) } if semVerCurrent.LessThan(semVerLatest) { From fa5b37a3e0b736c2702ec9f1357bba2f487c2058 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Mon, 6 Mar 2023 12:35:28 -0500 Subject: [PATCH 11/14] style: omit type on NagiosXIURL var --- nagios_exporter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nagios_exporter.go b/nagios_exporter.go index 48e2eab..d5d0e9d 100644 --- a/nagios_exporter.go +++ b/nagios_exporter.go @@ -190,7 +190,7 @@ var ( // Optional metric updateAvailable = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "update_available_info"), "NagiosXI update is available", nil, nil) - NagiosXIURL string = "https://assets.nagios.com/downloads/nagiosxi/versions.php" + NagiosXIURL = "https://assets.nagios.com/downloads/nagiosxi/versions.php" ) type Exporter struct { From c2a052b021f35e10a08b40972adc5b4b710631c0 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Mon, 6 Mar 2023 12:53:23 -0500 Subject: [PATCH 12/14] ci: add pre-commit hooks --- .pre-commit-config.yaml | 35 +++++++++++++++++++++++++++++++++++ README.md | 6 ++++++ 2 files changed, 41 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..151d69e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: +- repo: https://github.com/tekwizely/pre-commit-golang + rev: v1.0.0-rc.1 + hooks: + # + # Go Build + # + - id: go-build-mod + - id: go-build-repo-mod + # + # Go Mod Tidy + # + - id: go-mod-tidy + - id: go-mod-tidy-repo + # + # Go Test + # + - id: go-test-mod + - id: go-test-repo-mod + # + # Formatters + # + - id: go-fmt + - id: go-fmt-repo + # + # + # GolangCI-Lint + # - Fast Multi-Linter + # - Can be configured to replace MOST other hooks + # - Supports repo config file for configuration + # - https://github.com/golangci/golangci-lint + # + - id: golangci-lint + - id: golangci-lint-mod + - id: golangci-lint-repo-mod diff --git a/README.md b/README.md index bf3cb8a..28fe882 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,12 @@ ssh root@ curl -s localhost:9927/metrics | grep "^nagios" ``` +Install pre-commit hooks: + +```console +pre-commit install +``` + ## Releasing Follow goreleaser's [quick start](https://goreleaser.com/quick-start/) instructions. From 3addfe37f0c28c0e7f9bf05ffc4bec5eabe916d5 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Mon, 6 Mar 2023 13:24:21 -0500 Subject: [PATCH 13/14] ref: use html.Parse in GetLatestNagiosXIVersion Using `html.Parse` is more Go idomatic and helps account for loop failure/termination better. --- get_nagios_version/get_nagios_version.go | 40 +++++++++++++----------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/get_nagios_version/get_nagios_version.go b/get_nagios_version/get_nagios_version.go index 56d3a90..155124b 100755 --- a/get_nagios_version/get_nagios_version.go +++ b/get_nagios_version/get_nagios_version.go @@ -2,7 +2,6 @@ package get_nagios_version import ( "fmt" - "io" "net/http" "strings" @@ -20,31 +19,34 @@ func GetLatestNagiosXIVersion(NagiosXIURL string) (version string, err error) { } defer resp.Body.Close() - // Read the HTML data - htmlData, err := io.ReadAll(resp.Body) + // Parse the HTML data into a tree structure + doc, err := html.Parse(resp.Body) if err != nil { log.Info(err) return "", err } - // Convert HTML data to a tokenizer - r := strings.NewReader(string(htmlData)) - tokenizer := html.NewTokenizer(r) - - // Iterate through all tokens - for { - tokenType := tokenizer.Next() - - if tokenType == html.ErrorToken { - break - } else if tokenType == html.TextToken { - text := tokenizer.Token().Data - if strings.HasPrefix(text, "xi-") { + // https://pkg.go.dev/golang.org/x/net/html#Parse + // recursive function seems to be best practice + var traverse func(*html.Node) string + traverse = func(node *html.Node) string { + if node.Type == html.TextNode { + if strings.HasPrefix(node.Data, "xi-") { // the first `xi-` string is the latest NagiosXI version in semver - return text, nil + return node.Data } } - + for child := node.FirstChild; child != nil; child = child.NextSibling { + result := traverse(child) + if result != "" { + return result + } + } + return "" } - return "", err + + // traverse the HTML parse tree and return the version if found + version = traverse(doc) + + return version, nil } From 3b810d53e402d058e002baebf2d43d8d4d34f859 Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Mon, 6 Mar 2023 16:45:56 -0500 Subject: [PATCH 14/14] docs: re-add logo --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7df9964..da439dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@

+ +

+ # nagios_exporter @@ -26,7 +29,7 @@ This exporter does not output Nagios check results as Prometheus metrics; it is ## Table of Contents -- [nagios_exporter](#nagios_exporter) +- [nagios\_exporter](#nagios_exporter) - [Table of Contents](#table-of-contents) - [Configuration](#configuration) - [Nagios Core 3/4 support](#nagios-core-34-support) @@ -39,6 +42,7 @@ This exporter does not output Nagios check results as Prometheus metrics; it is - [NagiosXI](#nagiosxi) - [Nagios Core 3/4, CheckMK](#nagios-core-34-checkmk) - [Resources Used](#resources-used) + - [Contributors ✨](#contributors-) ## Configuration
MajorVersionSizeModifiedChecksum (sha256sum)
5xi-5.9.376.55M02/1/23 06:533f0170080064f215b16ca89d07e9ab9ec91d93936a921fae2051c5cf56f18baa
5xi-5.9.277.42M12/5/22 08:20xi-5.9.2 77.42M 12/5/22 08:20