Alerting: Add rule_matcher filter to Prometheus rules API (#115297)
**What is this feature?** Add `rule_matcher` filter to the Prometheus-compatible list rules API: `/api/prometheus/grafana/api/v1/rules`. It allows to filter rules by static labels (not by alert instance labels). **Special notes:** - Equality (`=`) and inequality (`!=`) matchers are pushed down to the database. Regex matchers (`=~`, `!~`) are applied in-memory at the API layer. - SQLite: Uses GLOB pattern matching - MySQL / PostgreSQL: Use JSON functions to compare label values --------- Co-authored-by: Konrad Lalik <konradlalik@gmail.com>
This commit is contained in:
co-authored by
Konrad Lalik
parent
c7c1dd4ead
commit
c0295d06a3
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -364,8 +365,6 @@ func TestIntegrationPrometheusRules(t *testing.T) {
|
||||
func TestIntegrationPrometheusRulesPagination(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
@@ -388,23 +387,30 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create 3 rule groups with different numbers of rules
|
||||
// Group 1: 5 rules, Group 2: 3 rules, Group 3: 2 rules (total: 10 rules)
|
||||
// Group 1: 5 rules with team=backend
|
||||
// Group 2: 3 rules with team=frontend
|
||||
// Group 3: 2 rules with team=platform
|
||||
for groupIdx := 1; groupIdx <= 3; groupIdx++ {
|
||||
var rulesCount int
|
||||
var team string
|
||||
switch groupIdx {
|
||||
case 1:
|
||||
rulesCount = 5
|
||||
team = "backend"
|
||||
case 2:
|
||||
rulesCount = 3
|
||||
team = "frontend"
|
||||
case 3:
|
||||
rulesCount = 2
|
||||
team = "platform"
|
||||
}
|
||||
|
||||
rules := make([]apimodels.PostableExtendedRuleNode, rulesCount)
|
||||
for i := 0; i < rulesCount; i++ {
|
||||
rules[i] = apimodels.PostableExtendedRuleNode{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: &interval,
|
||||
For: &interval,
|
||||
Labels: map[string]string{"team": team},
|
||||
},
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: fmt.Sprintf("rule-%d-%d", groupIdx, i+1),
|
||||
@@ -514,6 +520,61 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Len(t, result.Data.RuleGroups, 0, "should return no groups")
|
||||
})
|
||||
|
||||
t.Run("with rule_matcher filter returns only matching rules", func(t *testing.T) {
|
||||
matcher := url.QueryEscape(`{"name":"team","value":"frontend","isRegex":false,"isEqual":true}`)
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
var result apimodels.RuleResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// Should only return group-2 (team=frontend, 3 rules)
|
||||
foundGroups := []string{}
|
||||
total := 0
|
||||
for _, group := range result.Data.RuleGroups {
|
||||
foundGroups = append(foundGroups, group.Name)
|
||||
total += len(group.Rules)
|
||||
}
|
||||
require.Equal(t, []string{"group-2"}, foundGroups)
|
||||
require.Equal(t, 3, total)
|
||||
})
|
||||
|
||||
t.Run("with rule_matcher regex filter", func(t *testing.T) {
|
||||
// Filter with regex team=~plat.* (should match group-3 with team=platform)
|
||||
matcher := url.QueryEscape(`{"name":"team","value":"plat.*","isRegex":true,"isEqual":true}`)
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
var result apimodels.RuleResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// Should only return group-3 (team=platform matches plat.*)
|
||||
foundGroups := []string{}
|
||||
total := 0
|
||||
for _, group := range result.Data.RuleGroups {
|
||||
foundGroups = append(foundGroups, group.Name)
|
||||
total += len(group.Rules)
|
||||
}
|
||||
require.Equal(t, []string{"group-3"}, foundGroups)
|
||||
require.Equal(t, 2, total)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user