diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0992f0d..25c04f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Fixed error messages for the `hasSourceTenantsForMetrics` and `expressionDoesNotUseIrate` validators.
+- Added new config option `additionalDetails` to all validators providing possibility to add custom details about the error and how to solve it.
+ Those will be appended to the validator error message in a parenthesis if provided.
+ Example configuration:
+ ```yaml
+ - name: expressionDoesNotUseIrate
+ additionalDetails: "Just do as I say!"
+ ```
+ Example output:
+ ```yaml
+ expressionDoesNotUseIrate: you should not use the `irate` function in rules, for more info see https://www.robustperception.io/avoid-irate-in-alerts/ (Just do as I say!)
+ ```
## [v2.9.0] - 2024-03-02
- Added new `All rules` validator `expressionIsWellFormatted` to check if rules are well formatted as `promtool promql format` would do.
diff --git a/README.md b/README.md
index 58c85f1..9d0d0ff 100644
--- a/README.md
+++ b/README.md
@@ -154,6 +154,8 @@ validationRules:
validations:
# Name of the validation type. See the /docs/validations.md.
- type: hasLabels
+ # Additional detaild that will be appended to the default error message. Useful to customize the error message.
+ additionalDetails: "We do this because ..."
# Parameters of the validation.
params:
labels: [ "severity" ]
diff --git a/examples/human_readable.html b/examples/human_readable.html
index 46db1ff..2970e0d 100644
--- a/examples/human_readable.html
+++ b/examples/human_readable.html
@@ -8,6 +8,7 @@
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 does not use irate
Alert expression selectors actually matches any series in Prometheus
Alert expression does not use data older than 6h0m0s
diff --git a/examples/human_readable.md b/examples/human_readable.md
index da54640..13db534 100644
--- a/examples/human_readable.md
+++ b/examples/human_readable.md
@@ -7,6 +7,7 @@ Validation rules:
- 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 does not use irate
- Alert expression selectors actually matches any series in Prometheus
- Alert expression does not use data older than `6h0m0s`
diff --git a/examples/validation.yaml b/examples/validation.yaml
index 354e306..537a761 100644
--- a/examples/validation.yaml
+++ b/examples/validation.yaml
@@ -25,6 +25,8 @@ validationRules:
timeSeriesLimit: 20
evaluationDurationLimit: 10s
- type: expressionUsesExistingLabels
+ - type: expressionDoesNotUseIrate
+ additionalDetails: "Just do as I say!"
- type: expressionSelectorsMatchesAnything
- type: expressionDoesNotUseOlderDataThan
params:
diff --git a/main.go b/main.go
index 4fe6910..a34cbcd 100644
--- a/main.go
+++ b/main.go
@@ -81,7 +81,7 @@ rulesIteration:
if newValidator == nil {
continue
}
- newRule.AddValidator(newValidator)
+ newRule.AddValidator(newValidator, validatorConfig.AdditionalDetails)
}
validationRules = append(validationRules, newRule)
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 92bea98..971b8b6 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -54,8 +54,9 @@ type ValidationRule struct {
}
type ValidatorConfig struct {
- ValidatorType string `yaml:"type"`
- Params yaml.Node `yaml:"params"`
+ ValidatorType string `yaml:"type"`
+ AdditionalDetails string `yaml:"additionalDetails"`
+ Params yaml.Node `yaml:"params"`
}
type ValidationScope string
diff --git a/pkg/validate/validate.go b/pkg/validate/validate.go
index dd6a2bc..8d4036e 100644
--- a/pkg/validate/validate.go
+++ b/pkg/validate/validate.go
@@ -5,7 +5,7 @@ import (
"fmt"
"io"
"os"
- "reflect"
+ "slices"
"strings"
"time"
@@ -20,6 +20,23 @@ import (
"gopkg.in/yaml.v3"
)
+func validateWithDetails(v validationrule.ValidatorWithDetails, group unmarshaler.RuleGroup, rule rulefmt.Rule, prometheusClient *prometheus.Client) []error {
+ var reportedError error
+ validatorName := v.Name()
+ additionalDetails := v.AdditionalDetails()
+ validationErrors := v.Validate(group, rule, prometheusClient)
+ errs := make([]error, 0, len(validationErrors))
+ for _, err := range validationErrors {
+ if additionalDetails != "" {
+ reportedError = fmt.Errorf("%s: %w (%s)", validatorName, err, additionalDetails)
+ } else {
+ reportedError = fmt.Errorf("%s: %w", validatorName, err)
+ }
+ errs = append(errs, reportedError)
+ }
+ return errs
+}
+
func Files(fileNames []string, validationRules []*validationrule.ValidationRule, excludeAnnotationName, disableValidationsComment string, prometheusClient *prometheus.Client) *report.ValidationReport {
validationReport := report.NewValidationReport()
for _, r := range validationRules {
@@ -58,10 +75,7 @@ func Files(fileNames []string, validationRules []*validationrule.ValidationRule,
continue
}
for _, v := range rule.Validators() {
- validatorName := reflect.TypeOf(v).Elem().Name()
- for _, err := range v.Validate(group, rulefmt.Rule{}, prometheusClient) {
- groupReport.Errors = append(groupReport.Errors, fmt.Errorf("%s: %w", validatorName, err))
- }
+ groupReport.Errors = append(groupReport.Errors, validateWithDetails(v, group, rulefmt.Rule{}, prometheusClient)...)
}
}
if len(groupReport.Errors) > 0 {
@@ -103,19 +117,11 @@ func Files(fileNames []string, validationRules []*validationrule.ValidationRule,
continue
}
for _, v := range rule.Validators() {
- skipValidator := false
- validatorName := reflect.TypeOf(v).Elem().Name()
- for _, dv := range disabledValidators {
- if validatorName == dv {
- skipValidator = true
- }
- }
- if skipValidator {
+ validatorName := v.Name()
+ if slices.Contains(disabledValidators, validatorName) {
continue
}
- for _, err := range v.Validate(group, originalRule, prometheusClient) {
- ruleReport.Errors = append(ruleReport.Errors, fmt.Errorf("%s: %w", validatorName, err))
- }
+ ruleReport.Errors = append(ruleReport.Errors, validateWithDetails(v, group, originalRule, prometheusClient)...)
log.Debugf("validation of file %s group %s using \"%s\" took %s", fileName, group.Name, v, time.Since(start))
}
if len(ruleReport.Errors) > 0 {
diff --git a/pkg/validationrule/validation_rule.go b/pkg/validationrule/validation_rule.go
index 955fbc3..418ff22 100644
--- a/pkg/validationrule/validation_rule.go
+++ b/pkg/validationrule/validation_rule.go
@@ -1,30 +1,56 @@
package validationrule
import (
+ "reflect"
+
"github.com/fusakla/promruval/v2/pkg/config"
"github.com/fusakla/promruval/v2/pkg/validator"
)
+type ValidatorWithDetails interface {
+ validator.Validator
+ AdditionalDetails() string
+ Name() string
+}
+
+type validatorWithAdditionalDetails struct {
+ validator.Validator
+ additionalDetails string
+ name string
+}
+
+func (v validatorWithAdditionalDetails) AdditionalDetails() string {
+ return v.additionalDetails
+}
+
+func (v validatorWithAdditionalDetails) Name() string {
+ return v.name
+}
+
func New(name string, scope config.ValidationScope) *ValidationRule {
return &ValidationRule{
name: name,
scope: scope,
- validators: []validator.Validator{},
+ validators: make([]ValidatorWithDetails, 0),
}
}
type ValidationRule struct {
name string
scope config.ValidationScope
- validators []validator.Validator
+ validators []ValidatorWithDetails
}
-func (r *ValidationRule) Validators() []validator.Validator {
+func (r *ValidationRule) Validators() []ValidatorWithDetails {
return r.validators
}
-func (r *ValidationRule) AddValidator(newValidator validator.Validator) {
- r.validators = append(r.validators, newValidator)
+func (r *ValidationRule) AddValidator(newValidator validator.Validator, additionalDetails string) {
+ r.validators = append(r.validators, &validatorWithAdditionalDetails{
+ Validator: newValidator,
+ additionalDetails: additionalDetails,
+ name: reflect.TypeOf(newValidator).Elem().Name(),
+ })
}
func (r *ValidationRule) Name() string {