Skip to content

Commit d80ad38

Browse files
committed
Merge branch 'master' into add-query-frontend-parse
2 parents 7e97822 + 707a9a5 commit d80ad38

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+21118
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## unreleased / master
44

5+
* [FEATURE] Add `rules prepare` command. It allows you add a label to PromQL aggregations and lint your expressions in rule files.
6+
57
## v0.1.4 / 2020-03-10
68

79
* [CHANGE] Ensure 404 deletes do not trigger an error for `rules` and `alertmanager` commands #28

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,37 @@ This command will load each rule group in the specified files and load them into
6868

6969
cortextool rules load ./example_rules_one.yaml ./example_rules_two.yaml ...
7070

71+
#### Rules Lint
72+
73+
This command lints a rules file. The linter's aim is not to verify correctness but just YAML and PromQL expression formatting within the rule file. This command always edits in place, you can use the dry run flag (`-n`) if you'd like to perform a trial run that does not make any changes.
74+
75+
cortextool rules lint -n ./example_rules_one.yaml ./example_rules_two.yaml ...
76+
77+
#### Rules Prepare
78+
79+
This command prepares a rules file for upload to Cortex. It lints all your PromQL expressions and adds an specific label to your PromQL query aggregations in the file. Unlike, the previous command this one does not interact with your Cortex cluster.
80+
81+
cortextool rules prepare -i ./example_rules_one.yaml ./example_rules_two.yaml ...
82+
83+
There are two flags of note for this command:
84+
- `-i` which allows you to edit in place, otherwise a a new file with a `.output` extension is created with the results of the run.
85+
- `-l` which allows you specify the label you want you add for your aggregations, it is `cluster` by default.
86+
87+
At the end of the run, the command tells you whenever the operation was a success in the form of
88+
89+
INFO[0000] SUCESS: 194 rules found, 0 modified expressions
90+
91+
It is important to note that a modification can be a PromQL expression lint or a label add to your aggregation.
92+
7193
## chunktool
7294

7395
This repo also contains the `chunktool`. A client meant to interact with chunks stored and indexed in cortex backends.
7496

75-
#### Chunk Delete
97+
##### Chunk Delete
7698

7799
The delete command currently cleans all index entries pointing to chunks in the specified index. Only bigtable and the v10 schema are currently fully supported. This will not delete the entire index entry, only the corresponding chunk entries within the index row.
78100

79-
#### Chunk Migrate
101+
##### Chunk Migrate
80102

