cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.74.6

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

5196lines · modecode

1package parser_test
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "os"
8 "strconv"
9 "testing"
10 "time"
11
12 "github.com/prometheus/common/model"
13 "github.com/stretchr/testify/require"
14
15 "github.com/cloudflare/pint/internal/comments"
16 "github.com/cloudflare/pint/internal/diags"
17 "github.com/cloudflare/pint/internal/parser"
18
19 "github.com/google/go-cmp/cmp"
20 "github.com/google/go-cmp/cmp/cmpopts"
21)
22
23func TestParse(t *testing.T) {
24 type testCaseT struct {
25 input []byte
26 output parser.File
27 strict bool
28 schema parser.Schema
29 names model.ValidationScheme
30 }
31
32 testCases := []testCaseT{
33 {
34 input: nil,
35 output: parser.File{
36 IsRelaxed: true,
37 },
38 },
39 {
40 input: []byte{},
41 output: parser.File{
42 IsRelaxed: true,
43 },
44 },
45 {
46 input: []byte(""),
47 output: parser.File{
48 IsRelaxed: true,
49 },
50 },
51 {
52 input: []byte("\n"),
53 output: parser.File{
54 IsRelaxed: true,
55 },
56 },
57 {
58 input: []byte("\n\n\n"),
59 output: parser.File{
60 IsRelaxed: true,
61 },
62 },
63 {
64 input: []byte("---"),
65 output: parser.File{
66 IsRelaxed: true,
67 },
68 },
69 {
70 input: []byte("---\n"),
71 output: parser.File{
72 IsRelaxed: true,
73 },
74 },
75 {
76 input: []byte("\n---\n\n---\n"),
77 output: parser.File{
78 IsRelaxed: true,
79 },
80 },
81 {
82 input: []byte("\n---\n\n---\n---"),
83 output: parser.File{
84 IsRelaxed: true,
85 },
86 },
87 {
88 input: []byte(string("! !00 \xf6")),
89 output: parser.File{
90 IsRelaxed: true,
91 Error: parser.ParseError{
92 Err: errors.New("yaml: incomplete UTF-8 octet sequence"),
93 Line: 1,
94 },
95 },
96 },
97 {
98 input: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
99 output: parser.File{
100 IsRelaxed: true,
101 Groups: []parser.Group{
102 {
103 Rules: []parser.Rule{
104 {
105 Lines: diags.LineRange{First: 1, Last: 6},
106 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 6},
107 },
108 },
109 },
110 },
111 },
112 },
113 {
114 input: []byte("- record: |\n multiline\n"),
115 output: parser.File{
116 IsRelaxed: true,
117 Groups: []parser.Group{
118 {
119 Rules: []parser.Rule{
120 {
121 Lines: diags.LineRange{First: 1, Last: 2},
122 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 2},
123 },
124 },
125 },
126 },
127 },
128 },
129 {
130 input: []byte(`---
131- expr: foo
132 record: foo
133---
134- expr: bar
135`),
136 output: parser.File{
137 IsRelaxed: true,
138 Groups: []parser.Group{
139 {
140 Rules: []parser.Rule{
141 {
142 RecordingRule: &parser.RecordingRule{
143 Expr: parser.PromQLExpr{
144 Value: &parser.YamlNode{
145 Value: "foo",
146 Pos: diags.PositionRanges{
147 {Line: 2, FirstColumn: 9, LastColumn: 11},
148 },
149 },
150 },
151 Record: parser.YamlNode{
152 Value: "foo",
153 Pos: diags.PositionRanges{
154 {Line: 3, FirstColumn: 11, LastColumn: 13},
155 },
156 },
157 },
158 Lines: diags.LineRange{First: 2, Last: 3},
159 },
160 },
161 },
162 {
163 Rules: []parser.Rule{
164 {
165 Lines: diags.LineRange{First: 5, Last: 5},
166 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 5},
167 },
168 },
169 },
170 },
171 },
172 },
173 {
174 input: []byte("- expr: foo\n"),
175 output: parser.File{
176 IsRelaxed: true,
177 Groups: []parser.Group{
178 {
179 Rules: []parser.Rule{
180 {
181 Lines: diags.LineRange{First: 1, Last: 1},
182 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 1},
183 },
184 },
185 },
186 },
187 },
188 },
189 {
190 input: []byte("- alert: foo\n"),
191 output: parser.File{
192 IsRelaxed: true,
193 Groups: []parser.Group{
194 {
195 Rules: []parser.Rule{
196 {
197 Lines: diags.LineRange{First: 1, Last: 1},
198 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
199 },
200 },
201 },
202 },
203 },
204 },
205 {
206 input: []byte("- alert: foo\n record: foo\n"),
207 output: parser.File{
208 IsRelaxed: true,
209 Groups: []parser.Group{
210 {
211 Rules: []parser.Rule{
212 {
213 Lines: diags.LineRange{First: 1, Last: 2},
214 Error: parser.ParseError{Err: errors.New("got both record and alert keys in a single rule"), Line: 1},
215 },
216 },
217 },
218 },
219 },
220 },
221 {
222 input: []byte("- record: foo\n labels:\n foo: bar\n"),
223 output: parser.File{
224 IsRelaxed: true,
225 Groups: []parser.Group{
226 {
227 Rules: []parser.Rule{
228 {
229 Lines: diags.LineRange{First: 1, Last: 3},
230 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
231 },
232 },
233 },
234 },
235 },
236 },
237 {
238 input: []byte("- record: - foo\n"),
239 output: parser.File{
240 IsRelaxed: true,
241 Error: parser.ParseError{
242 Err: errors.New("yaml: block sequence entries are not allowed in this context"),
243 Line: 1,
244 },
245 },
246 },
247 {
248 input: []byte("- record: foo expr: sum(\n"),
249 output: parser.File{
250 IsRelaxed: true,
251 Error: parser.ParseError{
252 Err: errors.New("yaml: mapping values are not allowed in this context"),
253 Line: 1,
254 },
255 },
256 },
257 {
258 input: []byte("- record\n\texpr: foo\n"),
259 output: parser.File{
260 IsRelaxed: true,
261 Error: parser.ParseError{
262 Err: errors.New("found a tab character that violates indentation"),
263 Line: 2,
264 },
265 },
266 },
267 {
268 input: []byte(`
269- record: foo
270 expr: bar
271 expr: bar
272`),
273 output: parser.File{
274 IsRelaxed: true,
275 Groups: []parser.Group{
276 {
277 Rules: []parser.Rule{
278 {
279 Lines: diags.LineRange{First: 2, Last: 4},
280 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 4},
281 },
282 },
283 },
284 },
285 },
286 },
287 {
288 input: []byte(`
289- record: foo
290 expr: bar
291 record: bar
292`),
293 output: parser.File{
294 IsRelaxed: true,
295 Groups: []parser.Group{
296 {
297 Rules: []parser.Rule{
298 {
299 Lines: diags.LineRange{First: 2, Last: 4},
300 Error: parser.ParseError{Err: errors.New("duplicated record key"), Line: 4},
301 },
302 },
303 },
304 },
305 },
306 },
307 {
308 input: []byte(`
309- alert: foo
310 alert: bar
311 expr: bar
312`),
313 output: parser.File{
314 IsRelaxed: true,
315 Groups: []parser.Group{
316 {
317 Rules: []parser.Rule{
318 {
319 Lines: diags.LineRange{First: 2, Last: 3},
320 Error: parser.ParseError{Err: errors.New("duplicated alert key"), Line: 3},
321 },
322 },
323 },
324 },
325 },
326 },
327 {
328 input: []byte(`
329- alert: foo
330 for: 5m
331 expr: bar
332 for: 1m
333`),
334 output: parser.File{
335 IsRelaxed: true,
336 Groups: []parser.Group{
337 {
338 Rules: []parser.Rule{
339 {
340 Lines: diags.LineRange{First: 2, Last: 5},
341 Error: parser.ParseError{Err: errors.New("duplicated for key"), Line: 5},
342 },
343 },
344 },
345 },
346 },
347 },
348 {
349 input: []byte(`
350- alert: foo
351 keep_firing_for: 5m
352 expr: bar
353 keep_firing_for: 1m
354`),
355 output: parser.File{
356 IsRelaxed: true,
357 Groups: []parser.Group{
358 {
359 Rules: []parser.Rule{
360 {
361 Lines: diags.LineRange{First: 2, Last: 5},
362 Error: parser.ParseError{Err: errors.New("duplicated keep_firing_for key"), Line: 5},
363 },
364 },
365 },
366 },
367 },
368 },
369 {
370 input: []byte(`
371- alert: foo
372 labels: {}
373 expr: bar
374 labels: {}
375`),
376 output: parser.File{
377 IsRelaxed: true,
378 Groups: []parser.Group{
379 {
380 Rules: []parser.Rule{
381 {
382 Lines: diags.LineRange{First: 2, Last: 5},
383 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
384 },
385 },
386 },
387 },
388 },
389 },
390 {
391 input: []byte(`
392- record: foo
393 labels: {}
394 expr: bar
395 labels: {}
396`),
397 output: parser.File{
398 IsRelaxed: true,
399 Groups: []parser.Group{
400 {
401 Rules: []parser.Rule{
402 {
403 Lines: diags.LineRange{First: 2, Last: 5},
404 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
405 },
406 },
407 },
408 },
409 },
410 },
411 {
412 input: []byte(`
413- alert: foo
414 annotations: {}
415 expr: bar
416 annotations: {}
417`),
418 output: parser.File{
419 IsRelaxed: true,
420 Groups: []parser.Group{
421 {
422 Rules: []parser.Rule{
423 {
424 Lines: diags.LineRange{First: 2, Last: 5},
425 Error: parser.ParseError{Err: errors.New("duplicated annotations key"), Line: 5},
426 },
427 },
428 },
429 },
430 },
431 },
432 {
433 input: []byte("- record: foo\n expr: foo\n extra: true\n"),
434 output: parser.File{
435 IsRelaxed: true,
436 Groups: []parser.Group{
437 {
438 Rules: []parser.Rule{
439 {
440 Lines: diags.LineRange{First: 1, Last: 3},
441 Error: parser.ParseError{Err: errors.New("invalid key(s) found: extra"), Line: 3},
442 },
443 },
444 },
445 },
446 },
447 },
448 {
449 input: []byte(`- record: foo
450 expr: foo offset 10m
451`),
452 output: parser.File{
453 IsRelaxed: true,
454 Groups: []parser.Group{
455 {
456 Rules: []parser.Rule{
457 {
458 Lines: diags.LineRange{First: 1, Last: 2},
459 RecordingRule: &parser.RecordingRule{
460 Record: parser.YamlNode{
461 Value: "foo",
462 Pos: diags.PositionRanges{
463 {Line: 1, FirstColumn: 11, LastColumn: 13},
464 },
465 },
466 Expr: parser.PromQLExpr{
467 Value: &parser.YamlNode{
468 Pos: diags.PositionRanges{
469 {Line: 2, FirstColumn: 9, LastColumn: 22},
470 },
471 Value: "foo offset 10m",
472 },
473 },
474 },
475 },
476 },
477 },
478 },
479 },
480 },
481 {
482 input: []byte("- record: foo\n expr: foo offset -10m\n"),
483 output: parser.File{
484 IsRelaxed: true,
485 Groups: []parser.Group{
486 {
487 Rules: []parser.Rule{
488 {
489 Lines: diags.LineRange{First: 1, Last: 2},
490 RecordingRule: &parser.RecordingRule{
491 Record: parser.YamlNode{
492 Value: "foo",
493 Pos: diags.PositionRanges{
494 {Line: 1, FirstColumn: 11, LastColumn: 13},
495 },
496 },
497 Expr: parser.PromQLExpr{
498 Value: &parser.YamlNode{
499 Value: "foo offset -10m",
500 Pos: diags.PositionRanges{
501 {Line: 2, FirstColumn: 9, LastColumn: 23},
502 },
503 },
504 },
505 },
506 },
507 },
508 },
509 },
510 },
511 },
512 {
513 input: []byte(`
514# pint disable head comment
515- record: foo # pint disable record comment
516 expr: foo offset 10m # pint disable expr comment
517 # pint disable pre-labels comment
518 labels:
519 # pint disable pre-foo comment
520 foo: bar
521 # pint disable post-foo comment
522 bob: alice
523 # pint disable foot comment
524`),
525 output: parser.File{
526 IsRelaxed: true,
527 Groups: []parser.Group{
528 {
529 Rules: []parser.Rule{
530 {
531 Lines: diags.LineRange{First: 3, Last: 10},
532 Comments: []comments.Comment{
533 {
534 Type: comments.DisableType,
535 Value: comments.Disable{Match: "head comment"},
536 },
537 {
538 Type: comments.DisableType,
539 Value: comments.Disable{Match: "record comment"},
540 },
541 {
542 Type: comments.DisableType,
543 Value: comments.Disable{Match: "expr comment"},
544 },
545 {
546 Type: comments.DisableType,
547 Value: comments.Disable{Match: "pre-labels comment"},
548 },
549 {
550 Type: comments.DisableType,
551 Value: comments.Disable{Match: "foot comment"},
552 },
553 {
554 Type: comments.DisableType,
555 Value: comments.Disable{Match: "pre-foo comment"},
556 },
557 {
558 Type: comments.DisableType,
559 Value: comments.Disable{Match: "post-foo comment"},
560 },
561 },
562 RecordingRule: &parser.RecordingRule{
563 Record: parser.YamlNode{
564 Value: "foo",
565 Pos: diags.PositionRanges{
566 {Line: 3, FirstColumn: 11, LastColumn: 13},
567 },
568 },
569 Expr: parser.PromQLExpr{
570 Value: &parser.YamlNode{
571 Value: "foo offset 10m",
572 Pos: diags.PositionRanges{
573 {Line: 4, FirstColumn: 9, LastColumn: 22},
574 },
575 },
576 },
577 Labels: &parser.YamlMap{
578 Key: &parser.YamlNode{
579 Value: "labels",
580 },
581 Items: []*parser.YamlKeyValue{
582 {
583 Key: &parser.YamlNode{
584 Value: "foo",
585 },
586 Value: &parser.YamlNode{
587 Value: "bar",
588 },
589 },
590 {
591 Key: &parser.YamlNode{
592 Value: "bob",
593 },
594 Value: &parser.YamlNode{
595 Value: "alice",
596 },
597 },
598 },
599 },
600 },
601 },
602 },
603 },
604 },
605 },
606 },
607 {
608 input: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
609 output: parser.File{
610 IsRelaxed: true,
611 Groups: []parser.Group{
612 {
613 Rules: []parser.Rule{
614 {
615 Lines: diags.LineRange{First: 1, Last: 2},
616 RecordingRule: &parser.RecordingRule{
617 Record: parser.YamlNode{
618 Value: "foo",
619 Pos: diags.PositionRanges{
620 {Line: 1, FirstColumn: 11, LastColumn: 13},
621 },
622 },
623 Expr: parser.PromQLExpr{
624 Value: &parser.YamlNode{
625 Value: "foo[5m] offset 10m",
626 Pos: diags.PositionRanges{
627 {Line: 2, FirstColumn: 9, LastColumn: 26},
628 },
629 },
630 },
631 },
632 },
633 },
634 },
635 },
636 },
637 },
638 {
639 input: []byte(`
640- record: name
641 expr: sum(foo)
642 labels:
643 foo: bar
644 bob: alice
645`),
646 output: parser.File{
647 IsRelaxed: true,
648 Groups: []parser.Group{
649 {
650 Rules: []parser.Rule{
651 {
652 Lines: diags.LineRange{First: 2, Last: 6},
653 RecordingRule: &parser.RecordingRule{
654 Record: parser.YamlNode{
655 Value: "name",
656 Pos: diags.PositionRanges{
657 {Line: 2, FirstColumn: 11, LastColumn: 14},
658 },
659 },
660 Expr: parser.PromQLExpr{
661 Value: &parser.YamlNode{
662 Value: "sum(foo)",
663 Pos: diags.PositionRanges{
664 {Line: 3, FirstColumn: 9, LastColumn: 16},
665 },
666 },
667 },
668 Labels: &parser.YamlMap{
669 Key: &parser.YamlNode{
670 Value: "labels",
671 },
672 Items: []*parser.YamlKeyValue{
673 {
674 Key: &parser.YamlNode{
675 Value: "foo",
676 },
677 Value: &parser.YamlNode{
678 Value: "bar",
679 },
680 },
681 {
682 Key: &parser.YamlNode{
683 Value: "bob",
684 },
685 Value: &parser.YamlNode{
686 Value: "alice",
687 },
688 },
689 },
690 },
691 },
692 },
693 },
694 },
695 },
696 },
697 },
698 {
699 input: []byte(`
700groups:
701- name: custom_rules
702 rules:
703 - record: name
704 expr: sum(foo)
705 labels:
706 foo: bar
707 bob: alice
708`),
709 output: parser.File{
710 IsRelaxed: true,
711 Groups: []parser.Group{
712 {
713 Name: "custom_rules",
714 Rules: []parser.Rule{
715 {
716 Lines: diags.LineRange{First: 5, Last: 9},
717 RecordingRule: &parser.RecordingRule{
718 Record: parser.YamlNode{
719 Value: "name",
720 Pos: diags.PositionRanges{
721 {Line: 5, FirstColumn: 15, LastColumn: 18},
722 },
723 },
724 Expr: parser.PromQLExpr{
725 Value: &parser.YamlNode{
726 Value: "sum(foo)",
727 Pos: diags.PositionRanges{
728 {Line: 6, FirstColumn: 13, LastColumn: 20},
729 },
730 },
731 },
732 Labels: &parser.YamlMap{
733 Key: &parser.YamlNode{
734 Value: "labels",
735 },
736 Items: []*parser.YamlKeyValue{
737 {
738 Key: &parser.YamlNode{
739 Value: "foo",
740 },
741 Value: &parser.YamlNode{
742 Value: "bar",
743 },
744 },
745 {
746 Key: &parser.YamlNode{
747 Value: "bob",
748 },
749 Value: &parser.YamlNode{
750 Value: "alice",
751 },
752 },
753 },
754 },
755 },
756 },
757 },
758 },
759 },
760 },
761 },
762 {
763 input: []byte(`- alert: Down
764 expr: |
765 up == 0
766 for: |+
767 11m
768 labels:
769 severity: critical
770 annotations:
771 uri: https://docs.example.com/down.html
772
773- record: foo
774 expr: |-
775 bar
776 /
777 baz > 1
778 labels: {}
779`),
780 output: parser.File{
781 IsRelaxed: true,
782 Groups: []parser.Group{
783 {
784 Rules: []parser.Rule{
785 {
786 Lines: diags.LineRange{First: 1, Last: 9},
787 AlertingRule: &parser.AlertingRule{
788 Alert: parser.YamlNode{
789 Value: "Down",
790 Pos: diags.PositionRanges{
791 {Line: 1, FirstColumn: 10, LastColumn: 13},
792 },
793 },
794 Expr: parser.PromQLExpr{
795 Value: &parser.YamlNode{
796 Value: "up == 0\n",
797 Pos: diags.PositionRanges{
798 {Line: 3, FirstColumn: 5, LastColumn: 11},
799 },
800 },
801 },
802 For: &parser.YamlNode{
803 Value: "11m\n",
804 Pos: diags.PositionRanges{
805 {Line: 5, FirstColumn: 5, LastColumn: 7},
806 },
807 },
808 Labels: &parser.YamlMap{
809 Key: &parser.YamlNode{
810 Value: "labels",
811 },
812 Items: []*parser.YamlKeyValue{
813 {
814 Key: &parser.YamlNode{
815 Value: "severity",
816 },
817 Value: &parser.YamlNode{
818 Value: "critical",
819 },
820 },
821 },
822 },
823 Annotations: &parser.YamlMap{
824 Key: &parser.YamlNode{
825 Value: "annotations",
826 },
827 Items: []*parser.YamlKeyValue{
828 {
829 Key: &parser.YamlNode{
830 Value: "uri",
831 },
832 Value: &parser.YamlNode{
833 Value: "https://docs.example.com/down.html",
834 },
835 },
836 },
837 },
838 },
839 },
840 {
841 Lines: diags.LineRange{First: 11, Last: 16},
842 RecordingRule: &parser.RecordingRule{
843 Record: parser.YamlNode{
844 Value: "foo",
845 Pos: diags.PositionRanges{
846 {Line: 11, FirstColumn: 11, LastColumn: 13},
847 },
848 },
849 Expr: parser.PromQLExpr{
850 Value: &parser.YamlNode{
851 Value: "bar\n/\nbaz > 1",
852 Pos: diags.PositionRanges{
853 {Line: 13, FirstColumn: 5, LastColumn: 8},
854 {Line: 14, FirstColumn: 5, LastColumn: 6},
855 {Line: 15, FirstColumn: 5, LastColumn: 11},
856 },
857 },
858 },
859 Labels: &parser.YamlMap{
860 Key: &parser.YamlNode{
861 Value: "labels",
862 },
863 },
864 },
865 },
866 },
867 },
868 },
869 },
870 },
871 {
872 input: []byte(`- alert: Foo
873 expr:
874 (
875 xxx
876 -
877 yyy
878 ) * bar > 0
879 and on(instance, device) baz
880 for: 30m
881`),
882 output: parser.File{
883 IsRelaxed: true,
884 Groups: []parser.Group{
885 {
886 Rules: []parser.Rule{
887 {
888 Lines: diags.LineRange{First: 1, Last: 9},
889 AlertingRule: &parser.AlertingRule{
890 Alert: parser.YamlNode{
891 Value: "Foo",
892 Pos: diags.PositionRanges{
893 {Line: 1, FirstColumn: 10, LastColumn: 12},
894 },
895 },
896 Expr: parser.PromQLExpr{
897 Value: &parser.YamlNode{
898 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
899 Pos: diags.PositionRanges{
900 {Line: 3, FirstColumn: 5, LastColumn: 6},
901 {Line: 4, FirstColumn: 7, LastColumn: 10},
902 {Line: 5, FirstColumn: 7, LastColumn: 8},
903 {Line: 6, FirstColumn: 7, LastColumn: 10},
904 {Line: 7, FirstColumn: 5, LastColumn: 16},
905 {Line: 8, FirstColumn: 5, LastColumn: 32},
906 },
907 },
908 },
909 For: &parser.YamlNode{
910 Value: "30m",
911 Pos: diags.PositionRanges{
912 {Line: 9, FirstColumn: 8, LastColumn: 10},
913 },
914 },
915 },
916 },
917 },
918 },
919 },
920 },
921 },
922 {
923 input: []byte(`---
924kind: ConfigMap
925apiVersion: v1
926metadata:
927 name: example-app-alerts
928 labels:
929 app: example-app
930data:
931 alerts: |
932 groups:
933 - name: example-app-alerts
934 rules:
935 - alert: Example_High_Restart_Rate
936 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
937---
938kind: ConfigMap
939apiVersion: v1
940metadata:
941 name: other
942 labels:
943 app: other
944data:
945 alerts: |
946
947 groups:
948 - name: other alerts
949 rules:
950 - alert: Example_High_Restart_Rate
951 expr: "1"
952
953`),
954 output: parser.File{
955 IsRelaxed: true,
956 Groups: []parser.Group{
957 {
958 Name: "example-app-alerts",
959 Rules: []parser.Rule{
960 {
961 Lines: diags.LineRange{First: 13, Last: 14},
962 AlertingRule: &parser.AlertingRule{
963 Alert: parser.YamlNode{
964 Value: "Example_High_Restart_Rate",
965 Pos: diags.PositionRanges{
966 {Line: 13, FirstColumn: 20, LastColumn: 44},
967 },
968 },
969 Expr: parser.PromQLExpr{
970 Value: &parser.YamlNode{
971 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
972 Pos: diags.PositionRanges{
973 {Line: 14, FirstColumn: 19, LastColumn: 109},
974 },
975 },
976 },
977 },
978 },
979 },
980 },
981 {
982 Name: "other alerts",
983 Rules: []parser.Rule{
984 {
985 Lines: diags.LineRange{First: 28, Last: 29},
986 AlertingRule: &parser.AlertingRule{
987 Alert: parser.YamlNode{
988 Value: "Example_High_Restart_Rate",
989 Pos: diags.PositionRanges{
990 {Line: 28, FirstColumn: 20, LastColumn: 44},
991 },
992 },
993 Expr: parser.PromQLExpr{
994 Value: &parser.YamlNode{
995 Value: "1",
996 Pos: diags.PositionRanges{
997 {Line: 29, FirstColumn: 20, LastColumn: 20},
998 },
999 },
1000 },
1001 },
1002 },
1003 },
1004 },
1005 },
1006 },
1007 },
1008 {
1009 input: []byte(`---
1010kind: ConfigMap
1011apiVersion: v1
1012metadata:
1013 name: example-app-alerts
1014 labels:
1015 app: example-app
1016data:
1017 alerts: |
1018 groups:
1019 - name: example-app-alerts
1020 rules:
1021 - alert: Example_Is_Down
1022 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
1023 for: 5m
1024 labels:
1025 priority: "2"
1026 environment: production
1027 annotations:
1028 summary: "No replicas for Example have been running for 5 minutes"
1029
1030 - alert: Example_High_Restart_Rate
1031 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
1032`),
1033 output: parser.File{
1034 IsRelaxed: true,
1035 Groups: []parser.Group{
1036 {
1037 Name: "example-app-alerts",
1038 Rules: []parser.Rule{
1039 {
1040 Lines: diags.LineRange{First: 13, Last: 20},
1041 AlertingRule: &parser.AlertingRule{
1042 Alert: parser.YamlNode{
1043 Value: "Example_Is_Down",
1044 Pos: diags.PositionRanges{
1045 {Line: 13, FirstColumn: 20, LastColumn: 34},
1046 },
1047 },
1048 Expr: parser.PromQLExpr{
1049 Value: &parser.YamlNode{
1050 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
1051 Pos: diags.PositionRanges{
1052 {Line: 14, FirstColumn: 19, LastColumn: 88},
1053 },
1054 },
1055 },
1056 For: &parser.YamlNode{
1057 Value: "5m",
1058 Pos: diags.PositionRanges{
1059 {Line: 15, FirstColumn: 18, LastColumn: 19},
1060 },
1061 },
1062 Labels: &parser.YamlMap{
1063 Key: &parser.YamlNode{
1064 Value: "labels",
1065 },
1066 Items: []*parser.YamlKeyValue{
1067 {
1068 Key: &parser.YamlNode{
1069 Value: "priority",
1070 },
1071 Value: &parser.YamlNode{
1072 Value: "2",
1073 },
1074 },
1075 {
1076 Key: &parser.YamlNode{
1077 Value: "environment",
1078 },
1079 Value: &parser.YamlNode{
1080 Value: "production",
1081 },
1082 },
1083 },
1084 },
1085 Annotations: &parser.YamlMap{
1086 Key: &parser.YamlNode{
1087 Value: "annotations",
1088 },
1089 Items: []*parser.YamlKeyValue{
1090 {
1091 Key: &parser.YamlNode{
1092 Value: "summary",
1093 },
1094 Value: &parser.YamlNode{
1095 Value: "No replicas for Example have been running for 5 minutes",
1096 },
1097 },
1098 },
1099 },
1100 },
1101 },
1102 {
1103 Lines: diags.LineRange{First: 22, Last: 23},
1104 AlertingRule: &parser.AlertingRule{
1105 Alert: parser.YamlNode{
1106 Value: "Example_High_Restart_Rate",
1107 Pos: diags.PositionRanges{
1108 {Line: 22, FirstColumn: 20, LastColumn: 44},
1109 },
1110 },
1111 Expr: parser.PromQLExpr{
1112 Value: &parser.YamlNode{
1113 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
1114 Pos: diags.PositionRanges{
1115 {Line: 23, FirstColumn: 19, LastColumn: 109},
1116 },
1117 },
1118 },
1119 },
1120 },
1121 },
1122 },
1123 },
1124 },
1125 },
1126 {
1127 input: []byte(`groups:
1128- name: "haproxy.api_server.rules"
1129 rules:
1130 - alert: HaproxyServerHealthcheckFailure
1131 expr: increase(haproxy_server_check_failures_total[15m]) > 100
1132 for: 5m
1133 labels:
1134 severity: 24x7
1135 annotations:
1136 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
1137 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
1138`),
1139 output: parser.File{
1140 IsRelaxed: true,
1141 Groups: []parser.Group{
1142 {
1143 Name: "haproxy.api_server.rules",
1144 Rules: []parser.Rule{
1145 {
1146 Lines: diags.LineRange{First: 4, Last: 11},
1147 AlertingRule: &parser.AlertingRule{
1148 Alert: parser.YamlNode{
1149 Value: "HaproxyServerHealthcheckFailure",
1150 Pos: diags.PositionRanges{
1151 {Line: 4, FirstColumn: 12, LastColumn: 42},
1152 },
1153 },
1154 Expr: parser.PromQLExpr{
1155 Value: &parser.YamlNode{
1156 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
1157 Pos: diags.PositionRanges{
1158 {Line: 5, FirstColumn: 11, LastColumn: 66},
1159 },
1160 },
1161 },
1162 For: &parser.YamlNode{
1163 Value: "5m",
1164 Pos: diags.PositionRanges{
1165 {Line: 6, FirstColumn: 10, LastColumn: 11},
1166 },
1167 },
1168 Labels: &parser.YamlMap{
1169 Key: &parser.YamlNode{
1170 Value: "labels",
1171 },
1172 Items: []*parser.YamlKeyValue{
1173 {
1174 Key: &parser.YamlNode{
1175 Value: "severity",
1176 },
1177 Value: &parser.YamlNode{
1178 Value: "24x7",
1179 },
1180 },
1181 },
1182 },
1183 Annotations: &parser.YamlMap{
1184 Key: &parser.YamlNode{
1185 Value: "annotations",
1186 },
1187 Items: []*parser.YamlKeyValue{
1188 {
1189 Key: &parser.YamlNode{
1190 Value: "summary",
1191 },
1192 Value: &parser.YamlNode{
1193 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1194 },
1195 },
1196 {
1197 Key: &parser.YamlNode{
1198 Value: "description",
1199 },
1200 Value: &parser.YamlNode{
1201 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1202 },
1203 },
1204 },
1205 },
1206 },
1207 },
1208 },
1209 },
1210 },
1211 },
1212 },
1213 {
1214 input: []byte(`groups:
1215- name: certmanager
1216 rules:
1217 # pint disable before recordAnchor
1218 - &recordAnchor # pint disable recordAnchor
1219 record: name1 # pint disable name1
1220 expr: expr1 # pint disable expr1
1221 # pint disable after expr1
1222 - <<: *recordAnchor
1223 expr: expr2
1224 - <<: *recordAnchor
1225`),
1226 output: parser.File{
1227 IsRelaxed: true,
1228 Groups: []parser.Group{
1229 {
1230 Name: "certmanager",
1231 Rules: []parser.Rule{
1232 {
1233 Lines: diags.LineRange{First: 6, Last: 7},
1234 Comments: []comments.Comment{
1235 {
1236 Type: comments.DisableType,
1237 Value: comments.Disable{Match: "before recordAnchor"},
1238 },
1239 {
1240 Type: comments.DisableType,
1241 Value: comments.Disable{Match: "recordAnchor"},
1242 },
1243 {
1244 Type: comments.DisableType,
1245 Value: comments.Disable{Match: "name1"},
1246 },
1247 {
1248 Type: comments.DisableType,
1249 Value: comments.Disable{Match: "after expr1"},
1250 },
1251 {
1252 Type: comments.DisableType,
1253 Value: comments.Disable{Match: "expr1"},
1254 },
1255 },
1256 RecordingRule: &parser.RecordingRule{
1257 Record: parser.YamlNode{
1258 Value: "name1",
1259 Pos: diags.PositionRanges{
1260 {Line: 6, FirstColumn: 13, LastColumn: 17},
1261 },
1262 },
1263 Expr: parser.PromQLExpr{
1264 Value: &parser.YamlNode{
1265 Value: "expr1",
1266 Pos: diags.PositionRanges{
1267 {Line: 7, FirstColumn: 11, LastColumn: 15},
1268 },
1269 },
1270 },
1271 },
1272 },
1273 {
1274 Comments: []comments.Comment{
1275 {
1276 Type: comments.DisableType,
1277 Value: comments.Disable{Match: "before recordAnchor"},
1278 },
1279 {
1280 Type: comments.DisableType,
1281 Value: comments.Disable{Match: "recordAnchor"},
1282 },
1283 {
1284 Type: comments.DisableType,
1285 Value: comments.Disable{Match: "name1"},
1286 },
1287 },
1288 Lines: diags.LineRange{First: 6, Last: 10},
1289 RecordingRule: &parser.RecordingRule{
1290 Record: parser.YamlNode{
1291 Value: "name1",
1292 Pos: diags.PositionRanges{
1293 {Line: 6, FirstColumn: 13, LastColumn: 17},
1294 },
1295 },
1296 Expr: parser.PromQLExpr{
1297 Value: &parser.YamlNode{
1298 Value: "expr2",
1299 Pos: diags.PositionRanges{
1300 {Line: 10, FirstColumn: 11, LastColumn: 15},
1301 },
1302 },
1303 },
1304 },
1305 },
1306 {
1307 Comments: []comments.Comment{
1308 {
1309 Type: comments.DisableType,
1310 Value: comments.Disable{Match: "before recordAnchor"},
1311 },
1312 {
1313 Type: comments.DisableType,
1314 Value: comments.Disable{Match: "recordAnchor"},
1315 },
1316 {
1317 Type: comments.DisableType,
1318 Value: comments.Disable{Match: "name1"},
1319 },
1320 {
1321 Type: comments.DisableType,
1322 Value: comments.Disable{Match: "after expr1"},
1323 },
1324 {
1325 Type: comments.DisableType,
1326 Value: comments.Disable{Match: "expr1"},
1327 },
1328 },
1329 Lines: diags.LineRange{First: 6, Last: 7},
1330 RecordingRule: &parser.RecordingRule{
1331 Record: parser.YamlNode{
1332 Value: "name1",
1333 Pos: diags.PositionRanges{
1334 {Line: 6, FirstColumn: 13, LastColumn: 17},
1335 },
1336 },
1337 Expr: parser.PromQLExpr{
1338 Value: &parser.YamlNode{
1339 Value: "expr1",
1340 Pos: diags.PositionRanges{
1341 {Line: 7, FirstColumn: 11, LastColumn: 15},
1342 },
1343 },
1344 },
1345 },
1346 },
1347 },
1348 },
1349 },
1350 },
1351 },
1352 {
1353 input: []byte(`groups:
1354- name: certmanager
1355 rules:
1356 - record: name1
1357 expr: expr1
1358 labels: &labelsAnchor
1359 label1: val1
1360 label2: val2
1361 - record: name2
1362 expr: expr2
1363 labels: *labelsAnchor
1364 # pint disable foot comment
1365`),
1366 output: parser.File{
1367 IsRelaxed: true,
1368 Groups: []parser.Group{
1369 {
1370 Name: "certmanager",
1371 Rules: []parser.Rule{
1372 {
1373 Lines: diags.LineRange{First: 4, Last: 8},
1374 RecordingRule: &parser.RecordingRule{
1375 Record: parser.YamlNode{
1376 Value: "name1",
1377 Pos: diags.PositionRanges{
1378 {Line: 4, FirstColumn: 13, LastColumn: 17},
1379 },
1380 },
1381 Expr: parser.PromQLExpr{
1382 Value: &parser.YamlNode{
1383 Value: "expr1",
1384 Pos: diags.PositionRanges{
1385 {Line: 5, FirstColumn: 11, LastColumn: 15},
1386 },
1387 },
1388 },
1389 Labels: &parser.YamlMap{
1390 Key: &parser.YamlNode{
1391 Value: "labels",
1392 },
1393 Items: []*parser.YamlKeyValue{
1394 {
1395 Key: &parser.YamlNode{
1396 Value: "label1",
1397 },
1398 Value: &parser.YamlNode{
1399 Value: "val1",
1400 },
1401 },
1402 {
1403 Key: &parser.YamlNode{
1404 Value: "label2",
1405 },
1406 Value: &parser.YamlNode{
1407 Value: "val2",
1408 },
1409 },
1410 },
1411 },
1412 },
1413 },
1414 {
1415 Comments: []comments.Comment{
1416 {
1417 Type: comments.DisableType,
1418 Value: comments.Disable{Match: "foot comment"},
1419 },
1420 },
1421 Lines: diags.LineRange{First: 9, Last: 11},
1422 RecordingRule: &parser.RecordingRule{
1423 Record: parser.YamlNode{
1424 Value: "name2",
1425 Pos: diags.PositionRanges{
1426 {Line: 9, FirstColumn: 13, LastColumn: 17},
1427 },
1428 },
1429 Expr: parser.PromQLExpr{
1430 Value: &parser.YamlNode{
1431 Value: "expr2",
1432 Pos: diags.PositionRanges{
1433 {Line: 10, FirstColumn: 11, LastColumn: 15},
1434 },
1435 },
1436 },
1437 Labels: &parser.YamlMap{
1438 Key: &parser.YamlNode{
1439 Value: "labels",
1440 },
1441 Items: []*parser.YamlKeyValue{
1442 {
1443 Key: &parser.YamlNode{
1444 Value: "label1",
1445 },
1446 Value: &parser.YamlNode{
1447 Value: "val1",
1448 },
1449 },
1450 {
1451 Key: &parser.YamlNode{
1452 Value: "label2",
1453 },
1454 Value: &parser.YamlNode{
1455 Value: "val2",
1456 },
1457 },
1458 },
1459 },
1460 },
1461 },
1462 },
1463 },
1464 },
1465 },
1466 },
1467 {
1468 input: []byte("- alert:\n expr: vector(1)\n"),
1469 output: parser.File{
1470 IsRelaxed: true,
1471 Groups: []parser.Group{
1472 {
1473 Rules: []parser.Rule{
1474 {
1475 Lines: diags.LineRange{First: 1, Last: 2},
1476 Error: parser.ParseError{Err: errors.New("alert value cannot be empty"), Line: 1},
1477 },
1478 },
1479 },
1480 },
1481 },
1482 },
1483 {
1484 input: []byte("- alert: foo\n expr:\n"),
1485 output: parser.File{
1486 IsRelaxed: true,
1487 Groups: []parser.Group{
1488 {
1489 Rules: []parser.Rule{
1490 {
1491 Lines: diags.LineRange{First: 1, Last: 2},
1492 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1493 },
1494 },
1495 },
1496 },
1497 },
1498 },
1499 {
1500 input: []byte("- alert: foo\n"),
1501 output: parser.File{
1502 IsRelaxed: true,
1503 Groups: []parser.Group{
1504 {
1505 Rules: []parser.Rule{
1506 {
1507 Lines: diags.LineRange{First: 1, Last: 1},
1508 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1509 },
1510 },
1511 },
1512 },
1513 },
1514 },
1515 {
1516 input: []byte("- record:\n expr:\n"),
1517 output: parser.File{
1518 IsRelaxed: true,
1519 Groups: []parser.Group{
1520 {
1521 Rules: []parser.Rule{
1522 {
1523 Lines: diags.LineRange{First: 1, Last: 2},
1524 Error: parser.ParseError{Err: errors.New("record value cannot be empty"), Line: 1},
1525 },
1526 },
1527 },
1528 },
1529 },
1530 },
1531 {
1532 input: []byte("- record: foo\n expr:\n"),
1533 output: parser.File{
1534 IsRelaxed: true,
1535 Groups: []parser.Group{
1536 {
1537 Rules: []parser.Rule{
1538 {
1539 Lines: diags.LineRange{First: 1, Last: 2},
1540 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1541 },
1542 },
1543 },
1544 },
1545 },
1546 },
1547 {
1548 input: []byte("- record: foo\n"),
1549 output: parser.File{
1550 IsRelaxed: true,
1551 Groups: []parser.Group{
1552 {
1553 Rules: []parser.Rule{
1554 {
1555 Lines: diags.LineRange{First: 1, Last: 1},
1556 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1557 },
1558 },
1559 },
1560 },
1561 },
1562 },
1563 {
1564 input: []byte(string(`
1565# pint file/owner bob
1566# pint ignore/begin
1567# pint ignore/end
1568# pint disable up
1569
1570- record: foo
1571 expr: up
1572
1573# pint file/owner alice
1574
1575- record: foo
1576 expr: up
1577
1578# pint ignore/next-line
1579`)),
1580 output: parser.File{
1581 IsRelaxed: true,
1582 Comments: []comments.Comment{
1583 {
1584 Type: comments.FileOwnerType,
1585 Value: comments.Owner{
1586 Name: "bob",
1587 Line: 2,
1588 },
1589 },
1590 {
1591 Type: comments.FileOwnerType,
1592 Value: comments.Owner{
1593 Name: "alice",
1594 Line: 10,
1595 },
1596 },
1597 },
1598 Groups: []parser.Group{
1599 {
1600 Rules: []parser.Rule{
1601 {
1602 Lines: diags.LineRange{First: 7, Last: 8},
1603 RecordingRule: &parser.RecordingRule{
1604 Record: parser.YamlNode{
1605 Value: "foo",
1606 Pos: diags.PositionRanges{
1607 {Line: 7, FirstColumn: 11, LastColumn: 13},
1608 },
1609 },
1610 Expr: parser.PromQLExpr{
1611 Value: &parser.YamlNode{
1612 Value: "up",
1613 Pos: diags.PositionRanges{
1614 {Line: 8, FirstColumn: 9, LastColumn: 10},
1615 },
1616 },
1617 },
1618 },
1619 },
1620 {
1621 Lines: diags.LineRange{First: 12, Last: 13},
1622 RecordingRule: &parser.RecordingRule{
1623 Record: parser.YamlNode{
1624 Value: "foo",
1625 Pos: diags.PositionRanges{
1626 {Line: 12, FirstColumn: 11, LastColumn: 13},
1627 },
1628 },
1629 Expr: parser.PromQLExpr{
1630 Value: &parser.YamlNode{
1631 Value: "up",
1632 Pos: diags.PositionRanges{
1633 {Line: 13, FirstColumn: 9, LastColumn: 10},
1634 },
1635 },
1636 },
1637 },
1638 },
1639 },
1640 },
1641 },
1642 },
1643 },
1644 {
1645 input: []byte(string(`
1646- alert: Template
1647 expr: &expr up == 0
1648 labels:
1649 notify: &maybe_escalate_notify chat-alerts
1650- alert: Service Down
1651 expr: *expr
1652 labels:
1653 notify: *maybe_escalate_notify
1654 summary: foo
1655`)),
1656 output: parser.File{
1657 IsRelaxed: true,
1658 Groups: []parser.Group{
1659 {
1660 Rules: []parser.Rule{
1661 {
1662 Lines: diags.LineRange{First: 2, Last: 5},
1663 AlertingRule: &parser.AlertingRule{
1664 Alert: parser.YamlNode{
1665 Value: "Template",
1666 Pos: diags.PositionRanges{
1667 {Line: 2, FirstColumn: 10, LastColumn: 17},
1668 },
1669 },
1670 Expr: parser.PromQLExpr{
1671 Value: &parser.YamlNode{
1672 Value: "up == 0",
1673 Pos: diags.PositionRanges{
1674 {Line: 3, FirstColumn: 15, LastColumn: 21},
1675 },
1676 },
1677 },
1678 Labels: &parser.YamlMap{
1679 Key: &parser.YamlNode{
1680 Value: "labels",
1681 },
1682 Items: []*parser.YamlKeyValue{
1683 {
1684 Key: &parser.YamlNode{
1685 Value: "notify",
1686 },
1687 Value: &parser.YamlNode{
1688 Value: "chat-alerts",
1689 },
1690 },
1691 },
1692 },
1693 },
1694 },
1695 {
1696 Lines: diags.LineRange{First: 6, Last: 10},
1697 AlertingRule: &parser.AlertingRule{
1698 Alert: parser.YamlNode{
1699 Value: "Service Down",
1700 Pos: diags.PositionRanges{
1701 {Line: 6, FirstColumn: 10, LastColumn: 21},
1702 },
1703 },
1704 Expr: parser.PromQLExpr{
1705 Value: &parser.YamlNode{
1706 Value: "up == 0",
1707 Pos: diags.PositionRanges{
1708 {Line: 7, FirstColumn: 10, LastColumn: 13}, // points at anchor
1709 },
1710 },
1711 },
1712 Labels: &parser.YamlMap{
1713 Key: &parser.YamlNode{
1714 Value: "labels",
1715 },
1716 Items: []*parser.YamlKeyValue{
1717 {
1718 Key: &parser.YamlNode{
1719 Value: "notify",
1720 },
1721 Value: &parser.YamlNode{
1722 Value: "chat-alerts",
1723 },
1724 },
1725 {
1726 Key: &parser.YamlNode{
1727 Value: "summary",
1728 },
1729 Value: &parser.YamlNode{
1730 Value: "foo",
1731 },
1732 },
1733 },
1734 },
1735 },
1736 },
1737 },
1738 },
1739 },
1740 },
1741 },
1742 {
1743 input: []byte(`
1744- record: invalid metric name
1745 expr: bar
1746`),
1747 output: parser.File{
1748 IsRelaxed: true,
1749 Groups: []parser.Group{
1750 {
1751 Rules: []parser.Rule{
1752 {
1753 Lines: diags.LineRange{First: 2, Last: 3},
1754 Error: parser.ParseError{Err: errors.New("invalid recording rule name: invalid metric name"), Line: 2},
1755 },
1756 },
1757 },
1758 },
1759 },
1760 },
1761 {
1762 input: []byte(`
1763- record: utf-8 enabled name
1764 expr: bar
1765 labels:
1766 "a b c": bar
1767`),
1768 names: model.UTF8Validation,
1769 output: parser.File{
1770 IsRelaxed: true,
1771 Groups: []parser.Group{
1772 {
1773 Rules: []parser.Rule{
1774 {
1775 Lines: diags.LineRange{First: 2, Last: 5},
1776 RecordingRule: &parser.RecordingRule{
1777 Record: parser.YamlNode{
1778 Value: "utf-8 enabled name",
1779 Pos: diags.PositionRanges{
1780 {Line: 2, FirstColumn: 11, LastColumn: 28},
1781 },
1782 },
1783 Expr: parser.PromQLExpr{
1784 Value: &parser.YamlNode{
1785 Value: "bar",
1786 Pos: diags.PositionRanges{
1787 {Line: 3, FirstColumn: 9, LastColumn: 11},
1788 },
1789 },
1790 },
1791 Labels: &parser.YamlMap{
1792 Key: &parser.YamlNode{
1793 Value: "labels",
1794 },
1795 Items: []*parser.YamlKeyValue{
1796 {
1797 Key: &parser.YamlNode{
1798 Value: "a b c",
1799 },
1800 Value: &parser.YamlNode{
1801 Value: "bar",
1802 },
1803 },
1804 },
1805 },
1806 },
1807 },
1808 },
1809 },
1810 },
1811 },
1812 },
1813 {
1814 input: []byte(`
1815- record: foo
1816 expr: bar
1817 labels:
1818 "foo bar": yes
1819`),
1820 output: parser.File{
1821 IsRelaxed: true,
1822 Groups: []parser.Group{
1823 {
1824 Rules: []parser.Rule{
1825 {
1826 Lines: diags.LineRange{First: 2, Last: 5},
1827 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
1828 },
1829 },
1830 },
1831 },
1832 },
1833 },
1834 {
1835 input: []byte(`
1836- alert: foo
1837 expr: bar
1838 labels:
1839 "foo bar": yes
1840`),
1841 output: parser.File{
1842 IsRelaxed: true,
1843 Groups: []parser.Group{
1844 {
1845 Rules: []parser.Rule{
1846 {
1847 Lines: diags.LineRange{First: 2, Last: 5},
1848 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
1849 },
1850 },
1851 },
1852 },
1853 },
1854 },
1855 {
1856 input: []byte(`
1857- alert: foo
1858 expr: bar
1859 labels:
1860 "{{ $value }}": yes
1861`),
1862 output: parser.File{
1863 IsRelaxed: true,
1864 Groups: []parser.Group{
1865 {
1866 Rules: []parser.Rule{
1867 {
1868 Lines: diags.LineRange{First: 2, Last: 5},
1869 Error: parser.ParseError{Err: errors.New("invalid label name: {{ $value }}"), Line: 5},
1870 },
1871 },
1872 },
1873 },
1874 },
1875 },
1876 {
1877 input: []byte(`
1878- alert: foo
1879 expr: bar
1880 annotations:
1881 "foo bar": yes
1882`),
1883 output: parser.File{
1884 IsRelaxed: true,
1885 Groups: []parser.Group{
1886 {
1887 Rules: []parser.Rule{
1888 {
1889 Lines: diags.LineRange{First: 2, Last: 5},
1890 Error: parser.ParseError{Err: errors.New("invalid annotation name: foo bar"), Line: 5},
1891 },
1892 },
1893 },
1894 },
1895 },
1896 },
1897 {
1898 input: []byte(`
1899- alert: foo
1900 expr: bar
1901 labels:
1902 foo: ` + string("\xed\xbf\xbf")),
1903 // Label values are invalid only if they aren't valid UTF-8 strings
1904 // which also makes them unparsable by YAML.
1905 output: parser.File{
1906 IsRelaxed: true,
1907 Error: parser.ParseError{
1908 Err: errors.New("yaml: invalid Unicode character"),
1909 Line: 1,
1910 },
1911 },
1912 },
1913 {
1914 input: []byte(`
1915- alert: foo
1916 expr: bar
1917 annotations:
1918 "{{ $value }}": yes
1919`),
1920 output: parser.File{
1921 IsRelaxed: true,
1922 Groups: []parser.Group{
1923 {
1924 Rules: []parser.Rule{
1925 {
1926 Lines: diags.LineRange{First: 2, Last: 5},
1927 Error: parser.ParseError{Err: errors.New("invalid annotation name: {{ $value }}"), Line: 5},
1928 },
1929 },
1930 },
1931 },
1932 },
1933 },
1934 {
1935 input: []byte(`
1936- record: foo
1937 expr: bar
1938 keep_firing_for: 5m
1939`),
1940 output: parser.File{
1941 IsRelaxed: true,
1942 Groups: []parser.Group{
1943 {
1944 Rules: []parser.Rule{
1945 {
1946 Lines: diags.LineRange{First: 2, Last: 4},
1947 Error: parser.ParseError{Err: errors.New("invalid field 'keep_firing_for' in recording rule"), Line: 4},
1948 },
1949 },
1950 },
1951 },
1952 },
1953 },
1954 {
1955 input: []byte(`
1956- record: foo
1957 expr: bar
1958 for: 5m
1959`),
1960 output: parser.File{
1961 IsRelaxed: true,
1962 Groups: []parser.Group{
1963 {
1964 Rules: []parser.Rule{
1965 {
1966 Lines: diags.LineRange{First: 2, Last: 4},
1967 Error: parser.ParseError{Err: errors.New("invalid field 'for' in recording rule"), Line: 4},
1968 },
1969 },
1970 },
1971 },
1972 },
1973 },
1974 {
1975 input: []byte(`
1976- record: foo
1977 expr: bar
1978 annotations:
1979 foo: bar
1980`),
1981 output: parser.File{
1982 IsRelaxed: true,
1983 Groups: []parser.Group{
1984 {
1985 Rules: []parser.Rule{
1986 {
1987 Lines: diags.LineRange{First: 2, Last: 5},
1988 Error: parser.ParseError{Err: errors.New("invalid field 'annotations' in recording rule"), Line: 4},
1989 },
1990 },
1991 },
1992 },
1993 },
1994 },
1995 // Tag tests
1996 {
1997 input: []byte(`
1998- record: 5
1999 expr: bar
2000`),
2001 output: parser.File{
2002 IsRelaxed: true,
2003 Groups: []parser.Group{
2004 {
2005 Rules: []parser.Rule{
2006 {
2007 Lines: diags.LineRange{First: 2, Last: 3},
2008 Error: parser.ParseError{Err: errors.New("record value must be a string, got integer instead"), Line: 2},
2009 },
2010 },
2011 },
2012 },
2013 },
2014 },
2015 {
2016 input: []byte(`
2017- alert: 5
2018 expr: bar
2019`),
2020 output: parser.File{
2021 IsRelaxed: true,
2022 Groups: []parser.Group{
2023 {
2024 Rules: []parser.Rule{
2025 {
2026 Lines: diags.LineRange{First: 2, Last: 3},
2027 Error: parser.ParseError{Err: errors.New("alert value must be a string, got integer instead"), Line: 2},
2028 },
2029 },
2030 },
2031 },
2032 },
2033 },
2034 {
2035 input: []byte(`
2036- record: foo
2037 expr: 5
2038`),
2039 output: parser.File{
2040 IsRelaxed: true,
2041 Groups: []parser.Group{
2042 {
2043 Rules: []parser.Rule{
2044 {
2045 Lines: diags.LineRange{First: 2, Last: 3},
2046 Error: parser.ParseError{Err: errors.New("expr value must be a string, got integer instead"), Line: 3},
2047 },
2048 },
2049 },
2050 },
2051 },
2052 },
2053 {
2054 input: []byte(`
2055- alert: foo
2056 expr: bar
2057 for: 5
2058`),
2059 output: parser.File{
2060 IsRelaxed: true,
2061 Groups: []parser.Group{
2062 {
2063 Rules: []parser.Rule{
2064 {
2065 Lines: diags.LineRange{First: 2, Last: 4},
2066 Error: parser.ParseError{Err: errors.New("for value must be a string, got integer instead"), Line: 4},
2067 },
2068 },
2069 },
2070 },
2071 },
2072 },
2073 {
2074 input: []byte(`
2075- alert: foo
2076 expr: bar
2077 keep_firing_for: 5
2078`),
2079 output: parser.File{
2080 IsRelaxed: true,
2081 Groups: []parser.Group{
2082 {
2083 Rules: []parser.Rule{
2084 {
2085 Lines: diags.LineRange{First: 2, Last: 4},
2086 Error: parser.ParseError{Err: errors.New("keep_firing_for value must be a string, got integer instead"), Line: 4},
2087 },
2088 },
2089 },
2090 },
2091 },
2092 },
2093 {
2094 input: []byte(`
2095- record: foo
2096 expr: bar
2097 labels: []
2098`),
2099 output: parser.File{
2100 IsRelaxed: true,
2101 Groups: []parser.Group{
2102 {
2103 Rules: []parser.Rule{
2104 {
2105 Lines: diags.LineRange{First: 2, Last: 4},
2106 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got list instead"), Line: 4},
2107 },
2108 },
2109 },
2110 },
2111 },
2112 },
2113 {
2114 input: []byte(`
2115- alert: foo
2116 expr: bar
2117 labels: {}
2118 annotations: []
2119`),
2120 output: parser.File{
2121 IsRelaxed: true,
2122 Groups: []parser.Group{
2123 {
2124 Rules: []parser.Rule{
2125 {
2126 Lines: diags.LineRange{First: 2, Last: 5},
2127 Error: parser.ParseError{Err: errors.New("annotations value must be a mapping, got list instead"), Line: 5},
2128 },
2129 },
2130 },
2131 },
2132 },
2133 },
2134 {
2135 input: []byte(`
2136- alert: foo
2137 expr: bar
2138 labels:
2139 foo: 3
2140 annotations:
2141 bar: "5"
2142`),
2143 output: parser.File{
2144 IsRelaxed: true,
2145 Groups: []parser.Group{
2146 {
2147 Rules: []parser.Rule{
2148 {
2149 Lines: diags.LineRange{First: 2, Last: 7},
2150 Error: parser.ParseError{Err: errors.New("labels foo value must be a string, got integer instead"), Line: 5},
2151 },
2152 },
2153 },
2154 },
2155 },
2156 },
2157 {
2158 input: []byte(`
2159- alert: foo
2160 expr: bar
2161 labels: {}
2162 annotations:
2163 foo: "3"
2164 bar: 5
2165`),
2166 output: parser.File{
2167 IsRelaxed: true,
2168 Groups: []parser.Group{
2169 {
2170 Rules: []parser.Rule{
2171 {
2172 Lines: diags.LineRange{First: 2, Last: 7},
2173 Error: parser.ParseError{Err: errors.New("annotations bar value must be a string, got integer instead"), Line: 7},
2174 },
2175 },
2176 },
2177 },
2178 },
2179 },
2180 {
2181 input: []byte(`
2182- record: foo
2183 expr: bar
2184 labels: 4
2185`),
2186 output: parser.File{
2187 IsRelaxed: true,
2188 Groups: []parser.Group{
2189 {
2190 Rules: []parser.Rule{
2191 {
2192 Lines: diags.LineRange{First: 2, Last: 4},
2193 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got integer instead"), Line: 4},
2194 },
2195 },
2196 },
2197 },
2198 },
2199 },
2200 {
2201 input: []byte(`
2202- record: foo
2203 expr: bar
2204 labels: true
2205`),
2206 output: parser.File{
2207 IsRelaxed: true,
2208 Groups: []parser.Group{
2209 {
2210 Rules: []parser.Rule{
2211 {
2212 Lines: diags.LineRange{First: 2, Last: 4},
2213 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got bool instead"), Line: 4},
2214 },
2215 },
2216 },
2217 },
2218 },
2219 },
2220 {
2221 input: []byte(`
2222- record: foo
2223 expr: bar
2224 labels: null
2225`),
2226 output: parser.File{
2227 IsRelaxed: true,
2228 Groups: []parser.Group{
2229 {
2230 Rules: []parser.Rule{
2231 {
2232 Lines: diags.LineRange{First: 2, Last: 4},
2233 RecordingRule: &parser.RecordingRule{
2234 Record: parser.YamlNode{
2235 Value: "foo",
2236 Pos: diags.PositionRanges{
2237 {Line: 2, FirstColumn: 11, LastColumn: 13},
2238 },
2239 },
2240 Expr: parser.PromQLExpr{
2241 Value: &parser.YamlNode{
2242 Value: "bar",
2243 Pos: diags.PositionRanges{
2244 {Line: 3, FirstColumn: 9, LastColumn: 11},
2245 },
2246 },
2247 },
2248 Labels: &parser.YamlMap{
2249 Key: &parser.YamlNode{
2250 Value: "labels",
2251 },
2252 },
2253 },
2254 },
2255 },
2256 },
2257 },
2258 },
2259 },
2260 {
2261 input: []byte(`
2262- record: true
2263 expr: bar
2264`),
2265 output: parser.File{
2266 IsRelaxed: true,
2267 Groups: []parser.Group{
2268 {
2269 Rules: []parser.Rule{
2270 {
2271 Lines: diags.LineRange{First: 2, Last: 3},
2272 Error: parser.ParseError{Err: errors.New("record value must be a string, got bool instead"), Line: 2},
2273 },
2274 },
2275 },
2276 },
2277 },
2278 },
2279 {
2280 input: []byte(`
2281- record:
2282 query: foo
2283 expr: bar
2284`),
2285 output: parser.File{
2286 IsRelaxed: true,
2287 Groups: []parser.Group{
2288 {
2289 Rules: []parser.Rule{
2290 {
2291 Lines: diags.LineRange{First: 2, Last: 4},
2292 Error: parser.ParseError{Err: errors.New("record value must be a string, got mapping instead"), Line: 3},
2293 },
2294 },
2295 },
2296 },
2297 },
2298 },
2299 {
2300 input: []byte(`
2301- record: foo
2302 expr: bar
2303 labels: some
2304`),
2305 output: parser.File{
2306 IsRelaxed: true,
2307 Groups: []parser.Group{
2308 {
2309 Rules: []parser.Rule{
2310 {
2311 Lines: diags.LineRange{First: 2, Last: 4},
2312 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got string instead"), Line: 4},
2313 },
2314 },
2315 },
2316 },
2317 },
2318 },
2319 {
2320 input: []byte(`
2321- record: foo
2322 expr: bar
2323 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
2324`),
2325 output: parser.File{
2326 IsRelaxed: true,
2327 Groups: []parser.Group{
2328 {
2329 Rules: []parser.Rule{
2330 {
2331 Lines: diags.LineRange{First: 2, Last: 4},
2332 Error: parser.ParseError{
2333 Err: errors.New("labels value must be a mapping, got binary data instead"),
2334 Line: 4,
2335 },
2336 },
2337 },
2338 },
2339 },
2340 },
2341 },
2342 {
2343 input: []byte(`
2344- alert: foo
2345 expr: bar
2346 for: 1.23
2347`),
2348 output: parser.File{
2349 IsRelaxed: true,
2350 Groups: []parser.Group{
2351 {
2352 Rules: []parser.Rule{
2353 {
2354 Lines: diags.LineRange{First: 2, Last: 4},
2355 Error: parser.ParseError{
2356 Err: errors.New("for value must be a string, got float instead"),
2357 Line: 4,
2358 },
2359 },
2360 },
2361 },
2362 },
2363 },
2364 },
2365 {
2366 input: []byte(`
2367- record: foo
2368 expr: bar
2369 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
2370`),
2371 output: parser.File{
2372 IsRelaxed: true,
2373 Groups: []parser.Group{
2374 {
2375 Rules: []parser.Rule{
2376 {
2377 Lines: diags.LineRange{First: 2, Last: 4},
2378 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got garbage instead"), Line: 4},
2379 },
2380 },
2381 },
2382 },
2383 },
2384 },
2385 {
2386 input: []byte(`
2387- record: foo
2388 expr: bar
2389 labels: !! "SGVsbG8sIFdvcmxkIQ=="
2390`),
2391 output: parser.File{
2392 IsRelaxed: true,
2393 Error: parser.ParseError{
2394 Err: errors.New("did not find expected tag URI"),
2395 Line: 4,
2396 },
2397 },
2398 },
2399 {
2400 input: []byte(`
2401- record: &foo foo
2402 expr: bar
2403 labels: *foo
2404`),
2405 output: parser.File{
2406 IsRelaxed: true,
2407 Groups: []parser.Group{
2408 {
2409 Rules: []parser.Rule{
2410 {
2411 Lines: diags.LineRange{First: 2, Last: 4},
2412 Error: parser.ParseError{
2413 Err: errors.New("labels value must be a mapping, got string instead"),
2414 Line: 4,
2415 },
2416 },
2417 },
2418 },
2419 },
2420 },
2421 },
2422 // Multi-document tests
2423 {
2424 input: []byte(`---
2425- expr: foo
2426 record: foo
2427---
2428- expr: bar
2429`),
2430 output: parser.File{
2431 IsRelaxed: true,
2432 Groups: []parser.Group{
2433 {
2434 Rules: []parser.Rule{
2435 {
2436 Lines: diags.LineRange{First: 2, Last: 3},
2437 RecordingRule: &parser.RecordingRule{
2438 Record: parser.YamlNode{
2439 Value: "foo",
2440 Pos: diags.PositionRanges{
2441 {Line: 3, FirstColumn: 11, LastColumn: 13},
2442 },
2443 },
2444 Expr: parser.PromQLExpr{
2445 Value: &parser.YamlNode{
2446 Value: "foo",
2447 Pos: diags.PositionRanges{
2448 {Line: 2, FirstColumn: 9, LastColumn: 11},
2449 },
2450 },
2451 },
2452 },
2453 },
2454 },
2455 },
2456 {
2457 Rules: []parser.Rule{
2458 {
2459 Lines: diags.LineRange{First: 5, Last: 5},
2460 Error: parser.ParseError{
2461 Err: errors.New("incomplete rule, no alert or record key"),
2462 Line: 5,
2463 },
2464 },
2465 },
2466 },
2467 },
2468 },
2469 },
2470 {
2471 input: []byte(`---
2472- expr: foo
2473 record: foo
2474---
2475- expr: bar
2476 record: bar
2477 expr: bar
2478`),
2479 output: parser.File{
2480 IsRelaxed: true,
2481 Groups: []parser.Group{
2482 {
2483 Rules: []parser.Rule{
2484 {
2485 Lines: diags.LineRange{First: 2, Last: 3},
2486 RecordingRule: &parser.RecordingRule{
2487 Record: parser.YamlNode{
2488 Value: "foo",
2489 Pos: diags.PositionRanges{
2490 {Line: 3, FirstColumn: 11, LastColumn: 13},
2491 },
2492 },
2493 Expr: parser.PromQLExpr{
2494 Value: &parser.YamlNode{
2495 Value: "foo",
2496 Pos: diags.PositionRanges{
2497 {Line: 2, FirstColumn: 9, LastColumn: 11},
2498 },
2499 },
2500 },
2501 },
2502 },
2503 },
2504 },
2505 {
2506 Rules: []parser.Rule{
2507 {
2508 Lines: diags.LineRange{First: 5, Last: 7},
2509 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 7},
2510 },
2511 },
2512 },
2513 },
2514 },
2515 },
2516 {
2517 input: []byte(`---
2518- expr: foo
2519 record: foo
2520---
2521- expr: bar
2522 alert: foo
2523`),
2524 output: parser.File{
2525 IsRelaxed: true,
2526 Groups: []parser.Group{
2527 {
2528 Rules: []parser.Rule{
2529 {
2530 Lines: diags.LineRange{First: 2, Last: 3},
2531 RecordingRule: &parser.RecordingRule{
2532 Record: parser.YamlNode{
2533 Value: "foo",
2534 Pos: diags.PositionRanges{
2535 {Line: 3, FirstColumn: 11, LastColumn: 13},
2536 },
2537 },
2538 Expr: parser.PromQLExpr{
2539 Value: &parser.YamlNode{
2540 Value: "foo",
2541 Pos: diags.PositionRanges{
2542 {Line: 2, FirstColumn: 9, LastColumn: 11},
2543 },
2544 },
2545 },
2546 },
2547 },
2548 },
2549 },
2550 {
2551 Rules: []parser.Rule{
2552 {
2553 Lines: diags.LineRange{First: 5, Last: 6},
2554 AlertingRule: &parser.AlertingRule{
2555 Alert: parser.YamlNode{
2556 Value: "foo",
2557 Pos: diags.PositionRanges{
2558 {Line: 6, FirstColumn: 10, LastColumn: 12},
2559 },
2560 },
2561 Expr: parser.PromQLExpr{
2562 Value: &parser.YamlNode{
2563 Value: "bar",
2564 Pos: diags.PositionRanges{
2565 {Line: 5, FirstColumn: 9, LastColumn: 11},
2566 },
2567 },
2568 },
2569 },
2570 },
2571 },
2572 },
2573 },
2574 },
2575 },
2576 {
2577 input: []byte(`---
2578groups:
2579- name: v1
2580 rules:
2581 - record: up:count
2582 expr: count(up)
2583 labels:
2584 foo:
2585 bar: foo
2586`),
2587 output: parser.File{
2588 IsRelaxed: true,
2589 Groups: []parser.Group{
2590 {
2591 Name: "v1",
2592 Rules: []parser.Rule{
2593 {
2594 Lines: diags.LineRange{First: 5, Last: 9},
2595 Error: parser.ParseError{
2596 Err: errors.New("labels foo value must be a string, got mapping instead"),
2597 Line: 9,
2598 },
2599 },
2600 },
2601 },
2602 },
2603 },
2604 },
2605 {
2606 input: []byte(`
2607groups:
2608- name: v1
2609 rules:
2610 - record: up:count
2611 expr: count(up)
2612`),
2613 strict: true,
2614 output: parser.File{
2615 Groups: []parser.Group{
2616 {
2617 Name: "v1",
2618 Rules: []parser.Rule{
2619 {
2620 Lines: diags.LineRange{First: 5, Last: 6},
2621 RecordingRule: &parser.RecordingRule{
2622 Record: parser.YamlNode{
2623 Value: "up:count",
2624 Pos: diags.PositionRanges{
2625 {Line: 5, FirstColumn: 13, LastColumn: 20},
2626 },
2627 },
2628 Expr: parser.PromQLExpr{
2629 Value: &parser.YamlNode{
2630 Value: "count(up)",
2631 Pos: diags.PositionRanges{
2632 {Line: 6, FirstColumn: 11, LastColumn: 19},
2633 },
2634 },
2635 },
2636 },
2637 },
2638 },
2639 },
2640 },
2641 },
2642 },
2643 {
2644 input: []byte(`
2645groups:
2646- name: v1
2647 rules:
2648 - record: up:count
2649`),
2650 strict: true,
2651 output: parser.File{
2652 Groups: []parser.Group{
2653 {
2654 Name: "v1",
2655 Rules: []parser.Rule{
2656 {
2657 Lines: diags.LineRange{First: 5, Last: 5},
2658 Error: parser.ParseError{
2659 Err: errors.New("missing expr key"),
2660 Line: 5,
2661 },
2662 },
2663 },
2664 },
2665 },
2666 },
2667 },
2668 {
2669 input: []byte(`
2670- record: up:count
2671 expr: count(up)
2672`),
2673 strict: true,
2674 output: parser.File{
2675 Error: parser.ParseError{
2676 Err: errors.New("top level field must be a groups key, got list"),
2677 Line: 2,
2678 },
2679 },
2680 },
2681 {
2682 input: []byte(`
2683rules:
2684 - record: up:count
2685 expr: count(up)
2686`),
2687 strict: true,
2688 output: parser.File{
2689 Error: parser.ParseError{
2690 Err: errors.New("unexpected key rules"),
2691 Line: 2,
2692 },
2693 },
2694 },
2695 {
2696 input: []byte(`
2697groups:
2698 - record: up:count
2699 expr: count(up)
2700`),
2701 strict: true,
2702 output: parser.File{
2703 Groups: []parser.Group{
2704 {
2705 Error: parser.ParseError{
2706 Err: errors.New("invalid group key record"),
2707 Line: 3,
2708 },
2709 },
2710 },
2711 },
2712 },
2713 {
2714 input: []byte(`
2715groups:
2716- rules:
2717 - record: up:count
2718 expr: count(up)
2719`),
2720 strict: true,
2721 output: parser.File{
2722 Groups: []parser.Group{
2723 {
2724 Error: parser.ParseError{
2725 Err: errors.New("incomplete group definition, name is required and must be set"),
2726 Line: 3,
2727 },
2728 Rules: []parser.Rule{
2729 {
2730 Lines: diags.LineRange{First: 4, Last: 5},
2731 RecordingRule: &parser.RecordingRule{
2732 Record: parser.YamlNode{
2733 Value: "up:count",
2734 },
2735 Expr: parser.PromQLExpr{
2736 Value: &parser.YamlNode{
2737 Value: "count(up)",
2738 },
2739 },
2740 },
2741 },
2742 },
2743 },
2744 },
2745 },
2746 },
2747 {
2748 input: []byte(`
2749groups:
2750- name: foo
2751`),
2752 strict: true,
2753 output: parser.File{
2754 Groups: []parser.Group{
2755 {
2756 Name: "foo",
2757 },
2758 },
2759 },
2760 },
2761 {
2762 input: []byte(`
2763groups: {}
2764`),
2765 strict: true,
2766 output: parser.File{
2767 Error: parser.ParseError{
2768 Err: errors.New("groups value must be a list, got mapping"),
2769 Line: 2,
2770 },
2771 },
2772 },
2773 {
2774 input: []byte(`
2775groups:
2776- name: []
2777`),
2778 strict: true,
2779 output: parser.File{
2780 Groups: []parser.Group{
2781 {
2782 Error: parser.ParseError{
2783 Err: errors.New("group name must be a string, got list"),
2784 Line: 3,
2785 },
2786 },
2787 },
2788 },
2789 },
2790 {
2791 input: []byte(`
2792groups:
2793- name: foo
2794 name: bar
2795 name: bob
2796`),
2797 strict: true,
2798 output: parser.File{
2799 Groups: []parser.Group{
2800 {
2801 Name: "bar",
2802 Error: parser.ParseError{
2803 Err: errors.New("duplicated key name"),
2804 Line: 4,
2805 },
2806 },
2807 },
2808 },
2809 },
2810 {
2811 input: []byte(`
2812groups:
2813- name: v1
2814 rules:
2815 rules:
2816 - record: up:count
2817 expr: count(up)
2818`),
2819 strict: true,
2820 output: parser.File{
2821 Groups: []parser.Group{
2822 {
2823 Name: "v1",
2824 Error: parser.ParseError{
2825 Err: errors.New("rules must be a list, got mapping"),
2826 Line: 4,
2827 },
2828 },
2829 },
2830 },
2831 },
2832 {
2833 input: []byte(`
2834groups:
2835- name: v1
2836 rules:
2837 - rules:
2838 - record: up:count
2839 expr: count(up)
2840`),
2841 strict: true,
2842 output: parser.File{
2843 Groups: []parser.Group{
2844 {
2845 Name: "v1",
2846 Rules: []parser.Rule{
2847 {
2848 Error: parser.ParseError{
2849 Err: errors.New("invalid rule key rules"),
2850 Line: 5,
2851 },
2852 },
2853 },
2854 },
2855 },
2856 },
2857 },
2858 {
2859 input: []byte(`
2860groups:
2861- name: v1
2862 rules:
2863 - rules:
2864 - record: up:count
2865 expr: count(up)
2866`),
2867 strict: true,
2868 output: parser.File{
2869 Error: parser.ParseError{
2870 Err: errors.New("found a tab character that violates indentation"),
2871 Line: 6,
2872 },
2873 },
2874 },
2875 {
2876 input: []byte(`
2877---
2878groups:
2879- name: v1
2880 rules:
2881 - record: up:count
2882 expr: count(up)
2883---
2884groups:
2885- name: v1
2886 rules:
2887 - rules:
2888 - record: up:count
2889 expr: count(up)
2890`),
2891 strict: true,
2892 output: parser.File{
2893 Groups: []parser.Group{
2894 {
2895 Name: "v1",
2896 Rules: []parser.Rule{
2897 {
2898 Lines: diags.LineRange{First: 6, Last: 7},
2899 RecordingRule: &parser.RecordingRule{
2900 Record: parser.YamlNode{
2901 Value: "up:count",
2902 Pos: diags.PositionRanges{
2903 {Line: 6, FirstColumn: 15, LastColumn: 22},
2904 },
2905 },
2906 Expr: parser.PromQLExpr{
2907 Value: &parser.YamlNode{
2908 Value: "count(up)",
2909 Pos: diags.PositionRanges{
2910 {Line: 7, FirstColumn: 13, LastColumn: 21},
2911 },
2912 },
2913 },
2914 },
2915 },
2916 },
2917 },
2918 {
2919 Name: "v1",
2920 Rules: []parser.Rule{
2921 {
2922 Error: parser.ParseError{
2923 Err: errors.New("invalid rule key rules"),
2924 Line: 12,
2925 },
2926 },
2927 },
2928 },
2929 },
2930 Error: parser.ParseError{
2931 Line: 8,
2932 Err: errors.New("multi-document YAML files are not allowed"),
2933 },
2934 },
2935 },
2936 {
2937 input: []byte(`
2938---
2939groups: []
2940---
2941groups:
2942- name: foo
2943 rules:
2944 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
2945`),
2946 strict: true,
2947 output: parser.File{
2948 Groups: []parser.Group{
2949 {
2950 Name: "foo",
2951 Rules: []parser.Rule{
2952 {
2953 Lines: diags.LineRange{First: 8, Last: 8},
2954 Error: parser.ParseError{
2955 Line: 8,
2956 Err: errors.New("labels value must be a mapping, got binary data instead"),
2957 },
2958 },
2959 },
2960 },
2961 },
2962 Error: parser.ParseError{
2963 Line: 4,
2964 Err: errors.New("multi-document YAML files are not allowed"),
2965 },
2966 },
2967 },
2968 {
2969 input: []byte("[]"),
2970 strict: true,
2971 output: parser.File{
2972 Error: parser.ParseError{
2973 Err: errors.New("top level field must be a groups key, got list"),
2974 Line: 1,
2975 },
2976 },
2977 },
2978 {
2979 input: []byte("\n\n[]"),
2980 strict: true,
2981 output: parser.File{
2982 Error: parser.ParseError{
2983 Err: errors.New("top level field must be a groups key, got list"),
2984 Line: 3,
2985 },
2986 },
2987 },
2988 {
2989 input: []byte("groups: {}"),
2990 strict: true,
2991 output: parser.File{
2992 Error: parser.ParseError{
2993 Err: errors.New("groups value must be a list, got mapping"),
2994 Line: 1,
2995 },
2996 },
2997 },
2998 {
2999 input: []byte("groups: []"),
3000 strict: true,
3001 },
3002 {
3003 input: []byte("xgroups: {}"),
3004 strict: true,
3005 output: parser.File{
3006 Error: parser.ParseError{
3007 Err: errors.New("unexpected key xgroups"),
3008 Line: 1,
3009 },
3010 },
3011 },
3012 {
3013 input: []byte("\nbob\n"),
3014 strict: true,
3015 output: parser.File{
3016 Error: parser.ParseError{
3017 Err: errors.New("top level field must be a groups key, got string"),
3018 Line: 2,
3019 },
3020 },
3021 },
3022 {
3023 input: []byte(`groups: []
3024
3025rules: []
3026`),
3027 strict: true,
3028 output: parser.File{
3029 Error: parser.ParseError{
3030 Err: errors.New("unexpected key rules"),
3031 Line: 3,
3032 },
3033 },
3034 },
3035 {
3036 input: []byte(`
3037groups:
3038- name: foo
3039 rules: []
3040`),
3041 strict: true,
3042 output: parser.File{
3043 Groups: []parser.Group{
3044 {
3045 Name: "foo",
3046 },
3047 },
3048 },
3049 },
3050 {
3051 input: []byte(`
3052groups:
3053- name:
3054 rules: []
3055`),
3056 strict: true,
3057 output: parser.File{
3058 Groups: []parser.Group{
3059 {
3060 Error: parser.ParseError{
3061 Err: errors.New("group name must be a string, got null"),
3062 Line: 3,
3063 },
3064 },
3065 },
3066 },
3067 },
3068 {
3069 input: []byte(`
3070groups:
3071- name: foo
3072 rules:
3073 - record: foo
3074 expr: sum(up)
3075 labels:
3076 job: foo
3077`),
3078 strict: true,
3079 output: parser.File{
3080 Groups: []parser.Group{
3081 {
3082 Name: "foo",
3083 Rules: []parser.Rule{
3084 {
3085 Lines: diags.LineRange{First: 5, Last: 8},
3086 RecordingRule: &parser.RecordingRule{
3087 Record: parser.YamlNode{
3088 Value: "foo",
3089 Pos: diags.PositionRanges{
3090 {Line: 5, FirstColumn: 13, LastColumn: 15},
3091 },
3092 },
3093 Expr: parser.PromQLExpr{
3094 Value: &parser.YamlNode{
3095 Value: "sum(up)",
3096 Pos: diags.PositionRanges{
3097 {Line: 6, FirstColumn: 11, LastColumn: 17},
3098 },
3099 },
3100 },
3101 Labels: &parser.YamlMap{
3102 Key: &parser.YamlNode{
3103 Value: "labels",
3104 },
3105 Items: []*parser.YamlKeyValue{
3106 {
3107 Key: &parser.YamlNode{
3108 Value: "job",
3109 },
3110 Value: &parser.YamlNode{
3111 Value: "foo",
3112 },
3113 },
3114 },
3115 },
3116 },
3117 },
3118 },
3119 },
3120 },
3121 },
3122 },
3123 {
3124 input: []byte(`
3125groups:
3126- name: foo
3127 rules:
3128 - record: foo
3129 expr: sum(up)
3130 xxx: 1
3131 labels:
3132 job: foo
3133`),
3134 strict: true,
3135 output: parser.File{
3136 Groups: []parser.Group{
3137 {
3138 Name: "foo",
3139 Rules: []parser.Rule{
3140 {
3141 Error: parser.ParseError{
3142 Err: errors.New("invalid rule key xxx"),
3143 Line: 7,
3144 },
3145 },
3146 },
3147 },
3148 },
3149 },
3150 },
3151 {
3152 input: []byte(`
3153groups:
3154- name: foo
3155 rules:
3156 record: foo
3157 expr: sum(up)
3158 xxx: 1
3159 labels:
3160 job: foo
3161`),
3162 strict: true,
3163 output: parser.File{
3164 Groups: []parser.Group{
3165 {
3166 Name: "foo",
3167 Error: parser.ParseError{
3168 Err: errors.New("rules must be a list, got mapping"),
3169 Line: 4,
3170 },
3171 },
3172 },
3173 },
3174 },
3175 {
3176 input: []byte(`
3177groups:
3178- name: foo
3179 rules:
3180 - record: foo
3181 expr: sum(up)
3182 labels:
3183 job:
3184 foo: bar
3185`),
3186 strict: true,
3187 output: parser.File{
3188 Groups: []parser.Group{
3189 {
3190 Name: "foo",
3191 Rules: []parser.Rule{
3192 {
3193 Lines: diags.LineRange{First: 5, Last: 9},
3194 Error: parser.ParseError{
3195 Line: 9,
3196 Err: errors.New("labels job value must be a string, got mapping instead"),
3197 },
3198 },
3199 },
3200 },
3201 },
3202 },
3203 },
3204 {
3205 input: []byte(`
3206groups:
3207- name: foo
3208 rules:
3209 - record: foo
3210 expr:
3211 sum: sum(up)
3212`),
3213 strict: true,
3214 output: parser.File{
3215 Groups: []parser.Group{
3216 {
3217 Name: "foo",
3218 Rules: []parser.Rule{
3219 {
3220 Lines: diags.LineRange{First: 5, Last: 7},
3221 Error: parser.ParseError{
3222 Line: 7,
3223 Err: errors.New("expr value must be a string, got mapping instead"),
3224 },
3225 },
3226 },
3227 },
3228 },
3229 },
3230 },
3231 {
3232 input: []byte(`
3233groups:
3234- name: foo
3235 rules: []
3236- name: foo
3237 rules: []
3238`),
3239 strict: true,
3240 output: parser.File{
3241 Error: parser.ParseError{
3242 Err: errors.New("duplicated group name"),
3243 Line: 5,
3244 },
3245 },
3246 },
3247 {
3248 input: []byte(`
3249groups:
3250- name: foo
3251 rules:
3252 - record: foo
3253 expr: sum(up)
3254 labels:
3255 foo: bob
3256 foo: bar
3257`),
3258 strict: true,
3259 output: parser.File{
3260 Groups: []parser.Group{
3261 {
3262 Name: "foo",
3263 Rules: []parser.Rule{
3264 {
3265 Lines: diags.LineRange{First: 8, Last: 9},
3266 Error: parser.ParseError{
3267 Line: 9,
3268 Err: errors.New("duplicated labels key foo"),
3269 },
3270 },
3271 },
3272 },
3273 },
3274 },
3275 },
3276 {
3277 input: []byte(`
3278groups:
3279- name: v2
3280 rules:
3281 - record: up:count
3282 expr: count(up)
3283 expr: sum(up)`),
3284 strict: true,
3285 output: parser.File{
3286 Groups: []parser.Group{
3287 {
3288 Name: "v2",
3289 Rules: []parser.Rule{
3290 {
3291 Lines: diags.LineRange{First: 5, Last: 7},
3292 Error: parser.ParseError{
3293 Line: 7,
3294 Err: errors.New("duplicated expr key"),
3295 },
3296 },
3297 },
3298 },
3299 },
3300 },
3301 },
3302 {
3303 input: []byte(`
3304groups:
3305- name: v2
3306 rules:
3307 - record: up:count
3308 expr: count(up)
3309bogus: 1
3310`),
3311 strict: true,
3312 output: parser.File{
3313 Error: parser.ParseError{
3314 Err: errors.New("unexpected key bogus"),
3315 Line: 7,
3316 },
3317 },
3318 },
3319 {
3320 input: []byte(`
3321groups:
3322- name: v2
3323 rules:
3324 - record: up:count
3325 expr: count(up)
3326 bogus: 1
3327`),
3328 strict: true,
3329 output: parser.File{
3330 Groups: []parser.Group{
3331 {
3332 Name: "v2",
3333 Rules: []parser.Rule{
3334 {
3335 Error: parser.ParseError{
3336 Err: errors.New("invalid rule key bogus"),
3337 Line: 7,
3338 },
3339 },
3340 },
3341 },
3342 },
3343 },
3344 },
3345 {
3346 input: []byte(`
3347groups:
3348- name: v2
3349 rules:
3350 - alert: up:count
3351 for: 5m
3352 keep_firing_for: 5m
3353 expr: count(up)
3354 labels: {}
3355 annotations: {}
3356 bogus: 1
3357`),
3358 strict: true,
3359 output: parser.File{
3360 Groups: []parser.Group{
3361 {
3362 Name: "v2",
3363 Rules: []parser.Rule{
3364 {
3365 Error: parser.ParseError{
3366 Err: errors.New("invalid rule key bogus"),
3367 Line: 11,
3368 },
3369 },
3370 },
3371 },
3372 },
3373 },
3374 },
3375 {
3376 input: []byte(`
3377groups:
3378
3379- name: CloudflareKafkaZookeeperExporter
3380
3381 rules:
3382`),
3383 strict: true,
3384 output: parser.File{
3385 Groups: []parser.Group{
3386 {
3387 Name: "CloudflareKafkaZookeeperExporter",
3388 },
3389 },
3390 },
3391 },
3392 {
3393 input: []byte(`
3394groups:
3395- name: foo
3396 rules:
3397 expr: 1
3398`),
3399 strict: true,
3400 output: parser.File{
3401 Groups: []parser.Group{
3402 {
3403 Name: "foo",
3404 Error: parser.ParseError{
3405 Err: errors.New("rules must be a list, got mapping"),
3406 Line: 4,
3407 },
3408 },
3409 },
3410 },
3411 },
3412 {
3413 input: []byte(`
3414groups:
3415- name: foo
3416 rules:
3417 - expr: 1
3418`),
3419 strict: true,
3420 output: parser.File{
3421 Groups: []parser.Group{
3422 {
3423 Name: "foo",
3424 Rules: []parser.Rule{
3425 {
3426 Lines: diags.LineRange{First: 5, Last: 5},
3427 Error: parser.ParseError{
3428 Line: 5,
3429 Err: errors.New("incomplete rule, no alert or record key"),
3430 },
3431 },
3432 },
3433 },
3434 },
3435 },
3436 },
3437 {
3438 input: []byte(`
3439groups:
3440- name: foo
3441 rules:
3442 - expr: null
3443`),
3444 strict: true,
3445 output: parser.File{
3446 Groups: []parser.Group{
3447 {
3448 Name: "foo",
3449 Rules: []parser.Rule{
3450 {
3451 Lines: diags.LineRange{First: 5, Last: 5},
3452 Error: parser.ParseError{
3453 Line: 5,
3454 Err: errors.New("incomplete rule, no alert or record key"),
3455 },
3456 },
3457 },
3458 },
3459 },
3460 },
3461 },
3462 {
3463 input: []byte(`
3464groups:
3465- name: foo
3466 rules:
3467 - 1: null
3468`),
3469 strict: true,
3470 output: parser.File{
3471 Groups: []parser.Group{
3472 {
3473 Name: "foo",
3474 Rules: []parser.Rule{
3475 {
3476 Error: parser.ParseError{
3477 Err: errors.New("invalid rule key 1"),
3478 Line: 5,
3479 },
3480 },
3481 },
3482 },
3483 },
3484 },
3485 },
3486 {
3487 input: []byte(`
3488groups:
3489- name: foo
3490 rules:
3491 - true: !!binary "SGVsbG8sIFdvcmxkIQ=="
3492`),
3493 strict: true,
3494 output: parser.File{
3495 Groups: []parser.Group{
3496 {
3497 Name: "foo",
3498 Rules: []parser.Rule{
3499 {
3500 Error: parser.ParseError{
3501 Err: errors.New("invalid rule key true"),
3502 Line: 5,
3503 },
3504 },
3505 },
3506 },
3507 },
3508 },
3509 },
3510 {
3511 input: []byte(`
3512groups:
3513- name: foo
3514 rules:
3515 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3516`),
3517 strict: true,
3518 output: parser.File{
3519 Groups: []parser.Group{
3520 {
3521 Name: "foo",
3522 Rules: []parser.Rule{
3523 {
3524 Lines: diags.LineRange{First: 5, Last: 5},
3525 Error: parser.ParseError{
3526 Line: 5,
3527 Err: errors.New("incomplete rule, no alert or record key"),
3528 },
3529 },
3530 },
3531 },
3532 },
3533 },
3534 },
3535 {
3536 input: []byte(`
3537groups:
3538- name: foo
3539 rules:
3540 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3541 record: foo
3542`),
3543 strict: true,
3544 output: parser.File{
3545 Groups: []parser.Group{
3546 {
3547 Name: "foo",
3548 Rules: []parser.Rule{
3549 {
3550 Lines: diags.LineRange{First: 5, Last: 6},
3551 Error: parser.ParseError{
3552 Line: 5,
3553 Err: errors.New("expr value must be a string, got binary data instead"),
3554 },
3555 },
3556 },
3557 },
3558 },
3559 },
3560 },
3561 {
3562 input: []byte(`
3563groups:
3564- name: foo
3565 rules:
3566 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3567`),
3568 strict: true,
3569 output: parser.File{
3570 Groups: []parser.Group{
3571 {
3572 Name: "foo",
3573 Rules: []parser.Rule{
3574 {
3575 Lines: diags.LineRange{First: 5, Last: 5},
3576 Error: parser.ParseError{
3577 Line: 5,
3578 Err: errors.New("labels value must be a mapping, got binary data instead"),
3579 },
3580 },
3581 },
3582 },
3583 },
3584 },
3585 },
3586 {
3587 input: []byte(`
3588---
3589groups:
3590- name: foo
3591 rules:
3592 - record: foo
3593 expr: bar
3594---
3595groups:
3596- name: foo
3597 rules:
3598 - record: foo
3599 expr: bar
3600`),
3601 strict: true,
3602 output: parser.File{
3603 Error: parser.ParseError{
3604 Line: 8,
3605 Err: errors.New("multi-document YAML files are not allowed"),
3606 },
3607 Groups: []parser.Group{
3608 {
3609 Name: "foo",
3610 Rules: []parser.Rule{
3611 {
3612 Lines: diags.LineRange{First: 6, Last: 7},
3613 RecordingRule: &parser.RecordingRule{
3614 Record: parser.YamlNode{
3615 Value: "foo",
3616 Pos: diags.PositionRanges{
3617 {Line: 6, FirstColumn: 15, LastColumn: 17},
3618 },
3619 },
3620 Expr: parser.PromQLExpr{
3621 Value: &parser.YamlNode{
3622 Value: "bar",
3623 Pos: diags.PositionRanges{
3624 {Line: 7, FirstColumn: 13, LastColumn: 15},
3625 },
3626 },
3627 },
3628 },
3629 },
3630 },
3631 },
3632 {
3633 Name: "foo",
3634 Rules: []parser.Rule{
3635 {
3636 Lines: diags.LineRange{First: 12, Last: 13},
3637 RecordingRule: &parser.RecordingRule{
3638 Record: parser.YamlNode{
3639 Value: "foo",
3640 Pos: diags.PositionRanges{
3641 {Line: 12, FirstColumn: 15, LastColumn: 17},
3642 },
3643 },
3644 Expr: parser.PromQLExpr{
3645 Value: &parser.YamlNode{
3646 Value: "bar",
3647 Pos: diags.PositionRanges{
3648 {Line: 13, FirstColumn: 13, LastColumn: 15},
3649 },
3650 },
3651 },
3652 },
3653 },
3654 },
3655 },
3656 },
3657 },
3658 },
3659 {
3660 input: []byte(`
3661groups:
3662- name: foo
3663 rules:
3664 - record: foo
3665 expr: foo
3666 expr: foo
3667`),
3668 strict: true,
3669 output: parser.File{
3670 Groups: []parser.Group{
3671 {
3672 Name: "foo",
3673 Rules: []parser.Rule{
3674 {
3675 Lines: diags.LineRange{First: 5, Last: 7},
3676 Error: parser.ParseError{
3677 Line: 7,
3678 Err: errors.New("duplicated expr key"),
3679 },
3680 },
3681 },
3682 },
3683 },
3684 },
3685 },
3686 {
3687 input: []byte(`
3688groups:
3689- name: foo
3690 rules:
3691 - record: foo
3692 keep_firing_for: 1m
3693 keep_firing_for: 2m
3694`),
3695 strict: true,
3696 output: parser.File{
3697 Groups: []parser.Group{
3698 {
3699 Name: "foo",
3700 Rules: []parser.Rule{
3701 {
3702 Lines: diags.LineRange{First: 5, Last: 7},
3703 Error: parser.ParseError{
3704 Line: 7,
3705 Err: errors.New("duplicated keep_firing_for key"),
3706 },
3707 },
3708 },
3709 },
3710 },
3711 },
3712 },
3713 {
3714 input: []byte(`
3715groups:
3716- name: foo
3717 rules:
3718 - record: foo
3719 keep_firing_for: 1m
3720 record: 2m
3721`),
3722 strict: true,
3723 output: parser.File{
3724 Groups: []parser.Group{
3725 {
3726 Name: "foo",
3727 Rules: []parser.Rule{
3728 {
3729 Lines: diags.LineRange{First: 5, Last: 7},
3730 Error: parser.ParseError{
3731 Line: 7,
3732 Err: errors.New("duplicated record key"),
3733 },
3734 },
3735 },
3736 },
3737 },
3738 },
3739 },
3740 {
3741 input: []byte(`
3742groups:
3743- name: foo
3744 rules:
3745 - []
3746`),
3747 strict: true,
3748 output: parser.File{
3749 Groups: []parser.Group{
3750 {
3751 Name: "foo",
3752 Rules: []parser.Rule{
3753 {
3754 Error: parser.ParseError{
3755 Err: errors.New("rule definion must be a mapping, got list"),
3756 Line: 5,
3757 },
3758 },
3759 },
3760 },
3761 },
3762 },
3763 },
3764 {
3765 input: []byte(`
37661: 0
3767`),
3768 strict: true,
3769 output: parser.File{
3770 Error: parser.ParseError{
3771 Err: errors.New("groups key must be a string, got a integer"),
3772 Line: 2,
3773 },
3774 },
3775 },
3776 {
3777 input: []byte(`
3778true: 0
3779`),
3780 strict: true,
3781 output: parser.File{
3782 Error: parser.ParseError{
3783 Err: errors.New("groups key must be a string, got a bool"),
3784 Line: 2,
3785 },
3786 },
3787 },
3788 {
3789 input: []byte(`
3790groups: !!binary "SGVsbG8sIFdvcmxkIQ=="
3791`),
3792 strict: true,
3793 output: parser.File{
3794 Error: parser.ParseError{
3795 Err: errors.New("groups value must be a list, got binary data"),
3796 Line: 2,
3797 },
3798 },
3799 },
3800 {
3801 input: []byte(`
3802groups:
3803 - true: null"
3804`),
3805 strict: true,
3806 output: parser.File{
3807 Groups: []parser.Group{
3808 {
3809 Error: parser.ParseError{
3810 Err: errors.New("invalid group key true"),
3811 Line: 3,
3812 },
3813 },
3814 },
3815 },
3816 },
3817 {
3818 input: []byte(`
3819!!!binary "groups": true"
3820`),
3821 strict: true,
3822 output: parser.File{
3823 Error: parser.ParseError{
3824 Err: errors.New("groups key must be a string, got a binary"),
3825 Line: 2,
3826 },
3827 },
3828 },
3829 {
3830 input: []byte("[]"),
3831 strict: true,
3832 output: parser.File{
3833 Error: parser.ParseError{
3834 Err: errors.New("top level field must be a groups key, got list"),
3835 Line: 1,
3836 },
3837 },
3838 },
3839 {
3840 input: []byte(`
3841groups:
3842 - true
3843`),
3844 strict: true,
3845 output: parser.File{
3846 Groups: []parser.Group{
3847 {
3848 Error: parser.ParseError{
3849 Err: errors.New("group must be a mapping, got bool"),
3850 Line: 3,
3851 },
3852 },
3853 },
3854 },
3855 },
3856 {
3857 input: []byte(`
3858groups:
3859 - name:
3860 rules: []
3861`),
3862 strict: true,
3863 output: parser.File{
3864 Groups: []parser.Group{
3865 {
3866 Error: parser.ParseError{
3867 Err: errors.New("group name must be a string, got null"),
3868 Line: 3,
3869 },
3870 },
3871 },
3872 },
3873 },
3874 {
3875 input: []byte(`
3876groups:
3877 - name: ""
3878 rules: []
3879`),
3880 strict: true,
3881 output: parser.File{
3882 Groups: []parser.Group{
3883 {
3884 Error: parser.ParseError{
3885 Err: errors.New("group name cannot be empty"),
3886 Line: 3,
3887 },
3888 },
3889 },
3890 },
3891 },
3892 {
3893 input: []byte(`
3894groups:
3895 - name: 1
3896 rules: []
3897`),
3898 strict: true,
3899 output: parser.File{
3900 Groups: []parser.Group{
3901 {
3902 Error: parser.ParseError{
3903 Err: errors.New("group name must be a string, got integer"),
3904 Line: 3,
3905 },
3906 },
3907 },
3908 },
3909 },
3910 {
3911 input: []byte(`
3912groups:
3913 - name: foo
3914 interval: 1
3915 rules: []
3916`),
3917 strict: true,
3918 output: parser.File{
3919 Groups: []parser.Group{
3920 {
3921 Name: "foo",
3922 Error: parser.ParseError{
3923 Err: errors.New("group interval must be a string, got integer"),
3924 Line: 4,
3925 },
3926 },
3927 },
3928 },
3929 },
3930 {
3931 input: []byte(`
3932groups:
3933 - name: foo
3934 interval: xxx
3935 rules: []
3936`),
3937 strict: true,
3938 output: parser.File{
3939 Groups: []parser.Group{
3940 {
3941 Name: "foo",
3942 Error: parser.ParseError{
3943 Err: errors.New("invalid interval value: not a valid duration string: \"xxx\""),
3944 Line: 4,
3945 },
3946 },
3947 },
3948 },
3949 },
3950 {
3951 input: []byte(`
3952groups:
3953 - name: foo
3954 query_offset: 1
3955 rules: []
3956`),
3957 strict: true,
3958 output: parser.File{
3959 Groups: []parser.Group{
3960 {
3961 Name: "foo",
3962 Error: parser.ParseError{
3963 Err: errors.New("group query_offset must be a string, got integer"),
3964 Line: 4,
3965 },
3966 },
3967 },
3968 },
3969 },
3970 {
3971 input: []byte(`
3972groups:
3973 - name: foo
3974 query_offset: xxx
3975 rules: []
3976`),
3977 strict: true,
3978 output: parser.File{
3979 Groups: []parser.Group{
3980 {
3981 Name: "foo",
3982 Error: parser.ParseError{
3983 Err: errors.New("invalid query_offset value: not a valid duration string: \"xxx\""),
3984 Line: 4,
3985 },
3986 },
3987 },
3988 },
3989 },
3990 {
3991 input: []byte(`
3992groups:
3993 - name: foo
3994 query_offset: 1m
3995 limit: abc
3996 rules: []
3997`),
3998 strict: true,
3999 output: parser.File{
4000 Groups: []parser.Group{
4001 {
4002 Name: "foo",
4003 QueryOffset: time.Minute,
4004 Error: parser.ParseError{
4005 Err: errors.New("group limit must be a integer, got string"),
4006 Line: 5,
4007 },
4008 },
4009 },
4010 },
4011 },
4012 {
4013 input: []byte(`
4014groups:
4015- name: v2
4016 rules:
4017 - alert: up:count
4018 for: 5m &timeout
4019 keep_firing_for: **timeout
4020 expr: count(up)
4021 labels: {}
4022 annotations: {}
4023 bogus: 1
4024`),
4025 strict: true,
4026 output: parser.File{
4027 Error: parser.ParseError{
4028 Err: errors.New("did not find expected alphabetic or numeric character"),
4029 Line: 7,
4030 },
4031 },
4032 },
4033 {
4034 input: []byte(`
4035groups:
4036- name: v2
4037 rules:
4038 - alert: up:count
4039 for: &for 1
4040 keep_firing_for: *for
4041 expr: count(up)
4042 labels: {}
4043 annotations: {}
4044`),
4045 strict: true,
4046 output: parser.File{
4047 Groups: []parser.Group{
4048 {
4049 Name: "v2",
4050 Rules: []parser.Rule{
4051 {
4052 Lines: diags.LineRange{First: 5, Last: 10},
4053 Error: parser.ParseError{
4054 Line: 6,
4055 Err: errors.New("for value must be a string, got integer instead"),
4056 },
4057 },
4058 },
4059 },
4060 },
4061 },
4062 },
4063 {
4064 input: []byte(`
4065groups:
4066- name: v2
4067 limit: &for 1
4068 rules:
4069 - alert: up:count
4070 keep_firing_for: *for
4071 expr: count(up)
4072 labels: {}
4073 annotations: {}
4074`),
4075 strict: true,
4076 output: parser.File{
4077 Groups: []parser.Group{
4078 {
4079 Name: "v2",
4080 Limit: 1,
4081 Rules: []parser.Rule{
4082 {
4083 Lines: diags.LineRange{First: 6, Last: 10},
4084 Error: parser.ParseError{
4085 Line: 7,
4086 Err: errors.New("keep_firing_for value must be a string, got integer instead"),
4087 },
4088 },
4089 },
4090 },
4091 },
4092 },
4093 },
4094 {
4095 input: []byte(`
4096groups:
4097- name: "{{ source }}"
4098 rules:
4099# pint ignore/begin
4100
4101# pint ignore/end
4102`),
4103 strict: true,
4104 output: parser.File{
4105 Groups: []parser.Group{
4106 {
4107 Name: "{{ source }}",
4108 },
4109 },
4110 },
4111 },
4112 {
4113 input: []byte(`
4114groups:
4115- name: foo
4116 rules:
4117 - record: foo
4118 expr: |
4119 {"up"}
4120`),
4121 output: parser.File{
4122 IsRelaxed: true,
4123 Groups: []parser.Group{
4124 {
4125 Name: "foo",
4126 Rules: []parser.Rule{
4127 {
4128 Lines: diags.LineRange{First: 5, Last: 7},
4129 RecordingRule: &parser.RecordingRule{
4130 Record: parser.YamlNode{
4131 Value: "foo",
4132 Pos: diags.PositionRanges{
4133 {Line: 5, FirstColumn: 13, LastColumn: 15},
4134 },
4135 },
4136 Expr: parser.PromQLExpr{
4137 Value: &parser.YamlNode{
4138 Value: "{\"up\"}\n",
4139 Pos: diags.PositionRanges{
4140 {Line: 7, FirstColumn: 7, LastColumn: 12},
4141 },
4142 },
4143 },
4144 },
4145 },
4146 },
4147 },
4148 },
4149 },
4150 },
4151 {
4152 input: []byte(`
4153groups:
4154- name: foo
4155 rules:
4156 - record: foo
4157 expr: |
4158 {'up'}
4159`),
4160 output: parser.File{
4161 IsRelaxed: true,
4162 Groups: []parser.Group{
4163 {
4164 Name: "foo",
4165 Rules: []parser.Rule{
4166 {
4167 Lines: diags.LineRange{First: 5, Last: 7},
4168 RecordingRule: &parser.RecordingRule{
4169 Record: parser.YamlNode{
4170 Value: "foo",
4171 Pos: diags.PositionRanges{
4172 {Line: 5, FirstColumn: 13, LastColumn: 15},
4173 },
4174 },
4175 Expr: parser.PromQLExpr{
4176 Value: &parser.YamlNode{
4177 Value: "{'up'}\n",
4178 Pos: diags.PositionRanges{
4179 {Line: 7, FirstColumn: 7, LastColumn: 12},
4180 },
4181 },
4182 },
4183 },
4184 },
4185 },
4186 },
4187 },
4188 },
4189 },
4190 {
4191 input: []byte(`
4192groups:
4193- name: foo
4194 rules:
4195 - record: foo
4196 expr: |
4197 {'up' == 1}
4198`),
4199 output: parser.File{
4200 IsRelaxed: true,
4201 Groups: []parser.Group{
4202 {
4203 Name: "foo",
4204 Rules: []parser.Rule{
4205 {
4206 Lines: diags.LineRange{First: 5, Last: 7},
4207 RecordingRule: &parser.RecordingRule{
4208 Record: parser.YamlNode{
4209 Value: "foo",
4210 Pos: diags.PositionRanges{
4211 {Line: 5, FirstColumn: 13, LastColumn: 15},
4212 },
4213 },
4214 Expr: parser.PromQLExpr{
4215 Value: &parser.YamlNode{
4216 Value: "{'up' == 1}\n",
4217 Pos: diags.PositionRanges{
4218 {Line: 7, FirstColumn: 7, LastColumn: 17},
4219 },
4220 },
4221 SyntaxError: errors.New(`1:8: parse error: unexpected "=" in label matching, expected string`),
4222 },
4223 },
4224 },
4225 },
4226 },
4227 },
4228 },
4229 },
4230 {
4231 input: []byte(`
4232groups:
4233- name: mygroup
4234 partial_response_strategy: bob
4235 rules:
4236 - record: up:count
4237 expr: count(up)
4238`),
4239 strict: true,
4240 output: parser.File{
4241 Groups: []parser.Group{
4242 {
4243 Name: "mygroup",
4244 Error: parser.ParseError{
4245 Err: errors.New("partial_response_strategy is only valid when parser is configured to use the Thanos rule schema"),
4246 Line: 4,
4247 },
4248 },
4249 },
4250 },
4251 },
4252 {
4253 input: []byte(`
4254groups:
4255- name: mygroup
4256 partial_response_strategy: warn
4257 rules:
4258 - record: up:count
4259 expr: count(up)
4260`),
4261 strict: true,
4262 schema: parser.ThanosSchema,
4263 output: parser.File{
4264 Groups: []parser.Group{
4265 {
4266 Name: "mygroup",
4267 Rules: []parser.Rule{
4268 {
4269 Lines: diags.LineRange{First: 6, Last: 7},
4270 RecordingRule: &parser.RecordingRule{
4271 Record: parser.YamlNode{
4272 Value: "up:count",
4273 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4274 },
4275 Expr: parser.PromQLExpr{
4276 Value: &parser.YamlNode{
4277 Value: "count(up)",
4278 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4279 },
4280 },
4281 },
4282 },
4283 },
4284 },
4285 },
4286 },
4287 },
4288 {
4289 input: []byte(`
4290groups:
4291- name: mygroup
4292 partial_response_strategy: abort
4293 rules:
4294 - record: up:count
4295 expr: count(up)
4296`),
4297 strict: true,
4298 schema: parser.ThanosSchema,
4299 output: parser.File{
4300 Groups: []parser.Group{
4301 {
4302 Name: "mygroup",
4303 Rules: []parser.Rule{
4304 {
4305 Lines: diags.LineRange{First: 6, Last: 7},
4306 RecordingRule: &parser.RecordingRule{
4307 Record: parser.YamlNode{
4308 Value: "up:count",
4309 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4310 },
4311 Expr: parser.PromQLExpr{
4312 Value: &parser.YamlNode{
4313 Value: "count(up)",
4314 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4315 },
4316 },
4317 },
4318 },
4319 },
4320 },
4321 },
4322 },
4323 },
4324 {
4325 input: []byte(`
4326groups:
4327- name: mygroup
4328 partial_response_strategy: abort
4329 rules:
4330 - record: up:count
4331 expr: count(up)
4332`),
4333 strict: false,
4334 schema: parser.PrometheusSchema,
4335 output: parser.File{
4336 IsRelaxed: true,
4337 Groups: []parser.Group{
4338 {
4339 Name: "mygroup",
4340 Rules: []parser.Rule{
4341 {
4342 Lines: diags.LineRange{First: 6, Last: 7},
4343 RecordingRule: &parser.RecordingRule{
4344 Record: parser.YamlNode{
4345 Value: "up:count",
4346 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4347 },
4348 Expr: parser.PromQLExpr{
4349 Value: &parser.YamlNode{
4350 Value: "count(up)",
4351 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4352 },
4353 },
4354 },
4355 },
4356 },
4357 },
4358 },
4359 },
4360 },
4361 {
4362 input: []byte(`
4363groups:
4364- name: mygroup
4365 partial_response_strategy: bob
4366 rules:
4367 - record: up:count
4368 expr: count(up)
4369`),
4370 strict: true,
4371 schema: parser.ThanosSchema,
4372 output: parser.File{
4373 Groups: []parser.Group{
4374 {
4375 Name: "mygroup",
4376 Error: parser.ParseError{
4377 Err: errors.New("invalid partial_response_strategy value: bob"),
4378 Line: 4,
4379 },
4380 },
4381 },
4382 },
4383 },
4384 {
4385 input: []byte(`
4386groups:
4387- name: mygroup
4388 partial_response_strategy: 1
4389 rules:
4390 - record: up:count
4391 expr: count(up)
4392`),
4393 strict: true,
4394 schema: parser.ThanosSchema,
4395 output: parser.File{
4396 Groups: []parser.Group{
4397 {
4398 Name: "mygroup",
4399 Error: parser.ParseError{
4400 Err: errors.New("partial_response_strategy must be a string, got integer"),
4401 Line: 4,
4402 },
4403 },
4404 },
4405 },
4406 },
4407 {
4408 input: []byte(`
4409- alert: Multi Line
4410 expr: foo
4411 AND ON (instance)
4412 bar
4413`),
4414 strict: false,
4415 schema: parser.PrometheusSchema,
4416 output: parser.File{
4417 IsRelaxed: true,
4418 Groups: []parser.Group{
4419 {
4420 Rules: []parser.Rule{
4421 {
4422 Lines: diags.LineRange{First: 2, Last: 5},
4423 AlertingRule: &parser.AlertingRule{
4424 Alert: parser.YamlNode{
4425 Value: "Multi Line",
4426 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 10, LastColumn: 19}},
4427 },
4428 Expr: parser.PromQLExpr{
4429 Value: &parser.YamlNode{
4430 Value: "foo AND ON (instance) bar",
4431 Pos: diags.PositionRanges{
4432 {Line: 3, FirstColumn: 9, LastColumn: 12},
4433 {Line: 4, FirstColumn: 11, LastColumn: 28},
4434 {Line: 5, FirstColumn: 11, LastColumn: 13},
4435 },
4436 },
4437 },
4438 },
4439 },
4440 },
4441 },
4442 },
4443 },
4444 },
4445 {
4446 input: []byte(`
4447 - alert: FooBar
4448 expr: >-
4449 count(
4450 foo
4451 or
4452 bar
4453 ) > 0
4454`),
4455 strict: false,
4456 schema: parser.PrometheusSchema,
4457 output: parser.File{
4458 IsRelaxed: true,
4459 Groups: []parser.Group{
4460 {
4461 Rules: []parser.Rule{
4462 {
4463 Lines: diags.LineRange{First: 2, Last: 8},
4464 AlertingRule: &parser.AlertingRule{
4465 Alert: parser.YamlNode{
4466 Value: "FooBar",
4467 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4468 },
4469 Expr: parser.PromQLExpr{
4470 Value: &parser.YamlNode{
4471 Value: "count(\n foo\n or\n bar\n) > 0",
4472 Pos: diags.PositionRanges{
4473 {Line: 4, FirstColumn: 7, LastColumn: 13},
4474 {Line: 5, FirstColumn: 7, LastColumn: 12},
4475 {Line: 6, FirstColumn: 7, LastColumn: 11},
4476 {Line: 7, FirstColumn: 7, LastColumn: 12},
4477 {Line: 8, FirstColumn: 7, LastColumn: 11},
4478 },
4479 },
4480 },
4481 },
4482 },
4483 },
4484 },
4485 },
4486 },
4487 },
4488 {
4489 input: []byte(`
4490 - alert: FooBar
4491 expr: >-
4492 aaaaaaaaaaaaaaaaaaaaaaaa
4493 AND ON (colo_id) bbbbbbbbbbb
4494 > 2
4495 for: 1m
4496`),
4497 strict: false,
4498 schema: parser.PrometheusSchema,
4499 output: parser.File{
4500 IsRelaxed: true,
4501 Groups: []parser.Group{
4502 {
4503 Rules: []parser.Rule{
4504 {
4505 Lines: diags.LineRange{First: 2, Last: 7},
4506 AlertingRule: &parser.AlertingRule{
4507 Alert: parser.YamlNode{
4508 Value: "FooBar",
4509 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4510 },
4511 Expr: parser.PromQLExpr{
4512 Value: &parser.YamlNode{
4513 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
4514 Pos: diags.PositionRanges{
4515 {Line: 4, FirstColumn: 7, LastColumn: 31},
4516 {Line: 5, FirstColumn: 7, LastColumn: 35},
4517 {Line: 6, FirstColumn: 7, LastColumn: 9},
4518 },
4519 },
4520 },
4521 For: &parser.YamlNode{
4522 Value: "1m",
4523 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 10, LastColumn: 11}},
4524 },
4525 },
4526 },
4527 },
4528 },
4529 },
4530 },
4531 },
4532 {
4533 input: []byte(`
4534 - alert: FooBar
4535 expr: 'aaaaaaaaaaaaaaaaaaaaaaaa
4536 AND ON (colo_id) bbbbbbbbbbb
4537 > 2'
4538 for: 1m
4539`),
4540 strict: false,
4541 schema: parser.PrometheusSchema,
4542 output: parser.File{
4543 IsRelaxed: true,
4544 Groups: []parser.Group{
4545 {
4546 Rules: []parser.Rule{
4547 {
4548 Lines: diags.LineRange{First: 2, Last: 6},
4549 AlertingRule: &parser.AlertingRule{
4550 Alert: parser.YamlNode{
4551 Value: "FooBar",
4552 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4553 },
4554 Expr: parser.PromQLExpr{
4555 Value: &parser.YamlNode{
4556 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
4557 Pos: diags.PositionRanges{
4558 {Line: 3, FirstColumn: 12, LastColumn: 36},
4559 {Line: 4, FirstColumn: 11, LastColumn: 39},
4560 {Line: 5, FirstColumn: 11, LastColumn: 13},
4561 },
4562 },
4563 },
4564 For: &parser.YamlNode{
4565 Value: "1m",
4566 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 10, LastColumn: 11}},
4567 },
4568 },
4569 },
4570 },
4571 },
4572 },
4573 },
4574 },
4575 {
4576 input: []byte(`
4577groups:
4578- name: foo
4579 rules:
4580 - record: colo:foo:sum
4581 expr: sum without (instance) ( rate(my_metric[2m]) * on (instance)
4582 group_left (hardware_generation, hms_scope, sliver) (instance:metadata{})
4583 )
4584`),
4585 strict: false,
4586 schema: parser.PrometheusSchema,
4587 output: parser.File{
4588 IsRelaxed: true,
4589 Groups: []parser.Group{
4590 {
4591 Name: "foo",
4592 Rules: []parser.Rule{
4593 {
4594 Lines: diags.LineRange{First: 5, Last: 8},
4595 RecordingRule: &parser.RecordingRule{
4596 Record: parser.YamlNode{
4597 Value: "colo:foo:sum",
4598 Pos: diags.PositionRanges{{Line: 5, FirstColumn: 13, LastColumn: 24}},
4599 },
4600 Expr: parser.PromQLExpr{
4601 Value: &parser.YamlNode{
4602 Value: "sum without (instance) ( rate(my_metric[2m]) * on (instance) group_left (hardware_generation, hms_scope, sliver) (instance:metadata{}) )",
4603 Pos: diags.PositionRanges{
4604 {Line: 6, FirstColumn: 11, LastColumn: 71},
4605 {Line: 7, FirstColumn: 7, LastColumn: 80},
4606 {Line: 8, FirstColumn: 7, LastColumn: 7},
4607 },
4608 },
4609 },
4610 },
4611 },
4612 },
4613 },
4614 },
4615 },
4616 },
4617 {
4618 input: []byte(`
4619groups:
4620- name: "{{ source }}"
4621 rules:
4622 # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
4623 # BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
4624 - record: some_long_name
4625 labels:
4626 metricssource: receiver
4627 expr:
4628 clamp_max(
4629 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) /
4630 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)),
4631 2
4632 )
4633`),
4634 strict: true,
4635 schema: parser.PrometheusSchema,
4636 output: parser.File{
4637 Groups: []parser.Group{
4638 {
4639 Name: "{{ source }}",
4640 Rules: []parser.Rule{
4641 {
4642 Lines: diags.LineRange{First: 7, Last: 15},
4643 RecordingRule: &parser.RecordingRule{
4644 Record: parser.YamlNode{
4645 Value: "some_long_name",
4646 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 13, LastColumn: 26}},
4647 },
4648 Labels: &parser.YamlMap{
4649 Key: &parser.YamlNode{
4650 Value: "labels",
4651 },
4652 Items: []*parser.YamlKeyValue{
4653 {
4654 Key: &parser.YamlNode{
4655 Value: "metricssource",
4656 },
4657 Value: &parser.YamlNode{
4658 Value: "receiver",
4659 },
4660 },
4661 },
4662 },
4663 Expr: parser.PromQLExpr{
4664 Value: &parser.YamlNode{
4665 Value: `clamp_max( sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) / sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)), 2 )`,
4666 Pos: diags.PositionRanges{
4667 {Line: 11, FirstColumn: 7, LastColumn: 17},
4668 {Line: 12, FirstColumn: 9, LastColumn: 129},
4669 {Line: 13, FirstColumn: 9, LastColumn: 139},
4670 {Line: 14, FirstColumn: 9, LastColumn: 10},
4671 {Line: 15, FirstColumn: 7, LastColumn: 7},
4672 },
4673 },
4674 },
4675 },
4676 },
4677 },
4678 },
4679 },
4680 },
4681 },
4682 {
4683 input: []byte(`
4684groups:
4685- name: "{{ source }}"
4686 rules:
4687
4688 - alert: Director_Is_Not_Advertising_Any_Routes
4689 expr: |
4690 sum without (name) (
4691 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
4692 * on (instance) group_left (profile,cluster)
4693 cf_node_role{kubernetes_role="director",role="kubernetes"}
4694 ) <= 0
4695 for: 1m
4696`),
4697 strict: true,
4698 schema: parser.PrometheusSchema,
4699 output: parser.File{
4700 Groups: []parser.Group{
4701 {
4702 Name: "{{ source }}",
4703 Rules: []parser.Rule{
4704 {
4705 Lines: diags.LineRange{First: 6, Last: 13},
4706 AlertingRule: &parser.AlertingRule{
4707 Alert: parser.YamlNode{
4708 Value: "Director_Is_Not_Advertising_Any_Routes",
4709 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 12, LastColumn: 49}},
4710 },
4711 Expr: parser.PromQLExpr{
4712 Value: &parser.YamlNode{
4713 Value: `sum without (name) (
4714 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
4715 * on (instance) group_left (profile,cluster)
4716 cf_node_role{kubernetes_role="director",role="kubernetes"}
4717) <= 0
4718`,
4719 Pos: diags.PositionRanges{
4720 {Line: 8, FirstColumn: 9, LastColumn: 29},
4721 {Line: 9, FirstColumn: 9, LastColumn: 99},
4722 {Line: 10, FirstColumn: 9, LastColumn: 55},
4723 {Line: 11, FirstColumn: 9, LastColumn: 71},
4724 {Line: 12, FirstColumn: 9, LastColumn: 14},
4725 },
4726 },
4727 },
4728 For: &parser.YamlNode{
4729 Value: "1m",
4730 Pos: diags.PositionRanges{{Line: 13, FirstColumn: 10, LastColumn: 11}},
4731 },
4732 },
4733 },
4734 },
4735 },
4736 },
4737 },
4738 },
4739 {
4740 input: []byte(`
4741groups:
4742- name: xxx
4743 interval: 3m
4744 rules: []
4745`),
4746 strict: true,
4747 output: parser.File{
4748 Groups: []parser.Group{
4749 {
4750 Name: "xxx",
4751 Interval: time.Minute * 3,
4752 },
4753 },
4754 },
4755 },
4756 {
4757 input: []byte(`
4758groups:
4759- name: xxx
4760 interval: 3m
4761 rules: []
4762`),
4763 output: parser.File{
4764 IsRelaxed: true,
4765 Groups: []parser.Group{
4766 {
4767 Name: "xxx",
4768 Interval: time.Minute * 3,
4769 },
4770 },
4771 },
4772 },
4773 {
4774 input: []byte(`
4775groups:
4776- name: xxx
4777 interval: 3m
4778 rules: []
4779---
4780groups:
4781- name: yyy
4782 interval: 2m
4783 rules: []
4784`),
4785 output: parser.File{
4786 IsRelaxed: true,
4787 Groups: []parser.Group{
4788 {
4789 Name: "xxx",
4790 Interval: time.Minute * 3,
4791 },
4792 {
4793 Name: "yyy",
4794 Interval: time.Minute * 2,
4795 },
4796 },
4797 },
4798 },
4799 {
4800 input: []byte(`
4801groups:
4802- name: xxx
4803 interval: 3m
4804 labels:
4805 foo: bar
4806 rules: []
4807`),
4808 strict: true,
4809 output: parser.File{
4810 Groups: []parser.Group{
4811 {
4812 Name: "xxx",
4813 Interval: time.Minute * 3,
4814 Labels: &parser.YamlMap{
4815 Key: &parser.YamlNode{
4816 Value: "labels",
4817 },
4818 Items: []*parser.YamlKeyValue{
4819 {
4820 Key: &parser.YamlNode{
4821 Value: "foo",
4822 },
4823 Value: &parser.YamlNode{
4824 Value: "bar",
4825 },
4826 },
4827 },
4828 },
4829 },
4830 },
4831 },
4832 },
4833 {
4834 input: []byte(`
4835groups:
4836- name: xxx
4837 labels:
4838 - foo: bar
4839 rules: []
4840`),
4841 strict: true,
4842 output: parser.File{
4843 Groups: []parser.Group{
4844 {
4845 Name: "xxx",
4846 Error: parser.ParseError{
4847 Err: errors.New("group labels must be a mapping, got list"),
4848 Line: 4,
4849 },
4850 },
4851 },
4852 },
4853 },
4854 {
4855 input: []byte(`
4856groups:
4857- name: xxx
4858 labels:
4859 foo: 1
4860 rules: []
4861`),
4862 strict: true,
4863 output: parser.File{
4864 Groups: []parser.Group{
4865 {
4866 Name: "xxx",
4867 Error: parser.ParseError{
4868 Err: errors.New("labels foo value must be a string, got integer instead"),
4869 Line: 5,
4870 },
4871 },
4872 },
4873 },
4874 },
4875 {
4876 input: []byte(`
4877groups:
4878- name: xxx
4879 labels:
4880 foo: bar
4881 bob: foo
4882 foo: bob
4883 rules: []
4884`),
4885 strict: true,
4886 output: parser.File{
4887 Groups: []parser.Group{
4888 {
4889 Name: "xxx",
4890 Error: parser.ParseError{
4891 Err: errors.New("duplicated labels key foo"),
4892 Line: 7,
4893 },
4894 },
4895 },
4896 },
4897 },
4898 {
4899 input: []byte(`
4900groups:
4901- name: xxx
4902 interval: 3m
4903 query_offset: 1s
4904 limit: 5
4905 labels:
4906 foo: bar
4907 rules: []
4908`),
4909 output: parser.File{
4910 IsRelaxed: true,
4911 Groups: []parser.Group{
4912 {
4913 Name: "xxx",
4914 Interval: time.Minute * 3,
4915 QueryOffset: time.Second,
4916 Limit: 5,
4917 Labels: &parser.YamlMap{
4918 Key: &parser.YamlNode{
4919 Value: "labels",
4920 },
4921 Items: []*parser.YamlKeyValue{
4922 {
4923 Key: &parser.YamlNode{
4924 Value: "foo",
4925 },
4926 Value: &parser.YamlNode{
4927 Value: "bar",
4928 },
4929 },
4930 },
4931 },
4932 },
4933 },
4934 },
4935 },
4936 {
4937 input: []byte(`
4938groups:
4939- name: xxx
4940 labels:
4941 - foo: bar
4942 rules: []
4943`),
4944 output: parser.File{
4945 IsRelaxed: true,
4946 Groups: []parser.Group{
4947 {
4948 Name: "xxx",
4949 Labels: &parser.YamlMap{
4950 Key: &parser.YamlNode{
4951 Value: "labels",
4952 },
4953 },
4954 },
4955 },
4956 },
4957 },
4958 {
4959 input: []byte(`
4960groups:
4961- name: xxx
4962 labels:
4963 foo: 1
4964 rules: []
4965`),
4966 output: parser.File{
4967 IsRelaxed: true,
4968 Groups: []parser.Group{
4969 {
4970 Name: "xxx",
4971 Labels: &parser.YamlMap{
4972 Key: &parser.YamlNode{
4973 Value: "labels",
4974 },
4975 Items: []*parser.YamlKeyValue{
4976 {
4977 Key: &parser.YamlNode{
4978 Value: "foo",
4979 },
4980 Value: &parser.YamlNode{
4981 Value: "1",
4982 },
4983 },
4984 },
4985 },
4986 },
4987 },
4988 },
4989 },
4990 {
4991 input: []byte(`
4992groups:
4993- name: xxx
4994 labels:
4995 foo: bar
4996 bob: foo
4997 foo: bob
4998 rules: []
4999`),
5000 output: parser.File{
5001 IsRelaxed: true,
5002 Groups: []parser.Group{
5003 {
5004 Name: "xxx",
5005 Labels: &parser.YamlMap{
5006 Key: &parser.YamlNode{
5007 Value: "labels",
5008 },
5009 Items: []*parser.YamlKeyValue{
5010 {
5011 Key: &parser.YamlNode{
5012 Value: "foo",
5013 },
5014 Value: &parser.YamlNode{
5015 Value: "bar",
5016 },
5017 },
5018 {
5019 Key: &parser.YamlNode{
5020 Value: "bob",
5021 },
5022 Value: &parser.YamlNode{
5023 Value: "foo",
5024 },
5025 },
5026 {
5027 Key: &parser.YamlNode{
5028 Value: "foo",
5029 },
5030 Value: &parser.YamlNode{
5031 Value: "bob",
5032 },
5033 },
5034 },
5035 },
5036 },
5037 },
5038 },
5039 },
5040 {
5041 input: []byte(`
5042- name: xxx
5043 interval: 3m
5044 query_offset: 1s
5045 limit: 5
5046 labels:
5047 foo: bar
5048 rules:
5049 - record: up:count
5050 expr: count(up)
5051`),
5052 output: parser.File{
5053 IsRelaxed: true,
5054 Groups: []parser.Group{
5055 {
5056 Name: "xxx",
5057 Interval: time.Minute * 3,
5058 QueryOffset: time.Second,
5059 Limit: 5,
5060 Labels: &parser.YamlMap{
5061 Key: &parser.YamlNode{
5062 Value: "labels",
5063 },
5064 Items: []*parser.YamlKeyValue{
5065 {
5066 Key: &parser.YamlNode{
5067 Value: "foo",
5068 },
5069 Value: &parser.YamlNode{
5070 Value: "bar",
5071 },
5072 },
5073 },
5074 },
5075 Rules: []parser.Rule{
5076 {
5077 Lines: diags.LineRange{First: 9, Last: 10},
5078 RecordingRule: &parser.RecordingRule{
5079 Record: parser.YamlNode{
5080 Value: "up:count",
5081 Pos: diags.PositionRanges{{Line: 9, FirstColumn: 13, LastColumn: 20}},
5082 },
5083 Expr: parser.PromQLExpr{
5084 Value: &parser.YamlNode{
5085 Value: "count(up)",
5086 Pos: diags.PositionRanges{{Line: 10, FirstColumn: 11, LastColumn: 19}},
5087 },
5088 },
5089 },
5090 },
5091 },
5092 },
5093 },
5094 },
5095 },
5096 {
5097 input: []byte(`
5098
5099- interval: 3m
5100 query_offset: 1s
5101 limit: 5
5102 labels:
5103 foo: bar
5104 rules:
5105 - record: up:count
5106 expr: count(up)
5107`),
5108 output: parser.File{
5109 IsRelaxed: true,
5110 Groups: []parser.Group{
5111 {
5112 Name: "",
5113 Rules: []parser.Rule{
5114 {
5115 Lines: diags.LineRange{First: 9, Last: 10},
5116 RecordingRule: &parser.RecordingRule{
5117 Record: parser.YamlNode{
5118 Value: "up:count",
5119 Pos: diags.PositionRanges{{Line: 9, FirstColumn: 13, LastColumn: 20}},
5120 },
5121 Expr: parser.PromQLExpr{
5122 Value: &parser.YamlNode{
5123 Value: "count(up)",
5124 Pos: diags.PositionRanges{{Line: 10, FirstColumn: 11, LastColumn: 19}},
5125 },
5126 },
5127 },
5128 },
5129 },
5130 },
5131 },
5132 },
5133 },
5134 }
5135
5136 alwaysEqual := cmp.Comparer(func(_, _ any) bool { return true })
5137 ignorePrometheusExpr := cmp.FilterValues(func(x, y any) bool {
5138 _, xe := x.(*parser.PromQLNode)
5139 _, ye := y.(*parser.PromQLNode)
5140 return xe || ye
5141 }, alwaysEqual)
5142
5143 cmpErrorText := cmp.Comparer(func(x, y any) bool {
5144 xe := x.(error)
5145 ye := y.(error)
5146 return xe.Error() == ye.Error()
5147 })
5148 sameErrorText := cmp.FilterValues(func(x, y any) bool {
5149 _, xe := x.(error)
5150 _, ye := y.(error)
5151 return xe && ye
5152 }, cmpErrorText)
5153
5154 for i, tc := range testCases {
5155 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
5156 t.Logf("\n--- Content ---%s--- END ---", tc.input)
5157
5158 s := bufio.NewScanner(bytes.NewReader(tc.input))
5159 for s.Scan() {
5160 tc.output.TotalLines++
5161 }
5162
5163 p := parser.NewParser(tc.strict, tc.schema, tc.names)
5164 file := p.Parse(bytes.NewReader(tc.input))
5165
5166 if diff := cmp.Diff(tc.output, file,
5167 ignorePrometheusExpr,
5168 sameErrorText,
5169 cmpopts.IgnoreFields(parser.YamlNode{}, "Pos"), // FIXME remove?
5170 ); diff != "" {
5171 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
5172 return
5173 }
5174 })
5175 }
5176}
5177
5178func BenchmarkParse(b *testing.B) {
5179 data, err := os.ReadFile("testrules.yml")
5180 require.NoError(b, err)
5181
5182 p := parser.NewParser(true, parser.PrometheusSchema, model.LegacyValidation)
5183 for b.Loop() {
5184 b.StopTimer()
5185 r := bytes.NewReader(data)
5186 b.StartTimer()
5187
5188 f := p.Parse(r)
5189
5190 b.StopTimer()
5191 require.Len(b, f.Groups, 69)
5192 require.NoError(b, f.Error.Err)
5193 require.Equal(b, 4669, f.TotalLines)
5194 b.StartTimer()
5195 }
5196}
5197