diff --git a/cmd/karma/tests/testscript/059_log_full_config_env.txt b/cmd/karma/tests/testscript/059_log_full_config_env.txt index 2d6f3aefb..8af25f32b 100644 --- a/cmd/karma/tests/testscript/059_log_full_config_env.txt +++ b/cmd/karma/tests/testscript/059_log_full_config_env.txt @@ -221,9 +221,11 @@ level=info msg="receivers:" level=info msg=" keep:" level=info msg=" - keep1" level=info msg=" - keep2" +level=info msg=" keep_re: []" level=info msg=" strip:" level=info msg=" - strip1" level=info msg=" - strip2" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 1s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/060_log_full_config_file.txt b/cmd/karma/tests/testscript/060_log_full_config_file.txt index 8448d3f65..a3c1c5818 100644 --- a/cmd/karma/tests/testscript/060_log_full_config_file.txt +++ b/cmd/karma/tests/testscript/060_log_full_config_file.txt @@ -257,9 +257,11 @@ level=info msg="receivers:" level=info msg=" keep:" level=info msg=" - keep1" level=info msg=" - keep2" +level=info msg=" keep_re: []" level=info msg=" strip:" level=info msg=" - strip1" level=info msg=" - strip2" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 5m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt index e778efda2..08efbb596 100644 --- a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt +++ b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt @@ -113,7 +113,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/066_proxy.txt b/cmd/karma/tests/testscript/066_proxy.txt index a50cf3357..2cc7b12ed 100644 --- a/cmd/karma/tests/testscript/066_proxy.txt +++ b/cmd/karma/tests/testscript/066_proxy.txt @@ -113,7 +113,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/067_readonly.txt b/cmd/karma/tests/testscript/067_readonly.txt index d5b64cba9..31cefce8e 100644 --- a/cmd/karma/tests/testscript/067_readonly.txt +++ b/cmd/karma/tests/testscript/067_readonly.txt @@ -113,7 +113,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/070_upper_case_keys.txt b/cmd/karma/tests/testscript/070_upper_case_keys.txt index 76e3a9ca1..6f95b0dd4 100644 --- a/cmd/karma/tests/testscript/070_upper_case_keys.txt +++ b/cmd/karma/tests/testscript/070_upper_case_keys.txt @@ -123,7 +123,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/097_proxy_url_config.txt b/cmd/karma/tests/testscript/097_proxy_url_config.txt index 578dd26ac..e9a1395d8 100644 --- a/cmd/karma/tests/testscript/097_proxy_url_config.txt +++ b/cmd/karma/tests/testscript/097_proxy_url_config.txt @@ -113,7 +113,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/cmd/karma/tests/testscript/112_flag_defaultAlertmanagers.txt b/cmd/karma/tests/testscript/112_flag_defaultAlertmanagers.txt index 0f1d96c44..a246747e6 100644 --- a/cmd/karma/tests/testscript/112_flag_defaultAlertmanagers.txt +++ b/cmd/karma/tests/testscript/112_flag_defaultAlertmanagers.txt @@ -92,7 +92,9 @@ level=info msg=" requests: false" level=info msg=" timestamp: false" level=info msg="receivers:" level=info msg=" keep: []" +level=info msg=" keep_re: []" level=info msg=" strip: []" +level=info msg=" strip_re: []" level=info msg="silences:" level=info msg=" expired: 10m0s" level=info msg=" comments:" diff --git a/internal/alertmanager/dedup.go b/internal/alertmanager/dedup.go index 8462012e7..54a90cf92 100644 --- a/internal/alertmanager/dedup.go +++ b/internal/alertmanager/dedup.go @@ -37,7 +37,13 @@ func DedupAlerts() []models.AlertGroup { alert := alert // scopelint pin // remove all alerts for receiver(s) that the user doesn't // want to see in the UI - if transform.StripReceivers(config.Config.Receivers.Keep, config.Config.Receivers.Strip, alert.Receiver) { + if transform.StripReceivers( + config.Config.Receivers.Keep, + config.Config.Receivers.Strip, + config.Config.Receivers.CompiledKeepRegex, + config.Config.Receivers.CompiledStripRegex, + alert.Receiver, + ) { continue } diff --git a/internal/config/config.go b/internal/config/config.go index 1c9835790..90fcdf3b2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -126,8 +126,12 @@ func SetupFlags(f *pflag.FlagSet) { f.StringSlice("receivers.keep", []string{}, "List of receivers to keep, all alerts with different receivers will be ignored") + f.StringSlice("receivers.keep_re", []string{}, + "List of regular expressions to keep matching receivers, all other receivers will be ignored") f.StringSlice("receivers.strip", []string{}, "List of receivers to not display alerts for") + f.StringSlice("receivers.strip_re", []string{}, + "List of regular expressions to ignore matching receivers") f.Duration("silences.expired", time.Minute*10, "Maximum age of expired silences to show on active alerts") f.StringSlice("silenceForm.strip.labels", []string{}, "List of labels to ignore when auto-filling silence form from alerts") @@ -413,6 +417,22 @@ func (config *configSchema) Read(flags *pflag.FlagSet) (string, error) { } } + config.Receivers.CompiledKeepRegex = make([]*regexp.Regexp, len(config.Receivers.KeepRegex)) + for i, keepRegex := range config.Receivers.KeepRegex { + config.Receivers.CompiledKeepRegex[i], err = regex.CompileAnchored(keepRegex) + if err != nil { + return "", fmt.Errorf("keep regex rule '%s' is invalid: %w", keepRegex, err) + } + } + + config.Receivers.CompiledStripRegex = make([]*regexp.Regexp, len(config.Receivers.StripRegex)) + for i, stripRegex := range config.Receivers.KeepRegex { + config.Receivers.CompiledStripRegex[i], err = regex.CompileAnchored(stripRegex) + if err != nil { + return "", fmt.Errorf("strip regex rule '%s' is invalid: %w", stripRegex, err) + } + } + if !slices.StringInSlice([]string{"disabled", "startsAt", "label"}, config.Grid.Sorting.Order) { return "", fmt.Errorf("invalid grid.sorting.order value '%s', allowed options: disabled, startsAt, label", config.Grid.Sorting.Order) } diff --git a/internal/config/models.go b/internal/config/models.go index abe4bf23b..079c75f4c 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -195,8 +195,12 @@ type configSchema struct { Timestamp bool } Receivers struct { - Keep []string - Strip []string + Keep []string + KeepRegex []string `yaml:"keep_re" koanf:"keep_re"` + CompiledKeepRegex []*regexp.Regexp `yaml:"-"` + Strip []string + StripRegex []string `yaml:"strip_re" koanf:"strip_re"` + CompiledStripRegex []*regexp.Regexp `yaml:"-"` } Silences struct { Expired time.Duration diff --git a/internal/transform/strip.go b/internal/transform/strip.go index 9ec2f7f6f..e5f85a884 100644 --- a/internal/transform/strip.go +++ b/internal/transform/strip.go @@ -40,13 +40,14 @@ func StripLables(keptLabels, ignoredLabels []string, keptLabelsRegex, ignoredLab // StripReceivers allows filtering all alerts for specified receiver(s) // it will return true if alert uses receiver that should be stripped -func StripReceivers(keptReceivers, ignoredReceivers []string, alertReceiver string) bool { - // true if we keep by default - keepAll := len(keptReceivers) == 0 - // is this receiver on the whitelist ? - inKeep := slices.StringInSlice(keptReceivers, alertReceiver) - // is this receiver on the blacklist ? - inStrip := slices.StringInSlice(ignoredReceivers, alertReceiver) +func StripReceivers(keptReceivers, ignoredReceivers []string, keptReceiversRegex, ignoredReceiversRegex []*regexp.Regexp, alertReceiver string) bool { + // empty keep lists means keep everything by default + keepAll := len(keptReceivers) == 0 && len(keptReceiversRegex) == 0 + // is explicitly marked to be kept + inKeep := slices.StringInSlice(keptReceivers, alertReceiver) || slices.MatchesAnyRegex(alertReceiver, keptReceiversRegex) + // is explicitly marked to be stripped + inStrip := slices.StringInSlice(ignoredReceivers, alertReceiver) || slices.MatchesAnyRegex(alertReceiver, ignoredReceiversRegex) + if (keepAll || inKeep) && !inStrip { return false } diff --git a/internal/transform/strip_test.go b/internal/transform/strip_test.go index 2c4d03edb..eab8fda07 100644 --- a/internal/transform/strip_test.go +++ b/internal/transform/strip_test.go @@ -201,6 +201,8 @@ func getCompiledRegex(regexes []string, t *testing.T) []*regexp.Regexp { type stripReceiverTest struct { strip []string keep []string + stripRe []string + keepRe []string receiver string stripped bool } @@ -209,44 +211,82 @@ var stripReceiverTests = []stripReceiverTest{ { strip: []string{}, keep: []string{}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: false, }, { strip: []string{"default"}, keep: []string{}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: true, }, { strip: []string{"default"}, keep: []string{"default"}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: true, }, { strip: []string{}, keep: []string{"default"}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: false, }, { strip: []string{"foo", "bar"}, keep: []string{}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: false, }, { strip: []string{"foo", "default"}, keep: []string{"foo", "bar"}, + stripRe: []string{}, + keepRe: []string{}, receiver: "default", stripped: true, }, + { + strip: []string{}, + keep: []string{}, + stripRe: []string{}, + keepRe: []string{"default-.+"}, + receiver: "default-foo", + stripped: false, + }, + { + strip: []string{}, + keep: []string{}, + stripRe: []string{}, + keepRe: []string{"default-.+"}, + receiver: "foo-bar", + stripped: true, + }, + { + strip: []string{}, + keep: []string{"default-foo"}, + stripRe: []string{"default-.+"}, + keepRe: []string{}, + receiver: "default-foo", + stripped: true, + }, } func TestStripReceivers(t *testing.T) { for _, testCase := range stripReceiverTests { - stripped := transform.StripReceivers(testCase.keep, testCase.strip, testCase.receiver) + keepRegex := getCompiledRegex(testCase.keepRe, t) + stripRegex := getCompiledRegex(testCase.keepRe, t) + stripped := transform.StripReceivers(testCase.keep, testCase.strip, keepRegex, stripRegex, testCase.receiver) if stripped != testCase.stripped { t.Errorf("StripReceivers failed, expected %v, got %v", testCase.stripped, stripped) }