Skip to content

Commit 11f0788

Browse files
authored
Merge pull request #1223 from cloudflare/file/owner
Validate file/owner comments even if there are no rules
2 parents 0979306 + 02147c6 commit 11f0788

File tree

18 files changed

+232
-102
lines changed

18 files changed

+232
-102
lines changed

cmd/pint/bench_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func BenchmarkFindEntries(b *testing.B) {
2626
[]string{"bench/rules"},
2727
git.NewPathFilter(nil, nil, nil),
2828
parser.PrometheusSchema,
29+
nil,
2930
)
3031
for n := 0; n < b.N; n++ {
3132
_, _ = finder.Find()
@@ -39,6 +40,7 @@ func BenchmarkCheckRules(b *testing.B) {
3940
[]string{"bench/rules"},
4041
git.NewPathFilter(nil, nil, nil),
4142
parser.PrometheusSchema,
43+
nil,
4244
)
4345
entries, err := finder.Find()
4446
if err != nil {

cmd/pint/ci.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,21 @@ func actionCI(c *cli.Context) error {
9595

9696
slog.Info("Finding all rules to check on current git branch", slog.String("base", baseBranch))
9797

98-
var entries []discovery.Entry
9998
filter := git.NewPathFilter(
10099
config.MustCompileRegexes(meta.cfg.Parser.Include...),
101100
config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
102101
config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
103102
)
104-
schema := parseSchema(meta.cfg.Parser.Schema)
105103

106-
entries, err = discovery.NewGlobFinder([]string{"*"}, filter, schema).Find()
104+
schema := parseSchema(meta.cfg.Parser.Schema)
105+
allowedOwners := meta.cfg.Owners.CompileAllowed()
106+
var entries []discovery.Entry
107+
entries, err = discovery.NewGlobFinder([]string{"*"}, filter, schema, allowedOwners).Find()
107108
if err != nil {
108109
return err
109110
}
110111

111-
entries, err = discovery.NewGitBranchFinder(git.RunGit, filter, baseBranch, meta.cfg.CI.MaxCommits, schema).Find(entries)
112+
entries, err = discovery.NewGitBranchFinder(git.RunGit, filter, baseBranch, meta.cfg.CI.MaxCommits, schema, allowedOwners).Find(entries)
112113
if err != nil {
113114
return err
114115
}
@@ -130,7 +131,7 @@ func actionCI(c *cli.Context) error {
130131
}
131132

132133
if c.Bool(requireOwnerFlag) {
133-
summary.Report(verifyOwners(entries, meta.cfg.Owners.CompileAllowed())...)
134+
summary.Report(verifyOwners(entries, allowedOwners)...)
134135
}
135136

136137
reps := []reporter.Reporter{}

cmd/pint/lint.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,17 @@ func actionLint(c *cli.Context) error {
7575
}
7676

7777
slog.Info("Finding all rules to check", slog.Any("paths", paths))
78+
allowedOwners := meta.cfg.Owners.CompileAllowed()
7879
finder := discovery.NewGlobFinder(
7980
paths,
8081
git.NewPathFilter(
8182
config.MustCompileRegexes(meta.cfg.Parser.Include...),
8283
config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
8384
config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
8485
),
85-
parseSchema(meta.cfg.Parser.Schema))
86+
parseSchema(meta.cfg.Parser.Schema),
87+
allowedOwners,
88+
)
8689
entries, err := finder.Find()
8790
if err != nil {
8891
return err
@@ -103,7 +106,7 @@ func actionLint(c *cli.Context) error {
103106
}
104107

105108
if c.Bool(requireOwnerFlag) {
106-
summary.Report(verifyOwners(entries, meta.cfg.Owners.CompileAllowed())...)
109+
summary.Report(verifyOwners(entries, allowedOwners)...)
107110
}
108111

109112
minSeverity, err := checks.ParseSeverity(c.String(minSeverityFlag))

cmd/pint/tests/0066_lint_owner.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ groups:
4646

4747
# pint rule/owner bob
4848

49+
-- rules/4.yml --
50+
groups:
51+
- name: foo
52+
rules: []
53+
4954
-- .pint.hcl --
5055
parser {
5156
relaxed = ["foo", "bar", "rules/3.yml"]

cmd/pint/tests/0122_lint_owner_allowed.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ rules/1.yml:7-8 Bug: This rule is set as owned by `bob` but `bob` doesn't match
1313
7 | - alert: Invalid
1414
8 | expr: up == 0
1515

16-
level=INFO msg="Problems found" Bug=2
16+
rules/2.yml:4-5 Bug: `rule/owner` comments are required in all files, please add a `# pint file/owner $owner` somewhere in this file and/or `# pint rule/owner $owner` on top of each rule. (rule/owner)
17+
4 | - alert: No Owner
18+
5 | expr: up > 0
19+
20+
rules/3.yml:1 Bug: This file is set as owned by `ax` but `ax` doesn't match any of the allowed owner values. (rule/owner)
21+
1 | # pint file/owner ax
22+
23+
level=INFO msg="Problems found" Bug=4
1724
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
1825
-- rules/1.yml --
1926
groups:
@@ -28,6 +35,22 @@ groups:
2835
- alert: Owner Alice
2936
expr: up > 0
3037

38+
-- rules/2.yml --
39+
groups:
40+
- name: foo
41+
rules:
42+
- alert: No Owner
43+
expr: up > 0
44+
45+
# pint file/owner ax
46+
47+
-- rules/3.yml --
48+
# pint file/owner ax
49+
50+
groups:
51+
- name: foo
52+
rules: []
53+
3154
-- .pint.hcl --
3255
owners {
3356
allowed = ["alice", "max", "not-bob"]

cmd/pint/watch.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
_ "net/http/pprof"
1010
"os"
1111
"os/signal"
12+
"regexp"
1213
"slices"
1314
"strings"
1415
"sync"
@@ -204,11 +205,12 @@ func actionWatch(c *cli.Context, meta actionMeta, f pathFinderFunc) error {
204205
}
205206

206207
schema := parseSchema(meta.cfg.Parser.Schema)
208+
allowedOwners := meta.cfg.Owners.CompileAllowed()
207209

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

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

235-
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 {
237+
func startTimer(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, schema parser.Schema, allowedOwners []*regexp.Regexp, interval time.Duration, ack chan bool, collector *problemCollector) chan bool {
236238
ticker := time.NewTicker(time.Second)
237239
stop := make(chan bool, 1)
238240
wasBootstrapped := false
@@ -246,7 +248,7 @@ func startTimer(ctx context.Context, workers int, isOffline bool, gen *config.Pr
246248
ticker.Reset(interval)
247249
wasBootstrapped = true
248250
}
249-
if err := collector.scan(ctx, workers, isOffline, gen, schema); err != nil {
251+
if err := collector.scan(ctx, workers, isOffline, gen, schema, allowedOwners); err != nil {
250252
slog.Error("Got an error when running checks", slog.Any("err", err))
251253
}
252254
checkIterationsTotal.Inc()
@@ -304,7 +306,7 @@ func newProblemCollector(cfg config.Config, f pathFinderFunc, minSeverity checks
304306
}
305307
}
306308

307-
func (c *problemCollector) scan(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, schema parser.Schema) error {
309+
func (c *problemCollector) scan(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, schema parser.Schema, allowedOwners []*regexp.Regexp) error {
308310
paths, err := c.finder(ctx)
309311
if err != nil {
310312
return fmt.Errorf("failed to get the list of paths to check: %w", err)
@@ -319,6 +321,7 @@ func (c *problemCollector) scan(ctx context.Context, workers int, isOffline bool
319321
config.MustCompileRegexes(c.cfg.Parser.Relaxed...),
320322
),
321323
schema,
324+
allowedOwners,
322325
).Find()
323326
if err != nil {
324327
return err

docs/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v0.69.1
4+
5+
### Fixed
6+
7+
- `# pint file/owner` comments were not validated properly for files with no rules.
8+
This is now fixed.
9+
310
## v0.69.0
411

512
### Added

internal/checks/error.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func (c ErrorCheck) Check(_ context.Context, _ discovery.Path, _ parser.Rule, _
5959

6060
func parseRuleError(rule parser.Rule, err error) Problem {
6161
var commentErr comments.CommentError
62+
var ownerErr comments.OwnerError
6263
var ignoreErr discovery.FileIgnoreError
6364
var parseErr parser.ParseError
6465

@@ -87,6 +88,18 @@ func parseRuleError(rule parser.Rule, err error) Problem {
8788
Severity: Warning,
8889
}
8990

91+
case errors.As(err, &ownerErr):
92+
slog.Debug("invalid owner report", slog.Any("err", ownerErr))
93+
return Problem{
94+
Lines: parser.LineRange{
95+
First: ownerErr.Line,
96+
Last: ownerErr.Line,
97+
},
98+
Reporter: discovery.RuleOwnerComment,
99+
Text: fmt.Sprintf("This file is set as owned by `%s` but `%s` doesn't match any of the allowed owner values.", ownerErr.Name, ownerErr.Name),
100+
Severity: Bug,
101+
}
102+
90103
case errors.As(err, &parseErr):
91104
slog.Debug("parse error", slog.Any("err", parseErr))
92105
return Problem{

internal/comments/comments.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ func (ce CommentError) Error() string {
9494
return ce.Err.Error()
9595
}
9696

97+
type OwnerError struct {
98+
Name string
99+
Line int
100+
}
101+
102+
func (oe OwnerError) Error() string {
103+
return oe.Name
104+
}
105+
97106
type Invalid struct {
98107
Err CommentError
99108
}
@@ -104,6 +113,7 @@ func (i Invalid) String() string {
104113

105114
type Owner struct {
106115
Name string
116+
Line int
107117
}
108118

109119
func (o Owner) String() string {
@@ -152,7 +162,7 @@ func parseSnooze(s string) (snz Snooze, err error) {
152162
return snz, nil
153163
}
154164

155-
func parseValue(typ Type, s string) (CommentValue, error) {
165+
func parseValue(typ Type, s string, line int) (CommentValue, error) {
156166
switch typ {
157167
case IgnoreFileType, IgnoreLineType, IgnoreBeginType, IgnoreEndType, IgnoreNextLineType:
158168
if s != "" {
@@ -162,7 +172,7 @@ func parseValue(typ Type, s string) (CommentValue, error) {
162172
if s == "" {
163173
return nil, fmt.Errorf("missing %s value", FileOwnerComment)
164174
}
165-
return Owner{Name: s}, nil
175+
return Owner{Name: s, Line: line}, nil
166176
case RuleOwnerType:
167177
if s == "" {
168178
return nil, fmt.Errorf("missing %s value", RuleOwnerComment)
@@ -209,7 +219,7 @@ const (
209219
readsValue
210220
)
211221

212-
func parseComment(s string) (parsed []Comment, err error) {
222+
func parseComment(s string, line int) (parsed []Comment, err error) {
213223
var buf strings.Builder
214224
var c Comment
215225

@@ -291,7 +301,7 @@ func parseComment(s string) (parsed []Comment, err error) {
291301
}
292302

293303
if c.Type != UnknownType {
294-
c.Value, err = parseValue(c.Type, strings.TrimSpace(buf.String()))
304+
c.Value, err = parseValue(c.Type, strings.TrimSpace(buf.String()), line)
295305
parsed = append(parsed, c)
296306
}
297307

@@ -303,7 +313,7 @@ func Parse(lineno int, text string) (comments []Comment) {
303313
var index int
304314
for sc.Scan() {
305315
line := sc.Text()
306-
parsed, err := parseComment(line)
316+
parsed, err := parseComment(line, lineno+index)
307317
if err != nil {
308318
comments = append(comments, Comment{
309319
Type: InvalidComment,

internal/comments/comments_test.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,11 @@ func TestParse(t *testing.T) {
154154
input: "# pint file/owner bob and alice",
155155
output: []comments.Comment{
156156
{
157-
Type: comments.FileOwnerType,
158-
Value: comments.Owner{Name: "bob and alice"},
157+
Type: comments.FileOwnerType,
158+
Value: comments.Owner{
159+
Name: "bob and alice",
160+
Line: 1,
161+
},
159162
},
160163
},
161164
},
@@ -175,8 +178,10 @@ func TestParse(t *testing.T) {
175178
input: "# pint rule/owner bob and alice",
176179
output: []comments.Comment{
177180
{
178-
Type: comments.RuleOwnerType,
179-
Value: comments.Owner{Name: "bob and alice"},
181+
Type: comments.RuleOwnerType,
182+
Value: comments.Owner{
183+
Name: "bob and alice",
184+
},
180185
},
181186
},
182187
},
@@ -403,8 +408,11 @@ func TestParse(t *testing.T) {
403408
Offset: len("code "),
404409
},
405410
{
406-
Type: comments.FileOwnerType,
407-
Value: comments.Owner{Name: "bob"},
411+
Type: comments.FileOwnerType,
412+
Value: comments.Owner{
413+
Name: "bob",
414+
Line: 2,
415+
},
408416
Offset: 1,
409417
},
410418
},
@@ -496,6 +504,13 @@ func TestCommentValueString(t *testing.T) {
496504
}},
497505
expected: "foo bar",
498506
},
507+
{
508+
comment: comments.Invalid{Err: comments.CommentError{
509+
Line: 1,
510+
Err: comments.OwnerError{Name: "foo bar"},
511+
}},
512+
expected: "foo bar",
513+
},
499514
{
500515
comment: comments.Owner{Name: "bob & alice"},
501516
expected: "bob & alice",

0 commit comments

Comments
 (0)