cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

648lines · modecode

1package parser
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "gopkg.in/yaml.v3"
13
14 "github.com/prometheus/common/model"
15
16 "github.com/cloudflare/pint/internal/comments"
17)
18
19const (
20 recordKey = "record"
21 exprKey = "expr"
22 labelsKey = "labels"
23 alertKey = "alert"
24 forKey = "for"
25 keepFiringForKey = "keep_firing_for"
26 annotationsKey = "annotations"
27)
28
29var ErrRuleCommentOnFile = errors.New("this comment is only valid when attached to a rule")
30
31func NewParser(isStrict bool) Parser {
32 return Parser{
33 isStrict: isStrict,
34 }
35}
36
37type Parser struct {
38 isStrict bool
39}
40
41func (p Parser) Parse(content []byte) (rules []Rule, err error) {
42 if len(content) == 0 {
43 return nil, nil
44 }
45
46 defer func() {
47 if r := recover(); r != nil {
48 err = fmt.Errorf("unable to parse YAML file: %s", r)
49 }
50 }()
51
52 dec := yaml.NewDecoder(bytes.NewReader(content))
53 var index int
54 for {
55 var doc yaml.Node
56 decodeErr := dec.Decode(&doc)
57 if errors.Is(decodeErr, io.EOF) {
58 break
59 }
60 if decodeErr != nil {
61 return nil, tryDecodingYamlError(decodeErr)
62 }
63 index++
64 if p.isStrict {
65 r, err := parseGroups(content, &doc)
66 if err.Err != nil {
67 return rules, err
68 }
69 rules = append(rules, r...)
70 } else {
71 rules = append(rules, parseNode(content, &doc, 0)...)
72 }
73 if index > 1 && p.isStrict {
74 rules = append(rules, Rule{
75 Lines: LineRange{First: doc.Line, Last: doc.Line},
76 Error: ParseError{
77 Err: errors.New("multi-document YAML files are not allowed"),
78 Details: `This is a multi-document YAML file. Prometheus will only parse the first document and silently ignore the rest.
79To allow for multi-document YAML files set parser->relaxed option in pint config file.`,
80 Line: doc.Line,
81 },
82 })
83 }
84 }
85
86 return rules, err
87}
88
89func parseNode(content []byte, node *yaml.Node, offset int) (rules []Rule) {
90 ret, isEmpty := parseRule(content, node, offset)
91 if !isEmpty {
92 rules = append(rules, ret)
93 return rules
94 }
95
96 var rule Rule
97 for _, root := range node.Content {
98 // nolint: exhaustive
99 switch root.Kind {
100 case yaml.SequenceNode:
101 for _, n := range root.Content {
102 rules = append(rules, parseNode(content, n, offset)...)
103 }
104 case yaml.MappingNode:
105 rule, isEmpty = parseRule(content, root, offset)
106 if !isEmpty {
107 rules = append(rules, rule)
108 } else {
109 for _, n := range root.Content {
110 rules = append(rules, parseNode(content, n, offset)...)
111 }
112 }
113 case yaml.ScalarNode:
114 if root.Value != string(content) {
115 c := []byte(root.Value)
116 var n yaml.Node
117 if err := yaml.Unmarshal(c, &n); err == nil {
118 rules = append(rules, parseNode(c, &n, offset+root.Line)...)
119 }
120 }
121 }
122 }
123 return rules
124}
125
126func parseRule(content []byte, node *yaml.Node, offset int) (rule Rule, _ bool) {
127 if node.Kind != yaml.MappingNode {
128 return rule, true
129 }
130
131 var recordPart *YamlNode
132 var exprPart *PromQLExpr
133 var labelsPart *YamlMap
134
135 var alertPart *YamlNode
136 var forPart *YamlNode
137 var keepFiringForPart *YamlNode
138 var annotationsPart *YamlMap
139
140 var recordNode *yaml.Node
141 var alertNode *yaml.Node
142 var exprNode *yaml.Node
143 var forNode *yaml.Node
144 var keepFiringForNode *yaml.Node
145 var labelsNode *yaml.Node
146 var annotationsNode *yaml.Node
147 labelsNodes := []yamlMap{}
148 annotationsNodes := []yamlMap{}
149
150 var key *yaml.Node
151 unknownKeys := []*yaml.Node{}
152
153 var lines LineRange
154
155 var ruleComments []comments.Comment
156
157 for i, part := range unpackNodes(node) {
158 if lines.First == 0 || part.Line+offset < lines.First {
159 lines.First = part.Line + offset
160 }
161 lines.Last = max(lines.Last, part.Line+offset)
162
163 if i == 0 && node.HeadComment != "" && part.HeadComment == "" {
164 part.HeadComment = node.HeadComment
165 }
166 if i == 0 && node.LineComment != "" && part.LineComment == "" {
167 part.LineComment = node.LineComment
168 }
169 if i == len(node.Content)-1 && node.FootComment != "" && part.HeadComment == "" {
170 part.FootComment = node.FootComment
171 }
172 for _, s := range mergeComments(part) {
173 for _, c := range comments.Parse(part.Line, s) {
174 if comments.IsRuleComment(c.Type) {
175 ruleComments = append(ruleComments, c)
176 }
177 }
178 }
179
180 if i%2 == 0 {
181 key = part
182 } else {
183 switch key.Value {
184 case recordKey:
185 if recordPart != nil {
186 return duplicatedKeyError(lines, part.Line+offset, recordKey)
187 }
188 recordNode = part
189 recordPart = newYamlNodeWithKey(key, part, offset)
190 lines.Last = max(lines.Last, recordPart.Lines.Last)
191 case alertKey:
192 if alertPart != nil {
193 return duplicatedKeyError(lines, part.Line+offset, alertKey)
194 }
195 alertNode = part
196 alertPart = newYamlNodeWithKey(key, part, offset)
197 lines.Last = max(lines.Last, alertPart.Lines.Last)
198 case exprKey:
199 if exprPart != nil {
200 return duplicatedKeyError(lines, part.Line+offset, exprKey)
201 }
202 exprNode = part
203 exprPart = newPromQLExpr(key, part, offset)
204 lines.Last = max(lines.Last, exprPart.Value.Lines.Last)
205 case forKey:
206 if forPart != nil {
207 return duplicatedKeyError(lines, part.Line+offset, forKey)
208 }
209 forNode = part
210 forPart = newYamlNodeWithKey(key, part, offset)
211 lines.Last = max(lines.Last, forPart.Lines.Last)
212 case keepFiringForKey:
213 if keepFiringForPart != nil {
214 return duplicatedKeyError(lines, part.Line+offset, keepFiringForKey)
215 }
216 keepFiringForNode = part
217 keepFiringForPart = newYamlNodeWithKey(key, part, offset)
218 lines.Last = max(lines.Last, keepFiringForPart.Lines.Last)
219 case labelsKey:
220 if labelsPart != nil {
221 return duplicatedKeyError(lines, part.Line+offset, labelsKey)
222 }
223 labelsNode = part
224 labelsNodes = mappingNodes(part)
225 labelsPart = newYamlMap(key, part, offset)
226 lines.Last = max(lines.Last, labelsPart.Lines.Last)
227 case annotationsKey:
228 if annotationsPart != nil {
229 return duplicatedKeyError(lines, part.Line+offset, annotationsKey)
230 }
231 annotationsNode = part
232 annotationsNodes = mappingNodes(part)
233 annotationsPart = newYamlMap(key, part, offset)
234 lines.Last = max(lines.Last, annotationsPart.Lines.Last)
235 default:
236 unknownKeys = append(unknownKeys, key)
237 }
238 }
239 }
240
241 if exprPart != nil && exprPart.Value.Lines.First != exprPart.Value.Lines.Last {
242 contentLines := strings.Split(string(content), "\n")
243 for {
244 start := exprPart.Value.Lines.First
245 end := exprPart.Value.Lines.Last
246 if end > len(contentLines) {
247 end--
248 }
249 input := strings.Join(contentLines[start:end], "")
250 input = strings.ReplaceAll(input, " ", "")
251 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
252 output = strings.ReplaceAll(output, " ", "")
253 if end >= len(contentLines) {
254 break
255 }
256 if input == output {
257 break
258 }
259 exprPart.Value.Lines.Last = end + 1
260 }
261 }
262
263 if recordPart != nil && alertPart != nil {
264 rule = Rule{
265 Lines: lines,
266 Error: ParseError{
267 Line: node.Line + offset,
268 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
269 },
270 }
271 return rule, false
272 }
273 if exprPart != nil && alertPart == nil && recordPart == nil {
274 rule = Rule{
275 Lines: lines,
276 Error: ParseError{
277 Line: exprPart.Value.Lines.Last,
278 Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
279 },
280 }
281 return rule, false
282 }
283 if recordPart != nil && forPart != nil {
284 rule = Rule{
285 Lines: lines,
286 Error: ParseError{
287 Line: forPart.Lines.First,
288 Err: fmt.Errorf("invalid field '%s' in recording rule", forKey),
289 },
290 }
291 return rule, false
292 }
293 if recordPart != nil && keepFiringForPart != nil {
294 rule = Rule{
295 Lines: lines,
296 Error: ParseError{
297 Line: keepFiringForPart.Lines.First,
298 Err: fmt.Errorf("invalid field '%s' in recording rule", keepFiringForKey),
299 },
300 }
301 return rule, false
302 }
303 if recordPart != nil && annotationsPart != nil {
304 rule = Rule{
305 Lines: lines,
306 Error: ParseError{
307 Line: annotationsPart.Lines.First,
308 Err: fmt.Errorf("invalid field '%s' in recording rule", annotationsKey),
309 },
310 }
311 return rule, false
312 }
313 for _, entry := range []struct {
314 part *yaml.Node
315 key string
316 }{
317 {key: recordKey, part: recordNode},
318 {key: alertKey, part: alertNode},
319 {key: exprKey, part: exprNode},
320 {key: forKey, part: forNode},
321 {key: keepFiringForKey, part: keepFiringForNode},
322 } {
323 if entry.part != nil && !isTag(entry.part.ShortTag(), strTag) {
324 return invalidValueError(lines, entry.part.Line+offset, entry.key, describeTag(strTag), describeTag(entry.part.ShortTag()))
325 }
326 }
327
328 for _, entry := range []struct {
329 part *yaml.Node
330 key string
331 }{
332 {key: labelsKey, part: labelsNode},
333 {key: annotationsKey, part: annotationsNode},
334 } {
335 if entry.part != nil && !isTag(entry.part.ShortTag(), mapTag) {
336 return invalidValueError(lines, entry.part.Line+offset, entry.key, describeTag(mapTag), describeTag(entry.part.ShortTag()))
337 }
338 }
339
340 for _, elem := range []struct {
341 key string
342 parts []yamlMap
343 }{
344 {key: labelsKey, parts: labelsNodes},
345 {key: annotationsKey, parts: annotationsNodes},
346 } {
347 names := map[string]struct{}{}
348 for _, entry := range elem.parts {
349 if !isTag(entry.val.ShortTag(), strTag) {
350 return invalidValueError(lines, entry.val.Line+offset, fmt.Sprintf("%s %s", elem.key, nodeValue(entry.key)), describeTag(strTag), describeTag(entry.val.ShortTag()))
351 }
352 if _, ok := names[entry.key.Value]; ok {
353 return Rule{
354 Lines: rangeFromYamlMaps(elem.parts),
355 Error: ParseError{
356 Line: entry.key.Line,
357 Err: fmt.Errorf("duplicated %s key %s", elem.key, entry.key.Value),
358 },
359 }, false
360 }
361 names[entry.key.Value] = struct{}{}
362 }
363 }
364
365 if r, ok := ensureRequiredKeys(lines, recordKey, recordPart, exprPart); !ok {
366 return r, false
367 }
368 if r, ok := ensureRequiredKeys(lines, alertKey, alertPart, exprPart); !ok {
369 return r, false
370 }
371 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
372 var keys []string
373 for _, n := range unknownKeys {
374 keys = append(keys, n.Value)
375 }
376 rule = Rule{
377 Lines: lines,
378 Error: ParseError{
379 Line: unknownKeys[0].Line + offset,
380 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
381 },
382 }
383 return rule, false
384 }
385
386 if recordPart != nil && !model.IsValidMetricName(model.LabelValue(recordPart.Value)) {
387 return Rule{
388 Lines: lines,
389 Error: ParseError{
390 Line: recordPart.Lines.First,
391 Err: fmt.Errorf("invalid recording rule name: %s", recordPart.Value),
392 },
393 }, false
394 }
395
396 if (recordPart != nil || alertPart != nil) && labelsPart != nil {
397 for _, lab := range labelsPart.Items {
398 if !model.LabelName(lab.Key.Value).IsValid() || lab.Key.Value == model.MetricNameLabel {
399 return Rule{
400 Lines: lines,
401 Error: ParseError{
402 Line: lab.Key.Lines.First,
403 Err: fmt.Errorf("invalid label name: %s", lab.Key.Value),
404 },
405 }, false
406 }
407 if !model.LabelValue(lab.Value.Value).IsValid() {
408 return Rule{
409 Lines: lines,
410 Error: ParseError{
411 Line: lab.Key.Lines.First,
412 Err: fmt.Errorf("invalid label value: %s", lab.Value.Value),
413 },
414 }, false
415 }
416 }
417 }
418
419 if alertPart != nil && annotationsPart != nil {
420 for _, ann := range annotationsPart.Items {
421 if !model.LabelName(ann.Key.Value).IsValid() {
422 return Rule{
423 Lines: lines,
424 Error: ParseError{
425 Line: ann.Key.Lines.First,
426 Err: fmt.Errorf("invalid annotation name: %s", ann.Key.Value),
427 },
428 }, false
429 }
430 }
431 }
432
433 if recordPart != nil && exprPart != nil {
434 rule = Rule{
435 Lines: lines,
436 RecordingRule: &RecordingRule{
437 Record: *recordPart,
438 Expr: *exprPart,
439 Labels: labelsPart,
440 },
441 Comments: ruleComments,
442 }
443 return rule, false
444 }
445
446 if alertPart != nil && exprPart != nil {
447 rule = Rule{
448 Lines: lines,
449 AlertingRule: &AlertingRule{
450 Alert: *alertPart,
451 Expr: *exprPart,
452 For: forPart,
453 KeepFiringFor: keepFiringForPart,
454 Labels: labelsPart,
455 Annotations: annotationsPart,
456 },
457 Comments: ruleComments,
458 }
459 return rule, false
460 }
461
462 return rule, true
463}
464
465func unpackNodes(node *yaml.Node) []*yaml.Node {
466 nodes := make([]*yaml.Node, 0, len(node.Content))
467 var isMerge bool
468 for _, part := range node.Content {
469 if part.ShortTag() == mergeTag && part.Value == "<<" {
470 isMerge = true
471 }
472
473 if part.Alias != nil {
474 if isMerge {
475 nodes = append(nodes, resolveMapAlias(part, node).Content...)
476 } else {
477 nodes = append(nodes, resolveMapAlias(part, part))
478 }
479 isMerge = false
480 continue
481 }
482 if isMerge {
483 continue
484 }
485 nodes = append(nodes, part)
486 }
487 return nodes
488}
489
490func nodeKeys(node *yaml.Node) (keys []string) {
491 if node.Kind != yaml.MappingNode {
492 return keys
493 }
494 for i, n := range node.Content {
495 if i%2 == 0 && n.Value != "" {
496 keys = append(keys, n.Value)
497 }
498 }
499 return keys
500}
501
502func hasKey(node *yaml.Node, key string) bool {
503 for _, k := range nodeKeys(node) {
504 if k == key {
505 return true
506 }
507 }
508 return false
509}
510
511func hasValue(node *YamlNode) bool {
512 if node == nil {
513 return false
514 }
515 return node.Value != ""
516}
517
518func ensureRequiredKeys(lines LineRange, key string, keyVal *YamlNode, expr *PromQLExpr) (Rule, bool) {
519 if keyVal == nil {
520 return Rule{Lines: lines}, true
521 }
522 if !hasValue(keyVal) {
523 return Rule{
524 Lines: lines,
525 Error: ParseError{
526 Line: keyVal.Lines.Last,
527 Err: fmt.Errorf("%s value cannot be empty", key),
528 },
529 }, false
530 }
531 if expr == nil {
532 return Rule{
533 Lines: lines,
534 Error: ParseError{
535 Line: keyVal.Lines.Last,
536 Err: fmt.Errorf("missing %s key", exprKey),
537 },
538 }, false
539 }
540 if !hasValue(expr.Value) {
541 return Rule{
542 Lines: lines,
543 Error: ParseError{
544 Line: expr.Value.Lines.Last,
545 Err: fmt.Errorf("%s value cannot be empty", exprKey),
546 },
547 }, false
548 }
549 return Rule{Lines: lines}, true
550}
551
552func resolveMapAlias(part, parent *yaml.Node) *yaml.Node {
553 node := *part
554 node.Content = nil
555 var ok bool
556 for i, alias := range part.Alias.Content {
557 if i%2 == 0 {
558 ok = !hasKey(parent, alias.Value)
559 }
560 if ok {
561 node.Content = append(node.Content, alias)
562 }
563 if i%2 == 1 {
564 ok = false
565 }
566 }
567 return &node
568}
569
570func duplicatedKeyError(lines LineRange, line int, key string) (Rule, bool) {
571 rule := Rule{
572 Lines: lines,
573 Error: ParseError{
574 Line: line,
575 Err: fmt.Errorf("duplicated %s key", key),
576 },
577 }
578 return rule, false
579}
580
581func invalidValueError(lines LineRange, line int, key, expectedTag, gotTag string) (Rule, bool) {
582 rule := Rule{
583 Lines: lines,
584 Error: ParseError{
585 Line: line,
586 Err: fmt.Errorf("%s value must be a %s, got %s instead", key, expectedTag, gotTag),
587 },
588 }
589 return rule, false
590}
591
592func isTag(tag, expected string) bool {
593 if tag == nullTag {
594 return true
595 }
596 return tag == expected
597}
598
599type yamlMap struct {
600 key *yaml.Node
601 val *yaml.Node
602}
603
604func mappingNodes(node *yaml.Node) []yamlMap {
605 m := make([]yamlMap, 0, len(node.Content)/2)
606 var key *yaml.Node
607 for _, child := range node.Content {
608 if key != nil {
609 m = append(m, yamlMap{key: key, val: child})
610 key = nil
611 } else {
612 key = child
613 }
614 }
615 return m
616}
617
618func rangeFromYamlMaps(m []yamlMap) (lr LineRange) {
619 for _, entry := range m {
620 if lr.First == 0 {
621 lr.First = entry.key.Line
622 lr.Last = entry.val.Line
623 }
624 lr.First = min(lr.First, entry.key.Line, entry.val.Line)
625 lr.Last = max(lr.Last, entry.key.Line, entry.val.Line)
626 }
627 return lr
628}
629
630var (
631 yamlErrRe = regexp.MustCompile("^yaml: line (.+): (.+)")
632 yamlUnmarshalErrRe = regexp.MustCompile("^yaml: unmarshal errors:\n line (.+): (.+)")
633)
634
635func tryDecodingYamlError(err error) ParseError {
636 for _, re := range []*regexp.Regexp{yamlErrRe, yamlUnmarshalErrRe} {
637 parts := re.FindStringSubmatch(err.Error())
638 if len(parts) > 2 {
639 if line, err2 := strconv.Atoi(parts[1]); line > 0 && err2 == nil {
640 return ParseError{
641 Line: line,
642 Err: errors.New(parts[2]),
643 }
644 }
645 }
646 }
647 return ParseError{Line: 1, Err: err}
648}
649