cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.44.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/discovery.go

209lines · modecode

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