Skip to content

Commit

Permalink
Allow templating regexp matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
prymitive committed Feb 23, 2022
1 parent 74556da commit fa07683
Show file tree
Hide file tree
Showing 28 changed files with 661 additions and 197 deletions.
4 changes: 2 additions & 2 deletions cmd/pint/tests/0007_alerts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ rules/0001.yml:9-10: severity label is required (rule/label)
- alert: ServiceIsDown
expr: up == 0

rules/0001.yml:14: severity label value must match regex: ^critical|warning|info$ (rule/label)
rules/0001.yml:14: severity label value must match "^critical|warning|info$" (rule/label)
severity: bad

rules/0001.yml:16: url annotation value must match regex: ^https://wiki.example.com/page/(.+).html$ (alerts/annotation)
rules/0001.yml:16: url annotation value must match "^https://wiki.example.com/page/(.+).html$" (alerts/annotation)
url: bad

rules/0002.yml:5: template parse error: undefined variable "$label" (alerts/template)
Expand Down
43 changes: 43 additions & 0 deletions cmd/pint/tests/0058_templated_check.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
pint.error --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=info msg="Loading configuration file" path=.pint.hcl
level=info msg="File parsed" path=rules/0001.yml rules=3
rules/0001.yml:4-6: alert_for annotation is required (alerts/annotation)
- alert: Instance Is Down 2
expr: up == 0
for: 5m

rules/0001.yml:12: alert_for annotation value must match "^{{ $for }}$" (alerts/annotation)
alert_for: 4m

level=info msg="Problems found" Bug=2
level=fatal msg="Fatal error" error="problems found"
-- rules/0001.yml --
- alert: Instance Is Down 1
expr: up == 0

- alert: Instance Is Down 2
expr: up == 0
for: 5m

- alert: Instance Is Down 3
expr: up == 0
for: 5m
annotations:
alert_for: 4m

-- .pint.hcl --
rule {
match {
for = "> 0"
}

annotation "alert_for" {
required = true
value = "{{ $for }}"
severity = "bug"
}
}
23 changes: 23 additions & 0 deletions cmd/pint/tests/0059_templated_check_bad_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
pint.error --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=info msg="Loading configuration file" path=.pint.hcl
level=fatal msg="Fatal error" error="failed to load config file \".pint.hcl\": template: regexp:1:126: executing \"regexp\" at <nil>: nil is not a command"
-- rules/0001.yml --
- alert: Instance Is Down 1
expr: up == 0

-- .pint.hcl --
rule {
match {
for = "> 0"
}

annotation "alert_for" {
required = true
value = "{{ nil }}"
severity = "bug"
}
}
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
}
}
```
- Regexp matchers used in check rules can now reference rule fields.
See [Configuration](configuration.md) for details.

### Changed

Expand Down
20 changes: 19 additions & 1 deletion docs/checks/alerts/annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ annotation "$pattern" {
}
```

- `$pattern` - regexp pattern to match annotation name on
- `$pattern` - regexp pattern to match annotation name on, this can be templated
to reference checked rule fields, see [Configuration](../../configuration.md)
for details
- `severity` - set custom severity for reported issues, defaults to a warning
- `value` - optional value pattern to enforce, if not set only the
- `required` - if `true` pint will require every alert to have this annotation set,
Expand Down Expand Up @@ -55,6 +57,22 @@ rule {
}
```

Example that enforces all alerting rules with non-zero `for` field to have an
annotation called `alert_for` and value equal to `for` field.

```js
rule {
match {
for = "> 0"
}

annotation "alert_for" {
required = true
value = "{{ $for }}"
}
}
```

## How to disable it

You can disable this check globally by adding this config block:
Expand Down
4 changes: 3 additions & 1 deletion docs/checks/promql/aggregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ aggregate "$pattern" {
}
```

