Rules API: Allow filtering by rule name

Introduces support for a new query parameter in the `/rules` API endpoint that allows filtering by rule names.

If all the rules of a group are filtered, we skip the group entirely.

Signed-off-by: gotjosh <josue.abreu@gmail.com>
This commit is contained in:
gotjosh 2023-04-18 10:07:32 +01:00
parent 7309ac2721
commit f3394bf7a1
No known key found for this signature in database
GPG Key ID: A6E1DDE38FF3C74E
3 changed files with 56 additions and 6 deletions

View File

@ -673,7 +673,9 @@ GET /api/v1/rules
```
URL query parameters:
- `type=alert|record`: return only the alerting rules (e.g. `type=alert`) or the recording rules (e.g. `type=record`). When the parameter is absent or empty, no filtering is done.
- `rules=alertName,RuleName`: return only the alerting and recording rules with the specified names. If we've filtered out all the rules of a group, the group is not returned. When the parameter is absent or empty, no filtering is done.
```json
$ curl http://localhost:9090/api/v1/rules

View File

@ -1296,6 +1296,16 @@ func (api *API) rules(r *http.Request) apiFuncResult {
res := &RuleDiscovery{RuleGroups: make([]*RuleGroup, len(ruleGroups))}
typ := strings.ToLower(r.URL.Query().Get("type"))
// Parse the rule names into a comma separated list of rule names, then create a set.
rulesQuery := r.URL.Query().Get("rules")
ruleNamesSet := map[string]struct{}{}
if rulesQuery != "" {
names := strings.Split(rulesQuery, ",")
for _, rn := range names {
ruleNamesSet[strings.TrimSpace(rn)] = struct{}{}
}
}
if typ != "" && typ != "alert" && typ != "record" {
return invalidParamError(errors.Errorf("not supported value %q", typ), "type")
}
@ -1313,14 +1323,20 @@ func (api *API) rules(r *http.Request) apiFuncResult {
EvaluationTime: grp.GetEvaluationTime().Seconds(),
LastEvaluation: grp.GetLastEvaluation(),
}
for _, r := range grp.Rules() {
for _, rr := range grp.Rules() {
var enrichedRule Rule
lastError := ""
if r.LastError() != nil {
lastError = r.LastError().Error()
if len(ruleNamesSet) > 0 {
if _, ok := ruleNamesSet[rr.Name()]; !ok {
continue
}
}
switch rule := r.(type) {
lastError := ""
if rr.LastError() != nil {
lastError = rr.LastError().Error()
}
switch rule := rr.(type) {
case *rules.AlertingRule:
if !returnAlerts {
break
@ -1358,11 +1374,16 @@ func (api *API) rules(r *http.Request) apiFuncResult {
err := errors.Errorf("failed to assert type of rule '%v'", rule.Name())
return apiFuncResult{nil, &apiError{errorInternal, err}, nil, nil}
}
if enrichedRule != nil {
apiRuleGroup.Rules = append(apiRuleGroup.Rules, enrichedRule)
}
}
res.RuleGroups[i] = apiRuleGroup
// If the rule group response has no rules, skip it - this means we filtered all the rules of this group.
if len(apiRuleGroup.Rules) > 0 {
res.RuleGroups[i] = apiRuleGroup
}
}
return apiFuncResult{res, nil, nil, nil}
}

View File

@ -1973,6 +1973,33 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
},
},
},
{
endpoint: api.rules,
query: url.Values{"rules": []string{"test_metric4"}},
response: &RuleDiscovery{
RuleGroups: []*RuleGroup{
{
Name: "grp",
File: "/path/to/file",
Interval: 1,
Limit: 0,
Rules: []Rule{
AlertingRule{
State: "inactive",
Name: "test_metric4",
Query: "up == 1",
Duration: 1,
Labels: labels.Labels{},
Annotations: labels.Labels{},
Alerts: []*Alert{},
Health: "unknown",
Type: "alerting",
},
},
},
},
},
},
{
endpoint: api.queryExemplars,
query: url.Values{