81103
The migrate command helps with migrating chunks across cortex clusters. It also takes care of setting right index in the new cluster as per the specified schema config.
82104

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ require (
2020
github.com/prometheus/common v0.7.0
2121
github.com/prometheus/prometheus v1.8.2-0.20190918104050-8744afdd1ea0
2222
github.com/sirupsen/logrus v1.4.2
23+
github.com/stretchr/testify v1.4.0
2324
google.golang.org/api v0.8.0
2425
gopkg.in/alecthomas/kingpin.v2 v2.2.6
2526
gopkg.in/yaml.v2 v2.2.2
27+
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86
2628
sigs.k8s.io/yaml v1.1.0
2729
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
849849
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
850850
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
851851
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
852+
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
853+
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
852854
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
853855
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
854856
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

pkg/commands/rules.go

Lines changed: 169 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package commands
33
import (
44
"context"
55
"fmt"
6+
"io/ioutil"
67
"os"
78
"path/filepath"
89
"strings"
@@ -16,6 +17,11 @@ import (
1617
"github.com/grafana/cortextool/pkg/rules"
1718
log "github.com/sirupsen/logrus"
1819
"gopkg.in/alecthomas/kingpin.v2"
20+
yamlv3 "gopkg.in/yaml.v3"
21+
)
22+
23+
const (
24+
defaultPrepareAggregationLabel = "cluster"
1925
)
2026

2127
var (
@@ -31,7 +37,7 @@ var (
3137
})
3238
)
3339

34-
// RuleCommand configures and executes rule related cortex api operations
40+
// RuleCommand configures and executes rule related cortex operations
3541
type RuleCommand struct {
3642
ClientConfig client.Config
3743

@@ -41,7 +47,7 @@ type RuleCommand struct {
4147
Namespace string
4248
RuleGroup string
4349

44-
// Load Rules Configs
50+
// Load Rules Config
4551
RuleFilesList []string
4652
RuleFiles string
4753
RuleFilesPath string
@@ -50,38 +56,79 @@ type RuleCommand struct {
5056
IgnoredNamespaces string
5157
ignoredNamespacesMap map[string]struct{}
5258

59+
// Prepare Rules Config
60+
InPlaceEdit bool
61+
AggregationLabel string
62+
63+
// Lint Rules Config
64+
LintDryRun bool
65+
5366
DisableColor bool
5467
}
5568

5669
// Register rule related commands and flags with the kingpin application
5770
func (r *RuleCommand) Register(app *kingpin.Application) {
5871
rulesCmd := app.Command("rules", "View & edit rules stored in cortex.").PreAction(r.setup)
59-
rulesCmd.Flag("address", "Address of the cortex cluster, alternatively set CORTEX_ADDRESS.").Envar("CORTEX_ADDRESS").Required().StringVar(&r.ClientConfig.Address)
60-
rulesCmd.Flag("id", "Cortex tenant id, alternatively set CORTEX_TENANT_ID.").Envar("CORTEX_TENANT_ID").Required().StringVar(&r.ClientConfig.ID)
6172
rulesCmd.Flag("key", "Api key to use when contacting cortex, alternatively set $CORTEX_API_KEY.").Default("").Envar("CORTEX_API_KEY").StringVar(&r.ClientConfig.Key)
6273

63-
// List Rules Command
64-
rulesCmd.Command("list", "List the rules currently in the cortex ruler.").Action(r.listRules)
74+
// Register rule commands
75+
listCmd := rulesCmd.
76+
Command("list", "List the rules currently in the cortex ruler.").
77+
Action(r.listRules)
78+
printRulesCmd := rulesCmd.
79+
Command("print", "Print the rules currently in the cortex ruler.").
80+
Action(r.printRules)
81+
getRuleGroupCmd := rulesCmd.
82+
Command("get", "Retreive a rulegroup from the ruler.").
83+
Action(r.getRuleGroup)
84+
deleteRuleGroupCmd := rulesCmd.
85+
Command("delete", "Delete a rulegroup from the ruler.").
86+
Action(r.deleteRuleGroup)
87+
loadRulesCmd := rulesCmd.
88+
Command("load", "load a set of rules to a designated cortex endpoint").
89+
Action(r.loadRules)
90+
diffRulesCmd := rulesCmd.
91+
Command("diff", "diff a set of rules to a designated cortex endpoint").
92+
Action(r.diffRules)
93+
syncRulesCmd := rulesCmd.
94+
Command("sync", "sync a set of rules to a designated cortex endpoint").
95+
Action(r.syncRules)
96+
prepareCmd := rulesCmd.
97+
Command("prepare", "modifies a set of rules by including an specific label in aggregations.").
98+
Action(r.prepare)
99+
lintCmd := rulesCmd.
100+
Command("lint", "formats a set of rule files. It reorders keys alphabetically, uses 4 spaces as indentantion, and formats PromQL expressions to a single line.").
101+
Action(r.lint)
102+
103+
// Require Cortex cluster address and tentant ID on all these commands
104+
for _, c := range []*kingpin.CmdClause{listCmd, printRulesCmd, getRuleGroupCmd, deleteRuleGroupCmd, loadRulesCmd, diffRulesCmd, syncRulesCmd} {
105+
c.Flag("address", "Address of the cortex cluster, alternatively set CORTEX_ADDRESS.").
106+
Envar("CORTEX_ADDRESS").
107+
Required().
108+
StringVar(&r.ClientConfig.Address)
109+
110+
c.Flag("id", "Cortex tenant id, alternatively set CORTEX_TENANT_ID.").
111+
Envar("CORTEX_TENANT_ID").
112+
Required().
113+
StringVar(&r.ClientConfig.ID)
114+
}
65115

66116
// Print Rules Command
67-
printRulesCmd := rulesCmd.Command("print", "Print the rules currently in the cortex ruler.").Action(r.printRules)
68117
printRulesCmd.Flag("disable-color", "disable colored output").BoolVar(&r.DisableColor)
69118

70119
// Get RuleGroup Command
71-
getRuleGroupCmd := rulesCmd.Command("get", "Retreive a rulegroup from the ruler.").Action(r.getRuleGroup)
72120
getRuleGroupCmd.Arg("namespace", "Namespace of the rulegroup to retrieve.").Required().StringVar(&r.Namespace)
73121
getRuleGroupCmd.Arg("group", "Name of the rulegroup ot retrieve.").Required().StringVar(&r.RuleGroup)
74122
getRuleGroupCmd.Flag("disable-color", "disable colored output").BoolVar(&r.DisableColor)
75123

76124
// Delete RuleGroup Command
77-
deleteRuleGroupCmd := rulesCmd.Command("delete", "Delete a rulegroup from the ruler.").Action(r.deleteRuleGroup)
78125
deleteRuleGroupCmd.Arg("namespace", "Namespace of the rulegroup to delete.").Required().StringVar(&r.Namespace)
79126
deleteRuleGroupCmd.Arg("group", "Name of the rulegroup ot delete.").Required().StringVar(&r.RuleGroup)
80127

81-
loadRulesCmd := rulesCmd.Command("load", "load a set of rules to a designated cortex endpoint").Action(r.loadRules)
128+
// Load Rules Command
82129
loadRulesCmd.Arg("rule-files", "The rule files to check.").Required().ExistingFilesVar(&r.RuleFilesList)
83130

84-
diffRulesCmd := rulesCmd.Command("diff", "diff a set of rules to a designated cortex endpoint").Action(r.diffRules)
131+
// Diff Command
85132
diffRulesCmd.Flag("ignored-namespaces", "comma-separated list of namespaces to ignore during a diff.").StringVar(&r.IgnoredNamespaces)
86133
diffRulesCmd.Flag("rule-files", "The rule files to check. Flag can be reused to load multiple files.").StringVar(&r.RuleFiles)
87134
diffRulesCmd.Flag(
@@ -90,13 +137,35 @@ func (r *RuleCommand) Register(app *kingpin.Application) {
90137
).StringVar(&r.RuleFilesPath)
91138
diffRulesCmd.Flag("disable-color", "disable colored output").BoolVar(&r.DisableColor)
92139

93-
syncRulesCmd := rulesCmd.Command("sync", "sync a set of rules to a designated cortex endpoint").Action(r.syncRules)
140+
// Sync Command
94141
syncRulesCmd.Flag("ignored-namespaces", "comma-separated list of namespaces to ignore during a sync.").StringVar(&r.IgnoredNamespaces)
95142
syncRulesCmd.Flag("rule-files", "The rule files to check. Flag can be reused to load multiple files.").StringVar(&r.RuleFiles)
96143
syncRulesCmd.Flag(
97144
"rule-dirs",
98145
"Comma seperated list of paths to directories containing rules yaml files. Each file in a directory with a .yml or .yaml suffix will be parsed.",
99146
).StringVar(&r.RuleFilesPath)
147+
148+
// Prepare Command
149+
prepareCmd.Arg("rule-files", "The rule files to check.").Required().ExistingFilesVar(&r.RuleFilesList)
150+
prepareCmd.Flag("rule-files", "The rule files to check. Flag can be reused to load multiple files.").StringVar(&r.RuleFiles)
151+
prepareCmd.Flag(
152+
"rule-dirs",
153+
"Comma seperated list of paths to directories containing rules yaml files. Each file in a directory with a .yml or .yaml suffix will be parsed.",
154+
).StringVar(&r.RuleFilesPath)
155+
prepareCmd.Flag(
156+
"in-place",
157+
"edits the rule file in place",
158+
).Short('i').BoolVar(&r.InPlaceEdit)
159+
prepareCmd.Flag("label", "label to include as part of the aggregations.").Default(defaultPrepareAggregationLabel).Short('l').StringVar(&r.AggregationLabel)
160+
161+
// Lint Command
162+
lintCmd.Arg("rule-files", "The rule files to check.").Required().ExistingFilesVar(&r.RuleFilesList)
163+
lintCmd.Flag("rule-files", "The rule files to check. Flag can be reused to load multiple files.").StringVar(&r.RuleFiles)
164+
lintCmd.Flag(
165+
"rule-dirs",
166+
"Comma seperated list of paths to directories containing rules yaml files. Each file in a directory with a .yml or .yaml suffix will be parsed.",
167+
).StringVar(&r.RuleFilesPath)
168+
lintCmd.Flag("dry-run", "Performs a trial run that doesn't make any changes and (mostly) produces the same outpupt as a real run.").Short('n').BoolVar(&r.LintDryRun)
100169
}
101170

102171
func (r *RuleCommand) setup(k *kingpin.ParseContext) error {
@@ -420,3 +489,91 @@ func (r *RuleCommand) executeChanges(ctx context.Context, changes []rules.Namesp
420489
fmt.Printf("Sync Summary: %v Groups Created, %v Groups Updated, %v Groups Deleted\n", created, updated, deleted)
421490
return nil
422491
}
492+
493+
func (r *RuleCommand) prepare(k *kingpin.ParseContext) error {
494+
err := r.setupFiles()
495+
if err != nil {
496+
return errors.Wrap(err, "prepare operation unsuccessful, unable to load rules files")
497+
}
498+
499+
namespaces, err := rules.ParseFiles(r.RuleFilesList)
500+
if err != nil {
501+
return errors.Wrap(err, "prepare operation unsuccessful, unable to parse rules files")
502+
}
503+
504+
var count, mod int
505+
for _, ruleNamespace := range namespaces {
506+
c, m, err := ruleNamespace.AggregateBy(r.AggregationLabel)
507+
if err != nil {
508+
return err
509+
}
510+
511+
count += c
512+
mod += m
513+
}
514+
515+
// now, save all the files
516+
if err := save(namespaces, r.InPlaceEdit); err != nil {
517+
return err
518+
}
519+
520+
log.Infof("SUCCESS: %d rules found, %d modified expressions", count, mod)
521+
522+
return nil
523+
}
524+
525+
func (r *RuleCommand) lint(k *kingpin.ParseContext) error {
526+
err := r.setupFiles()
527+
if err != nil {
528+
return errors.Wrap(err, "prepare operation unsuccessful, unable to load rules files")
529+
}
530+
531+
namespaces, err := rules.ParseFiles(r.RuleFilesList)
532+
if err != nil {
533+
return errors.Wrap(err, "prepare operation unsuccessful, unable to parse rules files")
534+
}
535+
536+
var count, mod int
537+
for _, ruleNamespace := range namespaces {
538+
c, m, err := ruleNamespace.LintPromQLExpressions()
539+
if err != nil {
540+
return err
541+
}
542+
543+
count += c
544+
mod += m
545+
}
546+
547+
if !r.LintDryRun {
548+
// linting will always in-place edit unless is a dry-run.
549+
if err := save(namespaces, true); err != nil {
550+
return err
551+
}
552+
}
553+
554+
log.Infof("SUCCESS: %d rules found, %d linted expressions", count, mod)
555+
556+
return nil
557+
}
558+
559+
// save saves a set of rule files to to disk. You can specify whenever you want the
560+
// file(s) to be edited in-place.
561+
func save(nss map[string]rules.RuleNamespace, i bool) error {
562+
for _, ns := range nss {
563+
payload, err := yamlv3.Marshal(ns)
564+
if err != nil {
565+
return err
566+
}
567+
568+
filepath := ns.Filepath
569+
if !i {
570+
filepath = filepath + ".result"
571+
}
572+
573+
if err := ioutil.WriteFile(filepath, payload, 0644); err != nil {
574+
return err
575+
}
576+
}
577+
578+
return nil
579+
}

pkg/rules/parser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func ParseFiles(files []string) (map[string]RuleNamespace, error) {
3030
return nil, errFileReadError
3131
}
3232

33+
ns.Filepath = f
34+
3335
// Determine if the namespace is explicitly set. If not
3436
// the file name without the extension is used.
3537
namespace := ns.Namespace

0 commit comments

Comments
 (0)