cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.79.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

5504lines · modecode

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