cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/models.go

547lines · modecode

1package parser
2
3import (
4 "bufio"
5 "fmt"
6 "strings"
7
8 "golang.org/x/exp/slices"
9 "gopkg.in/yaml.v3"
10
11 promparser "github.com/prometheus/prometheus/promql/parser"
12)
13
14func appendLine(lines []int, newLines ...int) []int {
15 for _, nl := range newLines {
16 var present bool
17 for _, l := range lines {
18 if l == nl {
19 present = true
20 break
21 }
22 }
23 if !present {
24 lines = append(lines, nl)
25 }
26 }
27
28 return lines
29}
30
31func nodeLines(node *yaml.Node, offset int) (lines []int) {
32 lineCount := len(strings.Split(strings.TrimSuffix(node.Value, "\n"), "\n"))
33
34 var firstLine int
35 // nolint: exhaustive
36 switch node.Style {
37 case yaml.LiteralStyle, yaml.FoldedStyle:
38 firstLine = node.Line + 1 + offset
39 default:
40 firstLine = node.Line + offset
41 }
42
43 for i := 0; i < lineCount; i++ {
44 lines = appendLine(lines, firstLine+i)
45 }
46
47 return lines
48}
49
50func NewFilePosition(l []int) FilePosition {
51 return FilePosition{Lines: l}
52}
53
54type FilePosition struct {
55 Lines []int
56}
57
58func (fp FilePosition) FirstLine() (line int) {
59 for _, l := range fp.Lines {
60 if line == 0 || l < line {
61 line = l
62 }
63 }
64 return line
65}
66
67func (fp FilePosition) LastLine() (line int) {
68 for _, l := range fp.Lines {
69 if l > line {
70 line = l
71 }
72 }
73 return line
74}
75
76func mergeComments(node *yaml.Node) (comments []string) {
77 if node.HeadComment != "" {
78 comments = append(comments, node.HeadComment)
79 }
80 if node.LineComment != "" {
81 comments = append(comments, node.LineComment)
82 }
83 if node.FootComment != "" {
84 comments = append(comments, node.FootComment)
85 }
86 return comments
87}
88
89type YamlNode struct {
90 Position FilePosition
91 Value string
92 Comments []string
93}
94
95func newYamlNode(node *yaml.Node, offset int) *YamlNode {
96 return &YamlNode{
97 Position: NewFilePosition(nodeLines(node, offset)),
98 Value: node.Value,
99 Comments: mergeComments(node),
100 }
101}
102
103func newYamlKeyValue(key, val *yaml.Node, offset int) *YamlKeyValue {
104 return &YamlKeyValue{
105 Key: newYamlNode(key, offset),
106 Value: newYamlNode(val, offset),
107 }
108}
109
110type YamlKeyValue struct {
111 Key *YamlNode
112 Value *YamlNode
113}
114
115func (ykv YamlKeyValue) Lines() (lines []int) {
116 lines = appendLine(lines, ykv.Key.Position.Lines...)
117 lines = appendLine(lines, ykv.Value.Position.Lines...)
118 return lines
119}
120
121type YamlMap struct {
122 Key *YamlNode
123 Items []*YamlKeyValue
124}
125
126func (ym YamlMap) Lines() (lines []int) {
127 lines = appendLine(lines, ym.Key.Position.Lines...)
128 for _, item := range ym.Items {
129 lines = appendLine(lines, item.Lines()...)
130 }
131 return lines
132}
133
134func (ym YamlMap) GetValue(key string) *YamlNode {
135 for _, child := range ym.Items {
136 if child.Key.Value == key {
137 return child.Value
138 }
139 }
140 return nil
141}
142
143func newYamlMap(key, value *yaml.Node, offset int) *YamlMap {
144 ym := YamlMap{
145 Key: newYamlNode(key, offset),
146 }
147
148 var ckey *yaml.Node
149 for _, child := range value.Content {
150 if ckey != nil {
151 kv := YamlKeyValue{
152 Key: newYamlNode(ckey, offset),
153 Value: newYamlNode(child, offset),
154 }
155 ym.Items = append(ym.Items, &kv)
156 ckey = nil
157 } else {
158 ckey = child
159 }
160 }
161
162 return &ym
163}
164
165type PromQLNode struct {
166 Expr string
167 Node promparser.Expr
168 Children []*PromQLNode
169}
170
171type PromQLError struct {
172 node *PromQLNode
173 Err error
174}
175
176func (pqle PromQLError) Error() string {
177 return pqle.Err.Error()
178}
179
180func (pqle *PromQLError) Unwrap() error {
181 return pqle.Err
182}
183
184func (pqle PromQLError) Node() *PromQLNode {
185 return pqle.node
186}
187
188type PromQLExpr struct {
189 Key *YamlNode
190 Value *YamlNode
191 SyntaxError error
192 Query *PromQLNode
193}
194
195func (pqle PromQLExpr) Lines() (lines []int) {
196 lines = appendLine(lines, pqle.Key.Position.Lines...)
197 lines = appendLine(lines, pqle.Value.Position.Lines...)
198 return lines
199}
200
201func newPromQLExpr(key, val *yaml.Node, offset int) *PromQLExpr {
202 expr := PromQLExpr{
203 Key: newYamlNode(key, offset),
204 Value: newYamlNode(val, offset),
205 }
206
207 qlNode, err := DecodeExpr(val.Value)
208 if err != nil {
209 expr.SyntaxError = err
210 return &expr
211
212 }
213 expr.Query = qlNode
214 return &expr
215}
216
217type AlertingRule struct {
218 Alert YamlKeyValue
219 Expr PromQLExpr
220 For *YamlKeyValue
221 KeepFiringFor *YamlKeyValue
222 Labels *YamlMap
223 Annotations *YamlMap
224}
225
226func (ar AlertingRule) Lines() (lines []int) {
227 lines = appendLine(lines, ar.Alert.Lines()...)
228 lines = appendLine(lines, ar.Expr.Lines()...)
229 if ar.For != nil {
230 lines = appendLine(lines, ar.For.Lines()...)
231 }
232 if ar.KeepFiringFor != nil {
233 lines = appendLine(lines, ar.KeepFiringFor.Lines()...)
234 }
235 if ar.Labels != nil {
236 lines = appendLine(lines, ar.Labels.Lines()...)
237 }
238 if ar.Annotations != nil {
239 lines = appendLine(lines, ar.Annotations.Lines()...)
240 }
241 slices.Sort(lines)
242 return lines
243}
244
245func (ar AlertingRule) Comments() (comments []string) {
246 comments = append(comments, ar.Alert.Key.Comments...)
247 comments = append(comments, ar.Alert.Value.Comments...)
248 comments = append(comments, ar.Expr.Key.Comments...)
249 comments = append(comments, ar.Expr.Value.Comments...)
250 if ar.For != nil {
251 comments = append(comments, ar.For.Key.Comments...)
252 comments = append(comments, ar.For.Value.Comments...)
253 }
254 if ar.KeepFiringFor != nil {
255 comments = append(comments, ar.KeepFiringFor.Key.Comments...)
256 comments = append(comments, ar.KeepFiringFor.Value.Comments...)
257 }
258 if ar.Labels != nil {
259 comments = append(comments, ar.Labels.Key.Comments...)
260 for _, label := range ar.Labels.Items {
261 comments = append(comments, label.Key.Comments...)
262 comments = append(comments, label.Value.Comments...)
263 }
264 }
265 if ar.Annotations != nil {
266 comments = append(comments, ar.Annotations.Key.Comments...)
267 for _, annotation := range ar.Annotations.Items {
268 comments = append(comments, annotation.Key.Comments...)
269 comments = append(comments, annotation.Value.Comments...)
270 }
271 }
272 return comments
273}
274
275type RecordingRule struct {
276 Record YamlKeyValue
277 Expr PromQLExpr
278 Labels *YamlMap
279}
280
281func (rr RecordingRule) Lines() (lines []int) {
282 lines = appendLine(lines, rr.Record.Lines()...)
283 lines = appendLine(lines, rr.Expr.Lines()...)
284 if rr.Labels != nil {
285 lines = appendLine(lines, rr.Labels.Lines()...)
286 }
287 slices.Sort(lines)
288 return lines
289}
290
291func (rr RecordingRule) Comments() (comments []string) {
292 comments = append(comments, rr.Record.Key.Comments...)
293 comments = append(comments, rr.Record.Value.Comments...)
294 comments = append(comments, rr.Expr.Key.Comments...)
295 comments = append(comments, rr.Expr.Value.Comments...)
296 if rr.Labels != nil {
297 comments = append(comments, rr.Labels.Key.Comments...)
298 for _, label := range rr.Labels.Items {
299 comments = append(comments, label.Key.Comments...)
300 comments = append(comments, label.Value.Comments...)
301 }
302 }
303 return comments
304}
305
306type ParseError struct {
307 Fragment string
308 Err error
309 Line int
310}
311
312type Rule struct {
313 AlertingRule *AlertingRule
314 RecordingRule *RecordingRule
315 Error ParseError
316}
317
318func (r Rule) ToYAML() string {
319 if r.Error.Err != nil {
320 return fmt.Sprintf("line=%d fragment=%s err=%s", r.Error.Line, r.Error.Fragment, r.Error.Err)
321 }
322
323 if r.AlertingRule == nil && r.RecordingRule == nil {
324 return ""
325 }
326
327 var b strings.Builder
328 b.WriteString("- ")
329 if r.AlertingRule != nil {
330 b.WriteString(" ")
331 b.WriteString(r.AlertingRule.Alert.Key.Value)
332 b.WriteRune(':')
333 b.WriteString(r.AlertingRule.Alert.Value.Value)
334 b.WriteRune('\n')
335
336 b.WriteString(" ")
337 b.WriteString(r.AlertingRule.Expr.Key.Value)
338 b.WriteRune(':')
339 b.WriteString(r.AlertingRule.Expr.Value.Value)
340 b.WriteRune('\n')
341
342 if r.AlertingRule.For != nil {
343 b.WriteString(" ")
344 b.WriteString(r.AlertingRule.For.Key.Value)
345 b.WriteRune(':')
346 b.WriteString(r.AlertingRule.For.Value.Value)
347 b.WriteRune('\n')
348 }
349 if r.AlertingRule.KeepFiringFor != nil {
350 b.WriteString(" ")
351 b.WriteString(r.AlertingRule.KeepFiringFor.Key.Value)
352 b.WriteRune(':')
353 b.WriteString(r.AlertingRule.KeepFiringFor.Value.Value)
354 b.WriteRune('\n')
355 }
356
357 if r.AlertingRule.Annotations != nil {
358 b.WriteString(" annotations:\n")
359 for _, a := range r.AlertingRule.Annotations.Items {
360 b.WriteString(" ")
361 b.WriteString(a.Key.Value)
362 b.WriteRune(':')
363 b.WriteString(a.Value.Value)
364 b.WriteRune('\n')
365 }
366 }
367
368 if r.AlertingRule.Labels != nil {
369 b.WriteString(" labels:\n")
370 for _, l := range r.AlertingRule.Labels.Items {
371 b.WriteString(" ")
372 b.WriteString(l.Key.Value)
373 b.WriteRune(':')
374 b.WriteString(l.Value.Value)
375 b.WriteRune('\n')
376 }
377 }
378
379 return b.String()
380 }
381
382 b.WriteString(r.RecordingRule.Record.Key.Value)
383 b.WriteRune(':')
384 b.WriteString(r.RecordingRule.Record.Value.Value)
385
386 b.WriteString(" ")
387 b.WriteString(r.RecordingRule.Expr.Key.Value)
388 b.WriteRune(':')
389 b.WriteString(r.RecordingRule.Expr.Value.Value)
390 b.WriteRune('\n')
391
392 if r.RecordingRule.Labels != nil {
393 b.WriteString(" labels:\n")
394 for _, l := range r.RecordingRule.Labels.Items {
395 b.WriteString(" ")
396 b.WriteString(l.Key.Value)
397 b.WriteRune(':')
398 b.WriteString(l.Value.Value)
399 b.WriteRune('\n')
400 }
401 }
402
403 return b.String()
404}
405
406func (r Rule) IsSame(nr Rule) bool {
407 if (r.AlertingRule != nil) != (nr.AlertingRule != nil) {
408 return false
409 }
410 if (r.RecordingRule != nil) != (nr.RecordingRule != nil) {
411 return false
412 }
413 if r.Error != nr.Error {
414 return false
415 }
416 if !slices.Equal(r.Lines(), nr.Lines()) {
417 return false
418 }
419 return true
420}
421
422func (r Rule) Name() string {
423 if r.RecordingRule != nil {
424 return r.RecordingRule.Record.Value.Value
425 }
426 if r.AlertingRule != nil {
427 return r.AlertingRule.Alert.Value.Value
428 }
429 return ""
430}
431
432func (r Rule) Expr() PromQLExpr {
433 if r.RecordingRule != nil {
434 return r.RecordingRule.Expr
435 }
436 return r.AlertingRule.Expr
437}
438
439func (r Rule) Lines() []int {
440 if r.RecordingRule != nil {
441 return r.RecordingRule.Lines()
442 }
443 if r.AlertingRule != nil {
444 return r.AlertingRule.Lines()
445 }
446 if r.Error.Err != nil {
447 return []int{r.Error.Line}
448 }
449 return nil
450}
451
452func (r Rule) LineRange() []int {
453 var lmin, lmax int
454 for i, line := range r.Lines() {
455 if i == 0 {
456 lmin = line
457 lmax = line
458 continue
459 }
460 if line < lmin {
461 lmin = line
462 }
463 if line > lmax {
464 lmax = line
465 }
466 }
467
468 lines := []int{}
469 for i := lmin; i <= lmax; i++ {
470 lines = append(lines, i)
471 }
472 return lines
473}
474
475func (r Rule) HasComment(comment string) bool {
476 var comments []string
477 if r.RecordingRule != nil {
478 comments = r.RecordingRule.Comments()
479 } else if r.AlertingRule != nil {
480 comments = r.AlertingRule.Comments()
481 }
482 for _, c := range comments {
483 if hasComment(c, comment) {
484 return true
485 }
486 }
487 return false
488}
489
490func (r Rule) GetComment(comment ...string) (s Comment, ok bool) {
491 var comments []string
492 if r.RecordingRule != nil {
493 comments = r.RecordingRule.Comments()
494 } else if r.AlertingRule != nil {
495 comments = r.AlertingRule.Comments()
496 }
497 for _, c := range comments {
498 var val Comment
499 if val, ok = GetLastComment(c, comment...); ok {
500 return val, ok
501 }
502 }
503 return s, ok
504}
505
506func (r Rule) GetComments(key string) (cs []Comment) {
507 var comments []string
508 if r.RecordingRule != nil {
509 comments = r.RecordingRule.Comments()
510 } else if r.AlertingRule != nil {
511 comments = r.AlertingRule.Comments()
512 }
513 for _, c := range comments {
514 sc := bufio.NewScanner(strings.NewReader(c))
515 for sc.Scan() {
516 if val, ok := GetLastComment(sc.Text(), key); ok {
517 cs = append(cs, val)
518 }
519 }
520 }
521 return cs
522}
523
524type RuleType string
525
526const (
527 AlertingRuleType RuleType = "alerting"
528 RecordingRuleType RuleType = "recording"
529 InvalidRuleType RuleType = "invalid"
530)
531
532func (r Rule) Type() RuleType {
533 if r.AlertingRule != nil {
534 return AlertingRuleType
535 }
536 if r.RecordingRule != nil {
537 return RecordingRuleType
538 }
539 return InvalidRuleType
540}
541
542type Result struct {
543 Path string
544 Error error
545 Content []byte
546 Rules []Rule
547}
548