Skip to content

Commit 9830696

Browse files
committed
feat: added thanos check rules command
Signed-off-by: Martin Chodur <[email protected]>
1 parent 8498d91 commit 9830696

File tree

6 files changed

+175
-0
lines changed

6 files changed

+175
-0
lines changed

cmd/thanos/check.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
8+
"github.com/go-kit/kit/log"
9+
thanosrule "github.com/improbable-eng/thanos/pkg/rule"
10+
"github.com/oklog/run"
11+
"github.com/opentracing/opentracing-go"
12+
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/prometheus/pkg/rulefmt"
14+
"gopkg.in/alecthomas/kingpin.v2"
15+
"gopkg.in/yaml.v2"
16+
)
17+
18+
func registerChecks(m map[string]setupFunc, app *kingpin.Application, name string) {
19+
cmd := app.Command(name, "Linting tools for Thanos")
20+
registerRulesCheck(m, cmd, name)
21+
return
22+
}
23+
24+
func registerRulesCheck(m map[string]setupFunc, root *kingpin.CmdClause, name string) {
25+
checkRulesCmd := root.Command("rules", "Check if the rule files are valid or not.")
26+
ruleFiles := checkRulesCmd.Arg(
27+
"rule-files",
28+
"The rule files to check.",
29+
).Required().ExistingFiles()
30+
31+
m[name+" rules"] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, _ opentracing.Tracer, _ bool) error {
32+
// Dummy actor to immediately kill the group after the run function returns.
33+
g.Add(func() error { return nil }, func(error) {})
34+
return checkRulesFiles(ruleFiles)
35+
}
36+
}
37+
38+
func checkRulesFiles(files *[]string) error {
39+
var failed error
40+
41+
for _, f := range *files {
42+
if n, errs := checkRules(f); errs != nil {
43+
fmt.Fprintln(os.Stderr, " FAILED:")
44+
for _, e := range errs {
45+
fmt.Fprintln(os.Stderr, e.Error())
46+
}
47+
failed = fmt.Errorf("Errors: %v", errs)
48+
} else {
49+
fmt.Printf(" SUCCESS: %d rules found\n", n)
50+
}
51+
fmt.Println()
52+
}
53+
if failed != nil {
54+
return failed
55+
}
56+
return nil
57+
}
58+
59+
func checkRules(filename string) (int, []error) {
60+
fmt.Println("Checking", filename)
61+
62+
b, err := ioutil.ReadFile(filename)
63+
if err != nil {
64+
return 0, []error{err}
65+
}
66+
67+
var rgs thanosrule.RuleGroups
68+
if err := yaml.Unmarshal(b, &rgs); err != nil {
69+
return 0, []error{err}
70+
}
71+
72+
// We need to convert Thanos rules to Prometheus rules so we can use their validation
73+
promRgs := thanosRuleGroupsToPromRuleGroups(rgs)
74+
if errs := promRgs.Validate(); errs != nil {
75+
return 0, errs
76+
}
77+
78+
numRules := 0
79+
for _, rg := range rgs.Groups {
80+
numRules += len(rg.Rules)
81+
}
82+
83+
return numRules, nil
84+
}
85+
86+
func thanosRuleGroupsToPromRuleGroups(ruleGroups thanosrule.RuleGroups) rulefmt.RuleGroups {
87+
promRuleGroups := rulefmt.RuleGroups{Groups: []rulefmt.RuleGroup{}}
88+
for _, g := range ruleGroups.Groups {
89+
group := rulefmt.RuleGroup{
90+
Name: g.Name,
91+
Interval: g.Interval,
92+
Rules: []rulefmt.Rule{},
93+
}
94+
for _, r := range g.Rules {
95+
group.Rules = append(
96+
group.Rules,
97+
rulefmt.Rule{
98+
Record: r.Record,
99+
Alert: r.Alert,
100+
Expr: r.Expr,
101+
For: r.For,
102+
Labels: r.Labels,
103+
Annotations: r.Annotations,
104+
},
105+
)
106+
}
107+
promRuleGroups.Groups = append(
108+
promRuleGroups.Groups,
109+
group,
110+
)
111+
}
112+
return promRuleGroups
113+
}

cmd/thanos/check_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/improbable-eng/thanos/pkg/testutil"
7+
)
8+
9+
func Test_checkRules(t *testing.T) {
10+
11+
validFiles := []string{
12+
"./testdata/rules-files/valid.yaml",
13+
}
14+
15+
invalidFiles := [][]string{
16+
[]string{"./testdata/rules-files/non-existing-file.yaml"},
17+
[]string{"./testdata/rules-files/invalid-yaml-format.yaml"},
18+
[]string{"./testdata/rules-files/invalid-rules-data.yaml"},
19+
}
20+
21+
testutil.Ok(t, checkRulesFiles(&validFiles))
22+
23+
for _, fn := range invalidFiles {
24+
testutil.NotOk(t, checkRulesFiles(&fn))
25+
}
26+
}

cmd/thanos/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func main() {
7979
registerBucket(cmds, app, "bucket")
8080
registerDownsample(cmds, app, "downsample")
8181
registerReceive(cmds, app, "receive")
82+
registerChecks(cmds, app, "check")
8283

8384
cmd, err := app.Parse(os.Args[1:])
8485
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
groups:
2+
- name: null
3+
partial_response_strategy: "warn"
4+
interval: 2m
5+
rules:
6+
- alert: TestAlert
7+
partial_response_strategy: "warn"
8+
expr: 1
9+
labels:
10+
key: value
11+
annotations:
12+
key: value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
groups:
2+
- name: test
3+
invalid_yaml_reason
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
groups:
2+
- name: test-alert-group
3+
partial_response_strategy: "warn"
4+
interval: 2m
5+
rules:
6+
- alert: TestAlert
7+
partial_response_strategy: "warn"
8+
expr: 1
9+
labels:
10+
key: value
11+
annotations:
12+
key: value
13+
14+
- name: test-rule-group
15+
partial_response_strategy: "warn"
16+
interval: 2m
17+
rules:
18+
- record: test_metric
19+
expr: 1
20+
partial_response_strategy: "warn"

0 commit comments

Comments
 (0)