cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.72.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

5080lines · modecode

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