- `$pattern` - regexp pattern to match rule name on
- `$pattern` - regexp pattern to match rule name on, this can be templated
to reference checked rule fields, see [Configuration](../../configuration.md)
for details
- `severity` - set custom severity for reported issues, defaults to a warning
- `keep` - list of label names that must be preserved
- `strip` - list of label names that must be stripped
Expand Down
26 changes: 22 additions & 4 deletions docs/checks/rule/label.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ label "$pattern" {
}
```

- `$pattern` - regexp pattern to match label name on
- `$pattern` - regexp pattern to match label name on, this can be templated
to reference checked rule fields, see [Configuration](../../configuration.md)
for details
- `severity` - set custom severity for reported issues, defaults to a warning
- `value` - optional value pattern to enforce, if not set only the
- `required` - if `true` pint will require every rule to have this label set,
Expand All @@ -36,9 +38,8 @@ to work.
To enable it add one or more `rule {...}` blocks and specify all required
labels there.

Example:

Require `severity` label to be set on alert rules with two all possible values:
Example that will require `severity` label to be set on alert rules with two
all possible values:

```js
rule {
Expand All @@ -53,6 +54,23 @@ rule {
}
```

Example that enforces all alerting rules with `for` value present and greater
than 5 minutes field to have a label called `alert_for` and value equal to
`for` field.

```js
rule {
match {
for = "> 5m"
}

label "alert_for" {
required = true
value = "{{ $for }}"
}
}
```

## How to disable it

You can disable this check globally by adding this config block:
Expand Down
21 changes: 19 additions & 2 deletions docs/checks/rule/reject.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ reject "$pattern" {
}
```

- `$pattern` - regexp pattern to reject
- `$pattern` - regexp pattern to reject, this can be templated
to reference checked rule fields, see [Configuration](../../configuration.md)
for details
- `severity` - set custom severity for reported issues, defaults to a bug.
- `label_keys` - if true label keys for recording and alerting rules will
be checked.
Expand All @@ -39,7 +41,7 @@ to work.
To enable it add one or more `rule {...}` blocks and specify all rejected patterns
there.

Example:
Examples:

Disallow using URLs as label keys or values:

Expand Down Expand Up @@ -67,6 +69,21 @@ rule {
}
```

Disallow label and annotation values equal to alert name:

```js
rule {
match {
kind = "alerting"
}

reject "{{ $alert }}" {
annotation_values = true
label_values = true
}
}
```

## How to disable it

You can disable this check globally by adding this config block:
Expand Down
27 changes: 24 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,28 @@ nav_order: 2
1. TOC
{:toc}

**NOTE**: all regex patterns are anchored.
## Regexp matchers

All regexp patterns use [Go regexp](https://pkg.go.dev/regexp) module and are fully anchored.
This means that when you pass `.*` regexp expression internally it will be represented as
`^.*$`, where `^` indicates beginning of a string and `$` is the end of string.
This follow [PromQL behavior](https://prometheus.io/docs/prometheus/latest/querying/basics/)
for consistency with Prometheus.
If you have a string `alice bob john` and you want to match a substring `bob`, then be sure to use
`.*bob.*`.

When using regexp matcher in checks configuration you can reference alerting and recording rule
fields in the regexp using [Go text/template](https://pkg.go.dev/text/template) syntax.
Rule fields are exposed as:

- `$alert` - rule `alert` field
- `$record` - rule `record` field
- `$expr` - rule `expr` field
- `$for` - rule `for` field
- `$labels` - rule `labels` map, individual labels can be accessed as `$labels.foo`
- `$annotations` - rule `annotations` map, individual annotations can be accessed as `$annotations.foo`

Accessing a field that's not present in the rule will return an empty string.

## CI

Expand All @@ -30,7 +51,7 @@ ci {
```

- `include` - list of file patterns to check when running checks. Only files
matching those regex rules will be checked, other modified files will be ignored.
matching those regexp rules will be checked, other modified files will be ignored.
- `maxCommits` - by default pint will try to find all commits on the current branch,
this requires full git history to be present, if we have a shallow clone this
might fail to find only current branch commits and give us a huge list.
Expand Down Expand Up @@ -130,7 +151,7 @@ prometheus "$name" {
Default value for `required` is `false`. Set it to `true` if you want to hard fail
in case of remote Prometheus issues. Note that setting it to `true` might block
PRs when running `pint ci` until pint is able to talk to Prometheus again.
- `paths` - optional path filter, if specified only paths matching one of listed regex
- `paths` - optional path filter, if specified only paths matching one of listed regexp
patterns will use this Prometheus server for checks.

Example:
Expand Down
11 changes: 5 additions & 6 deletions internal/checks/alerts_annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package checks
import (
"context"
"fmt"
"regexp"

"github.com/cloudflare/pint/internal/parser"
)
Expand All @@ -12,20 +11,20 @@ const (
AnnotationCheckName = "alerts/annotation"
)

func NewAnnotationCheck(key string, valueRe *regexp.Regexp, isReguired bool, severity Severity) AnnotationCheck {
func NewAnnotationCheck(key string, valueRe *TemplatedRegexp, isReguired bool, severity Severity) AnnotationCheck {
return AnnotationCheck{key: key, valueRe: valueRe, isReguired: isReguired, severity: severity}
}

type AnnotationCheck struct {
key string
valueRe *regexp.Regexp
valueRe *TemplatedRegexp
isReguired bool
severity Severity
}

func (c AnnotationCheck) String() string {
if c.valueRe != nil {
return fmt.Sprintf("%s(%s=~%s:%v)", AnnotationCheckName, c.key, c.valueRe, c.isReguired)
return fmt.Sprintf("%s(%s=~%s:%v)", AnnotationCheckName, c.key, c.valueRe.anchored, c.isReguired)
}
return fmt.Sprintf("%s(%s:%v)", AnnotationCheckName, c.key, c.isReguired)
}
Expand Down Expand Up @@ -66,12 +65,12 @@ func (c AnnotationCheck) Check(ctx context.Context, rule parser.Rule) (problems
return
}

if c.valueRe != nil && !c.valueRe.MatchString(val.Value) {
if c.valueRe != nil && !c.valueRe.MustExpand(rule).MatchString(val.Value) {
problems = append(problems, Problem{
Fragment: fmt.Sprintf("%s: %s", c.key, val.Value),
Lines: val.Position.Lines,
Reporter: c.Reporter(),
Text: fmt.Sprintf("%s annotation value must match regex: %s", c.key, c.valueRe.String()),
Text: fmt.Sprintf("%s annotation value must match %q", c.key, c.valueRe.anchored),
Severity: c.severity,
})
return
Expand Down
Loading

0 comments on commit fa07683

Please sign in to comment.