Skip to content

Commit

Permalink
feat: add new validator maxRulesPerGroup (#61)
Browse files Browse the repository at this point in the history
* feat: add new validator maxRulesPerGroup

Signed-off-by: Martin Chodur <[email protected]>

* docs: update docs

Signed-off-by: Martin Chodur <[email protected]>

---------

Signed-off-by: Martin Chodur <[email protected]>
  • Loading branch information
FUSAKLA authored Mar 1, 2024
1 parent 02d2b9c commit 6e5970f
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
- Added new validator `expressionIsWellFormatted` to check if rules are well formatted as `promtool promql format` would do.
- Added new validator `maxRulesPerGroup` to check if the number of rules in the group is not exceeding the limit.

## [v2.8.1] - 2024-02-29
- Fixed param validation of the `hasAllowedEvaluationInterval` validator, if the `maximum` was not set.
Expand Down
10 changes: 10 additions & 0 deletions docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
- [`hasValidSourceTenants`](#hasvalidsourcetenants)
- [`hasAllowedEvaluationInterval`](#hasallowedevaluationinterval)
- [`hasValidPartialResponseStrategy`](#hasvalidpartialresponsestrategy)
- [`maxRulesPerGroup`](#maxrulespergroup)

## Labels

Expand Down Expand Up @@ -361,3 +362,12 @@ To enforce the `partial_response_strategy` to be set, set the `mustBeSet` to tru
params:
mustBeSet: false
```

### `maxRulesPerGroup`

Fails if the rule group has more rules than the specified limit.

```yaml
params:
limit: 10
```
1 change: 1 addition & 0 deletions examples/human_readable.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ <h2><a href="#check-groups">check-groups</a></h2>
<li>Group does not have other <code>source_tenants</code> than: <code>tenant1</code>, <code>tenant2</code>, <code>k8s</code></li>
<li>Group evaluation interval is between <code>20s</code> and <code>106751d23h47m16s854ms</code> if set</li>
<li>Group has valid partial_response_strategy (one of <code>warn</code> or <code>abort</code>) if set</li>
<li>Group has at most 10 rules</li>
</ul>

<h2><a href="#check-formatting">check-formatting</a></h2>
Expand Down
1 change: 1 addition & 0 deletions examples/human_readable.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Validation rules:
- Group does not have other `source_tenants` than: `tenant1`, `tenant2`, `k8s`
- Group evaluation interval is between `20s` and `106751d23h47m16s854ms` if set
- Group has valid partial_response_strategy (one of `warn` or `abort`) if set
- Group has at most 10 rules

check-formatting
- All rules expression is well formatted as would `promtool promql format` do or similar online tool such as https://o11y.tools/promqlparser/
Expand Down
3 changes: 3 additions & 0 deletions examples/validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ validationRules:
minimum: "20s"
intervalMustBeSet: false
- type: hasValidPartialStrategy
- type: maxRulesPerGroup
params:
limit: 10

- name: check-formatting
scope: All rules
Expand Down
10 changes: 5 additions & 5 deletions pkg/unmarshaler/unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ type RuleGroup struct {
Interval model.Duration `yaml:"interval,omitempty"`
PartialResponseStrategy string `yaml:"partial_response_strategy,omitempty"`
SourceTenants []string `yaml:"source_tenants,omitempty"`
Rules []ruleWithComment `yaml:"rules"`
Rules []RuleWithComment `yaml:"rules"`
}

type ruleWithComment struct {
type RuleWithComment struct {
node yaml.Node
rule rulefmt.RuleNode
}

func (r *ruleWithComment) OriginalRule() rulefmt.Rule {
func (r *RuleWithComment) OriginalRule() rulefmt.Rule {
return rulefmt.Rule{
Record: r.rule.Record.Value,
Alert: r.rule.Alert.Value,
Expand All @@ -44,7 +44,7 @@ func (r *ruleWithComment) OriginalRule() rulefmt.Rule {
}
}

func (r *ruleWithComment) UnmarshalYAML(value *yaml.Node) error {
func (r *RuleWithComment) UnmarshalYAML(value *yaml.Node) error {
err := value.Decode(&r.node)
if err != nil {
return err
Expand All @@ -56,7 +56,7 @@ func (r *ruleWithComment) UnmarshalYAML(value *yaml.Node) error {
return nil
}

func (r *ruleWithComment) DisabledValidators(commentPrefix string) []string {
func (r *RuleWithComment) DisabledValidators(commentPrefix string) []string {
commentPrefix += ":"
var disabledValidators []string
allComments := strings.Split(r.node.HeadComment, "\n")
Expand Down
1 change: 1 addition & 0 deletions pkg/validator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var registeredGroupValidators = map[string]validatorCreator{
"hasAllowedSourceTenants": newHasAllowedSourceTenants,
"hasAllowedEvaluationInterval": newHasAllowedEvaluationInterval,
"hasValidPartialStrategy": newHasValidPartialStrategy,
"maxRulesPerGroup": newMaxRulesPerGroup,
}

var registeredValidators = map[string]validatorCreator{}
Expand Down
25 changes: 25 additions & 0 deletions pkg/validator/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,28 @@ func (h hasValidPartialStrategy) Validate(group unmarshaler.RuleGroup, _ rulefmt
}
return []error{}
}

func newMaxRulesPerGroup(paramsConfig yaml.Node) (Validator, error) {
params := struct {
Limit int `yaml:"limit"`
}{}
if err := paramsConfig.Decode(&params); err != nil {
return nil, err
}
return &maxRulesPerGroup{limit: params.Limit}, nil
}

type maxRulesPerGroup struct {
limit int
}

func (h maxRulesPerGroup) String() string {
return fmt.Sprintf("has at most %d rules", h.limit)
}

func (h maxRulesPerGroup) Validate(group unmarshaler.RuleGroup, _ rulefmt.Rule, _ *prometheus.Client) []error {
if len(group.Rules) > h.limit {
return []error{fmt.Errorf("group has %d rules, maximum is %d", len(group.Rules), h.limit)}
}
return []error{}
}
4 changes: 4 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ var testCases = []struct {
{name: "invalidExpression", validator: expressionIsWellFormatted{showFormatted: true}, rule: rulefmt.Rule{Expr: `up == 1`}, expectedErrors: 1},
{name: "validWithCommentThatShouldBeIgnored", validator: expressionIsWellFormatted{showFormatted: true}, rule: rulefmt.Rule{Expr: `up == 1 # fooo`}, expectedErrors: 0},
{name: "invalidButWithCommentAndShouldBeSkipped", validator: expressionIsWellFormatted{showFormatted: true, skipExpressionsWithComments: true}, rule: rulefmt.Rule{Expr: `up == 1 # fooo`}, expectedErrors: 0},

// maxRulesPerGroup
{name: "allowedNumberOfGroups", validator: maxRulesPerGroup{limit: 2}, group: unmarshaler.RuleGroup{Rules: []unmarshaler.RuleWithComment{{}, {}}}, expectedErrors: 0},
{name: "tooManyRules", validator: maxRulesPerGroup{limit: 1}, group: unmarshaler.RuleGroup{Rules: []unmarshaler.RuleWithComment{{}, {}}}, expectedErrors: 1},
}

func Test(t *testing.T) {
Expand Down

0 comments on commit 6e5970f

Please sign in to comment.