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 @@

check-severity-label

  • 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 {