Skip to content

Commit f2cc10a

Browse files
authored
Merge pull request #129 from NETWAYS/label-regex
Support regex in include-exclude labels
2 parents 84f4ba4 + 7d8b651 commit f2cc10a

8 files changed

Lines changed: 277 additions & 38 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ OK - 1 Alerts: 0 Firing - 0 Pending - 1 Inactive
233233
\_[OK] [ApacheDown] is inactive
234234
```
235235
236+
The `--include-label` and `--exclude-label` flag support regular expressions in the values (e.g. `severity=warn.+`).
237+
236238
#### Checking watchdog alerts
237239
238240
In Prometheus a "watchdog" or "dead man's switch" is an alert that is always firing to ensure alerting pipeline is working. The `-W, --watchdog` flag can be used to flip/negate the exit state of the plugin for these kind of alerts:

cmd/alert.go

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,6 @@ inactive = 0`,
120120
}
121121
}
122122

123-
labelsMatchedInclude := matchesLabel(rl.AlertingRule.Labels, cliAlertConfig.IncludeLabels)
124-
125-
if len(cliAlertConfig.IncludeLabels) > 0 && !labelsMatchedInclude {
126-
// If the alert labels don't match here we can skip it.
127-
continue
128-
}
129-
130123
// Skip inactive alerts if flag is set
131124
if len(rl.AlertingRule.Alerts) == 0 && cliAlertConfig.ProblemsOnly {
132125
continue
@@ -143,7 +136,12 @@ inactive = 0`,
143136
continue
144137
}
145138

146-
labelsMatchedExclude := matchesLabel(rl.AlertingRule.Labels, cliAlertConfig.ExcludeLabels)
139+
// Check if the alert group should be excluded
140+
labelsMatchedExclude, regexErr := matchesLabel(rl.AlertingRule.Labels, cliAlertConfig.ExcludeLabels)
141+
142+
if regexErr != nil {
143+
check.ExitRaw(check.Unknown, "Invalid regular expression provided:", regexErr.Error())
144+
}
147145

148146
if len(cliAlertConfig.ExcludeLabels) > 0 && labelsMatchedExclude {
149147
// If the alert labels matches here we can skip it.
@@ -191,6 +189,28 @@ inactive = 0`,
191189
counterFiring++
192190
}
193191

192+
labelsMatchedInclude, regexErr := matchesLabel(alert.Labels, cliAlertConfig.IncludeLabels)
193+
194+
if regexErr != nil {
195+
check.ExitRaw(check.Unknown, "Invalid regular expression provided:", regexErr.Error())
196+
}
197+
198+
if len(cliAlertConfig.IncludeLabels) > 0 && !labelsMatchedInclude {
199+
// If the alert labels don't match here we can skip it.
200+
continue
201+
}
202+
203+
labelsMatchedExclude, regexErr := matchesLabel(alert.Labels, cliAlertConfig.ExcludeLabels)
204+
205+
if regexErr != nil {
206+
check.ExitRaw(check.Unknown, "Invalid regular expression provided:", regexErr.Error())
207+
}
208+
209+
if len(cliAlertConfig.ExcludeLabels) > 0 && labelsMatchedExclude {
210+
// If the alert labels matches here we can skip it.
211+
continue
212+
}
213+
194214
sc := result.NewPartialResult()
195215

196216
rlStatus := rl.GetStatus(cliAlertConfig.StateLabelKey)
@@ -261,12 +281,12 @@ func init() {
261281

262282
fs.StringArrayVar(&cliAlertConfig.IncludeLabels, "include-label", []string{},
263283
"The label of one or more specific alerts to include. "+
264-
"\nThis parameter can be repeated e.g.: '--include-label prio=high --include-label another=example'"+
284+
"\nThis parameter can be repeated e.g.: '--include-label prio=high --include-label another=example'. Supports regex for values"+
265285
"\nNote that repeated --include-label are combined using a union.")
266286

267287
fs.StringArrayVar(&cliAlertConfig.ExcludeLabels, "exclude-label", []string{},
268288
"The label of one or more specific alerts to exclude."+
269-
"\nThis parameter can be repeated e.g.: '--exclude-label prio=high --exclude-label another=example'")
289+
"\nThis parameter can be repeated e.g.: '--exclude-label prio=high --exclude-label another=example'. Supports regex for values")
270290

271291
fs.BoolVarP(&cliAlertConfig.ProblemsOnly, "problems", "P", false,
272292
"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")
@@ -314,22 +334,33 @@ func matches(input string, regexToExclude []string) (bool, error) {
314334
}
315335

