Skip to content

Commit

Permalink
Add support for parser schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
prymitive committed Dec 3, 2024
1 parent b114822 commit 468f728
Show file tree
Hide file tree
Showing 32 changed files with 533 additions and 104 deletions.
3 changes: 3 additions & 0 deletions cmd/pint/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/cloudflare/pint/internal/discovery"
"github.com/cloudflare/pint/internal/git"
"github.com/cloudflare/pint/internal/log"
"github.com/cloudflare/pint/internal/parser"
)

func BenchmarkFindEntries(b *testing.B) {
Expand All @@ -24,6 +25,7 @@ func BenchmarkFindEntries(b *testing.B) {
finder := discovery.NewGlobFinder(
[]string{"bench/rules"},
git.NewPathFilter(nil, nil, nil),
parser.PrometheusSchema,
)
for n := 0; n < b.N; n++ {
_, _ = finder.Find()
Expand All @@ -36,6 +38,7 @@ func BenchmarkCheckRules(b *testing.B) {
finder := discovery.NewGlobFinder(
[]string{"bench/rules"},
git.NewPathFilter(nil, nil, nil),
parser.PrometheusSchema,
)
entries, err := finder.Find()
if err != nil {
Expand Down
13 changes: 11 additions & 2 deletions cmd/pint/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/cloudflare/pint/internal/config"
"github.com/cloudflare/pint/internal/discovery"
"github.com/cloudflare/pint/internal/git"
"github.com/cloudflare/pint/internal/parser"
"github.com/cloudflare/pint/internal/reporter"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -100,13 +101,14 @@ func actionCI(c *cli.Context) error {
config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
)
schema := parseSchema(meta.cfg.Parser.Schema)

entries, err = discovery.NewGlobFinder([]string{"*"}, filter).Find()
entries, err = discovery.NewGlobFinder([]string{"*"}, filter, schema).Find()
if err != nil {
return err
}

entries, err = discovery.NewGitBranchFinder(git.RunGit, filter, baseBranch, meta.cfg.CI.MaxCommits).Find(entries)
entries, err = discovery.NewGitBranchFinder(git.RunGit, filter, baseBranch, meta.cfg.CI.MaxCommits, schema).Find(entries)
if err != nil {
return err
}
Expand Down Expand Up @@ -374,3 +376,10 @@ func detectGithubActions(gh *config.GitHub) *config.GitHub {
}
return gh
}

func parseSchema(s string) parser.Schema {
if s == config.SchemaThanos {
return parser.ThanosSchema
}
return parser.PrometheusSchema
}
13 changes: 8 additions & 5 deletions cmd/pint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,14 @@ func actionLint(c *cli.Context) error {
}

slog.Info("Finding all rules to check", slog.Any("paths", paths))
finder := discovery.NewGlobFinder(paths, git.NewPathFilter(
config.MustCompileRegexes(meta.cfg.Parser.Include...),
config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
))
finder := discovery.NewGlobFinder(
paths,
git.NewPathFilter(
config.MustCompileRegexes(meta.cfg.Parser.Include...),
config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
),
parseSchema(meta.cfg.Parser.Schema))
entries, err := finder.Find()
if err != nil {
return err
Expand Down
27 changes: 27 additions & 0 deletions cmd/pint/tests/0203_parser_schema_prom_err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
! exec pint --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
level=WARN msg="Failed to parse file content" err="error at line 7: partial_response_strategy is only valid when parser is configured to use the Thanos rule schema" path=rules/1.yml lines=1-9
rules/1.yml:7 Fatal: partial_response_strategy is only valid when parser is configured to use the Thanos rule schema (yaml/parse)
7 | partial_response_strategy: warn

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
-- rules/1.yml --
groups:
- name: foo
rules:
- alert: foo
expr: up == 0
- record: bar
partial_response_strategy: warn
expr: sum(up)

-- .pint.hcl --
parser {
schema = "prometheus"
}
26 changes: 26 additions & 0 deletions cmd/pint/tests/0204_parser_schema_thanos_err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
! exec pint --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
rules/1.yml:7 Fatal: This rule is not a valid Prometheus rule: `invalid partial_response_strategy value: bob`. (yaml/parse)
7 | partial_response_strategy: bob

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
-- rules/1.yml --
groups:
- name: foo
rules:
- alert: foo
expr: up == 0
- record: bar
partial_response_strategy: bob
expr: sum(up)

-- .pint.hcl --
parser {
schema = "thanos"
}
22 changes: 22 additions & 0 deletions cmd/pint/tests/0205_parser_schema_thanos_ok.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
exec pint --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
-- rules/1.yml --
groups:
- name: foo
rules:
- alert: foo
partial_response_strategy: warn
expr: up == 0
- record: bar
partial_response_strategy: abort
expr: sum(up)

-- .pint.hcl --
parser {
schema = "thanos"
}
21 changes: 21 additions & 0 deletions cmd/pint/tests/0206_parser_schema_err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
! exec pint --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=ERROR msg="Fatal error" err="failed to load config file \".pint.hcl\": unsupported parser scheme: bogus"
-- rules/1.yml --
groups:
- name: foo
rules:
- alert: foo
expr: up == 0
- record: bar
partial_response_strategy: bob
expr: sum(up)

-- .pint.hcl --
parser {
schema = "bogus"
}
25 changes: 16 additions & 9 deletions cmd/pint/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cloudflare/pint/internal/config"
"github.com/cloudflare/pint/internal/discovery"
"github.com/cloudflare/pint/internal/git"
"github.com/cloudflare/pint/internal/parser"
"github.com/cloudflare/pint/internal/promapi"
"github.com/cloudflare/pint/internal/reporter"

Expand Down Expand Up @@ -202,10 +203,12 @@ func actionWatch(c *cli.Context, meta actionMeta, f pathFinderFunc) error {
return err
}

schema := parseSchema(meta.cfg.Parser.Schema)

// start timer to run every $interval
ack := make(chan bool, 1)
mainCtx, mainCancel := context.WithCancel(context.WithValue(context.Background(), config.CommandKey, config.WatchCommand))
stop := startTimer(mainCtx, meta.workers, meta.isOffline, gen, interval, ack, collector)
stop := startTimer(mainCtx, meta.workers, meta.isOffline, gen, schema, interval, ack, collector)

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
Expand All @@ -229,7 +232,7 @@ func actionWatch(c *cli.Context, meta actionMeta, f pathFinderFunc) error {
return nil
}

func startTimer(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, interval time.Duration, ack chan bool, collector *problemCollector) chan bool {
func startTimer(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, schema parser.Schema, interval time.Duration, ack chan bool, collector *problemCollector) chan bool {
ticker := time.NewTicker(time.Second)
stop := make(chan bool, 1)
wasBootstrapped := false
Expand All @@ -243,7 +246,7 @@ func startTimer(ctx context.Context, workers int, isOffline bool, gen *config.Pr
ticker.Reset(interval)
wasBootstrapped = true
}
if err := collector.scan(ctx, workers, isOffline, gen); err != nil {
if err := collector.scan(ctx, workers, isOffline, gen, schema); err != nil {
slog.Error("Got an error when running checks", slog.Any("err", err))
}
checkIterationsTotal.Inc()
Expand Down Expand Up @@ -301,18 +304,22 @@ func newProblemCollector(cfg config.Config, f pathFinderFunc, minSeverity checks
}
}

func (c *problemCollector) scan(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator) error {
func (c *problemCollector) scan(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, schema parser.Schema) error {
paths, err := c.finder(ctx)
if err != nil {
return fmt.Errorf("failed to get the list of paths to check: %w", err)
}

slog.Info("Finding all rules to check", slog.Any("paths", paths))
entries, err := discovery.NewGlobFinder(paths, git.NewPathFilter(
config.MustCompileRegexes(c.cfg.Parser.Include...),
config.MustCompileRegexes(c.cfg.Parser.Exclude...),
config.MustCompileRegexes(c.cfg.Parser.Relaxed...),
)).Find()
entries, err := discovery.NewGlobFinder(
paths,
git.NewPathFilter(
config.MustCompileRegexes(c.cfg.Parser.Include...),
config.MustCompileRegexes(c.cfg.Parser.Exclude...),
config.MustCompileRegexes(c.cfg.Parser.Relaxed...),
),
schema,
).Find()
if err != nil {
return err
}
Expand Down
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## v0.69.0

### Added

- Added `schema` option to the `parser` configuration block for setting rule validation
schema. Default value is `prometheus` and tells pint to expect rules with the schema
expected by Prometheus itself. If you use pint to validate rules loaded into Thanos Rule
component then set `schema` to `thanos` in your pint config file:

```js
parser {
schema = "thanos"
}
```

## v0.68.0

### Added
Expand Down
9 changes: 9 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,21 @@ Syntax:

```js
parser {
schema = "prometheus|thanos"
include = [ "(.*)", ... ]
exclude = [ "(.*)", ... ]
relaxed = [ "(.*)", ... ]
}
```

- `schema` - rule file schema to use, valid values are `prometheus` and `thanos`.
Setting it to `prometheus` means that pint will assume that all rules have the schema
as defined in [alerting rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/)
and [recording rules](https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/)
Prometheus docs.
Setting it to `thanos` will tell pint to use the schema as defined
in [Thanos Rule](https://thanos.io/tip/components/rule.md/) docs.
Default value is `prometheus`.
- `include` - list of file patterns to check when running checks. Only files
matching those regexp rules will be checked, other modified files will be ignored.
- `exclude` - list of file patterns to ignore when running checks.
Expand Down
2 changes: 1 addition & 1 deletion internal/checks/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func runTests(t *testing.T, testCases []checkTest) {
}

func parseContent(content string) (entries []discovery.Entry, err error) {
p := parser.NewParser(false)
p := parser.NewParser(false, parser.PrometheusSchema)
rules, err := p.Parse([]byte(content))
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/checks/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestTemplatedRegexpExpand(t *testing.T) {
}

func newMustRule(content string) parser.Rule {
p := parser.NewParser(false)
p := parser.NewParser(false, parser.PrometheusSchema)
rules, err := p.Parse([]byte(content))
if err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func TestSetDisabledChecks(t *testing.T) {
}

func newRule(t *testing.T, content string) parser.Rule {
p := parser.NewParser(false)
p := parser.NewParser(false, parser.PrometheusSchema)
rules, err := p.Parse([]byte(content))
if err != nil {
t.Error(err)
Expand Down
21 changes: 21 additions & 0 deletions internal/config/parser.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
package config

import (
"fmt"
"regexp"
)

const (
SchemaPrometheus = "prometheus"
SchemaThanos = "thanos"
)

type Parser struct {
Schema string `hcl:"schema,optional" json:"schema,omitempty"`
Relaxed []string `hcl:"relaxed,optional" json:"relaxed,omitempty"`
Include []string `hcl:"include,optional" json:"include,omitempty"`
Exclude []string `hcl:"exclude,optional" json:"exclude,omitempty"`
}

func (p Parser) getSchema() string {
if p.Schema == "" {
return SchemaPrometheus
}
return p.Schema
}

func (p Parser) validate() error {
switch s := p.getSchema(); s {
case SchemaPrometheus:
case SchemaThanos:
default:
return fmt.Errorf("unsupported parser scheme: %s", s)
}

for _, pattern := range p.Relaxed {
_, err := regexp.Compile(pattern)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions internal/config/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ func TestParserSettings(t *testing.T) {
},
err: errors.New("error parsing regexp: invalid nested repetition operator: `++`"),
},
{
conf: Parser{
Schema: SchemaPrometheus,
},
},
{
conf: Parser{
Schema: SchemaThanos,
},
},
{
conf: Parser{
Schema: "xxx",
},
err: errors.New("unsupported parser scheme: xxx"),
},
}

for _, tc := range testCases {
Expand Down
4 changes: 2 additions & 2 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type Entry struct {
State ChangeType
}

func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (entries []Entry, err error) {
func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool, schema parser.Schema) (entries []Entry, err error) {
content, fileComments, err := parser.ReadContent(r)
if err != nil {
return nil, err
Expand Down Expand Up @@ -153,7 +153,7 @@ func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (ent
return entries, nil
}

p := parser.NewParser(isStrict)
p := parser.NewParser(isStrict, schema)
rules, err := p.Parse(content.Body)
if err != nil {
slog.Warn(
Expand Down
Loading

0 comments on commit 468f728

Please sign in to comment.