From a70d5e743031f898958abb74a8f37544042d3fce Mon Sep 17 00:00:00 2001 From: Martin Chodur Date: Thu, 29 Feb 2024 00:47:03 +0100 Subject: [PATCH] feat: add ignoreTemplatedValues param to the ignoreTemplatedValues validator Signed-off-by: Martin Chodur --- CHANGELOG.md | 1 + docs/validations.md | 4 ++++ examples/human_readable.html | 2 +- examples/human_readable.md | 2 +- examples/rules/rules.yaml | 13 +++++++++++++ examples/validation.yaml | 1 + pkg/validator/labels.go | 25 +++++++++++++++++-------- pkg/validator/validator_test.go | 2 ++ 8 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b24fa1..0717972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Added new param `ignoreTemplatedValues` to the `labelHasAllowedValue` validator to ignore templated values in the label. - Added new validation rule scope `Group` to validate the rule group itself (not the rules in it). - Added new `Group` scope validator `hasAllowedEvaluationInterval` to check if the rule group has the `interval` in the configured range and possibility to enforce it to be configured. - CHANGED: The validator `allowedSourceTenants` is now allowed only in the `Group` scope validation rules. diff --git a/docs/validations.md b/docs/validations.md index c56d4ed..a257c33 100644 --- a/docs/validations.md +++ b/docs/validations.md @@ -89,6 +89,9 @@ params: Fails if rule label value is not one of the allowed values. If the `commaSeparatedValue` is set to true, the label value to true, the label value is split by a comma, and the distinct values are checked if valid. +Since the labels can be templated, but Promruval cannot tell if the resulting value will be valid, +there is the `ignoreTemplatedValues` option, that allows you to ignore the templated values. + > It's quite common to have well known severities for alerts which can be important even in the > Alertmanager routing tree. Ths is how you can make sure only the well-known severities are used. @@ -97,6 +100,7 @@ params: label: "foo" allowedValues: [ "foo", "bar" ] commaSeparatedValue: true + ignoreTemplatedValues: false ``` ### `nonEmptyLabels` diff --git a/examples/human_readable.html b/examples/human_readable.html index 55b37ff..3be6a39 100644 --- a/examples/human_readable.html +++ b/examples/human_readable.html @@ -15,7 +15,7 @@

check-severity-label

check-team-label

check-playbook-annotation

