Skip to content

Commit 978afc8

Browse files
pracuccigotjosh
andauthored
Add --label-excluded-rule-groups to cortextool rules prepare (#174)
* Add --label-excluded-rule-groups to cortextool rules prepare Signed-off-by: Marco Pracucci <[email protected]> * Fixed PR number in CHANGELOG Signed-off-by: Marco Pracucci <[email protected]> * Simplify CLI flag parsing Signed-off-by: Marco Pracucci <[email protected]> * Update pkg/commands/rules.go Signed-off-by: Marco Pracucci <[email protected]> Co-authored-by: gotjosh <[email protected]> * Addressed review feedback Signed-off-by: Marco Pracucci <[email protected]> Co-authored-by: gotjosh <[email protected]>
1 parent b618805 commit 978afc8

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Order should be `CHANGE`, `FEATURE`, `ENHANCEMENT`, and `BUGFIX`
44

55
## unreleased/master
66

7+
* [FEATURE] Add `--label-excluded-rule-groups` support to `cortextool rules prepare` command. #174
78
* [ENHANCEMENT] Upgrade the Go version used in build images and tests to golang 1.16.3 to match upstream Cortex. #165
89

910
## v0.9.0

pkg/commands/rules.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ type RuleCommand struct {
6767
ignoredNamespacesMap map[string]struct{}
6868

6969
// Prepare Rules Config
70-
InPlaceEdit bool
71-
AggregationLabel string
70+
InPlaceEdit bool
71+
AggregationLabel string
72+
AggregationLabelExcludedRuleGroups string
73+
aggregationLabelExcludedRuleGroupsList map[string]struct{}
7274

7375
// Lint Rules Config
7476
LintDryRun bool
@@ -202,6 +204,7 @@ func (r *RuleCommand) Register(app *kingpin.Application) {
202204
"edits the rule file in place",
203205
).Short('i').BoolVar(&r.InPlaceEdit)
204206
prepareCmd.Flag("label", "label to include as part of the aggregations.").Default(defaultPrepareAggregationLabel).Short('l').StringVar(&r.AggregationLabel)
207+
prepareCmd.Flag("label-excluded-rule-groups", "Comma separated list of rule group names to exclude when including the configured label to aggregations.").StringVar(&r.AggregationLabelExcludedRuleGroups)
205208

206209
// Lint Command
207210
lintCmd.Arg("rule-files", "The rule files to check.").ExistingFilesVar(&r.RuleFilesList)
@@ -271,6 +274,14 @@ func (r *RuleCommand) setupFiles() error {
271274
}
272275
}
273276

277+
// Set up rule groups excluded from label aggregation.
278+
r.aggregationLabelExcludedRuleGroupsList = map[string]struct{}{}
279+
for _, name := range strings.Split(r.AggregationLabelExcludedRuleGroups, ",") {
280+
if name = strings.TrimSpace(name); name != "" {
281+
r.aggregationLabelExcludedRuleGroupsList[name] = struct{}{}
282+
}
283+
}
284+
274285
for _, file := range strings.Split(r.RuleFiles, ",") {
275286
if file != "" {
276287
log.WithFields(log.Fields{
@@ -613,9 +624,15 @@ func (r *RuleCommand) prepare(k *kingpin.ParseContext) error {
613624
return errors.Wrap(err, "prepare operation unsuccessful, unable to parse rules files")
614625
}
615626

627+
// Do not apply the aggregation label to excluded rule groups.
628+
applyTo := func(group rwrulefmt.RuleGroup, rule rulefmt.RuleNode) bool {
629+
_, excluded := r.aggregationLabelExcludedRuleGroupsList[group.Name]
630+
return !excluded
631+
}
632+
616633
var count, mod int
617634
for _, ruleNamespace := range namespaces {
618-
c, m, err := ruleNamespace.AggregateBy(r.AggregationLabel)
635+
c, m, err := ruleNamespace.AggregateBy(r.AggregationLabel, applyTo)
619636
if err != nil {
620637
return err
621638
}

pkg/rules/rules.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,27 @@ func (r RuleNamespace) CheckRecordingRules(strict bool) int {
106106
}
107107

108108
// AggregateBy modifies the aggregation rules in groups to include a given Label.
109-
func (r RuleNamespace) AggregateBy(label string) (int, int, error) {
110-
// `count` represents the number of rules we evalated.
109+
// If the applyTo function is provided, the aggregation is applied only to rules
110+
// for which the applyTo function returns true.
111+
func (r RuleNamespace) AggregateBy(label string, applyTo func(group rwrulefmt.RuleGroup, rule rulefmt.RuleNode) bool) (int, int, error) {
112+
// `count` represents the number of rules we evaluated.
111113
// `mod` represents the number of rules we modified - a modification can either be a lint or adding the
112114
// label in the aggregation.
113115
var count, mod int
114116

115117
for i, group := range r.Groups {
116118
for j, rule := range group.Rules {
119+
// Skip it if the applyTo function returns false.
120+
if applyTo != nil && !applyTo(group, rule) {
121+
log.WithFields(log.Fields{
122+
"group": group.Name,
123+
"rule": getRuleName(rule),
124+
}).Debugf("skipped")
125+
126+
count++
127+
continue
128+
}
129+
117130
log.WithFields(log.Fields{"rule": getRuleName(rule)}).Debugf("evaluating...")
118131
exp, err := parser.ParseExpr(rule.Expr.Value)
119132
if err != nil {

pkg/rules/rules_test.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func TestAggregateBy(t *testing.T) {
1515
tt := []struct {
1616
name string
1717
rn RuleNamespace
18+
applyTo func(group rwrulefmt.RuleGroup, rule rulefmt.RuleNode) bool
1819
expectedExpr []string
1920
count, modified int
2021
expect error
@@ -140,20 +141,63 @@ func TestAggregateBy(t *testing.T) {
140141
expectedExpr: []string{`count by(cluster, node) (sum by(node, cpu, cluster) (node_cpu_seconds_total{job="default/node-exporter"} * on(namespace, instance, cluster) group_left(node) node_namespace_pod:kube_pod_info:))`},
141142
count: 1, modified: 1, expect: nil,
142143
},
144+
{
145+
name: "with a query skipped",
146+
rn: RuleNamespace{
147+
Groups: []rwrulefmt.RuleGroup{
148+
{
149+
RuleGroup: rulefmt.RuleGroup{
150+
Name: "CountAggregation",
151+
Rules: []rulefmt.RuleNode{
152+
{
153+
Alert: yaml.Node{
154+
Value: "CountAggregation",
155+
},
156+
Expr: yaml.Node{
157+
Value: `count by(namespace) (test_series) > 1`,
158+
},
159+
},
160+
},
161+
},
162+
}, {
163+
RuleGroup: rulefmt.RuleGroup{
164+
Name: "CountSkipped",
165+
Rules: []rulefmt.RuleNode{
166+
{
167+
Alert: yaml.Node{
168+
Value: "CountSkipped",
169+
},
170+
Expr: yaml.Node{
171+
Value: `count by(namespace) (test_series) > 1`,
172+
},
173+
},
174+
},
175+
},
176+
},
177+
},
178+
},
179+
applyTo: func(group rwrulefmt.RuleGroup, rule rulefmt.RuleNode) bool {
180+
return group.Name != "CountSkipped"
181+
},
182+
expectedExpr: []string{`count by(namespace, cluster) (test_series) > 1`, `count by(namespace) (test_series) > 1`},
183+
count: 2, modified: 1, expect: nil,
184+
},
143185
}
144186

145187
for _, tc := range tt {
146188
t.Run(tc.name, func(t *testing.T) {
147-
c, m, err := tc.rn.AggregateBy("cluster")
189+
c, m, err := tc.rn.AggregateBy("cluster", tc.applyTo)
148190

149191
require.Equal(t, tc.expect, err)
150192
assert.Equal(t, tc.count, c)
151193
assert.Equal(t, tc.modified, m)
152194

153195
// Only verify the PromQL expression if it has been modified
196+
expectedIdx := 0
154197
for _, g := range tc.rn.Groups {
155-
for i, r := range g.Rules {
156-
require.Equal(t, tc.expectedExpr[i], r.Expr.Value)
198+
for _, r := range g.Rules {
199+
require.Equal(t, tc.expectedExpr[expectedIdx], r.Expr.Value)
200+
expectedIdx++
157201
}
158202
}
159203
})

0 commit comments

Comments
 (0)