Skip to content

Commit

Permalink
Run checks against symlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
prymitive committed Sep 16, 2022
1 parent b2706b4 commit c6e9719
Show file tree
Hide file tree
Showing 26 changed files with 479 additions and 71 deletions.
3 changes: 2 additions & 1 deletion cmd/pint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ func verifyOwners(entries []discovery.Entry) (reports []reporter.Report) {
continue
}
reports = append(reports, reporter.Report{
Path: entry.Path,
ReportedPath: entry.ReportedPath,
SourcePath: entry.SourcePath,
ModifiedLines: entry.ModifiedLines,
Rule: entry.Rule,
Problem: checks.Problem{
Expand Down
23 changes: 13 additions & 10 deletions cmd/pint/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,21 @@ func checkRules(ctx context.Context, workers int, cfg config.Config, entries []d
if entry.Rule.RecordingRule != nil {
rulesParsedTotal.WithLabelValues(config.RecordingRuleType).Inc()
log.Debug().
Str("path", entry.Path).
Str("path", entry.SourcePath).
Str("record", entry.Rule.RecordingRule.Record.Value.Value).
Str("lines", output.FormatLineRangeString(entry.Rule.Lines())).
Msg("Found recording rule")
}
if entry.Rule.AlertingRule != nil {
rulesParsedTotal.WithLabelValues(config.AlertingRuleType).Inc()
log.Debug().
Str("path", entry.Path).
Str("path", entry.SourcePath).
Str("alert", entry.Rule.AlertingRule.Alert.Value.Value).
Str("lines", output.FormatLineRangeString(entry.Rule.Lines())).
Msg("Found alerting rule")
}

checkList := cfg.GetChecksForRule(ctx, entry.Path, entry.Rule)
checkList := cfg.GetChecksForRule(ctx, entry.SourcePath, entry.Rule)
for _, check := range checkList {
checkIterationChecks.Inc()
check := check
Expand All @@ -122,7 +122,7 @@ func checkRules(ctx context.Context, workers int, cfg config.Config, entries []d
default:
if entry.Rule.Error.Err != nil {
log.Debug().
Str("path", entry.Path).
Str("path", entry.SourcePath).
Str("lines", output.FormatLineRangeString(entry.Rule.Lines())).
Msg("Found invalid rule")
rulesParsedTotal.WithLabelValues(config.InvalidRuleType).Inc()
Expand Down Expand Up @@ -163,7 +163,8 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
switch {
case errors.Is(job.entry.PathError, discovery.ErrFileIsIgnored):
results <- reporter.Report{
Path: job.entry.Path,
ReportedPath: job.entry.ReportedPath,
SourcePath: job.entry.SourcePath,
ModifiedLines: job.entry.ModifiedLines,
Problem: checks.Problem{
Lines: job.entry.ModifiedLines,
Expand All @@ -176,7 +177,8 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
case job.entry.PathError != nil:
line, e := tryDecodingYamlError(job.entry.PathError)
results <- reporter.Report{
Path: job.entry.Path,
ReportedPath: job.entry.ReportedPath,
SourcePath: job.entry.SourcePath,
ModifiedLines: job.entry.ModifiedLines,
Problem: checks.Problem{
Lines: []int{line},
Expand All @@ -188,7 +190,8 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
}
case job.entry.Rule.Error.Err != nil:
results <- reporter.Report{
Path: job.entry.Path,
ReportedPath: job.entry.ReportedPath,
SourcePath: job.entry.SourcePath,
ModifiedLines: job.entry.ModifiedLines,
Rule: job.entry.Rule,
Problem: checks.Problem{
Expand All @@ -203,11 +206,11 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
default:
start := time.Now()
problems := job.check.Check(ctx, job.entry.Rule, job.allEntries)
duration := time.Since(start)
checkDuration.WithLabelValues(job.check.Reporter()).Observe(duration.Seconds())
checkDuration.WithLabelValues(job.check.Reporter()).Observe(time.Since(start).Seconds())
for _, problem := range problems {
results <- reporter.Report{
Path: job.entry.Path,
ReportedPath: job.entry.ReportedPath,
SourcePath: job.entry.SourcePath,
ModifiedLines: job.entry.ModifiedLines,
Rule: job.entry.Rule,
Problem: problem,
Expand Down
File renamed without changes.
107 changes: 107 additions & 0 deletions cmd/pint/tests/0094_rule_file_symlink_bb.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
http response prometheus1m /api/v1/status/flags 200 {"status":"success","data":{"--storage.tsdb.retention.time": "1d"}}
http response prometheus1m /api/v1/metadata 200 {"status":"success","data":{}}
http response prometheus1m /api/v1/status/config 200 {"status":"success","data":{"yaml":"global:\n scrape_interval: 1m\n"}}
http response prometheus1m /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[]}}
http response prometheus1m /api/v1/query 200 {"status":"success","data":{"resultType":"vector","result":[]}}
http start prometheus1m 127.0.0.1:1094

http response prometheus5m /api/v1/status/flags 200 {"status":"success","data":{"--storage.tsdb.retention.time": "1d"}}
http response prometheus5m /api/v1/metadata 200 {"status":"success","data":{}}
http response prometheus5m /api/v1/status/config 200 {"status":"success","data":{"yaml":"global:\n scrape_interval: 5m\n"}}
http response prometheus5m /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[]}}
http response prometheus5m /api/v1/query 200 {"status":"success","data":{"resultType":"vector","result":[]}}
http start prometheus5m 127.0.0.1:2094

http response bitbucket / 200 OK
http start bitbucket 127.0.0.1:6094

mkdir testrepo
cd testrepo
exec git init --initial-branch=main .

cp ../src/v1.yml rules.yml
exec ln -s rules.yml symlink.yml
exec ln -s symlink.yml double.yml
cp ../src/.pint.hcl .
env GIT_AUTHOR_NAME=pint
env GIT_AUTHOR_EMAIL=pint@example.com
env GIT_COMMITTER_NAME=pint
env GIT_COMMITTER_EMAIL=pint@example.com
exec git add .
exec git commit -am 'import rules and config'

exec git checkout -b v2
cp ../src/v2.yml rules.yml
exec git commit -am 'v2'

env BITBUCKET_AUTH_TOKEN="12345"
pint.error -l debug -d promql/series --no-color ci
! stdout .

stderr 'path=rules.yml rule=rule1'
stderr 'path=rules.yml rule=rule2'
stderr 'path=symlink.yml rule=rule1'
stderr 'path=symlink.yml rule=rule2'
stderr 'path=double.yml rule=rule1'
stderr 'path=double.yml rule=rule2'

stderr 'level=info msg="Problems found" Bug=4'
stderr 'result\\":\\"FAIL\\"'

! stderr '^rules.yml:5: duration for rate'
! stderr '^rules.yml:7: duration for rate'
stderr 'symlink.yml ~> rules.yml:5: duration for rate'
stderr 'symlink.yml ~> rules.yml:7: duration for rate'
stderr 'double.yml ~> rules.yml:5: duration for rate'
stderr 'double.yml ~> rules.yml:7: duration for rate'

stderr '{\\"path\\":\\"rules.yml\\",\\"line\\":5,'
stderr '{\\"path\\":\\"rules.yml\\",\\"line\\":7,'
! stderr '{\\"path\\":\\"symlink.yml\\"'
! stderr '{\\"path\\":\\"double.yml\\"'

stderr 'Problem detected on symlinked file symlink.yml:'
stderr 'Problem detected on symlinked file double.yml:'

-- src/v1.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[5m]) > 0
- alert: rule2
expr: rate(errors_total[5m]) > 0

-- src/v2.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[2m]) > 0
- alert: rule2
expr: rate(errors_total[2m]) > 0

-- src/.pint.hcl --
ci {
baseBranch = "main"
}
repository {
bitbucket {
uri = "http://127.0.0.1:6094"
timeout = "10s"
project = "prometheus"
repository = "rules"
}
}
prometheus "1m" {
uri = "http://127.0.0.1:1094"
timeout = "5s"
required = true
include = ["rules.yml"]
}
prometheus "5m" {
uri = "http://127.0.0.1:2094"
timeout = "5s"
required = true
include = ["symlink.yml", "double.yml"]
}
21 changes: 21 additions & 0 deletions cmd/pint/tests/0095_rulefmt_symlink.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mkdir rules/strict
exec ln -s ../relaxed/1.yml rules/strict/symlink.yml

pint.ok -l debug --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=info msg="Loading configuration file" path=.pint.hcl
level=debug msg="File parsed" path=rules/relaxed/1.yml rules=1
level=debug msg="Found recording rule" lines=1-2 path=rules/relaxed/1.yml record=foo
level=debug msg="Configured checks for rule" enabled=["promql/syntax","alerts/for","alerts/comparison","alerts/template","promql/fragile","promql/regexp"] path=rules/relaxed/1.yml rule=foo
level=debug msg="Found recording rule" lines=1-2 path=rules/strict/symlink.yml record=foo
level=debug msg="Configured checks for rule" enabled=["promql/syntax","alerts/for","alerts/comparison","alerts/template","promql/fragile","promql/regexp"] path=rules/strict/symlink.yml rule=foo
-- rules/relaxed/1.yml --
- record: foo
expr: up == 0
-- .pint.hcl --
parser {
relaxed = ["rules/relaxed/.*"]
}
9 changes: 9 additions & 0 deletions cmd/pint/tests/0096_bad_symlink.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mkdir rules
exec ln -s ../bad.yml rules/symlink.yml

pint.error -l debug --no-color lint rules
! stdout .
cmp stderr stderr.txt

-- stderr.txt --
level=fatal msg="Fatal error" error="lstat rules/../bad.yml: no such file or directory"
45 changes: 45 additions & 0 deletions cmd/pint/tests/0097_rule_file_symlink_error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
mkdir testrepo
cd testrepo
exec git init --initial-branch=main .

cp ../src/v1.yml rules.yml
exec ln -s xxx.yml symlink.yml
cp ../src/.pint.hcl .
env GIT_AUTHOR_NAME=pint
env GIT_AUTHOR_EMAIL=pint@example.com
env GIT_COMMITTER_NAME=pint
env GIT_COMMITTER_EMAIL=pint@example.com
exec git add .
exec git commit -am 'import rules and config'

exec git checkout -b v2
cp ../src/v2.yml rules.yml
exec git commit -am 'v2'

pint.error -l debug -d promql/series --no-color ci
! stdout .
stderr 'level=fatal msg="Fatal error" error="symlink.yml is a symlink but target file cannot be evaluated: lstat xxx.yml: no such file or directory"'

-- src/v1.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[5m]) > 0
- alert: rule2
expr: rate(errors_total[5m]) > 0

-- src/v2.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[2m]) > 0
- alert: rule2
expr: rate(errors_total[2m]) > 0

-- src/.pint.hcl --
ci {
baseBranch = "main"
}

57 changes: 57 additions & 0 deletions cmd/pint/tests/0098_rule_file_symlink_gh.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
http response github / 200 {}
http start github 127.0.0.1:6098

mkdir testrepo
cd testrepo
exec git init --initial-branch=main .

cp ../src/v1.yml rules.yml
exec ln -s rules.yml symlink.yml
cp ../src/.pint.hcl .
env GIT_AUTHOR_NAME=pint
env GIT_AUTHOR_EMAIL=pint@example.com
env GIT_COMMITTER_NAME=pint
env GIT_COMMITTER_EMAIL=pint@example.com
exec git add .
exec git commit -am 'import rules and config'

exec git checkout -b v2
cp ../src/v2.yml rules.yml
exec git commit -am 'v2'

env GITHUB_AUTH_TOKEN=12345
env GITHUB_PULL_REQUEST_NUMBER=1
pint.ok -l debug -d promql/series --no-color ci
! stdout .
stderr 'level=info msg="Report submitted" status="200 OK"'

-- src/v1.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[5m]) > 0
- alert: rule2
expr: rate(errors_total[5m]) > 0

-- src/v2.yml --
groups:
- name: foo
rules:
- alert: rule1
expr: rate(errors_total[2m]) > 0
- alert: rule2
expr: rate(errors_total[2m])

-- src/.pint.hcl --
ci {
baseBranch = "main"
}
repository {
github {
baseuri = "http://127.0.0.1:6098"
uploaduri = "http://127.0.0.1:6098"
owner = "cloudflare"
repo = "pint"
}
}
2 changes: 1 addition & 1 deletion cmd/pint/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (c *problemCollector) Collect(ch chan<- prometheus.Metric) {
c.problem,
prometheus.GaugeValue,
1,
report.Path,
report.SourcePath,
kind,
name,
strings.ToLower(report.Problem.Severity.String()),
Expand Down
19 changes: 19 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@
This will make it more obvious that a CI check passed because pint didn't run any
checks due to file being excluded.

### Changed

- Prometheus rule files can be symlinked between directories.
If the symlink source and target files are in a different directory they can
end up querying different Prometheus server when running ping checks.
This means that when modifying symlink target file checks must be executed against
both symlink source and target.
Until now pint was ignoring symlinks but starting with this release it will try to
follow them. This means that if you modify a file that has symlinks pointint to them
pint will try run checks against those symlinks too.

**NOTE**: pint can only detect and check symlinks if they are located in the current
working directory (as seen by running pint process) or its subdirectories.

### Fixed

- Fixed a regression in [promql/vector_matching](checks/promql/vector_matching.md) that
would cause a panic when parsing function calls with optional arguments.

## v0.29.4

### Fixed
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 @@ -171,7 +171,7 @@ func parseContent(content string) (entries []discovery.Entry, err error) {

for _, rule := range rules {
entries = append(entries, discovery.Entry{
Path: "fake.yml",
SourcePath: "fake.yml",
ModifiedLines: rule.Lines(),
Rule: rule,
})
Expand Down
4 changes: 2 additions & 2 deletions internal/checks/promql_series.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (c SeriesCheck) Check(ctx context.Context, rule parser.Rule, entries []disc
}
}
if arEntry != nil {
log.Debug().Stringer("selector", &selector).Str("path", arEntry.Path).Msg("Metric is provided by alerting rule")
log.Debug().Stringer("selector", &selector).Str("path", arEntry.SourcePath).Msg("Metric is provided by alerting rule")
} else {
problems = append(problems, Problem{
Fragment: selector.String(),
Expand Down Expand Up @@ -180,7 +180,7 @@ func (c SeriesCheck) Check(ctx context.Context, rule parser.Rule, entries []disc
}
if rrEntry != nil {
// Validate recording rule instead
log.Debug().Stringer("selector", &bareSelector).Str("path", rrEntry.Path).Msg("Metric is provided by recording rule")
log.Debug().Stringer("selector", &bareSelector).Str("path", rrEntry.SourcePath).Msg("Metric is provided by recording rule")
problems = append(problems, Problem{
Fragment: bareSelector.String(),
Lines: expr.Lines(),
Expand Down
Loading

0 comments on commit c6e9719

Please sign in to comment.