From e64c05a7d484ea986e69c77c76238cfc285e165f Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Wed, 28 Jan 2026 15:15:56 +0100 Subject: [PATCH 1/2] Change to stdlib slices.Contains --- cmd/alert.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/cmd/alert.go b/cmd/alert.go index 4e2bbd3..7792f62 100644 --- a/cmd/alert.go +++ b/cmd/alert.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "regexp" + "slices" "strings" "github.com/NETWAYS/check_prometheus/internal/alert" @@ -23,17 +24,6 @@ type AlertConfig struct { var cliAlertConfig AlertConfig -func contains(s string, list []string) bool { - // Tiny helper to see if a string is in a list of strings - for _, elem := range list { - if s == elem { - return true - } - } - - return false -} - var alertCmd = &cobra.Command{ Use: "alert", Short: "Checks the status of a Prometheus alert", @@ -115,7 +105,7 @@ inactive = 0`, // If it's not the Alert we're looking for, Skip! if cliAlertConfig.AlertName != nil { - if !contains(rl.AlertingRule.Name, cliAlertConfig.AlertName) { + if !slices.Contains(cliAlertConfig.AlertName, rl.AlertingRule.Name) { continue } } From 48585eb558f4a3057f1fa7b7f0090362a834a8f7 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Wed, 28 Jan 2026 16:00:42 +0100 Subject: [PATCH 2/2] Add option to include exclude alerts via their labels --- cmd/alert.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/cmd/alert.go b/cmd/alert.go index 7792f62..542532b 100644 --- a/cmd/alert.go +++ b/cmd/alert.go @@ -11,6 +11,7 @@ import ( "github.com/NETWAYS/go-check" "github.com/NETWAYS/go-check/perfdata" "github.com/NETWAYS/go-check/result" + "github.com/prometheus/common/model" "github.com/spf13/cobra" ) @@ -18,6 +19,8 @@ type AlertConfig struct { AlertName []string Group []string ExcludeAlerts []string + ExcludeLabels []string + IncludeLabels []string ProblemsOnly bool NoAlertsState string } @@ -115,17 +118,31 @@ inactive = 0`, continue } - alertMatched, regexErr := matches(rl.AlertingRule.Name, cliAlertConfig.ExcludeAlerts) + alertMatchedExclude, regexErr := matches(rl.AlertingRule.Name, cliAlertConfig.ExcludeAlerts) if regexErr != nil { check.ExitRaw(check.Unknown, "Invalid regular expression provided:", regexErr.Error()) } - if alertMatched { + if alertMatchedExclude { // If the alert matches a regex from the list we can skip it. continue } + labelsMatchedInclude := matchesLabel(rl.AlertingRule.Labels, cliAlertConfig.IncludeLabels) + + if !labelsMatchedInclude { + // If the alert labels don't match here we can skip it. + continue + } + + labelsMatchedExclude := matchesLabel(rl.AlertingRule.Labels, cliAlertConfig.ExcludeLabels) + + if len(cliAlertConfig.ExcludeLabels) > 0 && labelsMatchedExclude { + // If the alert labels matches here we can skip it. + continue + } + // Handle Inactive Alerts if len(rl.AlertingRule.Alerts) == 0 { // Counting states for perfdata @@ -208,18 +225,27 @@ func init() { fs.StringVarP(&cliAlertConfig.NoAlertsState, "no-alerts-state", "T", "OK", "State to assign when no alerts are found (0, 1, 2, 3, OK, WARNING, CRITICAL, UNKNOWN). If not set this defaults to OK") - fs.StringArrayVar(&cliAlertConfig.ExcludeAlerts, "exclude-alert", []string{}, "Alerts to ignore. Can be used multiple times and supports regex.") + fs.StringArrayVar(&cliAlertConfig.ExcludeAlerts, "exclude-alert", []string{}, + "Alerts to ignore. Can be used multiple times and supports regex.") fs.StringSliceVarP(&cliAlertConfig.AlertName, "name", "n", nil, "The name of one or more specific alerts to check."+ - "\nThis parameter can be repeated e.G.: '--name alert1 --name alert2'"+ + "\nThis parameter can be repeated e.g.: '--name alert1 --name alert2'"+ "\nIf no name is given, all alerts will be evaluated") fs.StringSliceVarP(&cliAlertConfig.Group, "group", "g", nil, "The name of one or more specific groups to check for alerts."+ - "\nThis parameter can be repeated e.G.: '--group group1 --group group2'"+ + "\nThis parameter can be repeated e.g.: '--group group1 --group group2'"+ "\nIf no group is given, all groups will be scanned for alerts") + fs.StringArrayVar(&cliAlertConfig.IncludeLabels, "include-label", []string{}, + "The label of one or more specific alerts to include."+ + "\nThis parameter can be repeated e.g.: '--include-label prio=high --include-label another=example'") + + fs.StringArrayVar(&cliAlertConfig.ExcludeLabels, "exclude-label", []string{}, + "The label of one or more specific alerts to exclude."+ + "\nThis parameter can be repeated e.g.: '--exclude-label prio=high --exclude-label another=example'") + fs.BoolVarP(&cliAlertConfig.ProblemsOnly, "problems", "P", false, "Display only alerts which status is not inactive/OK. Note that in combination with the --name flag this might result in no alerts being displayed") } @@ -257,3 +283,39 @@ func matches(input string, regexToExclude []string) (bool, error) { return false, nil } + +// Matches a list of regular expressions against a string. +func matchesLabel(labels model.LabelSet, labelsToExclude []string) bool { + kv := sliceToMap(labelsToExclude) + + for k, v := range kv { + lname := model.LabelName(k) + + lv, ok := labels[lname] + + if !ok { + return false + } + + if string(lv) != v { + return false + } + } + + return true +} + +func sliceToMap(labels []string) map[string]string { + m := make(map[string]string, len(labels)) + + for _, s := range labels { + kv := strings.SplitN(s, "=", 2) + if len(kv) != 2 { + continue + } + + m[kv[0]] = kv[1] + } + + return m +}