cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.55.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

2048lines · modecode

1package parser_test
2
3import (
4 "fmt"
5 "strconv"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/cloudflare/pint/internal/comments"
11 "github.com/cloudflare/pint/internal/parser"
12
13 "github.com/google/go-cmp/cmp"
14 promparser "github.com/prometheus/prometheus/promql/parser"
15)
16
17func TestParse(t *testing.T) {
18 type testCaseT struct {
19 content []byte
20 output []parser.Rule
21 err string
22 }
23
24 testCases := []testCaseT{
25 {
26 content: nil,
27 output: nil,
28 },
29 {
30 content: []byte{},
31 output: nil,
32 },
33 {
34 content: []byte(string("! !00 \xf6")),
35 output: nil,
36 err: "yaml: incomplete UTF-8 octet sequence",
37 },
38 {
39 content: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
40 output: []parser.Rule{
41 {
42 Lines: parser.LineRange{First: 1, Last: 6},
43 Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 6},
44 },
45 },
46 },
47 {
48 content: []byte("- record: |\n multiline\n"),
49 output: []parser.Rule{
50 {
51 Lines: parser.LineRange{First: 1, Last: 2},
52 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 2},
53 },
54 },
55 },
56 {
57 content: []byte("- expr: foo\n"),
58 output: []parser.Rule{
59 {
60 Lines: parser.LineRange{First: 1, Last: 1},
61 Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 1},
62 },
63 },
64 },
65 {
66 content: []byte("- alert: foo\n"),
67 output: []parser.Rule{
68 {
69 Lines: parser.LineRange{First: 1, Last: 1},
70 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
71 },
72 },
73 },
74 {
75 content: []byte("- alert: foo\n record: foo\n"),
76 output: []parser.Rule{
77 {
78 Lines: parser.LineRange{First: 1, Last: 2},
79 Error: parser.ParseError{Err: fmt.Errorf("got both record and alert keys in a single rule"), Line: 1},
80 },
81 },
82 },
83 {
84 content: []byte("- record: foo\n labels:\n foo: bar\n"),
85 output: []parser.Rule{
86 {
87 Lines: parser.LineRange{First: 1, Last: 3},
88 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
89 },
90 },
91 },
92 {
93 content: []byte("- record: - foo\n"),
94 err: "yaml: block sequence entries are not allowed in this context",
95 },
96 {
97 content: []byte("- record: foo expr: sum(\n"),
98 err: "yaml: mapping values are not allowed in this context",
99 },
100 {
101 content: []byte("- record\n\texpr: foo\n"),
102 err: "yaml: line 2: found a tab character that violates indentation",
103 },
104 {
105 content: []byte(`
106- record: foo
107 expr: bar
108 expr: bar
109`),
110 output: []parser.Rule{
111 {
112 Lines: parser.LineRange{First: 2, Last: 4},
113 Error: parser.ParseError{Err: fmt.Errorf("duplicated expr key"), Line: 4},
114 },
115 },
116 },
117 {
118 content: []byte(`
119- record: foo
120 expr: bar
121 record: bar
122`),
123 output: []parser.Rule{
124 {
125 Lines: parser.LineRange{First: 2, Last: 4},
126 Error: parser.ParseError{Err: fmt.Errorf("duplicated record key"), Line: 4},
127 },
128 },
129 },
130 {
131 content: []byte(`
132- alert: foo
133 alert: bar
134 expr: bar
135`),
136 output: []parser.Rule{
137 {
138 Lines: parser.LineRange{First: 2, Last: 3},
139 Error: parser.ParseError{Err: fmt.Errorf("duplicated alert key"), Line: 3},
140 },
141 },
142 },
143 {
144 content: []byte(`
145- alert: foo
146 for: 5m
147 expr: bar
148 for: 1m
149`),
150 output: []parser.Rule{
151 {
152 Lines: parser.LineRange{First: 2, Last: 5},
153 Error: parser.ParseError{Err: fmt.Errorf("duplicated for key"), Line: 5},
154 },
155 },
156 },
157 {
158 content: []byte(`
159- alert: foo
160 keep_firing_for: 5m
161 expr: bar
162 keep_firing_for: 1m
163`),
164 output: []parser.Rule{
165 {
166 Lines: parser.LineRange{First: 2, Last: 5},
167 Error: parser.ParseError{Err: fmt.Errorf("duplicated keep_firing_for key"), Line: 5},
168 },
169 },
170 },
171 {
172 content: []byte(`
173- alert: foo
174 labels: {}
175 expr: bar
176 labels: {}
177`),
178 output: []parser.Rule{
179 {
180 Lines: parser.LineRange{First: 2, Last: 5},
181 Error: parser.ParseError{Err: fmt.Errorf("duplicated labels key"), Line: 5},
182 },
183 },
184 },
185 {
186 content: []byte(`
187- record: foo
188 labels: {}
189 expr: bar
190 labels: {}
191`),
192 output: []parser.Rule{
193 {
194 Lines: parser.LineRange{First: 2, Last: 5},
195 Error: parser.ParseError{Err: fmt.Errorf("duplicated labels key"), Line: 5},
196 },
197 },
198 },
199 {
200 content: []byte(`
201- alert: foo
202 annotations: {}
203 expr: bar
204 annotations: {}
205`),
206 output: []parser.Rule{
207 {
208 Lines: parser.LineRange{First: 2, Last: 5},
209 Error: parser.ParseError{Err: fmt.Errorf("duplicated annotations key"), Line: 5},
210 },
211 },
212 },
213 {
214 content: []byte("- record: foo\n expr: foo\n extra: true\n"),
215 output: []parser.Rule{
216 {
217 Lines: parser.LineRange{First: 1, Last: 3},
218 Error: parser.ParseError{Err: fmt.Errorf("invalid key(s) found: extra"), Line: 3},
219 },
220 },
221 },
222 {
223 content: []byte("- record: foo\n expr: foo offset 10m\n"),
224 output: []parser.Rule{
225 {
226 Lines: parser.LineRange{First: 1, Last: 2},
227 RecordingRule: &parser.RecordingRule{
228 Record: parser.YamlNode{
229 Lines: parser.LineRange{First: 1, Last: 1},
230 Value: "foo",
231 },
232 Expr: parser.PromQLExpr{
233 Value: &parser.YamlNode{
234 Lines: parser.LineRange{First: 2, Last: 2},
235 Value: "foo offset 10m",
236 },
237 Query: &parser.PromQLNode{
238 Expr: "foo offset 10m",
239 },
240 },
241 },
242 },
243 },
244 },
245 {
246 content: []byte("- record: foo\n expr: foo offset -10m\n"),
247 output: []parser.Rule{
248 {
249 Lines: parser.LineRange{First: 1, Last: 2},
250 RecordingRule: &parser.RecordingRule{
251 Record: parser.YamlNode{
252 Lines: parser.LineRange{First: 1, Last: 1},
253 Value: "foo",
254 },
255 Expr: parser.PromQLExpr{
256 Value: &parser.YamlNode{
257 Lines: parser.LineRange{First: 2, Last: 2},
258 Value: "foo offset -10m",
259 },
260 Query: &parser.PromQLNode{
261 Expr: "foo offset -10m",
262 },
263 },
264 },
265 },
266 },
267 },
268 {
269 content: []byte(`
270# pint disable head comment
271- record: foo # pint disable record comment
272 expr: foo offset 10m # pint disable expr comment
273 # pint disable pre-labels comment
274 labels:
275 # pint disable pre-foo comment
276 foo: bar
277 # pint disable post-foo comment
278 bob: alice
279 # pint disable foot comment
280`),
281 output: []parser.Rule{
282 {
283 Lines: parser.LineRange{First: 3, Last: 10},
284 Comments: []comments.Comment{
285 {
286 Type: comments.DisableType,
287 Value: comments.Disable{Match: "head comment"},
288 },
289 {
290 Type: comments.DisableType,
291 Value: comments.Disable{Match: "record comment"},
292 },
293 {
294 Type: comments.DisableType,
295 Value: comments.Disable{Match: "expr comment"},
296 },
297 {
298 Type: comments.DisableType,
299 Value: comments.Disable{Match: "pre-labels comment"},
300 },
301 {
302 Type: comments.DisableType,
303 Value: comments.Disable{Match: "foot comment"},
304 },
305 {
306 Type: comments.DisableType,
307 Value: comments.Disable{Match: "pre-foo comment"},
308 },
309 {
310 Type: comments.DisableType,
311 Value: comments.Disable{Match: "post-foo comment"},
312 },
313 },
314 RecordingRule: &parser.RecordingRule{
315 Record: parser.YamlNode{
316 Lines: parser.LineRange{First: 3, Last: 3},
317 Value: "foo",
318 },
319 Expr: parser.PromQLExpr{
320 Value: &parser.YamlNode{
321 Lines: parser.LineRange{First: 4, Last: 4},
322 Value: "foo offset 10m",
323 },
324 Query: &parser.PromQLNode{
325 Expr: "foo offset 10m",
326 },
327 },
328 Labels: &parser.YamlMap{
329 Lines: parser.LineRange{First: 6, Last: 10},
330 Key: &parser.YamlNode{
331 Lines: parser.LineRange{First: 6, Last: 6},
332 Value: "labels",
333 },
334 Items: []*parser.YamlKeyValue{
335 {
336 Key: &parser.YamlNode{
337 Lines: parser.LineRange{First: 8, Last: 8},
338 Value: "foo",
339 },
340 Value: &parser.YamlNode{
341 Lines: parser.LineRange{First: 8, Last: 8},
342 Value: "bar",
343 },
344 },
345 {
346 Key: &parser.YamlNode{
347 Lines: parser.LineRange{First: 10, Last: 10},
348 Value: "bob",
349 },
350 Value: &parser.YamlNode{
351 Lines: parser.LineRange{First: 10, Last: 10},
352 Value: "alice",
353 },
354 },
355 },
356 },
357 },
358 },
359 },
360 },
361 {
362 content: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
363 output: []parser.Rule{
364 {
365 Lines: parser.LineRange{First: 1, Last: 2},
366 RecordingRule: &parser.RecordingRule{
367 Record: parser.YamlNode{
368 Lines: parser.LineRange{First: 1, Last: 1},
369 Value: "foo",
370 },
371 Expr: parser.PromQLExpr{
372 Value: &parser.YamlNode{
373 Lines: parser.LineRange{First: 2, Last: 2},
374 Value: "foo[5m] offset 10m",
375 },
376 Query: &parser.PromQLNode{
377 Expr: "foo[5m] offset 10m",
378 Children: []*parser.PromQLNode{
379 {Expr: "foo offset 10m"},
380 },
381 },
382 },
383 },
384 },
385 },
386 },
387 {
388 content: []byte(`
389- record: name
390 expr: sum(foo)
391 labels:
392 foo: bar
393 bob: alice
394`),
395 output: []parser.Rule{
396 {
397 Lines: parser.LineRange{First: 2, Last: 6},
398 RecordingRule: &parser.RecordingRule{
399 Record: parser.YamlNode{
400 Lines: parser.LineRange{First: 2, Last: 2},
401 Value: "name",
402 },
403 Expr: parser.PromQLExpr{
404 Value: &parser.YamlNode{
405 Lines: parser.LineRange{First: 3, Last: 3},
406 Value: "sum(foo)",
407 },
408 Query: &parser.PromQLNode{
409 Expr: "sum(foo)",
410 Children: []*parser.PromQLNode{
411 {Expr: "foo"},
412 },
413 },
414 },
415 Labels: &parser.YamlMap{
416 Lines: parser.LineRange{First: 4, Last: 6},
417 Key: &parser.YamlNode{
418 Lines: parser.LineRange{First: 4, Last: 4},
419 Value: "labels",
420 },
421 Items: []*parser.YamlKeyValue{
422 {
423 Key: &parser.YamlNode{
424 Lines: parser.LineRange{First: 5, Last: 5},
425 Value: "foo",
426 },
427 Value: &parser.YamlNode{
428 Lines: parser.LineRange{First: 5, Last: 5},
429 Value: "bar",
430 },
431 },
432 {
433 Key: &parser.YamlNode{
434 Lines: parser.LineRange{First: 6, Last: 6},
435 Value: "bob",
436 },
437 Value: &parser.YamlNode{
438 Lines: parser.LineRange{First: 6, Last: 6},
439 Value: "alice",
440 },
441 },
442 },
443 },
444 },
445 },
446 },
447 },
448 {
449 content: []byte(`
450groups:
451- name: custom_rules
452 rules:
453 - record: name
454 expr: sum(foo)
455 labels:
456 foo: bar
457 bob: alice
458`),
459 output: []parser.Rule{
460 {
461 Lines: parser.LineRange{First: 5, Last: 9},
462 RecordingRule: &parser.RecordingRule{
463 Record: parser.YamlNode{
464 Lines: parser.LineRange{First: 5, Last: 5},
465 Value: "name",
466 },
467 Expr: parser.PromQLExpr{
468 Value: &parser.YamlNode{
469 Lines: parser.LineRange{First: 6, Last: 6},
470 Value: "sum(foo)",
471 },
472 Query: &parser.PromQLNode{
473 Expr: "sum(foo)",
474 Children: []*parser.PromQLNode{
475 {Expr: "foo"},
476 },
477 },
478 },
479 Labels: &parser.YamlMap{
480 Lines: parser.LineRange{First: 7, Last: 9},
481 Key: &parser.YamlNode{
482 Lines: parser.LineRange{First: 7, Last: 7},
483 Value: "labels",
484 },
485 Items: []*parser.YamlKeyValue{
486 {
487 Key: &parser.YamlNode{
488 Lines: parser.LineRange{First: 8, Last: 8},
489 Value: "foo",
490 },
491 Value: &parser.YamlNode{
492 Lines: parser.LineRange{First: 8, Last: 8},
493 Value: "bar",
494 },
495 },
496 {
497 Key: &parser.YamlNode{
498 Lines: parser.LineRange{First: 9, Last: 9},
499 Value: "bob",
500 },
501 Value: &parser.YamlNode{
502 Lines: parser.LineRange{First: 9, Last: 9},
503 Value: "alice",
504 },
505 },
506 },
507 },
508 },
509 },
510 },
511 },
512 {
513 content: []byte(`- alert: Down
514 expr: |
515 up == 0
516 for: |+
517 11m
518 labels:
519 severity: critical
520 annotations:
521 uri: https://docs.example.com/down.html
522
523- record: foo
524 expr: |-
525 bar
526 /
527 baz > 1
528 labels: {}
529`),
530 output: []parser.Rule{
531 {
532 Lines: parser.LineRange{First: 1, Last: 9},
533 AlertingRule: &parser.AlertingRule{
534 Alert: parser.YamlNode{
535 Lines: parser.LineRange{First: 1, Last: 1},
536 Value: "Down",
537 },
538 Expr: parser.PromQLExpr{
539 Value: &parser.YamlNode{
540 Lines: parser.LineRange{First: 2, Last: 3},
541 Value: "up == 0\n",
542 },
543 Query: &parser.PromQLNode{
544 Expr: "up == 0\n",
545 Children: []*parser.PromQLNode{
546 {Expr: "up"},
547 {Expr: "0"},
548 },
549 },
550 },
551 For: &parser.YamlNode{
552 Lines: parser.LineRange{First: 4, Last: 5},
553 Value: "11m\n",
554 },
555 Labels: &parser.YamlMap{
556 Lines: parser.LineRange{First: 6, Last: 7},
557 Key: &parser.YamlNode{
558 Lines: parser.LineRange{First: 6, Last: 6},
559 Value: "labels",
560 },
561 Items: []*parser.YamlKeyValue{
562 {
563 Key: &parser.YamlNode{
564 Lines: parser.LineRange{First: 7, Last: 7},
565 Value: "severity",
566 },
567 Value: &parser.YamlNode{
568 Lines: parser.LineRange{First: 7, Last: 7},
569 Value: "critical",
570 },
571 },
572 },
573 },
574 Annotations: &parser.YamlMap{
575 Lines: parser.LineRange{First: 8, Last: 9},
576 Key: &parser.YamlNode{
577 Lines: parser.LineRange{First: 8, Last: 8},
578 Value: "annotations",
579 },
580 Items: []*parser.YamlKeyValue{
581 {
582 Key: &parser.YamlNode{
583 Lines: parser.LineRange{First: 9, Last: 9},
584 Value: "uri",
585 },
586 Value: &parser.YamlNode{
587 Lines: parser.LineRange{First: 9, Last: 9},
588 Value: "https://docs.example.com/down.html",
589 },
590 },
591 },
592 },
593 },
594 },
595 {
596 Lines: parser.LineRange{First: 11, Last: 16},
597 RecordingRule: &parser.RecordingRule{
598 Record: parser.YamlNode{
599 Lines: parser.LineRange{First: 11, Last: 11},
600 Value: "foo",
601 },
602 Expr: parser.PromQLExpr{
603 Value: &parser.YamlNode{
604 Lines: parser.LineRange{First: 12, Last: 15},
605 Value: "bar\n/\nbaz > 1",
606 },
607 Query: &parser.PromQLNode{
608 Expr: "bar\n/\nbaz > 1",
609 Children: []*parser.PromQLNode{
610 {
611 Expr: "bar / baz", Children: []*parser.PromQLNode{
612 {Expr: "bar"},
613 {Expr: "baz"},
614 },
615 },
616 {Expr: "1"},
617 },
618 },
619 },
620 Labels: &parser.YamlMap{
621 Lines: parser.LineRange{First: 16, Last: 16},
622 Key: &parser.YamlNode{
623 Lines: parser.LineRange{First: 16, Last: 16},
624 Value: "labels",
625 },
626 },
627 },
628 },
629 },
630 },
631 {
632 content: []byte(`- alert: Foo
633 expr:
634 (
635 xxx
636 -
637 yyy
638 ) * bar > 0
639 and on(instance, device) baz
640 for: 30m
641`),
642 output: []parser.Rule{
643 {
644 Lines: parser.LineRange{First: 1, Last: 9},
645 AlertingRule: &parser.AlertingRule{
646 Alert: parser.YamlNode{
647 Lines: parser.LineRange{First: 1, Last: 1},
648 Value: "Foo",
649 },
650 Expr: parser.PromQLExpr{
651 Value: &parser.YamlNode{
652 Lines: parser.LineRange{First: 2, Last: 8},
653 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
654 },
655 Query: &parser.PromQLNode{
656 Expr: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
657 Children: []*parser.PromQLNode{
658 {
659 Expr: "(xxx - yyy) * bar > 0",
660 Children: []*parser.PromQLNode{
661 {
662 Expr: "(xxx - yyy) * bar",
663 Children: []*parser.PromQLNode{
664 {
665 Expr: "(xxx - yyy)",
666 Children: []*parser.PromQLNode{
667 {
668 Expr: "xxx - yyy",
669 Children: []*parser.PromQLNode{
670 {Expr: "xxx"},
671 {Expr: "yyy"},
672 },
673 },
674 },
675 },
676 {
677 Expr: "bar",
678 },
679 },
680 },
681 {
682 Expr: "0",
683 },
684 },
685 },
686 {Expr: "baz"},
687 },
688 },
689 },
690 For: &parser.YamlNode{
691 Lines: parser.LineRange{First: 9, Last: 9},
692 Value: "30m",
693 },
694 },
695 },
696 },
697 },
698 {
699 content: []byte(`---
700kind: ConfigMap
701apiVersion: v1
702metadata:
703 name: example-app-alerts
704 labels:
705 app: example-app
706data:
707 alerts: |
708 groups:
709 - name: example-app-alerts
710 rules:
711 - alert: Example_High_Restart_Rate
712 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
713---
714kind: ConfigMap
715apiVersion: v1
716metadata:
717 name: other
718 labels:
719 app: other
720data:
721 alerts: |
722 groups:
723 - name: other alerts
724 rules:
725 - alert: Example_High_Restart_Rate
726 expr: "1"
727
728`),
729 output: []parser.Rule{
730 {
731 Lines: parser.LineRange{First: 13, Last: 14},
732 AlertingRule: &parser.AlertingRule{
733 Alert: parser.YamlNode{
734 Lines: parser.LineRange{First: 13, Last: 13},
735 Value: "Example_High_Restart_Rate",
736 },
737 Expr: parser.PromQLExpr{
738 Value: &parser.YamlNode{
739 Lines: parser.LineRange{First: 14, Last: 14},
740 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
741 },
742 Query: &parser.PromQLNode{
743 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
744 Children: []*parser.PromQLNode{
745 {
746 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m]))`,
747 Children: []*parser.PromQLNode{
748 {
749 Expr: `rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])`,
750 Children: []*parser.PromQLNode{
751 {
752 Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}[5m]`,
753 Children: []*parser.PromQLNode{
754 {Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}`},
755 },
756 },
757 },
758 },
759 },
760 },
761 {
762 Expr: "(3 / 60)",
763 Children: []*parser.PromQLNode{
764 {
765 Expr: "3 / 60",
766 Children: []*parser.PromQLNode{
767 {Expr: "3"},
768 {Expr: "60"},
769 },
770 },
771 },
772 },
773 },
774 },
775 },
776 },
777 },
778 {
779 Lines: parser.LineRange{First: 27, Last: 28},
780 AlertingRule: &parser.AlertingRule{
781 Expr: parser.PromQLExpr{
782 Value: &parser.YamlNode{Value: "1", Lines: parser.LineRange{First: 28, Last: 28}},
783 Query: &parser.PromQLNode{Expr: "1"},
784 },
785 Alert: parser.YamlNode{Value: "Example_High_Restart_Rate", Lines: parser.LineRange{First: 27, Last: 27}},
786 },
787 },
788 },
789 },
790 {
791 content: []byte(`---
792kind: ConfigMap
793apiVersion: v1
794metadata:
795 name: example-app-alerts
796 labels:
797 app: example-app
798data:
799 alerts: |
800 groups:
801 - name: example-app-alerts
802 rules:
803 - alert: Example_Is_Down
804 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
805 for: 5m
806 labels:
807 priority: "2"
808 environment: production
809 annotations:
810 summary: "No replicas for Example have been running for 5 minutes"
811
812 - alert: Example_High_Restart_Rate
813 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
814`),
815 output: []parser.Rule{
816 {
817 Lines: parser.LineRange{First: 13, Last: 20},
818 AlertingRule: &parser.AlertingRule{
819 Alert: parser.YamlNode{
820 Lines: parser.LineRange{First: 13, Last: 13},
821 Value: "Example_Is_Down",
822 },
823 Expr: parser.PromQLExpr{
824 Value: &parser.YamlNode{
825 Lines: parser.LineRange{First: 14, Last: 14},
826 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
827 },
828 Query: &parser.PromQLNode{
829 Expr: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
830 Children: []*parser.PromQLNode{
831 {Expr: `kube_deployment_status_replicas_available{namespace="example-app"}`},
832 {Expr: "1"},
833 },
834 },
835 },
836 For: &parser.YamlNode{
837 Lines: parser.LineRange{First: 15, Last: 15},
838 Value: "5m",
839 },
840 Labels: &parser.YamlMap{
841 Lines: parser.LineRange{First: 16, Last: 18},
842 Key: &parser.YamlNode{
843 Lines: parser.LineRange{First: 16, Last: 16},
844 Value: "labels",
845 },
846 Items: []*parser.YamlKeyValue{
847 {
848 Key: &parser.YamlNode{
849 Lines: parser.LineRange{First: 17, Last: 17},
850 Value: "priority",
851 },
852 Value: &parser.YamlNode{
853 Lines: parser.LineRange{First: 17, Last: 17},
854 Value: "2",
855 },
856 },
857 {
858 Key: &parser.YamlNode{
859 Lines: parser.LineRange{First: 18, Last: 18},
860 Value: "environment",
861 },
862 Value: &parser.YamlNode{
863 Lines: parser.LineRange{First: 18, Last: 18},
864 Value: "production",
865 },
866 },
867 },
868 },
869 Annotations: &parser.YamlMap{
870 Lines: parser.LineRange{First: 19, Last: 20},
871 Key: &parser.YamlNode{
872 Lines: parser.LineRange{First: 19, Last: 19},
873 Value: "annotations",
874 },
875 Items: []*parser.YamlKeyValue{
876 {
877 Key: &parser.YamlNode{
878 Lines: parser.LineRange{First: 20, Last: 20},
879 Value: "summary",
880 },
881 Value: &parser.YamlNode{
882 Lines: parser.LineRange{First: 20, Last: 20},
883 Value: "No replicas for Example have been running for 5 minutes",
884 },
885 },
886 },
887 },
888 },
889 },
890 {
891 Lines: parser.LineRange{First: 22, Last: 23},
892 AlertingRule: &parser.AlertingRule{
893 Alert: parser.YamlNode{
894 Lines: parser.LineRange{First: 22, Last: 22},
895 Value: "Example_High_Restart_Rate",
896 },
897 Expr: parser.PromQLExpr{
898 Value: &parser.YamlNode{
899 Lines: parser.LineRange{First: 23, Last: 23},
900 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
901 },
902 Query: &parser.PromQLNode{
903 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
904 Children: []*parser.PromQLNode{
905 {
906 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m]))`,
907 Children: []*parser.PromQLNode{
908 {
909 Expr: `rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])`,
910 Children: []*parser.PromQLNode{
911 {
912 Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}[5m]`,
913 Children: []*parser.PromQLNode{
914 {Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}`},
915 },
916 },
917 },
918 },
919 },
920 },
921 {
922 Expr: "(3 / 60)",
923 Children: []*parser.PromQLNode{
924 {
925 Expr: "3 / 60",
926 Children: []*parser.PromQLNode{
927 {Expr: "3"},
928 {Expr: "60"},
929 },
930 },
931 },
932 },
933 },
934 },
935 },
936 },
937 },
938 },
939 },
940 {
941 content: []byte(`groups:
942- name: "haproxy.api_server.rules"
943 rules:
944 - alert: HaproxyServerHealthcheckFailure
945 expr: increase(haproxy_server_check_failures_total[15m]) > 100
946 for: 5m
947 labels:
948 severity: 24x7
949 annotations:
950 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
951 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
952`),
953 output: []parser.Rule{
954 {
955 Lines: parser.LineRange{First: 4, Last: 13},
956 AlertingRule: &parser.AlertingRule{
957 Alert: parser.YamlNode{
958 Lines: parser.LineRange{First: 4, Last: 4},
959 Value: "HaproxyServerHealthcheckFailure",
960 },
961 Expr: parser.PromQLExpr{
962 Value: &parser.YamlNode{
963 Lines: parser.LineRange{First: 5, Last: 5},
964 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
965 },
966 Query: &parser.PromQLNode{
967 Expr: "increase(haproxy_server_check_failures_total[15m]) > 100",
968 Children: []*parser.PromQLNode{
969 {
970 Expr: "increase(haproxy_server_check_failures_total[15m])",
971 Children: []*parser.PromQLNode{
972 {
973 Expr: "haproxy_server_check_failures_total[15m]",
974 Children: []*parser.PromQLNode{
975 {
976 Expr: "haproxy_server_check_failures_total",
977 },
978 },
979 },
980 },
981 },
982 {Expr: "100"},
983 },
984 },
985 },
986 For: &parser.YamlNode{
987 Lines: parser.LineRange{First: 6, Last: 6},
988 Value: "5m",
989 },
990 Labels: &parser.YamlMap{
991 Lines: parser.LineRange{First: 7, Last: 8},
992 Key: &parser.YamlNode{
993 Lines: parser.LineRange{First: 7, Last: 7},
994 Value: "labels",
995 },
996 Items: []*parser.YamlKeyValue{
997 {
998 Key: &parser.YamlNode{
999 Lines: parser.LineRange{First: 8, Last: 8},
1000 Value: "severity",
1001 },
1002 Value: &parser.YamlNode{
1003 Lines: parser.LineRange{First: 8, Last: 8},
1004 Value: "24x7",
1005 },
1006 },
1007 },
1008 },
1009 Annotations: &parser.YamlMap{
1010 Lines: parser.LineRange{First: 9, Last: 13},
1011 Key: &parser.YamlNode{
1012 Lines: parser.LineRange{First: 9, Last: 9},
1013 Value: "annotations",
1014 },
1015 Items: []*parser.YamlKeyValue{
1016 {
1017 Key: &parser.YamlNode{
1018 Lines: parser.LineRange{First: 10, Last: 10},
1019 Value: "summary",
1020 },
1021 Value: &parser.YamlNode{
1022 Lines: parser.LineRange{First: 10, Last: 10},
1023 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1024 },
1025 },
1026 {
1027 Key: &parser.YamlNode{
1028 Lines: parser.LineRange{First: 11, Last: 11},
1029 Value: "description",
1030 },
1031 Value: &parser.YamlNode{
1032 // FIXME https://github.com/cloudflare/pint/issues/20
1033 // Should be Lines: [11]
1034 Lines: parser.LineRange{First: 11, Last: 13},
1035 // Should be `Some ...` since \n should be escaped
1036 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1037 },
1038 },
1039 },
1040 },
1041 },
1042 },
1043 },
1044 },
1045 {
1046 content: []byte(`groups:
1047- name: certmanager
1048 rules:
1049 # pint disable before recordAnchor
1050 - &recordAnchor # pint disable recordAnchor
1051 record: name1 # pint disable name1
1052 expr: expr1 # pint disable expr1
1053 # pint disable after expr1
1054 - <<: *recordAnchor
1055 expr: expr2
1056 - <<: *recordAnchor
1057`),
1058 output: []parser.Rule{
1059 {
1060 Lines: parser.LineRange{First: 6, Last: 7},
1061 Comments: []comments.Comment{
1062 {
1063 Type: comments.DisableType,
1064 Value: comments.Disable{Match: "before recordAnchor"},
1065 },
1066 {
1067 Type: comments.DisableType,
1068 Value: comments.Disable{Match: "recordAnchor"},
1069 },
1070 {
1071 Type: comments.DisableType,
1072 Value: comments.Disable{Match: "name1"},
1073 },
1074 {
1075 Type: comments.DisableType,
1076 Value: comments.Disable{Match: "after expr1"},
1077 },
1078 {
1079 Type: comments.DisableType,
1080 Value: comments.Disable{Match: "expr1"},
1081 },
1082 },
1083 RecordingRule: &parser.RecordingRule{
1084 Record: parser.YamlNode{
1085 Lines: parser.LineRange{First: 6, Last: 6},
1086 Value: "name1",
1087 },
1088 Expr: parser.PromQLExpr{
1089 Value: &parser.YamlNode{
1090 Lines: parser.LineRange{First: 7, Last: 7},
1091 Value: "expr1",
1092 },
1093 Query: &parser.PromQLNode{Expr: "expr1"},
1094 },
1095 },
1096 },
1097 {
1098 Comments: []comments.Comment{
1099 {
1100 Type: comments.DisableType,
1101 Value: comments.Disable{Match: "before recordAnchor"},
1102 },
1103 {
1104 Type: comments.DisableType,
1105 Value: comments.Disable{Match: "recordAnchor"},
1106 },
1107 {
1108 Type: comments.DisableType,
1109 Value: comments.Disable{Match: "name1"},
1110 },
1111 },
1112 Lines: parser.LineRange{First: 6, Last: 10},
1113 RecordingRule: &parser.RecordingRule{
1114 Record: parser.YamlNode{
1115 Lines: parser.LineRange{First: 6, Last: 6},
1116 Value: "name1",
1117 },
1118 Expr: parser.PromQLExpr{
1119 Value: &parser.YamlNode{
1120 Lines: parser.LineRange{First: 10, Last: 10},
1121 Value: "expr2",
1122 },
1123 Query: &parser.PromQLNode{Expr: "expr2"},
1124 },
1125 },
1126 },
1127 {
1128 Comments: []comments.Comment{
1129 {
1130 Type: comments.DisableType,
1131 Value: comments.Disable{Match: "before recordAnchor"},
1132 },
1133 {
1134 Type: comments.DisableType,
1135 Value: comments.Disable{Match: "recordAnchor"},
1136 },
1137 {
1138 Type: comments.DisableType,
1139 Value: comments.Disable{Match: "name1"},
1140 },
1141 {
1142 Type: comments.DisableType,
1143 Value: comments.Disable{Match: "after expr1"},
1144 },
1145 {
1146 Type: comments.DisableType,
1147 Value: comments.Disable{Match: "expr1"},
1148 },
1149 },
1150 Lines: parser.LineRange{First: 6, Last: 7},
1151 RecordingRule: &parser.RecordingRule{
1152 Record: parser.YamlNode{
1153 Lines: parser.LineRange{First: 6, Last: 6},
1154 Value: "name1",
1155 },
1156 Expr: parser.PromQLExpr{
1157 Value: &parser.YamlNode{
1158 Lines: parser.LineRange{First: 7, Last: 7},
1159 Value: "expr1",
1160 },
1161 Query: &parser.PromQLNode{Expr: "expr1"},
1162 },
1163 },
1164 },
1165 },
1166 },
1167 {
1168 content: []byte(`groups:
1169- name: certmanager
1170 rules:
1171 - record: name1
1172 expr: expr1
1173 labels: &labelsAnchor
1174 label1: val1
1175 label2: val2
1176 - record: name2
1177 expr: expr2
1178 labels: *labelsAnchor
1179 # pint disable foot comment
1180`),
1181 output: []parser.Rule{
1182 {
1183 Lines: parser.LineRange{First: 4, Last: 8},
1184 RecordingRule: &parser.RecordingRule{
1185 Record: parser.YamlNode{
1186 Lines: parser.LineRange{First: 4, Last: 4},
1187 Value: "name1",
1188 },
1189 Expr: parser.PromQLExpr{
1190 Value: &parser.YamlNode{
1191 Lines: parser.LineRange{First: 5, Last: 5},
1192 Value: "expr1",
1193 },
1194 Query: &parser.PromQLNode{Expr: "expr1"},
1195 },
1196 Labels: &parser.YamlMap{
1197 Lines: parser.LineRange{First: 6, Last: 8},
1198 Key: &parser.YamlNode{
1199 Lines: parser.LineRange{First: 6, Last: 6},
1200 Value: "labels",
1201 },
1202 Items: []*parser.YamlKeyValue{
1203 {
1204 Key: &parser.YamlNode{
1205 Lines: parser.LineRange{First: 7, Last: 7},
1206 Value: "label1",
1207 },
1208 Value: &parser.YamlNode{
1209 Lines: parser.LineRange{First: 7, Last: 7},
1210 Value: "val1",
1211 },
1212 },
1213 {
1214 Key: &parser.YamlNode{
1215 Lines: parser.LineRange{First: 8, Last: 8},
1216 Value: "label2",
1217 },
1218 Value: &parser.YamlNode{
1219 Lines: parser.LineRange{First: 8, Last: 8},
1220 Value: "val2",
1221 },
1222 },
1223 },
1224 },
1225 },
1226 },
1227 {
1228 Comments: []comments.Comment{
1229 {
1230 Type: comments.DisableType,
1231 Value: comments.Disable{Match: "foot comment"},
1232 },
1233 },
1234 Lines: parser.LineRange{First: 9, Last: 11},
1235 RecordingRule: &parser.RecordingRule{
1236 Record: parser.YamlNode{
1237 Lines: parser.LineRange{First: 9, Last: 9},
1238 Value: "name2",
1239 },
1240 Expr: parser.PromQLExpr{
1241 Value: &parser.YamlNode{
1242 Lines: parser.LineRange{First: 10, Last: 10},
1243 Value: "expr2",
1244 },
1245 Query: &parser.PromQLNode{Expr: "expr2"},
1246 },
1247 Labels: &parser.YamlMap{
1248 Lines: parser.LineRange{First: 11, Last: 11},
1249 Key: &parser.YamlNode{
1250 Lines: parser.LineRange{First: 11, Last: 11},
1251 Value: "labels",
1252 },
1253 Items: []*parser.YamlKeyValue{
1254 {
1255 Key: &parser.YamlNode{
1256 Lines: parser.LineRange{First: 7, Last: 7},
1257 Value: "label1",
1258 },
1259 Value: &parser.YamlNode{
1260 Lines: parser.LineRange{First: 7, Last: 7},
1261 Value: "val1",
1262 },
1263 },
1264 {
1265 Key: &parser.YamlNode{
1266 Lines: parser.LineRange{First: 8, Last: 8},
1267 Value: "label2",
1268 },
1269 Value: &parser.YamlNode{
1270 Lines: parser.LineRange{First: 8, Last: 8},
1271 Value: "val2",
1272 },
1273 },
1274 },
1275 },
1276 },
1277 },
1278 },
1279 },
1280 {
1281 content: []byte("- alert:\n expr: vector(1)\n"),
1282 output: []parser.Rule{
1283 {
1284 Lines: parser.LineRange{First: 1, Last: 2},
1285 Error: parser.ParseError{Err: fmt.Errorf("alert value cannot be empty"), Line: 1},
1286 },
1287 },
1288 },
1289 {
1290 content: []byte("- alert: foo\n expr:\n"),
1291 output: []parser.Rule{
1292 {
1293 Lines: parser.LineRange{First: 1, Last: 2},
1294 Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2},
1295 },
1296 },
1297 },
1298 {
1299 content: []byte("- alert: foo\n"),
1300 output: []parser.Rule{
1301 {
1302 Lines: parser.LineRange{First: 1, Last: 1},
1303 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
1304 },
1305 },
1306 },
1307 {
1308 content: []byte("- record:\n expr:\n"),
1309 output: []parser.Rule{
1310 {
1311 Lines: parser.LineRange{First: 1, Last: 2},
1312 Error: parser.ParseError{Err: fmt.Errorf("record value cannot be empty"), Line: 1},
1313 },
1314 },
1315 },
1316 {
1317 content: []byte("- record: foo\n expr:\n"),
1318 output: []parser.Rule{
1319 {
1320 Lines: parser.LineRange{First: 1, Last: 2},
1321 Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2},
1322 },
1323 },
1324 },
1325 {
1326 content: []byte("- record: foo\n"),
1327 output: []parser.Rule{
1328 {
1329 Lines: parser.LineRange{First: 1, Last: 1},
1330 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
1331 },
1332 },
1333 },
1334 {
1335 content: []byte(string(`
1336# pint file/owner bob
1337# pint ignore/begin
1338# pint ignore/end
1339# pint disable up
1340
1341- record: foo
1342 expr: up
1343
1344# pint file/owner alice
1345
1346- record: foo
1347 expr: up
1348
1349# pint ignore/next-line
1350`)),
1351 output: []parser.Rule{
1352 {
1353 Lines: parser.LineRange{First: 7, Last: 8},
1354 RecordingRule: &parser.RecordingRule{
1355 Record: parser.YamlNode{
1356 Lines: parser.LineRange{First: 7, Last: 7},
1357 Value: "foo",
1358 },
1359 Expr: parser.PromQLExpr{
1360 Value: &parser.YamlNode{
1361 Lines: parser.LineRange{First: 8, Last: 8},
1362 Value: "up",
1363 },
1364 Query: &parser.PromQLNode{Expr: "up"},
1365 },
1366 },
1367 },
1368 {
1369 Lines: parser.LineRange{First: 12, Last: 13},
1370 RecordingRule: &parser.RecordingRule{
1371 Record: parser.YamlNode{
1372 Lines: parser.LineRange{First: 12, Last: 12},
1373 Value: "foo",
1374 },
1375 Expr: parser.PromQLExpr{
1376 Value: &parser.YamlNode{
1377 Lines: parser.LineRange{First: 13, Last: 13},
1378 Value: "up",
1379 },
1380 Query: &parser.PromQLNode{Expr: "up"},
1381 },
1382 },
1383 },
1384 },
1385 },
1386 {
1387 content: []byte(string(`
1388- alert: Template
1389 expr: &expr up == 0
1390 labels:
1391 notify: &maybe_escalate_notify chat-alerts
1392- alert: Service Down
1393 expr: *expr
1394 labels:
1395 notify: *maybe_escalate_notify
1396 summary: foo
1397`)),
1398 output: []parser.Rule{
1399 {
1400 Lines: parser.LineRange{First: 2, Last: 5},
1401 AlertingRule: &parser.AlertingRule{
1402 Alert: parser.YamlNode{
1403 Lines: parser.LineRange{First: 2, Last: 2},
1404 Value: "Template",
1405 },
1406 Expr: parser.PromQLExpr{
1407 Value: &parser.YamlNode{
1408 Lines: parser.LineRange{First: 3, Last: 3},
1409 Value: "up == 0",
1410 },
1411 Query: &parser.PromQLNode{
1412 Expr: "up == 0",
1413 Children: []*parser.PromQLNode{
1414 {Expr: "up"},
1415 {Expr: "0"},
1416 },
1417 },
1418 },
1419 Labels: &parser.YamlMap{
1420 Lines: parser.LineRange{First: 4, Last: 5},
1421 Key: &parser.YamlNode{
1422 Lines: parser.LineRange{First: 4, Last: 4},
1423 Value: "labels",
1424 },
1425 Items: []*parser.YamlKeyValue{
1426 {
1427 Key: &parser.YamlNode{
1428 Lines: parser.LineRange{First: 5, Last: 5},
1429 Value: "notify",
1430 },
1431 Value: &parser.YamlNode{
1432 Lines: parser.LineRange{First: 5, Last: 5},
1433 Value: "chat-alerts",
1434 },
1435 },
1436 },
1437 },
1438 },
1439 },
1440 {
1441 Lines: parser.LineRange{First: 6, Last: 10},
1442 AlertingRule: &parser.AlertingRule{
1443 Alert: parser.YamlNode{
1444 Lines: parser.LineRange{First: 6, Last: 6},
1445 Value: "Service Down",
1446 },
1447 Expr: parser.PromQLExpr{
1448 Value: &parser.YamlNode{
1449 Lines: parser.LineRange{First: 7, Last: 7},
1450 Value: "up == 0",
1451 },
1452 Query: &parser.PromQLNode{
1453 Expr: "up == 0",
1454 Children: []*parser.PromQLNode{
1455 {Expr: "up"},
1456 {Expr: "0"},
1457 },
1458 },
1459 },
1460 Labels: &parser.YamlMap{
1461 Lines: parser.LineRange{First: 8, Last: 10},
1462 Key: &parser.YamlNode{
1463 Lines: parser.LineRange{First: 8, Last: 8},
1464 Value: "labels",
1465 },
1466 Items: []*parser.YamlKeyValue{
1467 {
1468 Key: &parser.YamlNode{
1469 Lines: parser.LineRange{First: 9, Last: 9},
1470 Value: "notify",
1471 },
1472 Value: &parser.YamlNode{
1473 Lines: parser.LineRange{First: 9, Last: 9},
1474 Value: "chat-alerts",
1475 },
1476 },
1477 {
1478 Key: &parser.YamlNode{
1479 Lines: parser.LineRange{First: 10, Last: 10},
1480 Value: "summary",
1481 },
1482 Value: &parser.YamlNode{
1483 Lines: parser.LineRange{First: 10, Last: 10},
1484 Value: "foo",
1485 },
1486 },
1487 },
1488 },
1489 },
1490 },
1491 },
1492 },
1493 {
1494 content: []byte(`
1495- record: invalid metric name
1496 expr: bar
1497`),
1498 output: []parser.Rule{
1499 {
1500 Lines: parser.LineRange{First: 2, Last: 3},
1501 Error: parser.ParseError{Err: fmt.Errorf("invalid recording rule name: invalid metric name"), Line: 2},
1502 },
1503 },
1504 },
1505 {
1506 content: []byte(`
1507- record: foo
1508 expr: bar
1509 labels:
1510 "foo bar": yes
1511`),
1512 output: []parser.Rule{
1513 {
1514 Lines: parser.LineRange{First: 2, Last: 5},
1515 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: foo bar"), Line: 5},
1516 },
1517 },
1518 },
1519 {
1520 content: []byte(`
1521- alert: foo
1522 expr: bar
1523 labels:
1524 "foo bar": yes
1525`),
1526 output: []parser.Rule{
1527 {
1528 Lines: parser.LineRange{First: 2, Last: 5},
1529 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: foo bar"), Line: 5},
1530 },
1531 },
1532 },
1533 {
1534 content: []byte(`
1535- alert: foo
1536 expr: bar
1537 labels:
1538 "{{ $value }}": yes
1539`),
1540 output: []parser.Rule{
1541 {
1542 Lines: parser.LineRange{First: 2, Last: 5},
1543 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: {{ $value }}"), Line: 5},
1544 },
1545 },
1546 },
1547 {
1548 content: []byte(`
1549- alert: foo
1550 expr: bar
1551 annotations:
1552 "foo bar": yes
1553`),
1554 output: []parser.Rule{
1555 {
1556 Lines: parser.LineRange{First: 2, Last: 5},
1557 Error: parser.ParseError{Err: fmt.Errorf("invalid annotation name: foo bar"), Line: 5},
1558 },
1559 },
1560 },
1561 {
1562 content: []byte(`
1563- alert: foo
1564 expr: bar
1565 labels:
1566 foo: ` + string("\xed\xbf\xbf")),
1567 // Label values are invalid only if they aren't valid UTF-8 strings
1568 // which also makes them unparsable by YAML.
1569 err: "yaml: invalid Unicode character",
1570 },
1571 {
1572 content: []byte(`
1573- alert: foo
1574 expr: bar
1575 annotations:
1576 "{{ $value }}": yes
1577`),
1578 output: []parser.Rule{
1579 {
1580 Lines: parser.LineRange{First: 2, Last: 5},
1581 Error: parser.ParseError{Err: fmt.Errorf("invalid annotation name: {{ $value }}"), Line: 5},
1582 },
1583 },
1584 },
1585 {
1586 content: []byte(`
1587- record: foo
1588 expr: bar
1589 keep_firing_for: 5m
1590`),
1591 output: []parser.Rule{
1592 {
1593 Lines: parser.LineRange{First: 2, Last: 4},
1594 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'keep_firing_for' in recording rule"), Line: 4},
1595 },
1596 },
1597 },
1598 {
1599 content: []byte(`
1600- record: foo
1601 expr: bar
1602 for: 5m
1603`),
1604 output: []parser.Rule{
1605 {
1606 Lines: parser.LineRange{First: 2, Last: 4},
1607 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'for' in recording rule"), Line: 4},
1608 },
1609 },
1610 },
1611 {
1612 content: []byte(`
1613- record: foo
1614 expr: bar
1615 annotations:
1616 foo: bar
1617`),
1618 output: []parser.Rule{
1619 {
1620 Lines: parser.LineRange{First: 2, Last: 5},
1621 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'annotations' in recording rule"), Line: 4},
1622 },
1623 },
1624 },
1625 // Tag tests
1626 {
1627 content: []byte(`
1628- record: 5
1629 expr: bar
1630`),
1631 output: []parser.Rule{
1632 {
1633 Lines: parser.LineRange{First: 2, Last: 3},
1634 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got integer instead"), Line: 2},
1635 },
1636 },
1637 },
1638 {
1639 content: []byte(`
1640- alert: 5
1641 expr: bar
1642`),
1643 output: []parser.Rule{
1644 {
1645 Lines: parser.LineRange{First: 2, Last: 3},
1646 Error: parser.ParseError{Err: fmt.Errorf("alert value must be a YAML string, got integer instead"), Line: 2},
1647 },
1648 },
1649 },
1650 {
1651 content: []byte(`
1652- record: foo
1653 expr: 5
1654`),
1655 output: []parser.Rule{
1656 {
1657 Lines: parser.LineRange{First: 2, Last: 3},
1658 Error: parser.ParseError{Err: fmt.Errorf("expr value must be a YAML string, got integer instead"), Line: 3},
1659 },
1660 },
1661 },
1662 {
1663 content: []byte(`
1664- alert: foo
1665 expr: bar
1666 for: 5
1667`),
1668 output: []parser.Rule{
1669 {
1670 Lines: parser.LineRange{First: 2, Last: 4},
1671 Error: parser.ParseError{Err: fmt.Errorf("for value must be a YAML string, got integer instead"), Line: 4},
1672 },
1673 },
1674 },
1675 {
1676 content: []byte(`
1677- alert: foo
1678 expr: bar
1679 keep_firing_for: 5
1680`),
1681 output: []parser.Rule{
1682 {
1683 Lines: parser.LineRange{First: 2, Last: 4},
1684 Error: parser.ParseError{Err: fmt.Errorf("keep_firing_for value must be a YAML string, got integer instead"), Line: 4},
1685 },
1686 },
1687 },
1688 {
1689 content: []byte(`
1690- record: foo
1691 expr: bar
1692 labels: []
1693`),
1694 output: []parser.Rule{
1695 {
1696 Lines: parser.LineRange{First: 2, Last: 4},
1697 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got list instead"), Line: 4},
1698 },
1699 },
1700 },
1701 {
1702 content: []byte(`
1703- alert: foo
1704 expr: bar
1705 labels: {}
1706 annotations: []
1707`),
1708 output: []parser.Rule{
1709 {
1710 Lines: parser.LineRange{First: 2, Last: 5},
1711 Error: parser.ParseError{Err: fmt.Errorf("annotations value must be a YAML mapping, got list instead"), Line: 5},
1712 },
1713 },
1714 },
1715 {
1716 content: []byte(`
1717- alert: foo
1718 expr: bar
1719 labels:
1720 foo: 3
1721 annotations:
1722 bar: "5"
1723`),
1724 output: []parser.Rule{
1725 {
1726 Lines: parser.LineRange{First: 2, Last: 7},
1727 Error: parser.ParseError{Err: fmt.Errorf("labels foo value must be a YAML string, got integer instead"), Line: 5},
1728 },
1729 },
1730 },
1731 {
1732 content: []byte(`
1733- alert: foo
1734 expr: bar
1735 labels: {}
1736 annotations:
1737 foo: "3"
1738 bar: 5
1739`),
1740 output: []parser.Rule{
1741 {
1742 Lines: parser.LineRange{First: 2, Last: 7},
1743 Error: parser.ParseError{Err: fmt.Errorf("annotations bar value must be a YAML string, got integer instead"), Line: 7},
1744 },
1745 },
1746 },
1747 {
1748 content: []byte(`
1749- record: foo
1750 expr: bar
1751 labels: 4
1752`),
1753 output: []parser.Rule{
1754 {
1755 Lines: parser.LineRange{First: 2, Last: 4},
1756 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got integer instead"), Line: 4},
1757 },
1758 },
1759 },
1760 {
1761 content: []byte(`
1762- record: foo
1763 expr: bar
1764 labels: true
1765`),
1766 output: []parser.Rule{
1767 {
1768 Lines: parser.LineRange{First: 2, Last: 4},
1769 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got bool instead"), Line: 4},
1770 },
1771 },
1772 },
1773 {
1774 content: []byte(`
1775- record: foo
1776 expr: bar
1777 labels: null
1778`),
1779 output: []parser.Rule{
1780 {
1781 Lines: parser.LineRange{First: 2, Last: 4},
1782 RecordingRule: &parser.RecordingRule{
1783 Record: parser.YamlNode{
1784 Lines: parser.LineRange{First: 2, Last: 2},
1785 Value: "foo",
1786 },
1787 Expr: parser.PromQLExpr{
1788 Value: &parser.YamlNode{
1789 Lines: parser.LineRange{First: 3, Last: 3},
1790 Value: "bar",
1791 },
1792 Query: &parser.PromQLNode{Expr: "bar"},
1793 },
1794 Labels: &parser.YamlMap{
1795 Lines: parser.LineRange{First: 4, Last: 4},
1796 Key: &parser.YamlNode{
1797 Lines: parser.LineRange{First: 4, Last: 4},
1798 Value: "labels",
1799 },
1800 },
1801 },
1802 },
1803 },
1804 },
1805 {
1806 content: []byte(`
1807- record: true
1808 expr: bar
1809`),
1810 output: []parser.Rule{
1811 {
1812 Lines: parser.LineRange{First: 2, Last: 3},
1813 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got bool instead"), Line: 2},
1814 },
1815 },
1816 },
1817 {
1818 content: []byte(`
1819- record:
1820 query: foo
1821 expr: bar
1822`),
1823 output: []parser.Rule{
1824 {
1825 Lines: parser.LineRange{First: 2, Last: 4},
1826 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got mapping instead"), Line: 3},
1827 },
1828 },
1829 },
1830 {
1831 content: []byte(`
1832- record: foo
1833 expr: bar
1834 labels: some
1835`),
1836 output: []parser.Rule{
1837 {
1838 Lines: parser.LineRange{First: 2, Last: 4},
1839 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got string instead"), Line: 4},
1840 },
1841 },
1842 },
1843 {
1844 content: []byte(`
1845- record: foo
1846 expr: bar
1847 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
1848`),
1849 output: []parser.Rule{
1850 {
1851 Lines: parser.LineRange{First: 2, Last: 4},
1852 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got binary data instead"), Line: 4},
1853 },
1854 },
1855 },
1856 {
1857 content: []byte(`
1858- alert: foo
1859 expr: bar
1860 for: 1.23
1861`),
1862 output: []parser.Rule{
1863 {
1864 Lines: parser.LineRange{First: 2, Last: 4},
1865 Error: parser.ParseError{Err: fmt.Errorf("for value must be a YAML string, got float instead"), Line: 4},
1866 },
1867 },
1868 },
1869 {
1870 content: []byte(`
1871- record: foo
1872 expr: bar
1873 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
1874`),
1875 output: []parser.Rule{
1876 {
1877 Lines: parser.LineRange{First: 2, Last: 4},
1878 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got garbage instead"), Line: 4},
1879 },
1880 },
1881 },
1882 {
1883 content: []byte(`
1884- record: foo
1885 expr: bar
1886 labels: !! "SGVsbG8sIFdvcmxkIQ=="
1887`),
1888 err: "yaml: line 4: did not find expected tag URI",
1889 },
1890 {
1891 content: []byte(`
1892- record: &foo foo
1893 expr: bar
1894 labels: *foo
1895`),
1896 output: []parser.Rule{
1897 {
1898 Lines: parser.LineRange{First: 2, Last: 4},
1899 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got string instead"), Line: 4},
1900 },
1901 },
1902 },
1903 // Multi-document tests
1904 {
1905 content: []byte(`---
1906- expr: foo
1907 record: foo
1908---
1909- expr: bar
1910`),
1911 output: []parser.Rule{
1912 {
1913 Lines: parser.LineRange{First: 2, Last: 3},
1914 RecordingRule: &parser.RecordingRule{
1915 Record: parser.YamlNode{
1916 Lines: parser.LineRange{First: 3, Last: 3},
1917 Value: "foo",
1918 },
1919 Expr: parser.PromQLExpr{
1920 Value: &parser.YamlNode{
1921 Lines: parser.LineRange{First: 2, Last: 2},
1922 Value: "foo",
1923 },
1924 Query: &parser.PromQLNode{Expr: "foo"},
1925 },
1926 },
1927 },
1928 {
1929 Lines: parser.LineRange{First: 5, Last: 5},
1930 Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 5},
1931 },
1932 },
1933 },
1934 {
1935 content: []byte(`---
1936- expr: foo
1937 record: foo
1938---
1939- expr: bar
1940 record: bar
1941 expr: bar
1942`),
1943 output: []parser.Rule{
1944 {
1945 Lines: parser.LineRange{First: 2, Last: 3},
1946 RecordingRule: &parser.RecordingRule{
1947 Record: parser.YamlNode{
1948 Lines: parser.LineRange{First: 3, Last: 3},
1949 Value: "foo",
1950 },
1951 Expr: parser.PromQLExpr{
1952 Value: &parser.YamlNode{
1953 Lines: parser.LineRange{First: 2, Last: 2},
1954 Value: "foo",
1955 },
1956 Query: &parser.PromQLNode{Expr: "foo"},
1957 },
1958 },
1959 },
1960 {
1961 Lines: parser.LineRange{First: 5, Last: 7},
1962 Error: parser.ParseError{Err: fmt.Errorf("duplicated expr key"), Line: 7},
1963 },
1964 },
1965 },
1966 {
1967 content: []byte(`---
1968- expr: foo
1969 record: foo
1970---
1971- expr: bar
1972 alert: foo
1973`),
1974 output: []parser.Rule{
1975 {
1976 Lines: parser.LineRange{First: 2, Last: 3},
1977 RecordingRule: &parser.RecordingRule{
1978 Record: parser.YamlNode{
1979 Lines: parser.LineRange{First: 3, Last: 3},
1980 Value: "foo",
1981 },
1982 Expr: parser.PromQLExpr{
1983 Value: &parser.YamlNode{
1984 Lines: parser.LineRange{First: 2, Last: 2},
1985 Value: "foo",
1986 },
1987 Query: &parser.PromQLNode{Expr: "foo"},
1988 },
1989 },
1990 },
1991 {
1992 Lines: parser.LineRange{First: 5, Last: 6},
1993 AlertingRule: &parser.AlertingRule{
1994 Alert: parser.YamlNode{
1995 Lines: parser.LineRange{First: 6, Last: 6},
1996 Value: "foo",
1997 },
1998 Expr: parser.PromQLExpr{
1999 Value: &parser.YamlNode{
2000 Lines: parser.LineRange{First: 5, Last: 5},
2001 Value: "bar",
2002 },
2003 Query: &parser.PromQLNode{Expr: "bar"},
2004 },
2005 },
2006 },
2007 },
2008 },
2009 }
2010
2011 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
2012 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
2013 _, xe := x.(promparser.Expr)
2014 _, ye := y.(promparser.Expr)
2015 return xe || ye
2016 }, alwaysEqual)
2017
2018 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
2019 xe := x.(error)
2020 ye := y.(error)
2021 return xe.Error() == ye.Error()
2022 })
2023 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
2024 _, xe := x.(error)
2025 _, ye := y.(error)
2026 return xe && ye
2027 }, cmpErrorText)
2028
2029 for i, tc := range testCases {
2030 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
2031 t.Logf("--- Content ---%s--- END ---", tc.content)
2032
2033 p := parser.NewParser()
2034 output, err := p.Parse(tc.content)
2035
2036 if tc.err != "" {
2037 require.EqualError(t, err, tc.err)
2038 } else {
2039 require.NoError(t, err)
2040 }
2041
2042 if diff := cmp.Diff(tc.output, output, ignorePrometheusExpr, sameErrorText); diff != "" {
2043 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
2044 return
2045 }
2046 })
2047 }
2048}
2049