316336
// Matches a list of labels against a list of labels
317-
func matchesLabel(labels model.LabelSet, labelsToMatch []string) bool {
337+
func matchesLabel(labels model.LabelSet, labelsToMatch []string) (bool, error) {
318338
for _, lb := range labelsToMatch {
319-
kv := strings.SplitN(lb, "=", 2)
339+
expectedLabelSet := strings.SplitN(lb, "=", 2)
320340

321-
if len(kv) != 2 {
341+
if len(expectedLabelSet) != 2 {
322342
continue
323343
}
324344

325-
key, value := model.LabelName(kv[0]), model.LabelValue(kv[1])
345+
// Do we have a value for the expected key?
346+
actualValue, ok := labels[model.LabelName(expectedLabelSet[0])]
326347

327-
if val, ok := labels[key]; ok && val == value {
328-
return true
348+
if !ok {
349+
return false, nil
350+
}
351+
352+
re, err := regexp.Compile(expectedLabelSet[1])
353+
if err != nil {
354+
return false, err
355+
}
356+
357+
// Does the values match the expected label regex?
358+
if re.MatchString(string(actualValue)) {
359+
return true, nil
329360
}
330361
}
331362

332-
return false
363+
return false, nil
333364
}
334365

335366
// negateStatus turns an OK state into critical and a warning/critical state into OK

cmd/alert_test.go

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func TestAlertCmd(t *testing.T) {
3939

4040
alertTestDataSet4 := "../testdata/unittest/alertDataset4.json"
4141

42+
alertTestDataSet5 := "../testdata/unittest/alertDataset5.json"
43+
4244
tests := []AlertTest{
4345
{
4446
name: "alert-none",
@@ -85,8 +87,8 @@ func TestAlertCmd(t *testing.T) {
8587
args: []string{"run", "../main.go", "alert"},
8688
expected: `[CRITICAL] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
8789
\_ [OK] [HostOutOfMemory] is inactive
88-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
89-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
90+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
91+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
9092
|total=3 firing=1 pending=1 inactive=1
9193
9294
exit status 2
@@ -100,8 +102,8 @@ exit status 2
100102
})),
101103
args: []string{"run", "../main.go", "alert", "--problems"},
102104
expected: `[CRITICAL] - 2 Alerts: 1 Firing - 1 Pending - 0 Inactive
103-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
104-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
105+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
106+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
105107
|total=2 firing=1 pending=1 inactive=0
106108
107109
exit status 2
@@ -115,7 +117,7 @@ exit status 2
115117
})),
116118
args: []string{"run", "../main.go", "alert", "--problems", "-g", "TLS"},
117119
expected: `[CRITICAL] - 1 Alerts: 1 Firing - 0 Pending - 0 Inactive
118-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
120+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
119121
|total=1 firing=1 pending=0 inactive=0
120122
121123
exit status 2
@@ -129,8 +131,8 @@ exit status 2
129131
})),
130132
args: []string{"run", "../main.go", "alert", "--problems", "-g", "SQL", "-g", "TLS"},
131133
expected: `[CRITICAL] - 2 Alerts: 1 Firing - 1 Pending - 0 Inactive
132-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
133-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
134+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
135+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
134136
|total=2 firing=1 pending=1 inactive=0
135137
136138
exit status 2
@@ -144,7 +146,7 @@ exit status 2
144146
})),
145147
args: []string{"run", "../main.go", "alert", "--problems", "--exclude-alert", "Sql.*DeniedRate"},
146148
expected: `[CRITICAL] - 1 Alerts: 1 Firing - 0 Pending - 0 Inactive
147-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
149+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
148150
|total=1 firing=1 pending=0 inactive=0
149151
150152
exit status 2
@@ -196,7 +198,7 @@ exit status 3
196198
args: []string{"run", "../main.go", "alert", "--name", "HostOutOfMemory", "--name", "BlackboxTLS"},
197199
expected: `[CRITICAL] - 2 Alerts: 1 Firing - 0 Pending - 1 Inactive
198200
\_ [OK] [HostOutOfMemory] is inactive
199-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
201+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
200202
|total=2 firing=1 pending=0 inactive=1
201203
202204
exit status 2
@@ -210,7 +212,7 @@ exit status 2
210212
})),
211213
args: []string{"run", "../main.go", "alert", "--name", "HostOutOfMemory", "--name", "BlackboxTLS", "--problems"},
212214
expected: `[CRITICAL] - 1 Alerts: 1 Firing - 0 Pending - 0 Inactive
213-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
215+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
214216
|total=1 firing=1 pending=0 inactive=0
215217
216218
exit status 2
@@ -250,10 +252,10 @@ exit status 2
250252
w.Write(loadTestdata(alertTestDataSet1))
251253
})),
252254
args: []string{"run", "../main.go", "alert", "--include-label", "severity=critical"},
253-
expected: `[CRITICAL] - 2 Alerts: 1 Firing - 0 Pending - 1 Inactive
255+
expected: `[CRITICAL] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
254256
\_ [OK] [HostOutOfMemory] is inactive
255-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
256-
|total=2 firing=1 pending=0 inactive=1
257+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
258+
|total=3 firing=1 pending=1 inactive=1
257259
258260
exit status 2
259261
`,
@@ -266,7 +268,21 @@ exit status 2
266268
})),
267269
args: []string{"run", "../main.go", "alert", "--exclude-label", "severity=critical"},
268270
expected: `[WARNING] - 1 Alerts: 0 Firing - 1 Pending - 0 Inactive
269-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
271+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
272+
|total=1 firing=0 pending=1 inactive=0
273+
274+
exit status 1
275+
`,
276+
},
277+
{
278+
name: "alert-exclude-label-regex",
279+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
280+
w.WriteHeader(http.StatusOK)
281+
w.Write(loadTestdata(alertTestDataSet1))
282+
})),
283+
args: []string{"run", "../main.go", "alert", "--exclude-label", "severity=crit.*"},
284+
expected: `[WARNING] - 1 Alerts: 0 Firing - 1 Pending - 0 Inactive
285+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
270286
|total=1 firing=0 pending=1 inactive=0
271287
272288
exit status 1
@@ -281,8 +297,24 @@ exit status 1
281297
args: []string{"run", "../main.go", "alert", "--include-label", "team=database", "--include-label", "severity=critical"},
282298
expected: `[CRITICAL] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
283299
\_ [OK] [HostOutOfMemory] is inactive
284-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
285-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
300+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
301+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
302+
|total=3 firing=1 pending=1 inactive=1
303+
304+
exit status 2
305+
`,
306+
},
307+
{
308+
name: "alert-include-label-multiple-regex",
309+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
310+
w.WriteHeader(http.StatusOK)
311+
w.Write(loadTestdata(alertTestDataSet1))
312+
})),
313+
args: []string{"run", "../main.go", "alert", "--include-label", "team=data.+", "--include-label", "severity=critical"},
314+
expected: `[CRITICAL] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
315+
\_ [OK] [HostOutOfMemory] is inactive
316+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
317+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
286318
|total=3 firing=1 pending=1 inactive=1
287319
288320
exit status 2
@@ -297,8 +329,8 @@ exit status 2
297329
args: []string{"run", "../main.go", "alert", "--include-label", "severity=warning", "--include-label", "severity=critical"},
298330
expected: `[CRITICAL] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
299331
\_ [OK] [HostOutOfMemory] is inactive
300-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
301-
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
332+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
333+
\_ [CRITICAL] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
302334
|total=3 firing=1 pending=1 inactive=1
303335
304336
exit status 2
@@ -322,11 +354,26 @@ exit status 2
322354
args: []string{"run", "../main.go", "alert", "--label-key-state=icinga"},
323355
expected: `[WARNING] - 3 Alerts: 1 Firing - 1 Pending - 1 Inactive
324356
\_ [OK] [HostOutOfMemory] is inactive
325-
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning"}
326-
\_ [OK] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","instance":"https://localhost:443","job":"blackbox","severity":"critical"}
357+
\_ [WARNING] [SqlAccessDeniedRate] - Job: [mysql] on Instance: [localhost] is pending - value: 0.40 - {"alertname":"SqlAccessDeniedRate","instance":"localhost","job":"mysql","severity":"warning","team":"database"}
358+
\_ [OK] [BlackboxTLS] - Job: [blackbox] on Instance: [https://localhost:443] is firing - value: -6065338.00 - {"alertname":"TLS","icinga":"ok","instance":"https://localhost:443","job":"blackbox","severity":"critical","team":"network"}
327359
|total=3 firing=1 pending=1 inactive=1
328360
329361
exit status 1
362+
`,
363+
},
364+
{
365+
name: "alert-include-with-name-and-regex",
366+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
367+
w.WriteHeader(http.StatusOK)
368+
w.Write(loadTestdata(alertTestDataSet5))
369+
})),
370+
args: []string{"run", "../main.go", "alert", "--name", "ContainerKilled", "--include-label", "name=(mosquitto|nodered)"},
371+
expected: `[CRITICAL] - 3 Alerts: 3 Firing - 0 Pending - 0 Inactive
372+
\_ [CRITICAL] [ContainerKilled] - Job: [cadvisor] on Instance: [example:8888] is firing - value: 123.40 - {"alertname":"ContainerKilled","instance":"example:8888","job":"cadvisor","name":"nodered","severity":"warning"}
373+
\_ [CRITICAL] [ContainerKilled] - Job: [cadvisor] on Instance: [example:8888] is firing - value: 123.40 - {"alertname":"ContainerKilled","instance":"example:8888","job":"cadvisor","name":"mosquitto","severity":"warning"}
374+
|total=3 firing=3 pending=0 inactive=0
375+
376+
exit status 2
330377
`,
331378
},
332379
}

0 commit comments

Comments
 (0)