cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/discovery/discovery.go

210lines · modecode

1package discovery
2
3import (
4 "encoding/json"
5 "errors"
6 "io"
7 "log/slog"
8 "regexp"
9 "strings"
10
11 "github.com/prometheus/prometheus/model/rulefmt"
12 "golang.org/x/exp/slices"
13
14 "github.com/cloudflare/pint/internal/output"
15 "github.com/cloudflare/pint/internal/parser"
16)
17
18const (
19 FileOwnerComment = "file/owner"
20 FileDisabledCheckComment = "file/disable"
21 FileSnoozeCheckComment = "file/snooze"
22 RuleOwnerComment = "rule/owner"
23)
24
25var ignoredErrors = []string{
26 "one of 'record' or 'alert' must be set",
27 "field 'expr' must be set in rule",
28 "could not parse expression: ",
29 "cannot unmarshal !!seq into rulefmt.ruleGroups",
30 ": template: __",
31}
32
33var ErrFileIsIgnored = errors.New("file was ignored")
34
35func isStrictIgnored(err error) bool {
36 s := err.Error()
37 for _, ign := range ignoredErrors {
38 if strings.Contains(s, ign) {
39 return true
40 }
41 }
42 return false
43}
44
45type RuleFinder interface {
46 Find() ([]Entry, error)
47}
48
49type ChangeType int
50
51func (c ChangeType) String() string {
52 switch c {
53 case Unknown:
54 return "unknown"
55 case Noop:
56 return "noop"
57 case Added:
58 return "added"
59 case Modified:
60 return "modified"
61 case Removed:
62 return "removed"
63 default:
64 return "---"
65 }
66}
67
68func (c *ChangeType) MarshalJSON() ([]byte, error) {
69 return json.Marshal(c.String())
70}
71
72const (
73 Unknown ChangeType = iota
74 Noop
75 Added
76 Modified
77 Removed
78)
79
80type Entry struct {
81 State ChangeType
82 ReportedPath string // symlink target
83 SourcePath string // file path (can be symlink)
84 PathError error
85 ModifiedLines []int
86 Rule parser.Rule
87 Owner string
88 DisabledChecks []string
89}
90
91func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (entries []Entry, err error) {
92 p := parser.NewParser()
93
94 content, err := parser.ReadContent(r)
95 if err != nil {
96 return nil, err
97 }
98
99 contentLines := []int{}
100 for i := 1; i <= strings.Count(string(content.Body), "\n"); i++ {
101 contentLines = append(contentLines, i)
102 }
103
104 body := string(content.Body)
105 fileOwner, _ := parser.GetLastComment(body, FileOwnerComment)
106
107 var disabledChecks []string
108 for _, comment := range parser.GetComments(body, FileDisabledCheckComment) {
109 if !slices.Contains(disabledChecks, comment.Value) {
110 disabledChecks = append(disabledChecks, comment.Value)
111 }
112 }
113 for _, comment := range parser.GetComments(body, FileSnoozeCheckComment) {
114 s := parser.ParseSnooze(comment.Value)
115 if s == nil {
116 continue
117 }
118 if !slices.Contains(disabledChecks, s.Text) {
119 disabledChecks = append(disabledChecks, s.Text)
120 }
121 slog.Debug(
122 "Check snoozed by comment",
123 slog.String("check", s.Text),
124 slog.String("comment", comment.String()),
125 slog.Time("until", s.Until),
126 slog.String("snooze", s.Text),
127 )
128 }
129
130 if content.Ignored {
131 entries = append(entries, Entry{
132 ReportedPath: reportedPath,
133 SourcePath: sourcePath,
134 PathError: ErrFileIsIgnored,
135 Owner: fileOwner.Value,
136 ModifiedLines: contentLines,
137 })
138 return entries, nil
139 }
140
141 if isStrict {
142 if _, errs := rulefmt.Parse(content.Body); len(errs) > 0 {
143 seen := map[string]struct{}{}
144 for _, err := range errs {
145 if isStrictIgnored(err) {
146 continue
147 }
148 if _, ok := seen[err.Error()]; ok {
149 continue
150 }
151 seen[err.Error()] = struct{}{}
152 entries = append(entries, Entry{
153 ReportedPath: reportedPath,
154 SourcePath: sourcePath,
155 PathError: err,
156 Owner: fileOwner.Value,
157 ModifiedLines: contentLines,
158 })
159 }
160 if len(entries) > 0 {
161 return entries, nil
162 }
163 }
164 }
165
166 rules, err := p.Parse(content.Body)
167 if err != nil {
168 slog.Error(
169 "Failed to parse file content",
170 slog.Any("err", err),
171 slog.String("path", sourcePath),
172 slog.String("lines", output.FormatLineRangeString(contentLines)),
173 )
174 entries = append(entries, Entry{
175 ReportedPath: reportedPath,
176 SourcePath: sourcePath,
177 PathError: err,
178 Owner: fileOwner.Value,
179 ModifiedLines: contentLines,
180 })
181 return entries, nil
182 }
183
184 for _, rule := range rules {
185 owner, ok := rule.GetComment(RuleOwnerComment)
186 if !ok {
187 owner = fileOwner
188 }
189 entries = append(entries, Entry{
190 ReportedPath: reportedPath,
191 SourcePath: sourcePath,
192 Rule: rule,
193 ModifiedLines: rule.Lines(),
194 Owner: owner.Value,
195 DisabledChecks: disabledChecks,
196 })
197 }
198
199 slog.Debug("File parsed", slog.String("path", sourcePath), slog.Int("rules", len(entries)))
200 return entries, nil
201}
202
203func matchesAny(re []*regexp.Regexp, s string) bool {
204 for _, r := range re {
205 if v := r.MatchString(s); v {
206 return true
207 }
208 }
209 return false
210}
211