Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 69 additions & 17 deletions cmd/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,29 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strings"

"github.com/NETWAYS/check_prometheus/internal/alert"
"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"
)

type AlertConfig struct {
AlertName []string
Group []string
ExcludeAlerts []string
ExcludeLabels []string
IncludeLabels []string
ProblemsOnly bool
NoAlertsState string
}

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",
Expand Down Expand Up @@ -115,7 +108,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
}
}
Expand All @@ -125,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
Expand Down Expand Up @@ -218,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")
}
Expand Down Expand Up @@ -267,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
}