Skip to content

Commit

Permalink
feat: add hasSourceTenantsForMetrics validator (#37)
Browse files Browse the repository at this point in the history
* feat: add hasSourceTenantsForMetrics validator, refactor, optimize docs html rendering, add some additional tests

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>

* feat: additional things

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>

* refactor: expression helpers

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>

* chore: changelog

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>

---------

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>
  • Loading branch information
FUSAKLA authored Dec 6, 2023
1 parent 2da1bdd commit 7d11bb1
Show file tree
Hide file tree
Showing 26 changed files with 608 additions and 237 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

- Added new validator `expressionDoesNotUseMetrics`, see its [docs](docs/validations.md#expressiondoesnotusemetrics).
- Added new validator `hasSourceTenantsForMetrics`, see its [docs](docs/validations.md#hassourcetenantsformetrics).
- Improved the HTML output of human readable validation description.
- Added examples of the human-readable validation descriptions to the examples dir.
- Refactored the validation so it can use also group to validate the context of the rule.

## [v2.6.0] - 2023-12-06
- Added new validator `expressionWithNoMetricName`, see its [docs](docs/validations.md#expressionwithnometricname). Thanks @tizki !
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ PROMRUVAL_BIN := ./promruval
E2E_TESTS_VALIDATIONS_FILE := examples/validation.yaml
E2E_TESTS_ADDITIONAL_VALIDATIONS_FILE := examples/additional-validation.yaml
E2E_TESTS_RULES_FILES := examples/rules/*.yaml
E2E_TESTS_DOCS_FILE_MD := examples/human_readable.md
E2E_TESTS_DOCS_FILE_HTML := examples/human_readable.html

all: deps lint build test e2e-test
all: clean deps lint build test e2e-test

$(TMP_DIR):
mkdir -p $(TMP_DIR)
Expand All @@ -36,7 +38,8 @@ build:

e2e-test: build
$(PROMRUVAL_BIN) validate --config-file $(E2E_TESTS_VALIDATIONS_FILE) --config-file $(E2E_TESTS_ADDITIONAL_VALIDATIONS_FILE) $(E2E_TESTS_RULES_FILES)
$(PROMRUVAL_BIN) validation-docs --config-file $(E2E_TESTS_VALIDATIONS_FILE) --config-file $(E2E_TESTS_ADDITIONAL_VALIDATIONS_FILE)
$(PROMRUVAL_BIN) validation-docs --config-file $(E2E_TESTS_VALIDATIONS_FILE) --config-file $(E2E_TESTS_ADDITIONAL_VALIDATIONS_FILE) > $(E2E_TESTS_DOCS_FILE_MD)
$(PROMRUVAL_BIN) validation-docs --config-file $(E2E_TESTS_VALIDATIONS_FILE) --config-file $(E2E_TESTS_ADDITIONAL_VALIDATIONS_FILE) --output=html > $(E2E_TESTS_DOCS_FILE_HTML)

docker: build
docker build -t fusakla/promruval .
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ or [watch a lightning talk about it from PromCon](https://www.youtube.com/watch?
- and many more...

Validations are quite variable, so you can use them as you fit.
Full list of supported validations can be found [here](docs/validations.md).

**👉 Full list of supported validations can be found [here](docs/validations.md).**

In case of any missing, please create a feature request!

Expand Down Expand Up @@ -65,7 +66,7 @@ Prometheus rules validation tool.

Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
-c, --config-file=CONFIG-FILE ...
-c, --config-file=CONFIG-FILE ...
Path to validation config file. Can be passed multiple times, only validationRules will be reflected from the additional configs.
--debug Enable debug logging.

Expand All @@ -81,17 +82,17 @@ Commands:
validate [<flags>] <path>...
Validate Prometheus rule files using validation rules from config file.

-d, --disable-rule=DISABLE-RULE ...
-d, --disable-rule=DISABLE-RULE ...
Allows to disable any validation rules by it's name. Can be passed multiple times.
-e, --enable-rule=ENABLE-RULE ...
-e, --enable-rule=ENABLE-RULE ...
Only enable these validation rules. Can be passed multiple times.
-o, --output=[text,json,yaml] Format of the output.
--color Use color output.
validation-docs [<flags>]
Print human readable form of the validation rules from config file.
-o, --output=[text,markdown,html]
-o, --output=[text,markdown,html]
Format of the output.
```
Expand Down Expand Up @@ -132,7 +133,7 @@ customExcludeAnnotation: my_disable_annotation
prometheus:
# URL of the running prometheus instance to be used
url: https://foo.bar/
# OPTIONAL Skip TLS verification
# OPTIONAL Skip TLS verification
insecureSkipTlsVerify: false
# OPTIONAL Timeout for any request on the Prometheus instance
timeout: 30s
Expand Down Expand Up @@ -243,7 +244,8 @@ groups:
If you want more human readable validation summary (for a documentation or generating readable git pages)
you can use the `validation-docs` command, see the [usage](#usage).
It should print out more human readable form than the configuration file is
and supports multiple output formats.
and supports multiple output formats such as `text`, `markdown` and `HTML`.
See the examples for the output for [Markdown](./examples/human_readable.md) and [HTML](./examples/human_readable.html).

```bash
promruval validation-docs --config-file examples/validation.yaml --output=html
Expand Down
15 changes: 15 additions & 0 deletions docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
- [`expressionWithNoMetricName`](#expressionwithnometricname)
- [Alert](#alert)
- [`forIsNotLongerThan`](#forisnotlongerthan)
- [Others](#others)
- [`hasSourceTenantsForMetrics`](#hassourcetenantsformetrics)

## Labels

Expand Down Expand Up @@ -288,3 +290,16 @@ Fails if the alert uses longer `for` than the specified limit.
params:
limit: "1h"
```
## Others

### `hasSourceTenantsForMetrics`

Fails, if the rule uses metric, that matches the specified regular expression for any tenant, but does not have the tenant configured in the `source_tenants` of the rule group option the rule belongs to.

```yaml
params:
sourceTenants:
<tenant_name>: <metric_name_regexp> # The regexp will be fully anchored (surrounded by ^...$)
# Example:
# k8s: "kube_.*|container_.*"
```
52 changes: 52 additions & 0 deletions examples/human_readable.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

<h1>Validation rules</h1>

<h2><a href="#check-severity-label">check-severity-label</a></h2>
<ul>
<li>Alert has labels: <code>severity</code></li>
<li>Alert label <code>severity</code> has one of the allowed values: <code>info</code>,<code>warning</code>,<code>critical</code></li>
<li>Alert if rule has label <code>severity</code> with value <code>info</code> , it cannot have label <code>page</code></li>
<li>Alert expression can be successfully evaluated on the live Prometheus instance</li>
<li>Alert expression uses only labels that are actually present in Prometheus</li>
<li>Alert expression selectors actually matches any series in Prometheus</li>
<li>Alert expression does not use data older than <code>6h0m0s</code></li>
</ul>

<h2><a href="#check-team-label">check-team-label</a></h2>
<ul>
<li>Alert has labels: <code>xxx</code></li>
<li>Alert label <code>team</code> has one of the allowed values: <code>sre@company.com</code></li>
</ul>

<h2><a href="#check-playbook-annotation">check-playbook-annotation</a></h2>
<ul>
<li>Alert has any of these annotations: <code>playbook</code>,<code>link</code></li>
<li>Alert Annotation <code>link</code> is a valid URL and does not return HTTP status 404</li>
</ul>

<h2><a href="#check-alert-title">check-alert-title</a></h2>
<ul>
<li>Alert has all of these annotations: <code>title</code></li>
</ul>

<h2><a href="#check-prometheus-limitations">check-prometheus-limitations</a></h2>
<ul>
<li>All rules expression does not use data older than <code>6h0m0s</code></li>
<li>All rules does not use any of the <code>cluster</code>,<code>locality</code>,<code>prometheus-type</code>,<code>replica</code> labels is in its expression</li>
</ul>

<h2><a href="#check-source-tenants">check-source-tenants</a></h2>
<ul>
<li>All rules verifies if the rule group, the rule belongs to, has the required source_tenants configured, according to the mapping of metric names to tenants: <code>k8s</code>:<code>^container_.*|kube_.*$</code></li>
</ul>

<h2><a href="#check-metric-name">check-metric-name</a></h2>
<ul>
<li>Alert expression with no metric name</li>
</ul>

<h2><a href="#another-checks">another-checks</a></h2>
<ul>
<li>All rules labels does not have empty values</li>
</ul>

36 changes: 36 additions & 0 deletions examples/human_readable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Validation rules:

check-severity-label
- Alert has labels: `severity`
- Alert label `severity` has one of the allowed values: `info`,`warning`,`critical`
- Alert if rule has label `severity` with value `info` , it cannot have label `page`
- Alert expression can be successfully evaluated on the live Prometheus instance
- Alert expression uses only labels that are actually present in Prometheus
- Alert expression selectors actually matches any series in Prometheus
- Alert expression does not use data older than `6h0m0s`

check-team-label
- Alert has labels: `xxx`
- Alert label `team` has one of the allowed values: `sre@company.com`

check-playbook-annotation
- Alert has any of these annotations: `playbook`,`link`
- Alert Annotation `link` is a valid URL and does not return HTTP status 404

check-alert-title
- Alert has all of these annotations: `title`

check-prometheus-limitations
- All rules expression does not use data older than `6h0m0s`
- All rules does not use any of the `cluster`,`locality`,`prometheus-type`,`replica` labels is in its expression

check-source-tenants
- All rules verifies if the rule group, the rule belongs to, has the required source_tenants configured, according to the mapping of metric names to tenants: `k8s`:`^container_.*|kube_.*$`

check-metric-name
- Alert expression with no metric name

another-checks
- All rules labels does not have empty values

3 changes: 2 additions & 1 deletion examples/rules/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ groups:
disabled_validation_rules: check-team-label,check-prometheus-limitations

- name: testIgnoreValidationsInExpr
source_tenants: ["k8s"]
rules:
- alert: test
expr: |
# Comment before.
# Comment on the same line. ignore_validations: labelHasAllowedValue
# Comment after.
foo{
kube_pod_labels{
# ignore_validations: expressionSelectorsMatchesAnything, hasLabels
}
for: 1m
Expand Down
20 changes: 14 additions & 6 deletions examples/validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ validationRules:
validations:
- type: hasLabels
params:
labels: [ "severity" ]
labels: ["severity"]
- type: labelHasAllowedValue
params:
label: "severity"
allowedValues: [ "info", "warning", "critical" ]
allowedValues: ["info", "warning", "critical"]
- type: exclusiveLabels
params:
firstLabel: severity
Expand All @@ -35,7 +35,7 @@ validationRules:
validations:
- type: hasLabels
params:
labels: [ "xxx" ]
labels: ["xxx"]
- type: labelHasAllowedValue
params:
label: "team"
Expand All @@ -47,7 +47,7 @@ validationRules:
validations:
- type: hasAnyOfAnnotations
params:
annotations: [ "playbook", "link" ]
annotations: ["playbook", "link"]
- type: annotationIsValidURL
params:
annotation: "link"
Expand All @@ -58,7 +58,7 @@ validationRules:
validations:
- type: hasAnnotations
params:
annotations: [ "title" ]
annotations: ["title"]

- name: check-prometheus-limitations
scope: All rules
Expand All @@ -68,7 +68,15 @@ validationRules:
limit: "6h"
- type: expressionDoesNotUseLabels
params:
labels: [ "cluster", "locality", "prometheus-type", "replica" ]
labels: ["cluster", "locality", "prometheus-type", "replica"]

- name: check-source-tenants
scope: All rules
validations:
- type: hasSourceTenantsForMetrics
params:
sourceTenants:
k8s: "container_.*|kube_.*"

- name: check-metric-name
scope: Alert
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ require (
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.45.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v3 v3.0.1
gotest.tools v2.2.0+incompatible
Expand Down Expand Up @@ -55,14 +57,12 @@ require (
github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/prometheus v0.48.0
github.com/stretchr/testify v1.8.4 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
Expand Down
15 changes: 8 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/fusakla/promruval/v2/pkg/prometheus"
"github.com/fusakla/promruval/v2/pkg/report"
"github.com/fusakla/promruval/v2/pkg/validate"
"github.com/fusakla/promruval/v2/pkg/validationrule"
"github.com/fusakla/promruval/v2/pkg/validator"
log "github.com/sirupsen/logrus"
"gopkg.in/alecthomas/kingpin.v2"
Expand Down Expand Up @@ -55,22 +56,22 @@ func loadConfigFile(configFilePath string) (*config.Config, error) {
return validationConfig, nil
}

func validationRulesFromConfig(config *config.Config) ([]*validate.ValidationRule, error) {
var validationRules []*validate.ValidationRule
func validationRulesFromConfig(config *config.Config) ([]*validationrule.ValidationRule, error) {
var validationRules []*validationrule.ValidationRule
rulesIteration:
for _, rule := range config.ValidationRules {
for _, validationRule := range config.ValidationRules {
for _, disabledRule := range *disabledRules {
if disabledRule == rule.Name {
if disabledRule == validationRule.Name {
continue rulesIteration
}
}
for _, enabledRule := range *enabledRules {
if enabledRule != rule.Name {
if enabledRule != validationRule.Name {
continue rulesIteration
}
}
newRule := validate.NewValidationRule(rule.Name, rule.Scope)
for _, validatorConfig := range rule.Validations {
newRule := validationrule.New(validationRule.Name, validationRule.Scope)
for _, validatorConfig := range validationRule.Validations {
newValidator, err := validator.NewFromConfig(validatorConfig)
if err != nil {
return nil, fmt.Errorf("loading validator config: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package config

import (
"fmt"
"gopkg.in/yaml.v3"
"time"

"gopkg.in/yaml.v3"

"github.com/creasty/defaults"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

type ValidationRule interface {
Name() string
Scope() string
Scope() config.ValidationScope
ValidationTexts() []string
}

Expand Down
Loading

0 comments on commit 7d11bb1

Please sign in to comment.