diff --git a/examples/human_readable.md b/examples/human_readable.md index fb79771..c4bfab0 100644 --- a/examples/human_readable.md +++ b/examples/human_readable.md @@ -12,7 +12,7 @@ Validation rules: check-team-label - Alert has labels: `xxx` - - Alert label `team` has one of the allowed values: `sre@company.com` + - Alert label `team` has one of the allowed values: `sre@company.com` (templated values are ignored) check-playbook-annotation - Alert has any of these annotations: `playbook`,`link` diff --git a/examples/rules/rules.yaml b/examples/rules/rules.yaml index 8f08a14..3cba55c 100644 --- a/examples/rules/rules.yaml +++ b/examples/rules/rules.yaml @@ -41,6 +41,19 @@ groups: # ignore_validations: expressionSelectorsMatchesAnything, hasLabels } for: 1m + labels: + team: "{{ .Labels.team }}" + annotations: + title: test alert + playbook: http://foo.bar/nonexisting/playbook + + - alert: test2 + expr: up == 0 + for: 1m + labels: + team: "{{ .Labels.team }}" + xxx: fooo + severity: critical annotations: title: test alert playbook: http://foo.bar/nonexisting/playbook diff --git a/examples/validation.yaml b/examples/validation.yaml index e2bb8c8..e51a1a3 100644 --- a/examples/validation.yaml +++ b/examples/validation.yaml @@ -41,6 +41,7 @@ validationRules: label: "team" allowedValues: - "sre@company.com" + ignoreTemplatedValues: true - name: check-playbook-annotation scope: Alert diff --git a/pkg/validator/labels.go b/pkg/validator/labels.go index 261f489..beaf5ac 100644 --- a/pkg/validator/labels.go +++ b/pkg/validator/labels.go @@ -143,9 +143,10 @@ func (h hasAnyOfLabels) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rule, _ * func newLabelHasAllowedValue(paramsConfig yaml.Node) (Validator, error) { params := struct { - Label string `yaml:"label"` - AllowedValues []string `yaml:"allowedValues"` - CommaSeparatedValue bool `yaml:"commaSeparatedValue"` + Label string `yaml:"label"` + AllowedValues []string `yaml:"allowedValues"` + CommaSeparatedValue bool `yaml:"commaSeparatedValue"` + IgnoreTemplatedValues bool `yaml:"ignoreTemplatedValues"` }{} if err := paramsConfig.Decode(¶ms); err != nil { return nil, err @@ -156,13 +157,14 @@ func newLabelHasAllowedValue(paramsConfig yaml.Node) (Validator, error) { if len(params.AllowedValues) == 0 { return nil, fmt.Errorf("missing allowedValues") } - return &labelHasAllowedValue{label: params.Label, allowedValues: params.AllowedValues, commaSeparatedValue: params.CommaSeparatedValue}, nil + return &labelHasAllowedValue{label: params.Label, allowedValues: params.AllowedValues, commaSeparatedValue: params.CommaSeparatedValue, ignoreTemplatedValues: params.IgnoreTemplatedValues}, nil } type labelHasAllowedValue struct { - label string - allowedValues []string - commaSeparatedValue bool + label string + allowedValues []string + commaSeparatedValue bool + ignoreTemplatedValues bool } func (h labelHasAllowedValue) String() string { @@ -170,7 +172,11 @@ func (h labelHasAllowedValue) String() string { if h.commaSeparatedValue { text = "split by comma " + text } - return fmt.Sprintf("label `%s` %s", h.label, text) + text = fmt.Sprintf("label `%s` %s", h.label, text) + if h.ignoreTemplatedValues { + text += " (templated values are ignored)" + } + return text } func (h labelHasAllowedValue) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rule, _ *prometheus.Client) []error { @@ -178,6 +184,9 @@ func (h labelHasAllowedValue) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rul if !ok { return []error{} } + if h.ignoreTemplatedValues && strings.Contains(ruleValue, "{{") { + return []error{} + } valuesToCheck := []string{ruleValue} if h.commaSeparatedValue { valuesToCheck = strings.Split(ruleValue, ",") diff --git a/pkg/validator/validator_test.go b/pkg/validator/validator_test.go index c7dc7fa..7b7737d 100644 --- a/pkg/validator/validator_test.go +++ b/pkg/validator/validator_test.go @@ -65,6 +65,8 @@ var testCases = []struct { {name: "ruleHasCsvLabelWithAllowedValue", validator: labelHasAllowedValue{label: "foo", allowedValues: []string{"bar"}, commaSeparatedValue: true}, rule: rulefmt.Rule{Labels: map[string]string{"foo": "xxx,bar"}}, expectedErrors: 0}, {name: "ruleDoesNotHaveLabelWithAllowedValue", validator: labelHasAllowedValue{label: "foo", allowedValues: []string{"bar"}}, rule: rulefmt.Rule{Labels: map[string]string{"foo": "xxx"}}, expectedErrors: 1}, {name: "ruleHasCsvLabelWithoutAllowedValue", validator: labelHasAllowedValue{label: "foo", allowedValues: []string{"bar"}, commaSeparatedValue: true}, rule: rulefmt.Rule{Labels: map[string]string{"foo": "xxx,yyy"}}, expectedErrors: 1}, + {name: "ruleHasTemplatedLabelAndCannotHave", validator: labelHasAllowedValue{label: "foo", allowedValues: []string{"bar"}, ignoreTemplatedValues: false}, rule: rulefmt.Rule{Labels: map[string]string{"foo": "{{ .Labels.foo }}"}}, expectedErrors: 1}, + {name: "ruleHasTemplatedLabelAndCannotHave", validator: labelHasAllowedValue{label: "foo", allowedValues: []string{"bar"}, ignoreTemplatedValues: true}, rule: rulefmt.Rule{Labels: map[string]string{"foo": "{{ .Labels.foo }}"}}, expectedErrors: 0}, // annotationHasAllowedValue {name: "ruleHasAnnotationWithAllowedValue", validator: annotationHasAllowedValue{annotation: "foo", allowedValues: []string{"bar"}}, rule: rulefmt.Rule{Annotations: map[string]string{"foo": "bar"}}, expectedErrors: 0},