cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
5bfeffcd0e666ca2da0b27742d74ea360907f2fc

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

5231lines · 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 record: foo
2946 expr: foo
2947`),
2948 strict: true,
2949 output: parser.File{
2950 Groups: []parser.Group{
2951 {
2952 Name: "foo",
2953 Rules: []parser.Rule{
2954 {
2955 Lines: diags.LineRange{First: 8, Last: 10},
2956 Error: parser.ParseError{
2957 Line: 8,
2958 Err: errors.New("labels value must be a mapping, got binary data instead"),
2959 },
2960 },
2961 },
2962 },
2963 },
2964 Error: parser.ParseError{
2965 Line: 4,
2966 Err: errors.New("multi-document YAML files are not allowed"),
2967 },
2968 },
2969 },
2970 {
2971 input: []byte("[]"),
2972 strict: true,
2973 output: parser.File{
2974 Error: parser.ParseError{
2975 Err: errors.New("top level field must be a groups key, got list"),
2976 Line: 1,
2977 },
2978 },
2979 },
2980 {
2981 input: []byte("\n\n[]"),
2982 strict: true,
2983 output: parser.File{
2984 Error: parser.ParseError{
2985 Err: errors.New("top level field must be a groups key, got list"),
2986 Line: 3,
2987 },
2988 },
2989 },
2990 {
2991 input: []byte("groups: {}"),
2992 strict: true,
2993 output: parser.File{
2994 Error: parser.ParseError{
2995 Err: errors.New("groups value must be a list, got mapping"),
2996 Line: 1,
2997 },
2998 },
2999 },
3000 {
3001 input: []byte("groups: []"),
3002 strict: true,
3003 },
3004 {
3005 input: []byte("xgroups: {}"),
3006 strict: true,
3007 output: parser.File{
3008 Error: parser.ParseError{
3009 Err: errors.New("unexpected key xgroups"),
3010 Line: 1,
3011 },
3012 },
3013 },
3014 {
3015 input: []byte("\nbob\n"),
3016 strict: true,
3017 output: parser.File{
3018 Error: parser.ParseError{
3019 Err: errors.New("top level field must be a groups key, got string"),
3020 Line: 2,
3021 },
3022 },
3023 },
3024 {
3025 input: []byte(`groups: []
3026
3027rules: []
3028`),
3029 strict: true,
3030 output: parser.File{
3031 Error: parser.ParseError{
3032 Err: errors.New("unexpected key rules"),
3033 Line: 3,
3034 },
3035 },
3036 },
3037 {
3038 input: []byte(`
3039groups:
3040- name: foo
3041 rules: []
3042`),
3043 strict: true,
3044 output: parser.File{
3045 Groups: []parser.Group{
3046 {
3047 Name: "foo",
3048 },
3049 },
3050 },
3051 },
3052 {
3053 input: []byte(`
3054groups:
3055- name:
3056 rules: []
3057`),
3058 strict: true,
3059 output: parser.File{
3060 Groups: []parser.Group{
3061 {
3062 Error: parser.ParseError{
3063 Err: errors.New("group name must be a string, got null"),
3064 Line: 3,
3065 },
3066 },
3067 },
3068 },
3069 },
3070 {
3071 input: []byte(`
3072groups:
3073- name: foo
3074 rules:
3075 - record: foo
3076 expr: sum(up)
3077 labels:
3078 job: foo
3079`),
3080 strict: true,
3081 output: parser.File{
3082 Groups: []parser.Group{
3083 {
3084 Name: "foo",
3085 Rules: []parser.Rule{
3086 {
3087 Lines: diags.LineRange{First: 5, Last: 8},
3088 RecordingRule: &parser.RecordingRule{
3089 Record: parser.YamlNode{
3090 Value: "foo",
3091 Pos: diags.PositionRanges{
3092 {Line: 5, FirstColumn: 13, LastColumn: 15},
3093 },
3094 },
3095 Expr: parser.PromQLExpr{
3096 Value: &parser.YamlNode{
3097 Value: "sum(up)",
3098 Pos: diags.PositionRanges{
3099 {Line: 6, FirstColumn: 11, LastColumn: 17},
3100 },
3101 },
3102 },
3103 Labels: &parser.YamlMap{
3104 Key: &parser.YamlNode{
3105 Value: "labels",
3106 },
3107 Items: []*parser.YamlKeyValue{
3108 {
3109 Key: &parser.YamlNode{
3110 Value: "job",
3111 },
3112 Value: &parser.YamlNode{
3113 Value: "foo",
3114 },
3115 },
3116 },
3117 },
3118 },
3119 },
3120 },
3121 },
3122 },
3123 },
3124 },
3125 {
3126 input: []byte(`
3127groups:
3128- name: foo
3129 rules:
3130 - record: foo
3131 expr: sum(up)
3132 xxx: 1
3133 labels:
3134 job: foo
3135`),
3136 strict: true,
3137 output: parser.File{
3138 Groups: []parser.Group{
3139 {
3140 Name: "foo",
3141 Rules: []parser.Rule{
3142 {
3143 Error: parser.ParseError{
3144 Err: errors.New("invalid rule key xxx"),
3145 Line: 7,
3146 },
3147 },
3148 },
3149 },
3150 },
3151 },
3152 },
3153 {
3154 input: []byte(`
3155groups:
3156- name: foo
3157 rules:
3158 record: foo
3159 expr: sum(up)
3160 xxx: 1
3161 labels:
3162 job: foo
3163`),
3164 strict: true,
3165 output: parser.File{
3166 Groups: []parser.Group{
3167 {
3168 Name: "foo",
3169 Error: parser.ParseError{
3170 Err: errors.New("rules must be a list, got mapping"),
3171 Line: 4,
3172 },
3173 },
3174 },
3175 },
3176 },
3177 {
3178 input: []byte(`
3179groups:
3180- name: foo
3181 rules:
3182 - record: foo
3183 expr: sum(up)
3184 labels:
3185 job:
3186 foo: bar
3187`),
3188 strict: true,
3189 output: parser.File{
3190 Groups: []parser.Group{
3191 {
3192 Name: "foo",
3193 Rules: []parser.Rule{
3194 {
3195 Lines: diags.LineRange{First: 5, Last: 9},
3196 Error: parser.ParseError{
3197 Line: 9,
3198 Err: errors.New("labels job value must be a string, got mapping instead"),
3199 },
3200 },
3201 },
3202 },
3203 },
3204 },
3205 },
3206 {
3207 input: []byte(`
3208groups:
3209- name: foo
3210 rules:
3211 - record: foo
3212 expr:
3213 sum: sum(up)
3214`),
3215 strict: true,
3216 output: parser.File{
3217 Groups: []parser.Group{
3218 {
3219 Name: "foo",
3220 Rules: []parser.Rule{
3221 {
3222 Lines: diags.LineRange{First: 5, Last: 7},
3223 Error: parser.ParseError{
3224 Line: 7,
3225 Err: errors.New("expr value must be a string, got mapping instead"),
3226 },
3227 },
3228 },
3229 },
3230 },
3231 },
3232 },
3233 {
3234 input: []byte(`
3235groups:
3236- name: foo
3237 rules: []
3238- name: foo
3239 rules: []
3240`),
3241 strict: true,
3242 output: parser.File{
3243 Error: parser.ParseError{
3244 Err: errors.New("duplicated group name"),
3245 Line: 5,
3246 },
3247 },
3248 },
3249 {
3250 input: []byte(`
3251groups:
3252- name: foo
3253 rules:
3254 - record: foo
3255 expr: sum(up)
3256 labels:
3257 foo: bob
3258 foo: bar
3259`),
3260 strict: true,
3261 output: parser.File{
3262 Groups: []parser.Group{
3263 {
3264 Name: "foo",
3265 Rules: []parser.Rule{
3266 {
3267 Lines: diags.LineRange{First: 8, Last: 9},
3268 Error: parser.ParseError{
3269 Line: 9,
3270 Err: errors.New("duplicated labels key foo"),
3271 },
3272 },
3273 },
3274 },
3275 },
3276 },
3277 },
3278 {
3279 input: []byte(`
3280groups:
3281- name: v2
3282 rules:
3283 - record: up:count
3284 expr: count(up)
3285 expr: sum(up)`),
3286 strict: true,
3287 output: parser.File{
3288 Groups: []parser.Group{
3289 {
3290 Name: "v2",
3291 Rules: []parser.Rule{
3292 {
3293 Lines: diags.LineRange{First: 5, Last: 7},
3294 Error: parser.ParseError{
3295 Line: 7,
3296 Err: errors.New("duplicated expr key"),
3297 },
3298 },
3299 },
3300 },
3301 },
3302 },
3303 },
3304 {
3305 input: []byte(`
3306groups:
3307- name: v2
3308 rules:
3309 - record: up:count
3310 expr: count(up)
3311bogus: 1
3312`),
3313 strict: true,
3314 output: parser.File{
3315 Error: parser.ParseError{
3316 Err: errors.New("unexpected key bogus"),
3317 Line: 7,
3318 },
3319 },
3320 },
3321 {
3322 input: []byte(`
3323groups:
3324- name: v2
3325 rules:
3326 - record: up:count
3327 expr: count(up)
3328 bogus: 1
3329`),
3330 strict: true,
3331 output: parser.File{
3332 Groups: []parser.Group{
3333 {
3334 Name: "v2",
3335 Rules: []parser.Rule{
3336 {
3337 Error: parser.ParseError{
3338 Err: errors.New("invalid rule key bogus"),
3339 Line: 7,
3340 },
3341 },
3342 },
3343 },
3344 },
3345 },
3346 },
3347 {
3348 input: []byte(`
3349groups:
3350- name: v2
3351 rules:
3352 - alert: up:count
3353 for: 5m
3354 keep_firing_for: 5m
3355 expr: count(up)
3356 labels: {}
3357 annotations: {}
3358 bogus: 1
3359`),
3360 strict: true,
3361 output: parser.File{
3362 Groups: []parser.Group{
3363 {
3364 Name: "v2",
3365 Rules: []parser.Rule{
3366 {
3367 Error: parser.ParseError{
3368 Err: errors.New("invalid rule key bogus"),
3369 Line: 11,
3370 },
3371 },
3372 },
3373 },
3374 },
3375 },
3376 },
3377 {
3378 input: []byte(`
3379groups:
3380
3381- name: CloudflareKafkaZookeeperExporter
3382
3383 rules:
3384`),
3385 strict: true,
3386 output: parser.File{
3387 Groups: []parser.Group{
3388 {
3389 Name: "CloudflareKafkaZookeeperExporter",
3390 },
3391 },
3392 },
3393 },
3394 {
3395 input: []byte(`
3396groups:
3397- name: foo
3398 rules:
3399 expr: 1
3400`),
3401 strict: true,
3402 output: parser.File{
3403 Groups: []parser.Group{
3404 {
3405 Name: "foo",
3406 Error: parser.ParseError{
3407 Err: errors.New("rules must be a list, got mapping"),
3408 Line: 4,
3409 },
3410 },
3411 },
3412 },
3413 },
3414 {
3415 input: []byte(`
3416groups:
3417- name: foo
3418 rules:
3419 - expr: 1
3420`),
3421 strict: true,
3422 output: parser.File{
3423 Groups: []parser.Group{
3424 {
3425 Name: "foo",
3426 Rules: []parser.Rule{
3427 {
3428 Lines: diags.LineRange{First: 5, Last: 5},
3429 Error: parser.ParseError{
3430 Line: 5,
3431 Err: errors.New("incomplete rule, no alert or record key"),
3432 },
3433 },
3434 },
3435 },
3436 },
3437 },
3438 },
3439 {
3440 input: []byte(`
3441groups:
3442- name: foo
3443 rules:
3444 - expr: null
3445`),
3446 strict: true,
3447 output: parser.File{
3448 Groups: []parser.Group{
3449 {
3450 Name: "foo",
3451 Rules: []parser.Rule{
3452 {
3453 Lines: diags.LineRange{First: 5, Last: 5},
3454 Error: parser.ParseError{
3455 Line: 5,
3456 Err: errors.New("incomplete rule, no alert or record key"),
3457 },
3458 },
3459 },
3460 },
3461 },
3462 },
3463 },
3464 {
3465 input: []byte(`
3466groups:
3467- name: foo
3468 rules:
3469 - 1: null
3470`),
3471 strict: true,
3472 output: parser.File{
3473 Groups: []parser.Group{
3474 {
3475 Name: "foo",
3476 Rules: []parser.Rule{
3477 {
3478 Error: parser.ParseError{
3479 Err: errors.New("invalid rule key 1"),
3480 Line: 5,
3481 },
3482 },
3483 },
3484 },
3485 },
3486 },
3487 },
3488 {
3489 input: []byte(`
3490groups:
3491- name: foo
3492 rules:
3493 - true: !!binary "SGVsbG8sIFdvcmxkIQ=="
3494`),
3495 strict: true,
3496 output: parser.File{
3497 Groups: []parser.Group{
3498 {
3499 Name: "foo",
3500 Rules: []parser.Rule{
3501 {
3502 Error: parser.ParseError{
3503 Err: errors.New("invalid rule key true"),
3504 Line: 5,
3505 },
3506 },
3507 },
3508 },
3509 },
3510 },
3511 },
3512 {
3513 input: []byte(`
3514groups:
3515- name: foo
3516 rules:
3517 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3518`),
3519 strict: true,
3520 output: parser.File{
3521 Groups: []parser.Group{
3522 {
3523 Name: "foo",
3524 Rules: []parser.Rule{
3525 {
3526 Lines: diags.LineRange{First: 5, Last: 5},
3527 Error: parser.ParseError{
3528 Line: 5,
3529 Err: errors.New("incomplete rule, no alert or record key"),
3530 },
3531 },
3532 },
3533 },
3534 },
3535 },
3536 },
3537 {
3538 input: []byte(`
3539groups:
3540- name: foo
3541 rules:
3542 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3543 record: foo
3544`),
3545 strict: true,
3546 output: parser.File{
3547 Groups: []parser.Group{
3548 {
3549 Name: "foo",
3550 Rules: []parser.Rule{
3551 {
3552 Lines: diags.LineRange{First: 5, Last: 6},
3553 Error: parser.ParseError{
3554 Line: 5,
3555 Err: errors.New("expr value must be a string, got binary data instead"),
3556 },
3557 },
3558 },
3559 },
3560 },
3561 },
3562 },
3563 {
3564 input: []byte(`
3565groups:
3566- name: foo
3567 rules:
3568 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3569 record: foo
3570 expr: foo
3571`),
3572 strict: true,
3573 output: parser.File{
3574 Groups: []parser.Group{
3575 {
3576 Name: "foo",
3577 Rules: []parser.Rule{
3578 {
3579 Lines: diags.LineRange{First: 5, Last: 7},
3580 Error: parser.ParseError{
3581 Line: 5,
3582 Err: errors.New("labels value must be a mapping, got binary data instead"),
3583 },
3584 },
3585 },
3586 },
3587 },
3588 },
3589 },
3590 {
3591 input: []byte(`
3592---
3593groups:
3594- name: foo
3595 rules:
3596 - record: foo
3597 expr: bar
3598---
3599groups:
3600- name: foo
3601 rules:
3602 - record: foo
3603 expr: bar
3604`),
3605 strict: true,
3606 output: parser.File{
3607 Error: parser.ParseError{
3608 Line: 8,
3609 Err: errors.New("multi-document YAML files are not allowed"),
3610 },
3611 Groups: []parser.Group{
3612 {
3613 Name: "foo",
3614 Rules: []parser.Rule{
3615 {
3616 Lines: diags.LineRange{First: 6, Last: 7},
3617 RecordingRule: &parser.RecordingRule{
3618 Record: parser.YamlNode{
3619 Value: "foo",
3620 Pos: diags.PositionRanges{
3621 {Line: 6, FirstColumn: 15, LastColumn: 17},
3622 },
3623 },
3624 Expr: parser.PromQLExpr{
3625 Value: &parser.YamlNode{
3626 Value: "bar",
3627 Pos: diags.PositionRanges{
3628 {Line: 7, FirstColumn: 13, LastColumn: 15},
3629 },
3630 },
3631 },
3632 },
3633 },
3634 },
3635 },
3636 {
3637 Name: "foo",
3638 Rules: []parser.Rule{
3639 {
3640 Lines: diags.LineRange{First: 12, Last: 13},
3641 RecordingRule: &parser.RecordingRule{
3642 Record: parser.YamlNode{
3643 Value: "foo",
3644 Pos: diags.PositionRanges{
3645 {Line: 12, FirstColumn: 15, LastColumn: 17},
3646 },
3647 },
3648 Expr: parser.PromQLExpr{
3649 Value: &parser.YamlNode{
3650 Value: "bar",
3651 Pos: diags.PositionRanges{
3652 {Line: 13, FirstColumn: 13, LastColumn: 15},
3653 },
3654 },
3655 },
3656 },
3657 },
3658 },
3659 },
3660 },
3661 },
3662 },
3663 {
3664 input: []byte(`
3665groups:
3666- name: foo
3667 rules:
3668 - record: foo
3669 expr: foo
3670 expr: foo
3671`),
3672 strict: true,
3673 output: parser.File{
3674 Groups: []parser.Group{
3675 {
3676 Name: "foo",
3677 Rules: []parser.Rule{
3678 {
3679 Lines: diags.LineRange{First: 5, Last: 7},
3680 Error: parser.ParseError{
3681 Line: 7,
3682 Err: errors.New("duplicated expr key"),
3683 },
3684 },
3685 },
3686 },
3687 },
3688 },
3689 },
3690 {
3691 input: []byte(`
3692groups:
3693- name: foo
3694 rules:
3695 - record: foo
3696 keep_firing_for: 1m
3697 keep_firing_for: 2m
3698`),
3699 strict: true,
3700 output: parser.File{
3701 Groups: []parser.Group{
3702 {
3703 Name: "foo",
3704 Rules: []parser.Rule{
3705 {
3706 Lines: diags.LineRange{First: 5, Last: 7},
3707 Error: parser.ParseError{
3708 Line: 7,
3709 Err: errors.New("duplicated keep_firing_for key"),
3710 },
3711 },
3712 },
3713 },
3714 },
3715 },
3716 },
3717 {
3718 input: []byte(`
3719groups:
3720- name: foo
3721 rules:
3722 - record: foo
3723 keep_firing_for: 1m
3724 record: 2m
3725`),
3726 strict: true,
3727 output: parser.File{
3728 Groups: []parser.Group{
3729 {
3730 Name: "foo",
3731 Rules: []parser.Rule{
3732 {
3733 Lines: diags.LineRange{First: 5, Last: 7},
3734 Error: parser.ParseError{
3735 Line: 7,
3736 Err: errors.New("duplicated record key"),
3737 },
3738 },
3739 },
3740 },
3741 },
3742 },
3743 },
3744 {
3745 input: []byte(`
3746groups:
3747- name: foo
3748 rules:
3749 - []
3750`),
3751 strict: true,
3752 output: parser.File{
3753 Groups: []parser.Group{
3754 {
3755 Name: "foo",
3756 Rules: []parser.Rule{
3757 {
3758 Error: parser.ParseError{
3759 Err: errors.New("rule definion must be a mapping, got list"),
3760 Line: 5,
3761 },
3762 },
3763 },
3764 },
3765 },
3766 },
3767 },
3768 {
3769 input: []byte(`
37701: 0
3771`),
3772 strict: true,
3773 output: parser.File{
3774 Error: parser.ParseError{
3775 Err: errors.New("groups key must be a string, got a integer"),
3776 Line: 2,
3777 },
3778 },
3779 },
3780 {
3781 input: []byte(`
3782true: 0
3783`),
3784 strict: true,
3785 output: parser.File{
3786 Error: parser.ParseError{
3787 Err: errors.New("groups key must be a string, got a bool"),
3788 Line: 2,
3789 },
3790 },
3791 },
3792 {
3793 input: []byte(`
3794groups: !!binary "SGVsbG8sIFdvcmxkIQ=="
3795`),
3796 strict: true,
3797 output: parser.File{
3798 Error: parser.ParseError{
3799 Err: errors.New("groups value must be a list, got binary data"),
3800 Line: 2,
3801 },
3802 },
3803 },
3804 {
3805 input: []byte(`
3806groups:
3807 - true: null"
3808`),
3809 strict: true,
3810 output: parser.File{
3811 Groups: []parser.Group{
3812 {
3813 Error: parser.ParseError{
3814 Err: errors.New("invalid group key true"),
3815 Line: 3,
3816 },
3817 },
3818 },
3819 },
3820 },
3821 {
3822 input: []byte(`
3823!!!binary "groups": true"
3824`),
3825 strict: true,
3826 output: parser.File{
3827 Error: parser.ParseError{
3828 Err: errors.New("groups key must be a string, got a binary"),
3829 Line: 2,
3830 },
3831 },
3832 },
3833 {
3834 input: []byte("[]"),
3835 strict: true,
3836 output: parser.File{
3837 Error: parser.ParseError{
3838 Err: errors.New("top level field must be a groups key, got list"),
3839 Line: 1,
3840 },
3841 },
3842 },
3843 {
3844 input: []byte(`
3845groups:
3846 - true
3847`),
3848 strict: true,
3849 output: parser.File{
3850 Groups: []parser.Group{
3851 {
3852 Error: parser.ParseError{
3853 Err: errors.New("group must be a mapping, got bool"),
3854 Line: 3,
3855 },
3856 },
3857 },
3858 },
3859 },
3860 {
3861 input: []byte(`
3862groups:
3863 - name:
3864 rules: []
3865`),
3866 strict: true,
3867 output: parser.File{
3868 Groups: []parser.Group{
3869 {
3870 Error: parser.ParseError{
3871 Err: errors.New("group name must be a string, got null"),
3872 Line: 3,
3873 },
3874 },
3875 },
3876 },
3877 },
3878 {
3879 input: []byte(`
3880groups:
3881 - name: ""
3882 rules: []
3883`),
3884 strict: true,
3885 output: parser.File{
3886 Groups: []parser.Group{
3887 {
3888 Error: parser.ParseError{
3889 Err: errors.New("group name cannot be empty"),
3890 Line: 3,
3891 },
3892 },
3893 },
3894 },
3895 },
3896 {
3897 input: []byte(`
3898groups:
3899 - name: 1
3900 rules: []
3901`),
3902 strict: true,
3903 output: parser.File{
3904 Groups: []parser.Group{
3905 {
3906 Error: parser.ParseError{
3907 Err: errors.New("group name must be a string, got integer"),
3908 Line: 3,
3909 },
3910 },
3911 },
3912 },
3913 },
3914 {
3915 input: []byte(`
3916groups:
3917 - name: foo
3918 interval: 1
3919 rules: []
3920`),
3921 strict: true,
3922 output: parser.File{
3923 Groups: []parser.Group{
3924 {
3925 Name: "foo",
3926 Error: parser.ParseError{
3927 Err: errors.New("group interval must be a string, got integer"),
3928 Line: 4,
3929 },
3930 },
3931 },
3932 },
3933 },
3934 {
3935 input: []byte(`
3936groups:
3937 - name: foo
3938 interval: xxx
3939 rules: []
3940`),
3941 strict: true,
3942 output: parser.File{
3943 Groups: []parser.Group{
3944 {
3945 Name: "foo",
3946 Error: parser.ParseError{
3947 Err: errors.New("invalid interval value: not a valid duration string: \"xxx\""),
3948 Line: 4,
3949 },
3950 },
3951 },
3952 },
3953 },
3954 {
3955 input: []byte(`
3956groups:
3957 - name: foo
3958 query_offset: 1
3959 rules: []
3960`),
3961 strict: true,
3962 output: parser.File{
3963 Groups: []parser.Group{
3964 {
3965 Name: "foo",
3966 Error: parser.ParseError{
3967 Err: errors.New("group query_offset must be a string, got integer"),
3968 Line: 4,
3969 },
3970 },
3971 },
3972 },
3973 },
3974 {
3975 input: []byte(`
3976groups:
3977 - name: foo
3978 query_offset: xxx
3979 rules: []
3980`),
3981 strict: true,
3982 output: parser.File{
3983 Groups: []parser.Group{
3984 {
3985 Name: "foo",
3986 Error: parser.ParseError{
3987 Err: errors.New("invalid query_offset value: not a valid duration string: \"xxx\""),
3988 Line: 4,
3989 },
3990 },
3991 },
3992 },
3993 },
3994 {
3995 input: []byte(`
3996groups:
3997 - name: foo
3998 query_offset: 1m
3999 limit: abc
4000 rules: []
4001`),
4002 strict: true,
4003 output: parser.File{
4004 Groups: []parser.Group{
4005 {
4006 Name: "foo",
4007 QueryOffset: time.Minute,
4008 Error: parser.ParseError{
4009 Err: errors.New("group limit must be a integer, got string"),
4010 Line: 5,
4011 },
4012 },
4013 },
4014 },
4015 },
4016 {
4017 input: []byte(`
4018groups:
4019- name: v2
4020 rules:
4021 - alert: up:count
4022 for: 5m &timeout
4023 keep_firing_for: **timeout
4024 expr: count(up)
4025 labels: {}
4026 annotations: {}
4027 bogus: 1
4028`),
4029 strict: true,
4030 output: parser.File{
4031 Error: parser.ParseError{
4032 Err: errors.New("did not find expected alphabetic or numeric character"),
4033 Line: 7,
4034 },
4035 },
4036 },
4037 {
4038 input: []byte(`
4039groups:
4040- name: v2
4041 rules:
4042 - alert: up:count
4043 for: &for 1
4044 keep_firing_for: *for
4045 expr: count(up)
4046 labels: {}
4047 annotations: {}
4048`),
4049 strict: true,
4050 output: parser.File{
4051 Groups: []parser.Group{
4052 {
4053 Name: "v2",
4054 Rules: []parser.Rule{
4055 {
4056 Lines: diags.LineRange{First: 5, Last: 10},
4057 Error: parser.ParseError{
4058 Line: 6,
4059 Err: errors.New("for value must be a string, got integer instead"),
4060 },
4061 },
4062 },
4063 },
4064 },
4065 },
4066 },
4067 {
4068 input: []byte(`
4069groups:
4070- name: v2
4071 limit: &for 1
4072 rules:
4073 - alert: up:count
4074 keep_firing_for: *for
4075 expr: count(up)
4076 labels: {}
4077 annotations: {}
4078`),
4079 strict: true,
4080 output: parser.File{
4081 Groups: []parser.Group{
4082 {
4083 Name: "v2",
4084 Limit: 1,
4085 Rules: []parser.Rule{
4086 {
4087 Lines: diags.LineRange{First: 6, Last: 10},
4088 Error: parser.ParseError{
4089 Line: 7,
4090 Err: errors.New("keep_firing_for value must be a string, got integer instead"),
4091 },
4092 },
4093 },
4094 },
4095 },
4096 },
4097 },
4098 {
4099 input: []byte(`
4100groups:
4101- name: "{{ source }}"
4102 rules:
4103# pint ignore/begin
4104
4105# pint ignore/end
4106`),
4107 strict: true,
4108 output: parser.File{
4109 Groups: []parser.Group{
4110 {
4111 Name: "{{ source }}",
4112 },
4113 },
4114 },
4115 },
4116 {
4117 input: []byte(`
4118groups:
4119- name: foo
4120 rules:
4121 - record: foo
4122 expr: |
4123 {"up"}
4124`),
4125 output: parser.File{
4126 IsRelaxed: true,
4127 Groups: []parser.Group{
4128 {
4129 Name: "foo",
4130 Rules: []parser.Rule{
4131 {
4132 Lines: diags.LineRange{First: 5, Last: 7},
4133 RecordingRule: &parser.RecordingRule{
4134 Record: parser.YamlNode{
4135 Value: "foo",
4136 Pos: diags.PositionRanges{
4137 {Line: 5, FirstColumn: 13, LastColumn: 15},
4138 },
4139 },
4140 Expr: parser.PromQLExpr{
4141 Value: &parser.YamlNode{
4142 Value: "{\"up\"}\n",
4143 Pos: diags.PositionRanges{
4144 {Line: 7, FirstColumn: 7, LastColumn: 12},
4145 },
4146 },
4147 },
4148 },
4149 },
4150 },
4151 },
4152 },
4153 },
4154 },
4155 {
4156 input: []byte(`
4157groups:
4158- name: foo
4159 rules:
4160 - record: foo
4161 expr: |
4162 {'up'}
4163`),
4164 output: parser.File{
4165 IsRelaxed: true,
4166 Groups: []parser.Group{
4167 {
4168 Name: "foo",
4169 Rules: []parser.Rule{
4170 {
4171 Lines: diags.LineRange{First: 5, Last: 7},
4172 RecordingRule: &parser.RecordingRule{
4173 Record: parser.YamlNode{
4174 Value: "foo",
4175 Pos: diags.PositionRanges{
4176 {Line: 5, FirstColumn: 13, LastColumn: 15},
4177 },
4178 },
4179 Expr: parser.PromQLExpr{
4180 Value: &parser.YamlNode{
4181 Value: "{'up'}\n",
4182 Pos: diags.PositionRanges{
4183 {Line: 7, FirstColumn: 7, LastColumn: 12},
4184 },
4185 },
4186 },
4187 },
4188 },
4189 },
4190 },
4191 },
4192 },
4193 },
4194 {
4195 input: []byte(`
4196groups:
4197- name: foo
4198 rules:
4199 - record: foo
4200 expr: |
4201 {'up' == 1}
4202`),
4203 output: parser.File{
4204 IsRelaxed: true,
4205 Groups: []parser.Group{
4206 {
4207 Name: "foo",
4208 Rules: []parser.Rule{
4209 {
4210 Lines: diags.LineRange{First: 5, Last: 7},
4211 RecordingRule: &parser.RecordingRule{
4212 Record: parser.YamlNode{
4213 Value: "foo",
4214 Pos: diags.PositionRanges{
4215 {Line: 5, FirstColumn: 13, LastColumn: 15},
4216 },
4217 },
4218 Expr: parser.PromQLExpr{
4219 Value: &parser.YamlNode{
4220 Value: "{'up' == 1}\n",
4221 Pos: diags.PositionRanges{
4222 {Line: 7, FirstColumn: 7, LastColumn: 17},
4223 },
4224 },
4225 SyntaxError: errors.New(`1:8: parse error: unexpected "=" in label matching, expected string`),
4226 },
4227 },
4228 },
4229 },
4230 },
4231 },
4232 },
4233 },
4234 {
4235 input: []byte(`
4236groups:
4237- name: mygroup
4238 partial_response_strategy: bob
4239 rules:
4240 - record: up:count
4241 expr: count(up)
4242`),
4243 strict: true,
4244 output: parser.File{
4245 Groups: []parser.Group{
4246 {
4247 Name: "mygroup",
4248 Error: parser.ParseError{
4249 Err: errors.New("partial_response_strategy is only valid when parser is configured to use the Thanos rule schema"),
4250 Line: 4,
4251 },
4252 },
4253 },
4254 },
4255 },
4256 {
4257 input: []byte(`
4258groups:
4259- name: mygroup
4260 partial_response_strategy: warn
4261 rules:
4262 - record: up:count
4263 expr: count(up)
4264`),
4265 strict: true,
4266 schema: parser.ThanosSchema,
4267 output: parser.File{
4268 Groups: []parser.Group{
4269 {
4270 Name: "mygroup",
4271 Rules: []parser.Rule{
4272 {
4273 Lines: diags.LineRange{First: 6, Last: 7},
4274 RecordingRule: &parser.RecordingRule{
4275 Record: parser.YamlNode{
4276 Value: "up:count",
4277 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4278 },
4279 Expr: parser.PromQLExpr{
4280 Value: &parser.YamlNode{
4281 Value: "count(up)",
4282 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4283 },
4284 },
4285 },
4286 },
4287 },
4288 },
4289 },
4290 },
4291 },
4292 {
4293 input: []byte(`
4294groups:
4295- name: mygroup
4296 partial_response_strategy: abort
4297 rules:
4298 - record: up:count
4299 expr: count(up)
4300`),
4301 strict: true,
4302 schema: parser.ThanosSchema,
4303 output: parser.File{
4304 Groups: []parser.Group{
4305 {
4306 Name: "mygroup",
4307 Rules: []parser.Rule{
4308 {
4309 Lines: diags.LineRange{First: 6, Last: 7},
4310 RecordingRule: &parser.RecordingRule{
4311 Record: parser.YamlNode{
4312 Value: "up:count",
4313 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4314 },
4315 Expr: parser.PromQLExpr{
4316 Value: &parser.YamlNode{
4317 Value: "count(up)",
4318 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4319 },
4320 },
4321 },
4322 },
4323 },
4324 },
4325 },
4326 },
4327 },
4328 {
4329 input: []byte(`
4330groups:
4331- name: mygroup
4332 partial_response_strategy: abort
4333 rules:
4334 - record: up:count
4335 expr: count(up)
4336`),
4337 strict: false,
4338 schema: parser.PrometheusSchema,
4339 output: parser.File{
4340 IsRelaxed: true,
4341 Groups: []parser.Group{
4342 {
4343 Name: "mygroup",
4344 Rules: []parser.Rule{
4345 {
4346 Lines: diags.LineRange{First: 6, Last: 7},
4347 RecordingRule: &parser.RecordingRule{
4348 Record: parser.YamlNode{
4349 Value: "up:count",
4350 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
4351 },
4352 Expr: parser.PromQLExpr{
4353 Value: &parser.YamlNode{
4354 Value: "count(up)",
4355 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
4356 },
4357 },
4358 },
4359 },
4360 },
4361 },
4362 },
4363 },
4364 },
4365 {
4366 input: []byte(`
4367groups:
4368- name: mygroup
4369 partial_response_strategy: bob
4370 rules:
4371 - record: up:count
4372 expr: count(up)
4373`),
4374 strict: true,
4375 schema: parser.ThanosSchema,
4376 output: parser.File{
4377 Groups: []parser.Group{
4378 {
4379 Name: "mygroup",
4380 Error: parser.ParseError{
4381 Err: errors.New("invalid partial_response_strategy value: bob"),
4382 Line: 4,
4383 },
4384 },
4385 },
4386 },
4387 },
4388 {
4389 input: []byte(`
4390groups:
4391- name: mygroup
4392 partial_response_strategy: 1
4393 rules:
4394 - record: up:count
4395 expr: count(up)
4396`),
4397 strict: true,
4398 schema: parser.ThanosSchema,
4399 output: parser.File{
4400 Groups: []parser.Group{
4401 {
4402 Name: "mygroup",
4403 Error: parser.ParseError{
4404 Err: errors.New("partial_response_strategy must be a string, got integer"),
4405 Line: 4,
4406 },
4407 },
4408 },
4409 },
4410 },
4411 {
4412 input: []byte(`
4413- alert: Multi Line
4414 expr: foo
4415 AND ON (instance)
4416 bar
4417`),
4418 strict: false,
4419 schema: parser.PrometheusSchema,
4420 output: parser.File{
4421 IsRelaxed: true,
4422 Groups: []parser.Group{
4423 {
4424 Rules: []parser.Rule{
4425 {
4426 Lines: diags.LineRange{First: 2, Last: 5},
4427 AlertingRule: &parser.AlertingRule{
4428 Alert: parser.YamlNode{
4429 Value: "Multi Line",
4430 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 10, LastColumn: 19}},
4431 },
4432 Expr: parser.PromQLExpr{
4433 Value: &parser.YamlNode{
4434 Value: "foo AND ON (instance) bar",
4435 Pos: diags.PositionRanges{
4436 {Line: 3, FirstColumn: 9, LastColumn: 12},
4437 {Line: 4, FirstColumn: 11, LastColumn: 28},
4438 {Line: 5, FirstColumn: 11, LastColumn: 13},
4439 },
4440 },
4441 },
4442 },
4443 },
4444 },
4445 },
4446 },
4447 },
4448 },
4449 {
4450 input: []byte(`
4451 - alert: FooBar
4452 expr: >-
4453 count(
4454 foo
4455 or
4456 bar
4457 ) > 0
4458`),
4459 strict: false,
4460 schema: parser.PrometheusSchema,
4461 output: parser.File{
4462 IsRelaxed: true,
4463 Groups: []parser.Group{
4464 {
4465 Rules: []parser.Rule{
4466 {
4467 Lines: diags.LineRange{First: 2, Last: 8},
4468 AlertingRule: &parser.AlertingRule{
4469 Alert: parser.YamlNode{
4470 Value: "FooBar",
4471 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4472 },
4473 Expr: parser.PromQLExpr{
4474 Value: &parser.YamlNode{
4475 Value: "count(\n foo\n or\n bar\n) > 0",
4476 Pos: diags.PositionRanges{
4477 {Line: 4, FirstColumn: 7, LastColumn: 13},
4478 {Line: 5, FirstColumn: 7, LastColumn: 12},
4479 {Line: 6, FirstColumn: 7, LastColumn: 11},
4480 {Line: 7, FirstColumn: 7, LastColumn: 12},
4481 {Line: 8, FirstColumn: 7, LastColumn: 11},
4482 },
4483 },
4484 },
4485 },
4486 },
4487 },
4488 },
4489 },
4490 },
4491 },
4492 {
4493 input: []byte(`
4494 - alert: FooBar
4495 expr: >-
4496 aaaaaaaaaaaaaaaaaaaaaaaa
4497 AND ON (colo_id) bbbbbbbbbbb
4498 > 2
4499 for: 1m
4500`),
4501 strict: false,
4502 schema: parser.PrometheusSchema,
4503 output: parser.File{
4504 IsRelaxed: true,
4505 Groups: []parser.Group{
4506 {
4507 Rules: []parser.Rule{
4508 {
4509 Lines: diags.LineRange{First: 2, Last: 7},
4510 AlertingRule: &parser.AlertingRule{
4511 Alert: parser.YamlNode{
4512 Value: "FooBar",
4513 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4514 },
4515 Expr: parser.PromQLExpr{
4516 Value: &parser.YamlNode{
4517 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
4518 Pos: diags.PositionRanges{
4519 {Line: 4, FirstColumn: 7, LastColumn: 31},
4520 {Line: 5, FirstColumn: 7, LastColumn: 35},
4521 {Line: 6, FirstColumn: 7, LastColumn: 9},
4522 },
4523 },
4524 },
4525 For: &parser.YamlNode{
4526 Value: "1m",
4527 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 10, LastColumn: 11}},
4528 },
4529 },
4530 },
4531 },
4532 },
4533 },
4534 },
4535 },
4536 {
4537 input: []byte(`
4538 - alert: FooBar
4539 expr: 'aaaaaaaaaaaaaaaaaaaaaaaa
4540 AND ON (colo_id) bbbbbbbbbbb
4541 > 2'
4542 for: 1m
4543`),
4544 strict: false,
4545 schema: parser.PrometheusSchema,
4546 output: parser.File{
4547 IsRelaxed: true,
4548 Groups: []parser.Group{
4549 {
4550 Rules: []parser.Rule{
4551 {
4552 Lines: diags.LineRange{First: 2, Last: 6},
4553 AlertingRule: &parser.AlertingRule{
4554 Alert: parser.YamlNode{
4555 Value: "FooBar",
4556 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
4557 },
4558 Expr: parser.PromQLExpr{
4559 Value: &parser.YamlNode{
4560 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
4561 Pos: diags.PositionRanges{
4562 {Line: 3, FirstColumn: 12, LastColumn: 36},
4563 {Line: 4, FirstColumn: 11, LastColumn: 39},
4564 {Line: 5, FirstColumn: 11, LastColumn: 13},
4565 },
4566 },
4567 },
4568 For: &parser.YamlNode{
4569 Value: "1m",
4570 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 10, LastColumn: 11}},
4571 },
4572 },
4573 },
4574 },
4575 },
4576 },
4577 },
4578 },
4579 {
4580 input: []byte(`
4581groups:
4582- name: foo
4583 rules:
4584 - record: colo:foo:sum
4585 expr: sum without (instance) ( rate(my_metric[2m]) * on (instance)
4586 group_left (hardware_generation, hms_scope, sliver) (instance:metadata{})
4587 )
4588`),
4589 strict: false,
4590 schema: parser.PrometheusSchema,
4591 output: parser.File{
4592 IsRelaxed: true,
4593 Groups: []parser.Group{
4594 {
4595 Name: "foo",
4596 Rules: []parser.Rule{
4597 {
4598 Lines: diags.LineRange{First: 5, Last: 8},
4599 RecordingRule: &parser.RecordingRule{
4600 Record: parser.YamlNode{
4601 Value: "colo:foo:sum",
4602 Pos: diags.PositionRanges{{Line: 5, FirstColumn: 13, LastColumn: 24}},
4603 },
4604 Expr: parser.PromQLExpr{
4605 Value: &parser.YamlNode{
4606 Value: "sum without (instance) ( rate(my_metric[2m]) * on (instance) group_left (hardware_generation, hms_scope, sliver) (instance:metadata{}) )",
4607 Pos: diags.PositionRanges{
4608 {Line: 6, FirstColumn: 11, LastColumn: 71},
4609 {Line: 7, FirstColumn: 7, LastColumn: 80},
4610 {Line: 8, FirstColumn: 7, LastColumn: 7},
4611 },
4612 },
4613 },
4614 },
4615 },
4616 },
4617 },
4618 },
4619 },
4620 },
4621 {
4622 input: []byte(`
4623groups:
4624- name: "{{ source }}"
4625 rules:
4626 # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
4627 # BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
4628 - record: some_long_name
4629 labels:
4630 metricssource: receiver
4631 expr:
4632 clamp_max(
4633 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) /
4634 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)),
4635 2
4636 )
4637`),
4638 strict: true,
4639 schema: parser.PrometheusSchema,
4640 output: parser.File{
4641 Groups: []parser.Group{
4642 {
4643 Name: "{{ source }}",
4644 Rules: []parser.Rule{
4645 {
4646 Lines: diags.LineRange{First: 7, Last: 15},
4647 RecordingRule: &parser.RecordingRule{
4648 Record: parser.YamlNode{
4649 Value: "some_long_name",
4650 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 13, LastColumn: 26}},
4651 },
4652 Labels: &parser.YamlMap{
4653 Key: &parser.YamlNode{
4654 Value: "labels",
4655 },
4656 Items: []*parser.YamlKeyValue{
4657 {
4658 Key: &parser.YamlNode{
4659 Value: "metricssource",
4660 },
4661 Value: &parser.YamlNode{
4662 Value: "receiver",
4663 },
4664 },
4665 },
4666 },
4667 Expr: parser.PromQLExpr{
4668 Value: &parser.YamlNode{
4669 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 )`,
4670 Pos: diags.PositionRanges{
4671 {Line: 11, FirstColumn: 7, LastColumn: 17},
4672 {Line: 12, FirstColumn: 9, LastColumn: 129},
4673 {Line: 13, FirstColumn: 9, LastColumn: 139},
4674 {Line: 14, FirstColumn: 9, LastColumn: 10},
4675 {Line: 15, FirstColumn: 7, LastColumn: 7},
4676 },
4677 },
4678 },
4679 },
4680 },
4681 },
4682 },
4683 },
4684 },
4685 },
4686 {
4687 input: []byte(`
4688groups:
4689- name: "{{ source }}"
4690 rules:
4691
4692 - alert: Director_Is_Not_Advertising_Any_Routes
4693 expr: |
4694 sum without (name) (
4695 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
4696 * on (instance) group_left (profile,cluster)
4697 cf_node_role{kubernetes_role="director",role="kubernetes"}
4698 ) <= 0
4699 for: 1m
4700`),
4701 strict: true,
4702 schema: parser.PrometheusSchema,
4703 output: parser.File{
4704 Groups: []parser.Group{
4705 {
4706 Name: "{{ source }}",
4707 Rules: []parser.Rule{
4708 {
4709 Lines: diags.LineRange{First: 6, Last: 13},
4710 AlertingRule: &parser.AlertingRule{
4711 Alert: parser.YamlNode{
4712 Value: "Director_Is_Not_Advertising_Any_Routes",
4713 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 12, LastColumn: 49}},
4714 },
4715 Expr: parser.PromQLExpr{
4716 Value: &parser.YamlNode{
4717 Value: `sum without (name) (
4718 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
4719 * on (instance) group_left (profile,cluster)
4720 cf_node_role{kubernetes_role="director",role="kubernetes"}
4721) <= 0
4722`,
4723 Pos: diags.PositionRanges{
4724 {Line: 8, FirstColumn: 9, LastColumn: 29},
4725 {Line: 9, FirstColumn: 9, LastColumn: 99},
4726 {Line: 10, FirstColumn: 9, LastColumn: 55},
4727 {Line: 11, FirstColumn: 9, LastColumn: 71},
4728 {Line: 12, FirstColumn: 9, LastColumn: 14},
4729 },
4730 },
4731 },
4732 For: &parser.YamlNode{
4733 Value: "1m",
4734 Pos: diags.PositionRanges{{Line: 13, FirstColumn: 10, LastColumn: 11}},
4735 },
4736 },
4737 },
4738 },
4739 },
4740 },
4741 },
4742 },
4743 {
4744 input: []byte(`
4745groups:
4746- name: xxx
4747 interval: 3m
4748 rules: []
4749`),
4750 strict: true,
4751 output: parser.File{
4752 Groups: []parser.Group{
4753 {
4754 Name: "xxx",
4755 Interval: time.Minute * 3,
4756 },
4757 },
4758 },
4759 },
4760 {
4761 input: []byte(`
4762groups:
4763- name: xxx
4764 interval: 3m
4765 rules: []
4766`),
4767 output: parser.File{
4768 IsRelaxed: true,
4769 Groups: []parser.Group{
4770 {
4771 Name: "xxx",
4772 Interval: time.Minute * 3,
4773 },
4774 },
4775 },
4776 },
4777 {
4778 input: []byte(`
4779groups:
4780- name: xxx
4781 interval: 3m
4782 rules: []
4783---
4784groups:
4785- name: yyy
4786 interval: 2m
4787 rules: []
4788`),
4789 output: parser.File{
4790 IsRelaxed: true,
4791 Groups: []parser.Group{
4792 {
4793 Name: "xxx",
4794 Interval: time.Minute * 3,
4795 },
4796 {
4797 Name: "yyy",
4798 Interval: time.Minute * 2,
4799 },
4800 },
4801 },
4802 },
4803 {
4804 input: []byte(`
4805groups:
4806- name: xxx
4807 interval: 3m
4808 labels:
4809 foo: bar
4810 rules: []
4811`),
4812 strict: true,
4813 output: parser.File{
4814 Groups: []parser.Group{
4815 {
4816 Name: "xxx",
4817 Interval: time.Minute * 3,
4818 Labels: &parser.YamlMap{
4819 Key: &parser.YamlNode{
4820 Value: "labels",
4821 },
4822 Items: []*parser.YamlKeyValue{
4823 {
4824 Key: &parser.YamlNode{
4825 Value: "foo",
4826 },
4827 Value: &parser.YamlNode{
4828 Value: "bar",
4829 },
4830 },
4831 },
4832 },
4833 },
4834 },
4835 },
4836 },
4837 {
4838 input: []byte(`
4839groups:
4840- name: xxx
4841 labels:
4842 - foo: bar
4843 rules: []
4844`),
4845 strict: true,
4846 output: parser.File{
4847 Groups: []parser.Group{
4848 {
4849 Name: "xxx",
4850 Error: parser.ParseError{
4851 Err: errors.New("group labels must be a mapping, got list"),
4852 Line: 4,
4853 },
4854 },
4855 },
4856 },
4857 },
4858 {
4859 input: []byte(`
4860groups:
4861- name: xxx
4862 labels:
4863 foo: 1
4864 rules: []
4865`),
4866 strict: true,
4867 output: parser.File{
4868 Groups: []parser.Group{
4869 {
4870 Name: "xxx",
4871 Error: parser.ParseError{
4872 Err: errors.New("labels foo value must be a string, got integer instead"),
4873 Line: 5,
4874 },
4875 },
4876 },
4877 },
4878 },
4879 {
4880 input: []byte(`
4881groups:
4882- name: xxx
4883 labels:
4884 foo: bar
4885 bob: foo
4886 foo: bob
4887 rules: []
4888`),
4889 strict: true,
4890 output: parser.File{
4891 Groups: []parser.Group{
4892 {
4893 Name: "xxx",
4894 Error: parser.ParseError{
4895 Err: errors.New("duplicated labels key foo"),
4896 Line: 7,
4897 },
4898 },
4899 },
4900 },
4901 },
4902 {
4903 input: []byte(`
4904groups:
4905- name: xxx
4906 interval: 3m
4907 query_offset: 1s
4908 limit: 5
4909 labels:
4910 foo: bar
4911 rules: []
4912`),
4913 output: parser.File{
4914 IsRelaxed: true,
4915 Groups: []parser.Group{
4916 {
4917 Name: "xxx",
4918 Interval: time.Minute * 3,
4919 QueryOffset: time.Second,
4920 Limit: 5,
4921 Labels: &parser.YamlMap{
4922 Key: &parser.YamlNode{
4923 Value: "labels",
4924 },
4925 Items: []*parser.YamlKeyValue{
4926 {
4927 Key: &parser.YamlNode{
4928 Value: "foo",
4929 },
4930 Value: &parser.YamlNode{
4931 Value: "bar",
4932 },
4933 },
4934 },
4935 },
4936 },
4937 },
4938 },
4939 },
4940 {
4941 input: []byte(`
4942groups:
4943- name: xxx
4944 labels:
4945 - foo: bar
4946 rules: []
4947`),
4948 output: parser.File{
4949 IsRelaxed: true,
4950 Groups: []parser.Group{
4951 {
4952 Name: "xxx",
4953 Labels: &parser.YamlMap{
4954 Key: &parser.YamlNode{
4955 Value: "labels",
4956 },
4957 },
4958 },
4959 },
4960 },
4961 },
4962 {
4963 input: []byte(`
4964groups:
4965- name: xxx
4966 labels:
4967 foo: 1
4968 rules: []
4969`),
4970 output: parser.File{
4971 IsRelaxed: true,
4972 Groups: []parser.Group{
4973 {
4974 Name: "xxx",
4975 Labels: &parser.YamlMap{
4976 Key: &parser.YamlNode{
4977 Value: "labels",
4978 },
4979 Items: []*parser.YamlKeyValue{
4980 {
4981 Key: &parser.YamlNode{
4982 Value: "foo",
4983 },
4984 Value: &parser.YamlNode{
4985 Value: "1",
4986 },
4987 },
4988 },
4989 },
4990 },
4991 },
4992 },
4993 },
4994 {
4995 input: []byte(`
4996groups:
4997- name: xxx
4998 labels:
4999 foo: bar
5000 bob: foo
5001 foo: bob
5002 rules: []
5003`),
5004 output: parser.File{
5005 IsRelaxed: true,
5006 Groups: []parser.Group{
5007 {
5008 Name: "xxx",
5009 Labels: &parser.YamlMap{
5010 Key: &parser.YamlNode{
5011 Value: "labels",
5012 },
5013 Items: []*parser.YamlKeyValue{
5014 {
5015 Key: &parser.YamlNode{
5016 Value: "foo",
5017 },
5018 Value: &parser.YamlNode{
5019 Value: "bar",
5020 },
5021 },
5022 {
5023 Key: &parser.YamlNode{
5024 Value: "bob",
5025 },
5026 Value: &parser.YamlNode{
5027 Value: "foo",
5028 },
5029 },
5030 {
5031 Key: &parser.YamlNode{
5032 Value: "foo",
5033 },
5034 Value: &parser.YamlNode{
5035 Value: "bob",
5036 },
5037 },
5038 },
5039 },
5040 },
5041 },
5042 },
5043 },
5044 {
5045 input: []byte(`
5046- name: xxx
5047 interval: 3m
5048 query_offset: 1s
5049 limit: 5
5050 labels:
5051 foo: bar
5052 rules:
5053 - record: up:count
5054 expr: count(up)
5055`),
5056 output: parser.File{
5057 IsRelaxed: true,
5058 Groups: []parser.Group{
5059 {
5060 Name: "xxx",
5061 Interval: time.Minute * 3,
5062 QueryOffset: time.Second,
5063 Limit: 5,
5064 Labels: &parser.YamlMap{
5065 Key: &parser.YamlNode{
5066 Value: "labels",
5067 },
5068 Items: []*parser.YamlKeyValue{
5069 {
5070 Key: &parser.YamlNode{
5071 Value: "foo",
5072 },
5073 Value: &parser.YamlNode{
5074 Value: "bar",
5075 },
5076 },
5077 },
5078 },
5079 Rules: []parser.Rule{
5080 {
5081 Lines: diags.LineRange{First: 9, Last: 10},
5082 RecordingRule: &parser.RecordingRule{
5083 Record: parser.YamlNode{
5084 Value: "up:count",
5085 Pos: diags.PositionRanges{{Line: 9, FirstColumn: 13, LastColumn: 20}},
5086 },
5087 Expr: parser.PromQLExpr{
5088 Value: &parser.YamlNode{
5089 Value: "count(up)",
5090 Pos: diags.PositionRanges{{Line: 10, FirstColumn: 11, LastColumn: 19}},
5091 },
5092 },
5093 },
5094 },
5095 },
5096 },
5097 },
5098 },
5099 },
5100 {
5101 input: []byte(`
5102
5103- interval: 3m
5104 query_offset: 1s
5105 limit: 5
5106 labels:
5107 foo: bar
5108 rules:
5109 - record: up:count
5110 expr: count(up)
5111`),
5112 output: parser.File{
5113 IsRelaxed: true,
5114 Groups: []parser.Group{
5115 {
5116 Name: "",
5117 Rules: []parser.Rule{
5118 {
5119 Lines: diags.LineRange{First: 9, Last: 10},
5120 RecordingRule: &parser.RecordingRule{
5121 Record: parser.YamlNode{
5122 Value: "up:count",
5123 Pos: diags.PositionRanges{{Line: 9, FirstColumn: 13, LastColumn: 20}},
5124 },
5125 Expr: parser.PromQLExpr{
5126 Value: &parser.YamlNode{
5127 Value: "count(up)",
5128 Pos: diags.PositionRanges{{Line: 10, FirstColumn: 11, LastColumn: 19}},
5129 },
5130 },
5131 },
5132 },
5133 },
5134 },
5135 },
5136 },
5137 },
5138 {
5139 input: []byte(`
5140apiVersion: v1
5141kind: ConfigMap
5142metadata:
5143 labels:
5144 shard: "0"
5145 total-shards: "1"
5146 name: shard-jobs
5147 namespace: foo
5148data:
5149 jobs.yaml: |
5150 jobs:
5151 - name: foo
5152 interval: 1m
5153 queries:
5154 - name: xxx
5155 help: ''
5156 labels:
5157 - account_id
5158 - bucket
5159 - cache_status
5160 mtls_identity:
5161 cert_path: /etc/identity/tls.crt
5162 key_path: /etc/identity/tls.key
5163`),
5164 output: parser.File{
5165 Groups: nil,
5166 IsRelaxed: true,
5167 },
5168 },
5169 }
5170
5171 alwaysEqual := cmp.Comparer(func(_, _ any) bool { return true })
5172 ignorePrometheusExpr := cmp.FilterValues(func(x, y any) bool {
5173 _, xe := x.(*parser.PromQLNode)
5174 _, ye := y.(*parser.PromQLNode)
5175 return xe || ye
5176 }, alwaysEqual)
5177
5178 cmpErrorText := cmp.Comparer(func(x, y any) bool {
5179 xe := x.(error)
5180 ye := y.(error)
5181 return xe.Error() == ye.Error()
5182 })
5183 sameErrorText := cmp.FilterValues(func(x, y any) bool {
5184 _, xe := x.(error)
5185 _, ye := y.(error)
5186 return xe && ye
5187 }, cmpErrorText)
5188
5189 for i, tc := range testCases {
5190 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
5191 t.Logf("\n--- Content ---%s--- END ---", tc.input)
5192
5193 s := bufio.NewScanner(bytes.NewReader(tc.input))
5194 for s.Scan() {
5195 tc.output.TotalLines++
5196 }
5197
5198 p := parser.NewParser(tc.strict, tc.schema, tc.names)
5199 file := p.Parse(bytes.NewReader(tc.input))
5200
5201 if diff := cmp.Diff(tc.output, file,
5202 ignorePrometheusExpr,
5203 sameErrorText,
5204 cmpopts.IgnoreFields(parser.YamlNode{}, "Pos"), // FIXME remove?
5205 ); diff != "" {
5206 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
5207 return
5208 }
5209 })
5210 }
5211}
5212
5213func BenchmarkParse(b *testing.B) {
5214 data, err := os.ReadFile("testrules.yml")
5215 require.NoError(b, err)
5216
5217 p := parser.NewParser(true, parser.PrometheusSchema, model.LegacyValidation)
5218 for b.Loop() {
5219 b.StopTimer()
5220 r := bytes.NewReader(data)
5221 b.StartTimer()
5222
5223 f := p.Parse(r)
5224
5225 b.StopTimer()
5226 require.Len(b, f.Groups, 90)
5227 require.NoError(b, f.Error.Err)
5228 require.Equal(b, 5501, f.TotalLines)
5229 b.StartTimer()
5230 }
5231}
5232