cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.71.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

3669lines · modecode

1package parser_test
2
3import (
4 "errors"
5 "strconv"
6 "testing"
7
8 "github.com/prometheus/common/model"
9 "github.com/stretchr/testify/require"
10
11 "github.com/cloudflare/pint/internal/comments"
12 "github.com/cloudflare/pint/internal/diags"
13 "github.com/cloudflare/pint/internal/parser"
14
15 "github.com/google/go-cmp/cmp"
16 "github.com/google/go-cmp/cmp/cmpopts"
17)
18
19func TestParse(t *testing.T) {
20 type testCaseT struct {
21 err string
22 content []byte
23 output []parser.Rule
24 strict bool
25 schema parser.Schema
26 names model.ValidationScheme
27 }
28
29 testCases := []testCaseT{
30 {
31 content: nil,
32 output: nil,
33 },
34 {
35 content: []byte{},
36 output: nil,
37 },
38 {
39 content: []byte(string("! !00 \xf6")),
40 output: nil,
41 err: "error at line 1: yaml: incomplete UTF-8 octet sequence",
42 },
43 {
44 content: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
45 output: []parser.Rule{
46 {
47 Lines: diags.LineRange{First: 1, Last: 6},
48 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 6},
49 },
50 },
51 },
52 {
53 content: []byte("- record: |\n multiline\n"),
54 output: []parser.Rule{
55 {
56 Lines: diags.LineRange{First: 1, Last: 2},
57 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 2},
58 },
59 },
60 },
61 {
62 content: []byte(`---
63- expr: foo
64 record: foo
65---
66- expr: bar
67`),
68 output: []parser.Rule{
69 {
70 RecordingRule: &parser.RecordingRule{
71 Expr: parser.PromQLExpr{
72 Value: &parser.YamlNode{
73 Lines: diags.LineRange{First: 2, Last: 2},
74 Value: "foo",
75 Pos: diags.PositionRanges{
76 {Line: 2, FirstColumn: 9, LastColumn: 11},
77 },
78 },
79 },
80 Record: parser.YamlNode{
81 Lines: diags.LineRange{First: 3, Last: 3},
82 Value: "foo",
83 Pos: diags.PositionRanges{
84 {Line: 3, FirstColumn: 11, LastColumn: 13},
85 },
86 },
87 },
88 Lines: diags.LineRange{First: 2, Last: 3},
89 },
90 {
91 Lines: diags.LineRange{First: 5, Last: 5},
92 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 5},
93 },
94 },
95 },
96 {
97 content: []byte("- expr: foo\n"),
98 output: []parser.Rule{
99 {
100 Lines: diags.LineRange{First: 1, Last: 1},
101 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 1},
102 },
103 },
104 },
105 {
106 content: []byte("- alert: foo\n"),
107 output: []parser.Rule{
108 {
109 Lines: diags.LineRange{First: 1, Last: 1},
110 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
111 },
112 },
113 },
114 {
115 content: []byte("- alert: foo\n record: foo\n"),
116 output: []parser.Rule{
117 {
118 Lines: diags.LineRange{First: 1, Last: 2},
119 Error: parser.ParseError{Err: errors.New("got both record and alert keys in a single rule"), Line: 1},
120 },
121 },
122 },
123 {
124 content: []byte("- record: foo\n labels:\n foo: bar\n"),
125 output: []parser.Rule{
126 {
127 Lines: diags.LineRange{First: 1, Last: 3},
128 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
129 },
130 },
131 },
132 {
133 content: []byte("- record: - foo\n"),
134 err: "error at line 1: yaml: block sequence entries are not allowed in this context",
135 },
136 {
137 content: []byte("- record: foo expr: sum(\n"),
138 err: "error at line 1: yaml: mapping values are not allowed in this context",
139 },
140 {
141 content: []byte("- record\n\texpr: foo\n"),
142 err: "error at line 2: found a tab character that violates indentation",
143 },
144 {
145 content: []byte(`
146- record: foo
147 expr: bar
148 expr: bar
149`),
150 output: []parser.Rule{
151 {
152 Lines: diags.LineRange{First: 2, Last: 4},
153 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 4},
154 },
155 },
156 },
157 {
158 content: []byte(`
159- record: foo
160 expr: bar
161 record: bar
162`),
163 output: []parser.Rule{
164 {
165 Lines: diags.LineRange{First: 2, Last: 4},
166 Error: parser.ParseError{Err: errors.New("duplicated record key"), Line: 4},
167 },
168 },
169 },
170 {
171 content: []byte(`
172- alert: foo
173 alert: bar
174 expr: bar
175`),
176 output: []parser.Rule{
177 {
178 Lines: diags.LineRange{First: 2, Last: 3},
179 Error: parser.ParseError{Err: errors.New("duplicated alert key"), Line: 3},
180 },
181 },
182 },
183 {
184 content: []byte(`
185- alert: foo
186 for: 5m
187 expr: bar
188 for: 1m
189`),
190 output: []parser.Rule{
191 {
192 Lines: diags.LineRange{First: 2, Last: 5},
193 Error: parser.ParseError{Err: errors.New("duplicated for key"), Line: 5},
194 },
195 },
196 },
197 {
198 content: []byte(`
199- alert: foo
200 keep_firing_for: 5m
201 expr: bar
202 keep_firing_for: 1m
203`),
204 output: []parser.Rule{
205 {
206 Lines: diags.LineRange{First: 2, Last: 5},
207 Error: parser.ParseError{Err: errors.New("duplicated keep_firing_for key"), Line: 5},
208 },
209 },
210 },
211 {
212 content: []byte(`
213- alert: foo
214 labels: {}
215 expr: bar
216 labels: {}
217`),
218 output: []parser.Rule{
219 {
220 Lines: diags.LineRange{First: 2, Last: 5},
221 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
222 },
223 },
224 },
225 {
226 content: []byte(`
227- record: foo
228 labels: {}
229 expr: bar
230 labels: {}
231`),
232 output: []parser.Rule{
233 {
234 Lines: diags.LineRange{First: 2, Last: 5},
235 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
236 },
237 },
238 },
239 {
240 content: []byte(`
241- alert: foo
242 annotations: {}
243 expr: bar
244 annotations: {}
245`),
246 output: []parser.Rule{
247 {
248 Lines: diags.LineRange{First: 2, Last: 5},
249 Error: parser.ParseError{Err: errors.New("duplicated annotations key"), Line: 5},
250 },
251 },
252 },
253 {
254 content: []byte("- record: foo\n expr: foo\n extra: true\n"),
255 output: []parser.Rule{
256 {
257 Lines: diags.LineRange{First: 1, Last: 3},
258 Error: parser.ParseError{Err: errors.New("invalid key(s) found: extra"), Line: 3},
259 },
260 },
261 },
262 {
263 content: []byte(`- record: foo
264 expr: foo offset 10m
265`),
266 output: []parser.Rule{
267 {
268 Lines: diags.LineRange{First: 1, Last: 2},
269 RecordingRule: &parser.RecordingRule{
270 Record: parser.YamlNode{
271 Lines: diags.LineRange{First: 1, Last: 1},
272 Value: "foo",
273 Pos: diags.PositionRanges{
274 {Line: 1, FirstColumn: 11, LastColumn: 13},
275 },
276 },
277 Expr: parser.PromQLExpr{
278 Value: &parser.YamlNode{
279 Lines: diags.LineRange{First: 2, Last: 2},
280 Pos: diags.PositionRanges{
281 {Line: 2, FirstColumn: 9, LastColumn: 22},
282 },
283 Value: "foo offset 10m",
284 },
285 },
286 },
287 },
288 },
289 },
290 {
291 content: []byte("- record: foo\n expr: foo offset -10m\n"),
292 output: []parser.Rule{
293 {
294 Lines: diags.LineRange{First: 1, Last: 2},
295 RecordingRule: &parser.RecordingRule{
296 Record: parser.YamlNode{
297 Lines: diags.LineRange{First: 1, Last: 1},
298 Value: "foo",
299 Pos: diags.PositionRanges{
300 {Line: 1, FirstColumn: 11, LastColumn: 13},
301 },
302 },
303 Expr: parser.PromQLExpr{
304 Value: &parser.YamlNode{
305 Lines: diags.LineRange{First: 2, Last: 2},
306 Value: "foo offset -10m",
307 Pos: diags.PositionRanges{
308 {Line: 2, FirstColumn: 9, LastColumn: 23},
309 },
310 },
311 },
312 },
313 },
314 },
315 },
316 {
317 content: []byte(`
318# pint disable head comment
319- record: foo # pint disable record comment
320 expr: foo offset 10m # pint disable expr comment
321 # pint disable pre-labels comment
322 labels:
323 # pint disable pre-foo comment
324 foo: bar
325 # pint disable post-foo comment
326 bob: alice
327 # pint disable foot comment
328`),
329 output: []parser.Rule{
330 {
331 Lines: diags.LineRange{First: 3, Last: 10},
332 Comments: []comments.Comment{
333 {
334 Type: comments.DisableType,
335 Value: comments.Disable{Match: "head comment"},
336 },
337 {
338 Type: comments.DisableType,
339 Value: comments.Disable{Match: "record comment"},
340 },
341 {
342 Type: comments.DisableType,
343 Value: comments.Disable{Match: "expr comment"},
344 },
345 {
346 Type: comments.DisableType,
347 Value: comments.Disable{Match: "pre-labels comment"},
348 },
349 {
350 Type: comments.DisableType,
351 Value: comments.Disable{Match: "foot comment"},
352 },
353 {
354 Type: comments.DisableType,
355 Value: comments.Disable{Match: "pre-foo comment"},
356 },
357 {
358 Type: comments.DisableType,
359 Value: comments.Disable{Match: "post-foo comment"},
360 },
361 },
362 RecordingRule: &parser.RecordingRule{
363 Record: parser.YamlNode{
364 Lines: diags.LineRange{First: 3, Last: 3},
365 Value: "foo",
366 Pos: diags.PositionRanges{
367 {Line: 3, FirstColumn: 11, LastColumn: 13},
368 },
369 },
370 Expr: parser.PromQLExpr{
371 Value: &parser.YamlNode{
372 Lines: diags.LineRange{First: 4, Last: 4},
373 Value: "foo offset 10m",
374 Pos: diags.PositionRanges{
375 {Line: 4, FirstColumn: 9, LastColumn: 22},
376 },
377 },
378 },
379 Labels: &parser.YamlMap{
380 Lines: diags.LineRange{First: 6, Last: 10},
381
382 Key: &parser.YamlNode{
383 Lines: diags.LineRange{First: 6, Last: 6},
384 Value: "labels",
385 },
386 Items: []*parser.YamlKeyValue{
387 {
388 Key: &parser.YamlNode{
389 Lines: diags.LineRange{First: 8, Last: 8},
390 Value: "foo",
391 },
392 Value: &parser.YamlNode{
393 Lines: diags.LineRange{First: 8, Last: 8},
394 Value: "bar",
395 },
396 },
397 {
398 Key: &parser.YamlNode{
399 Lines: diags.LineRange{First: 10, Last: 10},
400 Value: "bob",
401 },
402 Value: &parser.YamlNode{
403 Lines: diags.LineRange{First: 10, Last: 10},
404 Value: "alice",
405 },
406 },
407 },
408 },
409 },
410 },
411 },
412 },
413 {
414 content: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
415 output: []parser.Rule{
416 {
417 Lines: diags.LineRange{First: 1, Last: 2},
418 RecordingRule: &parser.RecordingRule{
419 Record: parser.YamlNode{
420 Lines: diags.LineRange{First: 1, Last: 1},
421 Value: "foo",
422 Pos: diags.PositionRanges{
423 {Line: 1, FirstColumn: 11, LastColumn: 13},
424 },
425 },
426 Expr: parser.PromQLExpr{
427 Value: &parser.YamlNode{
428 Lines: diags.LineRange{First: 2, Last: 2},
429 Value: "foo[5m] offset 10m",
430 Pos: diags.PositionRanges{
431 {Line: 2, FirstColumn: 9, LastColumn: 26},
432 },
433 },
434 },
435 },
436 },
437 },
438 },
439 {
440 content: []byte(`
441- record: name
442 expr: sum(foo)
443 labels:
444 foo: bar
445 bob: alice
446`),
447 output: []parser.Rule{
448 {
449 Lines: diags.LineRange{First: 2, Last: 6},
450 RecordingRule: &parser.RecordingRule{
451 Record: parser.YamlNode{
452 Lines: diags.LineRange{First: 2, Last: 2},
453 Value: "name",
454 Pos: diags.PositionRanges{
455 {Line: 2, FirstColumn: 11, LastColumn: 14},
456 },
457 },
458 Expr: parser.PromQLExpr{
459 Value: &parser.YamlNode{
460 Lines: diags.LineRange{First: 3, Last: 3},
461 Value: "sum(foo)",
462 Pos: diags.PositionRanges{
463 {Line: 3, FirstColumn: 9, LastColumn: 16},
464 },
465 },
466 },
467 Labels: &parser.YamlMap{
468 Lines: diags.LineRange{First: 4, Last: 6},
469 Key: &parser.YamlNode{
470 Lines: diags.LineRange{First: 4, Last: 4},
471 Value: "labels",
472 },
473 Items: []*parser.YamlKeyValue{
474 {
475 Key: &parser.YamlNode{
476 Lines: diags.LineRange{First: 5, Last: 5},
477 Value: "foo",
478 },
479 Value: &parser.YamlNode{
480 Lines: diags.LineRange{First: 5, Last: 5},
481 Value: "bar",
482 },
483 },
484 {
485 Key: &parser.YamlNode{
486 Lines: diags.LineRange{First: 6, Last: 6},
487 Value: "bob",
488 },
489 Value: &parser.YamlNode{
490 Lines: diags.LineRange{First: 6, Last: 6},
491 Value: "alice",
492 },
493 },
494 },
495 },
496 },
497 },
498 },
499 },
500 {
501 content: []byte(`
502groups:
503- name: custom_rules
504 rules:
505 - record: name
506 expr: sum(foo)
507 labels:
508 foo: bar
509 bob: alice
510`),
511 output: []parser.Rule{
512 {
513 Lines: diags.LineRange{First: 5, Last: 9},
514 RecordingRule: &parser.RecordingRule{
515 Record: parser.YamlNode{
516 Lines: diags.LineRange{First: 5, Last: 5},
517 Value: "name",
518 Pos: diags.PositionRanges{
519 {Line: 5, FirstColumn: 15, LastColumn: 18},
520 },
521 },
522 Expr: parser.PromQLExpr{
523 Value: &parser.YamlNode{
524 Lines: diags.LineRange{First: 6, Last: 6},
525 Value: "sum(foo)",
526 Pos: diags.PositionRanges{
527 {Line: 6, FirstColumn: 13, LastColumn: 20},
528 },
529 },
530 },
531 Labels: &parser.YamlMap{
532 Lines: diags.LineRange{First: 7, Last: 9},
533 Key: &parser.YamlNode{
534 Lines: diags.LineRange{First: 7, Last: 7},
535 Value: "labels",
536 },
537 Items: []*parser.YamlKeyValue{
538 {
539 Key: &parser.YamlNode{
540 Lines: diags.LineRange{First: 8, Last: 8},
541 Value: "foo",
542 },
543 Value: &parser.YamlNode{
544 Lines: diags.LineRange{First: 8, Last: 8},
545 Value: "bar",
546 },
547 },
548 {
549 Key: &parser.YamlNode{
550 Lines: diags.LineRange{First: 9, Last: 9},
551 Value: "bob",
552 },
553 Value: &parser.YamlNode{
554 Lines: diags.LineRange{First: 9, Last: 9},
555 Value: "alice",
556 },
557 },
558 },
559 },
560 },
561 },
562 },
563 },
564 {
565 content: []byte(`- alert: Down
566 expr: |
567 up == 0
568 for: |+
569 11m
570 labels:
571 severity: critical
572 annotations:
573 uri: https://docs.example.com/down.html
574
575- record: foo
576 expr: |-
577 bar
578 /
579 baz > 1
580 labels: {}
581`),
582 output: []parser.Rule{
583 {
584 Lines: diags.LineRange{First: 1, Last: 9},
585 AlertingRule: &parser.AlertingRule{
586 Alert: parser.YamlNode{
587 Lines: diags.LineRange{First: 1, Last: 1},
588 Value: "Down",
589 Pos: diags.PositionRanges{
590 {Line: 1, FirstColumn: 10, LastColumn: 13},
591 },
592 },
593 Expr: parser.PromQLExpr{
594 Value: &parser.YamlNode{
595 Lines: diags.LineRange{First: 3, Last: 3},
596 Value: "up == 0\n",
597 Pos: diags.PositionRanges{
598 {Line: 3, FirstColumn: 5, LastColumn: 11},
599 },
600 },
601 },
602 For: &parser.YamlNode{
603 Lines: diags.LineRange{First: 5, Last: 5},
604 Value: "11m\n",
605 Pos: diags.PositionRanges{
606 {Line: 5, FirstColumn: 5, LastColumn: 7},
607 },
608 },
609 Labels: &parser.YamlMap{
610 Lines: diags.LineRange{First: 6, Last: 7},
611 Key: &parser.YamlNode{
612 Lines: diags.LineRange{First: 6, Last: 6},
613 Value: "labels",
614 },
615 Items: []*parser.YamlKeyValue{
616 {
617 Key: &parser.YamlNode{
618 Lines: diags.LineRange{First: 7, Last: 7},
619 Value: "severity",
620 },
621 Value: &parser.YamlNode{
622 Lines: diags.LineRange{First: 7, Last: 7},
623 Value: "critical",
624 },
625 },
626 },
627 },
628 Annotations: &parser.YamlMap{
629 Lines: diags.LineRange{First: 8, Last: 9},
630 Key: &parser.YamlNode{
631 Lines: diags.LineRange{First: 8, Last: 8},
632 Value: "annotations",
633 },
634 Items: []*parser.YamlKeyValue{
635 {
636 Key: &parser.YamlNode{
637 Lines: diags.LineRange{First: 9, Last: 9},
638 Value: "uri",
639 },
640 Value: &parser.YamlNode{
641 Lines: diags.LineRange{First: 9, Last: 9},
642 Value: "https://docs.example.com/down.html",
643 },
644 },
645 },
646 },
647 },
648 },
649 {
650 Lines: diags.LineRange{First: 11, Last: 16},
651 RecordingRule: &parser.RecordingRule{
652 Record: parser.YamlNode{
653 Lines: diags.LineRange{First: 11, Last: 11},
654 Value: "foo",
655 Pos: diags.PositionRanges{
656 {Line: 11, FirstColumn: 11, LastColumn: 13},
657 },
658 },
659 Expr: parser.PromQLExpr{
660 Value: &parser.YamlNode{
661 Lines: diags.LineRange{First: 13, Last: 15},
662 Value: "bar\n/\nbaz > 1",
663 Pos: diags.PositionRanges{
664 {Line: 13, FirstColumn: 5, LastColumn: 8},
665 {Line: 14, FirstColumn: 5, LastColumn: 6},
666 {Line: 15, FirstColumn: 5, LastColumn: 11},
667 },
668 },
669 },
670 Labels: &parser.YamlMap{
671 Lines: diags.LineRange{First: 16, Last: 16},
672 Key: &parser.YamlNode{
673 Lines: diags.LineRange{First: 16, Last: 16},
674 Value: "labels",
675 },
676 },
677 },
678 },
679 },
680 },
681 {
682 content: []byte(`- alert: Foo
683 expr:
684 (
685 xxx
686 -
687 yyy
688 ) * bar > 0
689 and on(instance, device) baz
690 for: 30m
691`),
692 output: []parser.Rule{
693 {
694 Lines: diags.LineRange{First: 1, Last: 9},
695 AlertingRule: &parser.AlertingRule{
696 Alert: parser.YamlNode{
697 Lines: diags.LineRange{First: 1, Last: 1},
698 Value: "Foo",
699 Pos: diags.PositionRanges{
700 {Line: 1, FirstColumn: 10, LastColumn: 12},
701 },
702 },
703 Expr: parser.PromQLExpr{
704 Value: &parser.YamlNode{
705 Lines: diags.LineRange{First: 3, Last: 8},
706 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
707 Pos: diags.PositionRanges{
708 {Line: 3, FirstColumn: 5, LastColumn: 6},
709 {Line: 4, FirstColumn: 7, LastColumn: 10},
710 {Line: 5, FirstColumn: 7, LastColumn: 8},
711 {Line: 6, FirstColumn: 7, LastColumn: 10},
712 {Line: 7, FirstColumn: 5, LastColumn: 16},
713 {Line: 8, FirstColumn: 5, LastColumn: 32},
714 },
715 },
716 },
717 For: &parser.YamlNode{
718 Lines: diags.LineRange{First: 9, Last: 9},
719 Value: "30m",
720 Pos: diags.PositionRanges{
721 {Line: 9, FirstColumn: 8, LastColumn: 10},
722 },
723 },
724 },
725 },
726 },
727 },
728 {
729 content: []byte(`---
730kind: ConfigMap
731apiVersion: v1
732metadata:
733 name: example-app-alerts
734 labels:
735 app: example-app
736data:
737 alerts: |
738 groups:
739 - name: example-app-alerts
740 rules:
741 - alert: Example_High_Restart_Rate
742 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
743---
744kind: ConfigMap
745apiVersion: v1
746metadata:
747 name: other
748 labels:
749 app: other
750data:
751 alerts: |
752 groups:
753 - name: other alerts
754 rules:
755 - alert: Example_High_Restart_Rate
756 expr: "1"
757
758`),
759 output: []parser.Rule{
760 {
761 Lines: diags.LineRange{First: 13, Last: 14},
762 AlertingRule: &parser.AlertingRule{
763 Alert: parser.YamlNode{
764 Lines: diags.LineRange{First: 13, Last: 13},
765 Value: "Example_High_Restart_Rate",
766 Pos: diags.PositionRanges{
767 {Line: 13, FirstColumn: 20, LastColumn: 44},
768 },
769 },
770 Expr: parser.PromQLExpr{
771 Value: &parser.YamlNode{
772 Lines: diags.LineRange{First: 14, Last: 14},
773 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
774 Pos: diags.PositionRanges{
775 {Line: 14, FirstColumn: 19, LastColumn: 109},
776 },
777 },
778 },
779 },
780 },
781 {
782 Lines: diags.LineRange{First: 27, Last: 28},
783 AlertingRule: &parser.AlertingRule{
784 Alert: parser.YamlNode{
785 Lines: diags.LineRange{First: 27, Last: 27},
786 Value: "Example_High_Restart_Rate",
787 Pos: diags.PositionRanges{
788 {Line: 27, FirstColumn: 20, LastColumn: 44},
789 },
790 },
791 Expr: parser.PromQLExpr{
792 Value: &parser.YamlNode{
793 Lines: diags.LineRange{First: 28, Last: 28},
794 Value: "1",
795 Pos: diags.PositionRanges{
796 {Line: 28, FirstColumn: 20, LastColumn: 20},
797 },
798 },
799 },
800 },
801 },
802 },
803 },
804 {
805 content: []byte(`---
806kind: ConfigMap
807apiVersion: v1
808metadata:
809 name: example-app-alerts
810 labels:
811 app: example-app
812data:
813 alerts: |
814 groups:
815 - name: example-app-alerts
816 rules:
817 - alert: Example_Is_Down
818 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
819 for: 5m
820 labels:
821 priority: "2"
822 environment: production
823 annotations:
824 summary: "No replicas for Example have been running for 5 minutes"
825
826 - alert: Example_High_Restart_Rate
827 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
828`),
829 output: []parser.Rule{
830 {
831 Lines: diags.LineRange{First: 13, Last: 20},
832 AlertingRule: &parser.AlertingRule{
833 Alert: parser.YamlNode{
834 Lines: diags.LineRange{First: 13, Last: 13},
835 Value: "Example_Is_Down",
836 Pos: diags.PositionRanges{
837 {Line: 13, FirstColumn: 20, LastColumn: 34},
838 },
839 },
840 Expr: parser.PromQLExpr{
841 Value: &parser.YamlNode{
842 Lines: diags.LineRange{First: 14, Last: 14},
843 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
844 Pos: diags.PositionRanges{
845 {Line: 14, FirstColumn: 19, LastColumn: 88},
846 },
847 },
848 },
849 For: &parser.YamlNode{
850 Lines: diags.LineRange{First: 15, Last: 15},
851 Value: "5m",
852 Pos: diags.PositionRanges{
853 {Line: 15, FirstColumn: 18, LastColumn: 19},
854 },
855 },
856 Labels: &parser.YamlMap{
857 Lines: diags.LineRange{First: 16, Last: 18},
858 Key: &parser.YamlNode{
859 Lines: diags.LineRange{First: 16, Last: 16},
860 Value: "labels",
861 },
862 Items: []*parser.YamlKeyValue{
863 {
864 Key: &parser.YamlNode{
865 Lines: diags.LineRange{First: 17, Last: 17},
866 Value: "priority",
867 },
868 Value: &parser.YamlNode{
869 Lines: diags.LineRange{First: 17, Last: 17},
870 Value: "2",
871 },
872 },
873 {
874 Key: &parser.YamlNode{
875 Lines: diags.LineRange{First: 18, Last: 18},
876 Value: "environment",
877 },
878 Value: &parser.YamlNode{
879 Lines: diags.LineRange{First: 18, Last: 18},
880 Value: "production",
881 },
882 },
883 },
884 },
885 Annotations: &parser.YamlMap{
886 Lines: diags.LineRange{First: 19, Last: 20},
887 Key: &parser.YamlNode{
888 Lines: diags.LineRange{First: 19, Last: 19},
889 Value: "annotations",
890 },
891 Items: []*parser.YamlKeyValue{
892 {
893 Key: &parser.YamlNode{
894 Lines: diags.LineRange{First: 20, Last: 20},
895 Value: "summary",
896 },
897 Value: &parser.YamlNode{
898 Lines: diags.LineRange{First: 20, Last: 20},
899 Value: "No replicas for Example have been running for 5 minutes",
900 },
901 },
902 },
903 },
904 },
905 },
906 {
907 Lines: diags.LineRange{First: 22, Last: 23},
908 AlertingRule: &parser.AlertingRule{
909 Alert: parser.YamlNode{
910 Lines: diags.LineRange{First: 22, Last: 22},
911 Value: "Example_High_Restart_Rate",
912 Pos: diags.PositionRanges{
913 {Line: 22, FirstColumn: 20, LastColumn: 44},
914 },
915 },
916 Expr: parser.PromQLExpr{
917 Value: &parser.YamlNode{
918 Lines: diags.LineRange{First: 23, Last: 23},
919 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
920 Pos: diags.PositionRanges{
921 {Line: 23, FirstColumn: 19, LastColumn: 109},
922 },
923 },
924 },
925 },
926 },
927 },
928 },
929 {
930 content: []byte(`groups:
931- name: "haproxy.api_server.rules"
932 rules:
933 - alert: HaproxyServerHealthcheckFailure
934 expr: increase(haproxy_server_check_failures_total[15m]) > 100
935 for: 5m
936 labels:
937 severity: 24x7
938 annotations:
939 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
940 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
941`),
942 output: []parser.Rule{
943 {
944 Lines: diags.LineRange{First: 4, Last: 11},
945 AlertingRule: &parser.AlertingRule{
946 Alert: parser.YamlNode{
947 Lines: diags.LineRange{First: 4, Last: 4},
948 Value: "HaproxyServerHealthcheckFailure",
949 Pos: diags.PositionRanges{
950 {Line: 4, FirstColumn: 12, LastColumn: 42},
951 },
952 },
953 Expr: parser.PromQLExpr{
954 Value: &parser.YamlNode{
955 Lines: diags.LineRange{First: 5, Last: 5},
956 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
957 Pos: diags.PositionRanges{
958 {Line: 5, FirstColumn: 11, LastColumn: 66},
959 },
960 },
961 },
962 For: &parser.YamlNode{
963 Lines: diags.LineRange{First: 6, Last: 6},
964 Value: "5m",
965 Pos: diags.PositionRanges{
966 {Line: 6, FirstColumn: 10, LastColumn: 11},
967 },
968 },
969 Labels: &parser.YamlMap{
970 Lines: diags.LineRange{First: 7, Last: 8},
971 Key: &parser.YamlNode{
972 Lines: diags.LineRange{First: 7, Last: 7},
973 Value: "labels",
974 },
975 Items: []*parser.YamlKeyValue{
976 {
977 Key: &parser.YamlNode{
978 Lines: diags.LineRange{First: 8, Last: 8},
979 Value: "severity",
980 },
981 Value: &parser.YamlNode{
982 Lines: diags.LineRange{First: 8, Last: 8},
983 Value: "24x7",
984 },
985 },
986 },
987 },
988 Annotations: &parser.YamlMap{
989 Lines: diags.LineRange{First: 9, Last: 11},
990 Key: &parser.YamlNode{
991 Lines: diags.LineRange{First: 9, Last: 9},
992 Value: "annotations",
993 },
994 Items: []*parser.YamlKeyValue{
995 {
996 Key: &parser.YamlNode{
997 Lines: diags.LineRange{First: 10, Last: 10},
998 Value: "summary",
999 },
1000 Value: &parser.YamlNode{
1001 Lines: diags.LineRange{First: 10, Last: 10},
1002 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1003 },
1004 },
1005 {
1006 Key: &parser.YamlNode{
1007 Lines: diags.LineRange{First: 11, Last: 11},
1008 Value: "description",
1009 },
1010 Value: &parser.YamlNode{
1011 Lines: diags.LineRange{First: 11, Last: 11},
1012 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1013 },
1014 },
1015 },
1016 },
1017 },
1018 },
1019 },
1020 },
1021 {
1022 content: []byte(`groups:
1023- name: certmanager
1024 rules:
1025 # pint disable before recordAnchor
1026 - &recordAnchor # pint disable recordAnchor
1027 record: name1 # pint disable name1
1028 expr: expr1 # pint disable expr1
1029 # pint disable after expr1
1030 - <<: *recordAnchor
1031 expr: expr2
1032 - <<: *recordAnchor
1033`),
1034 output: []parser.Rule{
1035 {
1036 Lines: diags.LineRange{First: 6, Last: 7},
1037 Comments: []comments.Comment{
1038 {
1039 Type: comments.DisableType,
1040 Value: comments.Disable{Match: "before recordAnchor"},
1041 },
1042 {
1043 Type: comments.DisableType,
1044 Value: comments.Disable{Match: "recordAnchor"},
1045 },
1046 {
1047 Type: comments.DisableType,
1048 Value: comments.Disable{Match: "name1"},
1049 },
1050 {
1051 Type: comments.DisableType,
1052 Value: comments.Disable{Match: "after expr1"},
1053 },
1054 {
1055 Type: comments.DisableType,
1056 Value: comments.Disable{Match: "expr1"},
1057 },
1058 },
1059 RecordingRule: &parser.RecordingRule{
1060 Record: parser.YamlNode{
1061 Lines: diags.LineRange{First: 6, Last: 6},
1062 Value: "name1",
1063 Pos: diags.PositionRanges{
1064 {Line: 6, FirstColumn: 13, LastColumn: 17},
1065 },
1066 },
1067 Expr: parser.PromQLExpr{
1068 Value: &parser.YamlNode{
1069 Lines: diags.LineRange{First: 7, Last: 7},
1070 Value: "expr1",
1071 Pos: diags.PositionRanges{
1072 {Line: 7, FirstColumn: 11, LastColumn: 15},
1073 },
1074 },
1075 },
1076 },
1077 },
1078 {
1079 Comments: []comments.Comment{
1080 {
1081 Type: comments.DisableType,
1082 Value: comments.Disable{Match: "before recordAnchor"},
1083 },
1084 {
1085 Type: comments.DisableType,
1086 Value: comments.Disable{Match: "recordAnchor"},
1087 },
1088 {
1089 Type: comments.DisableType,
1090 Value: comments.Disable{Match: "name1"},
1091 },
1092 },
1093 Lines: diags.LineRange{First: 6, Last: 10},
1094 RecordingRule: &parser.RecordingRule{
1095 Record: parser.YamlNode{
1096 Lines: diags.LineRange{First: 6, Last: 6},
1097 Value: "name1",
1098 Pos: diags.PositionRanges{
1099 {Line: 6, FirstColumn: 13, LastColumn: 17},
1100 },
1101 },
1102 Expr: parser.PromQLExpr{
1103 Value: &parser.YamlNode{
1104 Lines: diags.LineRange{First: 10, Last: 10},
1105 Value: "expr2",
1106 Pos: diags.PositionRanges{
1107 {Line: 10, FirstColumn: 11, LastColumn: 15},
1108 },
1109 },
1110 },
1111 },
1112 },
1113 {
1114 Comments: []comments.Comment{
1115 {
1116 Type: comments.DisableType,
1117 Value: comments.Disable{Match: "before recordAnchor"},
1118 },
1119 {
1120 Type: comments.DisableType,
1121 Value: comments.Disable{Match: "recordAnchor"},
1122 },
1123 {
1124 Type: comments.DisableType,
1125 Value: comments.Disable{Match: "name1"},
1126 },
1127 {
1128 Type: comments.DisableType,
1129 Value: comments.Disable{Match: "after expr1"},
1130 },
1131 {
1132 Type: comments.DisableType,
1133 Value: comments.Disable{Match: "expr1"},
1134 },
1135 },
1136 Lines: diags.LineRange{First: 6, Last: 7},
1137 RecordingRule: &parser.RecordingRule{
1138 Record: parser.YamlNode{
1139 Lines: diags.LineRange{First: 6, Last: 6},
1140 Value: "name1",
1141 Pos: diags.PositionRanges{
1142 {Line: 6, FirstColumn: 13, LastColumn: 17},
1143 },
1144 },
1145 Expr: parser.PromQLExpr{
1146 Value: &parser.YamlNode{
1147 Lines: diags.LineRange{First: 7, Last: 7},
1148 Value: "expr1",
1149 Pos: diags.PositionRanges{
1150 {Line: 7, FirstColumn: 11, LastColumn: 15},
1151 },
1152 },
1153 },
1154 },
1155 },
1156 },
1157 },
1158 {
1159 content: []byte(`groups:
1160- name: certmanager
1161 rules:
1162 - record: name1
1163 expr: expr1
1164 labels: &labelsAnchor
1165 label1: val1
1166 label2: val2
1167 - record: name2
1168 expr: expr2
1169 labels: *labelsAnchor
1170 # pint disable foot comment
1171`),
1172 output: []parser.Rule{
1173 {
1174 Lines: diags.LineRange{First: 4, Last: 8},
1175 RecordingRule: &parser.RecordingRule{
1176 Record: parser.YamlNode{
1177 Lines: diags.LineRange{First: 4, Last: 4},
1178 Value: "name1",
1179 Pos: diags.PositionRanges{
1180 {Line: 4, FirstColumn: 13, LastColumn: 17},
1181 },
1182 },
1183 Expr: parser.PromQLExpr{
1184 Value: &parser.YamlNode{
1185 Lines: diags.LineRange{First: 5, Last: 5},
1186 Value: "expr1",
1187 Pos: diags.PositionRanges{
1188 {Line: 5, FirstColumn: 11, LastColumn: 15},
1189 },
1190 },
1191 },
1192 Labels: &parser.YamlMap{
1193 Lines: diags.LineRange{First: 6, Last: 8},
1194 Key: &parser.YamlNode{
1195 Lines: diags.LineRange{First: 6, Last: 6},
1196 Value: "labels",
1197 },
1198 Items: []*parser.YamlKeyValue{
1199 {
1200 Key: &parser.YamlNode{
1201 Lines: diags.LineRange{First: 7, Last: 7},
1202 Value: "label1",
1203 },
1204 Value: &parser.YamlNode{
1205 Lines: diags.LineRange{First: 7, Last: 7},
1206 Value: "val1",
1207 },
1208 },
1209 {
1210 Key: &parser.YamlNode{
1211 Lines: diags.LineRange{First: 8, Last: 8},
1212 Value: "label2",
1213 },
1214 Value: &parser.YamlNode{
1215 Lines: diags.LineRange{First: 8, Last: 8},
1216 Value: "val2",
1217 },
1218 },
1219 },
1220 },
1221 },
1222 },
1223 {
1224 Comments: []comments.Comment{
1225 {
1226 Type: comments.DisableType,
1227 Value: comments.Disable{Match: "foot comment"},
1228 },
1229 },
1230 Lines: diags.LineRange{First: 9, Last: 11},
1231 RecordingRule: &parser.RecordingRule{
1232 Record: parser.YamlNode{
1233 Lines: diags.LineRange{First: 9, Last: 9},
1234 Value: "name2",
1235 Pos: diags.PositionRanges{
1236 {Line: 9, FirstColumn: 13, LastColumn: 17},
1237 },
1238 },
1239 Expr: parser.PromQLExpr{
1240 Value: &parser.YamlNode{
1241 Lines: diags.LineRange{First: 10, Last: 10},
1242 Value: "expr2",
1243 Pos: diags.PositionRanges{
1244 {Line: 10, FirstColumn: 11, LastColumn: 15},
1245 },
1246 },
1247 },
1248 Labels: &parser.YamlMap{
1249 Lines: diags.LineRange{First: 11, Last: 11},
1250 Key: &parser.YamlNode{
1251 Lines: diags.LineRange{First: 11, Last: 11},
1252 Value: "labels",
1253 },
1254 Items: []*parser.YamlKeyValue{
1255 {
1256 Key: &parser.YamlNode{
1257 Lines: diags.LineRange{First: 7, Last: 7},
1258 Value: "label1",
1259 },
1260 Value: &parser.YamlNode{
1261 Lines: diags.LineRange{First: 7, Last: 7},
1262 Value: "val1",
1263 },
1264 },
1265 {
1266 Key: &parser.YamlNode{
1267 Lines: diags.LineRange{First: 8, Last: 8},
1268 Value: "label2",
1269 },
1270 Value: &parser.YamlNode{
1271 Lines: diags.LineRange{First: 8, Last: 8},
1272 Value: "val2",
1273 },
1274 },
1275 },
1276 },
1277 },
1278 },
1279 },
1280 },
1281 {
1282 content: []byte("- alert:\n expr: vector(1)\n"),
1283 output: []parser.Rule{
1284 {
1285 Lines: diags.LineRange{First: 1, Last: 2},
1286 Error: parser.ParseError{Err: errors.New("alert value cannot be empty"), Line: 1},
1287 },
1288 },
1289 },
1290 {
1291 content: []byte("- alert: foo\n expr:\n"),
1292 output: []parser.Rule{
1293 {
1294 Lines: diags.LineRange{First: 1, Last: 2},
1295 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1296 },
1297 },
1298 },
1299 {
1300 content: []byte("- alert: foo\n"),
1301 output: []parser.Rule{
1302 {
1303 Lines: diags.LineRange{First: 1, Last: 1},
1304 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1305 },
1306 },
1307 },
1308 {
1309 content: []byte("- record:\n expr:\n"),
1310 output: []parser.Rule{
1311 {
1312 Lines: diags.LineRange{First: 1, Last: 2},
1313 Error: parser.ParseError{Err: errors.New("record value cannot be empty"), Line: 1},
1314 },
1315 },
1316 },
1317 {
1318 content: []byte("- record: foo\n expr:\n"),
1319 output: []parser.Rule{
1320 {
1321 Lines: diags.LineRange{First: 1, Last: 2},
1322 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1323 },
1324 },
1325 },
1326 {
1327 content: []byte("- record: foo\n"),
1328 output: []parser.Rule{
1329 {
1330 Lines: diags.LineRange{First: 1, Last: 1},
1331 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1332 },
1333 },
1334 },
1335 {
1336 content: []byte(string(`
1337# pint file/owner bob
1338# pint ignore/begin
1339# pint ignore/end
1340# pint disable up
1341
1342- record: foo
1343 expr: up
1344
1345# pint file/owner alice
1346
1347- record: foo
1348 expr: up
1349
1350# pint ignore/next-line
1351`)),
1352 output: []parser.Rule{
1353 {
1354 Lines: diags.LineRange{First: 7, Last: 8},
1355 RecordingRule: &parser.RecordingRule{
1356 Record: parser.YamlNode{
1357 Lines: diags.LineRange{First: 7, Last: 7},
1358 Value: "foo",
1359 Pos: diags.PositionRanges{
1360 {Line: 7, FirstColumn: 11, LastColumn: 13},
1361 },
1362 },
1363 Expr: parser.PromQLExpr{
1364 Value: &parser.YamlNode{
1365 Lines: diags.LineRange{First: 8, Last: 8},
1366 Value: "up",
1367 Pos: diags.PositionRanges{
1368 {Line: 8, FirstColumn: 9, LastColumn: 10},
1369 },
1370 },
1371 },
1372 },
1373 },
1374 {
1375 Lines: diags.LineRange{First: 12, Last: 13},
1376 RecordingRule: &parser.RecordingRule{
1377 Record: parser.YamlNode{
1378 Lines: diags.LineRange{First: 12, Last: 12},
1379 Value: "foo",
1380 Pos: diags.PositionRanges{
1381 {Line: 12, FirstColumn: 11, LastColumn: 13},
1382 },
1383 },
1384 Expr: parser.PromQLExpr{
1385 Value: &parser.YamlNode{
1386 Lines: diags.LineRange{First: 13, Last: 13},
1387 Value: "up",
1388 Pos: diags.PositionRanges{
1389 {Line: 13, FirstColumn: 9, LastColumn: 10},
1390 },
1391 },
1392 },
1393 },
1394 },
1395 },
1396 },
1397 {
1398 content: []byte(string(`
1399- alert: Template
1400 expr: &expr up == 0
1401 labels:
1402 notify: &maybe_escalate_notify chat-alerts
1403- alert: Service Down
1404 expr: *expr
1405 labels:
1406 notify: *maybe_escalate_notify
1407 summary: foo
1408`)),
1409 output: []parser.Rule{
1410 {
1411 Lines: diags.LineRange{First: 2, Last: 5},
1412 AlertingRule: &parser.AlertingRule{
1413 Alert: parser.YamlNode{
1414 Lines: diags.LineRange{First: 2, Last: 2},
1415 Value: "Template",
1416 Pos: diags.PositionRanges{
1417 {Line: 2, FirstColumn: 10, LastColumn: 17},
1418 },
1419 },
1420 Expr: parser.PromQLExpr{
1421 Value: &parser.YamlNode{
1422 Lines: diags.LineRange{First: 3, Last: 3},
1423 Value: "up == 0",
1424 Pos: diags.PositionRanges{
1425 {Line: 3, FirstColumn: 15, LastColumn: 21},
1426 },
1427 },
1428 },
1429 Labels: &parser.YamlMap{
1430 Lines: diags.LineRange{First: 4, Last: 5},
1431 Key: &parser.YamlNode{
1432 Lines: diags.LineRange{First: 4, Last: 4},
1433 Value: "labels",
1434 },
1435 Items: []*parser.YamlKeyValue{
1436 {
1437 Key: &parser.YamlNode{
1438 Lines: diags.LineRange{First: 5, Last: 5},
1439 Value: "notify",
1440 },
1441 Value: &parser.YamlNode{
1442 Lines: diags.LineRange{First: 5, Last: 5},
1443 Value: "chat-alerts",
1444 },
1445 },
1446 },
1447 },
1448 },
1449 },
1450 {
1451 Lines: diags.LineRange{First: 6, Last: 10},
1452 AlertingRule: &parser.AlertingRule{
1453 Alert: parser.YamlNode{
1454 Lines: diags.LineRange{First: 6, Last: 6},
1455 Value: "Service Down",
1456 Pos: diags.PositionRanges{
1457 {Line: 6, FirstColumn: 10, LastColumn: 21},
1458 },
1459 },
1460 Expr: parser.PromQLExpr{
1461 Value: &parser.YamlNode{
1462 Lines: diags.LineRange{First: 7, Last: 7},
1463 Value: "up == 0",
1464 Pos: diags.PositionRanges{
1465 {Line: 7, FirstColumn: 10, LastColumn: 13}, // points at anchor
1466 },
1467 },
1468 },
1469 Labels: &parser.YamlMap{
1470 Lines: diags.LineRange{First: 8, Last: 10},
1471 Key: &parser.YamlNode{
1472 Lines: diags.LineRange{First: 8, Last: 8},
1473 Value: "labels",
1474 },
1475 Items: []*parser.YamlKeyValue{
1476 {
1477 Key: &parser.YamlNode{
1478 Lines: diags.LineRange{First: 9, Last: 9},
1479 Value: "notify",
1480 },
1481 Value: &parser.YamlNode{
1482 Lines: diags.LineRange{First: 9, Last: 9},
1483 Value: "chat-alerts",
1484 },
1485 },
1486 {
1487 Key: &parser.YamlNode{
1488 Lines: diags.LineRange{First: 10, Last: 10},
1489 Value: "summary",
1490 },
1491 Value: &parser.YamlNode{
1492 Lines: diags.LineRange{First: 10, Last: 10},
1493 Value: "foo",
1494 },
1495 },
1496 },
1497 },
1498 },
1499 },
1500 },
1501 },
1502 {
1503 content: []byte(`
1504- record: invalid metric name
1505 expr: bar
1506`),
1507 output: []parser.Rule{
1508 {
1509 Lines: diags.LineRange{First: 2, Last: 3},
1510 Error: parser.ParseError{Err: errors.New("invalid recording rule name: invalid metric name"), Line: 2},
1511 },
1512 },
1513 },
1514 {
1515 content: []byte(`
1516- record: utf-8 enabled name
1517 expr: bar
1518 labels:
1519 "a b c": bar
1520`),
1521 names: model.UTF8Validation,
1522 output: []parser.Rule{
1523 {
1524 Lines: diags.LineRange{First: 2, Last: 5},
1525 RecordingRule: &parser.RecordingRule{
1526 Record: parser.YamlNode{
1527 Lines: diags.LineRange{First: 2, Last: 2},
1528 Value: "utf-8 enabled name",
1529 Pos: diags.PositionRanges{
1530 {Line: 2, FirstColumn: 11, LastColumn: 28},
1531 },
1532 },
1533 Expr: parser.PromQLExpr{
1534 Value: &parser.YamlNode{
1535 Lines: diags.LineRange{First: 3, Last: 3},
1536 Value: "bar",
1537 Pos: diags.PositionRanges{
1538 {Line: 3, FirstColumn: 9, LastColumn: 11},
1539 },
1540 },
1541 },
1542 Labels: &parser.YamlMap{
1543 Key: &parser.YamlNode{
1544 Lines: diags.LineRange{First: 4, Last: 4},
1545 Value: "labels",
1546 },
1547 Items: []*parser.YamlKeyValue{
1548 {
1549 Key: &parser.YamlNode{
1550 Lines: diags.LineRange{First: 5, Last: 5},
1551 Value: "a b c",
1552 },
1553 Value: &parser.YamlNode{
1554 Lines: diags.LineRange{First: 5, Last: 5},
1555 Value: "bar",
1556 },
1557 },
1558 },
1559 Lines: diags.LineRange{First: 4, Last: 5},
1560 },
1561 },
1562 },
1563 },
1564 },
1565 {
1566 content: []byte(`
1567- record: foo
1568 expr: bar
1569 labels:
1570 "foo bar": yes
1571`),
1572 output: []parser.Rule{
1573 {
1574 Lines: diags.LineRange{First: 2, Last: 5},
1575 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
1576 },
1577 },
1578 },
1579 {
1580 content: []byte(`
1581- alert: foo
1582 expr: bar
1583 labels:
1584 "foo bar": yes
1585`),
1586 output: []parser.Rule{
1587 {
1588 Lines: diags.LineRange{First: 2, Last: 5},
1589 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
1590 },
1591 },
1592 },
1593 {
1594 content: []byte(`
1595- alert: foo
1596 expr: bar
1597 labels:
1598 "{{ $value }}": yes
1599`),
1600 output: []parser.Rule{
1601 {
1602 Lines: diags.LineRange{First: 2, Last: 5},
1603 Error: parser.ParseError{Err: errors.New("invalid label name: {{ $value }}"), Line: 5},
1604 },
1605 },
1606 },
1607 {
1608 content: []byte(`
1609- alert: foo
1610 expr: bar
1611 annotations:
1612 "foo bar": yes
1613`),
1614 output: []parser.Rule{
1615 {
1616 Lines: diags.LineRange{First: 2, Last: 5},
1617 Error: parser.ParseError{Err: errors.New("invalid annotation name: foo bar"), Line: 5},
1618 },
1619 },
1620 },
1621 {
1622 content: []byte(`
1623- alert: foo
1624 expr: bar
1625 labels:
1626 foo: ` + string("\xed\xbf\xbf")),
1627 // Label values are invalid only if they aren't valid UTF-8 strings
1628 // which also makes them unparsable by YAML.
1629 err: "error at line 1: yaml: invalid Unicode character",
1630 },
1631 {
1632 content: []byte(`
1633- alert: foo
1634 expr: bar
1635 annotations:
1636 "{{ $value }}": yes
1637`),
1638 output: []parser.Rule{
1639 {
1640 Lines: diags.LineRange{First: 2, Last: 5},
1641 Error: parser.ParseError{Err: errors.New("invalid annotation name: {{ $value }}"), Line: 5},
1642 },
1643 },
1644 },
1645 {
1646 content: []byte(`
1647- record: foo
1648 expr: bar
1649 keep_firing_for: 5m
1650`),
1651 output: []parser.Rule{
1652 {
1653 Lines: diags.LineRange{First: 2, Last: 4},
1654 Error: parser.ParseError{Err: errors.New("invalid field 'keep_firing_for' in recording rule"), Line: 4},
1655 },
1656 },
1657 },
1658 {
1659 content: []byte(`
1660- record: foo
1661 expr: bar
1662 for: 5m
1663`),
1664 output: []parser.Rule{
1665 {
1666 Lines: diags.LineRange{First: 2, Last: 4},
1667 Error: parser.ParseError{Err: errors.New("invalid field 'for' in recording rule"), Line: 4},
1668 },
1669 },
1670 },
1671 {
1672 content: []byte(`
1673- record: foo
1674 expr: bar
1675 annotations:
1676 foo: bar
1677`),
1678 output: []parser.Rule{
1679 {
1680 Lines: diags.LineRange{First: 2, Last: 5},
1681 Error: parser.ParseError{Err: errors.New("invalid field 'annotations' in recording rule"), Line: 4},
1682 },
1683 },
1684 },
1685 // Tag tests
1686 {
1687 content: []byte(`
1688- record: 5
1689 expr: bar
1690`),
1691 output: []parser.Rule{
1692 {
1693 Lines: diags.LineRange{First: 2, Last: 3},
1694 Error: parser.ParseError{Err: errors.New("record value must be a string, got integer instead"), Line: 2},
1695 },
1696 },
1697 },
1698 {
1699 content: []byte(`
1700- alert: 5
1701 expr: bar
1702`),
1703 output: []parser.Rule{
1704 {
1705 Lines: diags.LineRange{First: 2, Last: 3},
1706 Error: parser.ParseError{Err: errors.New("alert value must be a string, got integer instead"), Line: 2},
1707 },
1708 },
1709 },
1710 {
1711 content: []byte(`
1712- record: foo
1713 expr: 5
1714`),
1715 output: []parser.Rule{
1716 {
1717 Lines: diags.LineRange{First: 2, Last: 3},
1718 Error: parser.ParseError{Err: errors.New("expr value must be a string, got integer instead"), Line: 3},
1719 },
1720 },
1721 },
1722 {
1723 content: []byte(`
1724- alert: foo
1725 expr: bar
1726 for: 5
1727`),
1728 output: []parser.Rule{
1729 {
1730 Lines: diags.LineRange{First: 2, Last: 4},
1731 Error: parser.ParseError{Err: errors.New("for value must be a string, got integer instead"), Line: 4},
1732 },
1733 },
1734 },
1735 {
1736 content: []byte(`
1737- alert: foo
1738 expr: bar
1739 keep_firing_for: 5
1740`),
1741 output: []parser.Rule{
1742 {
1743 Lines: diags.LineRange{First: 2, Last: 4},
1744 Error: parser.ParseError{Err: errors.New("keep_firing_for value must be a string, got integer instead"), Line: 4},
1745 },
1746 },
1747 },
1748 {
1749 content: []byte(`
1750- record: foo
1751 expr: bar
1752 labels: []
1753`),
1754 output: []parser.Rule{
1755 {
1756 Lines: diags.LineRange{First: 2, Last: 4},
1757 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got list instead"), Line: 4},
1758 },
1759 },
1760 },
1761 {
1762 content: []byte(`
1763- alert: foo
1764 expr: bar
1765 labels: {}
1766 annotations: []
1767`),
1768 output: []parser.Rule{
1769 {
1770 Lines: diags.LineRange{First: 2, Last: 5},
1771 Error: parser.ParseError{Err: errors.New("annotations value must be a mapping, got list instead"), Line: 5},
1772 },
1773 },
1774 },
1775 {
1776 content: []byte(`
1777- alert: foo
1778 expr: bar
1779 labels:
1780 foo: 3
1781 annotations:
1782 bar: "5"
1783`),
1784 output: []parser.Rule{
1785 {
1786 Lines: diags.LineRange{First: 2, Last: 7},
1787 Error: parser.ParseError{Err: errors.New("labels foo value must be a string, got integer instead"), Line: 5},
1788 },
1789 },
1790 },
1791 {
1792 content: []byte(`
1793- alert: foo
1794 expr: bar
1795 labels: {}
1796 annotations:
1797 foo: "3"
1798 bar: 5
1799`),
1800 output: []parser.Rule{
1801 {
1802 Lines: diags.LineRange{First: 2, Last: 7},
1803 Error: parser.ParseError{Err: errors.New("annotations bar value must be a string, got integer instead"), Line: 7},
1804 },
1805 },
1806 },
1807 {
1808 content: []byte(`
1809- record: foo
1810 expr: bar
1811 labels: 4
1812`),
1813 output: []parser.Rule{
1814 {
1815 Lines: diags.LineRange{First: 2, Last: 4},
1816 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got integer instead"), Line: 4},
1817 },
1818 },
1819 },
1820 {
1821 content: []byte(`
1822- record: foo
1823 expr: bar
1824 labels: true
1825`),
1826 output: []parser.Rule{
1827 {
1828 Lines: diags.LineRange{First: 2, Last: 4},
1829 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got bool instead"), Line: 4},
1830 },
1831 },
1832 },
1833 {
1834 content: []byte(`
1835- record: foo
1836 expr: bar
1837 labels: null
1838`),
1839 output: []parser.Rule{
1840 {
1841 Lines: diags.LineRange{First: 2, Last: 4},
1842 RecordingRule: &parser.RecordingRule{
1843 Record: parser.YamlNode{
1844 Lines: diags.LineRange{First: 2, Last: 2},
1845 Value: "foo",
1846 Pos: diags.PositionRanges{
1847 {Line: 2, FirstColumn: 11, LastColumn: 13},
1848 },
1849 },
1850 Expr: parser.PromQLExpr{
1851 Value: &parser.YamlNode{
1852 Lines: diags.LineRange{First: 3, Last: 3},
1853 Value: "bar",
1854 Pos: diags.PositionRanges{
1855 {Line: 3, FirstColumn: 9, LastColumn: 11},
1856 },
1857 },
1858 },
1859 Labels: &parser.YamlMap{
1860 Lines: diags.LineRange{First: 4, Last: 4},
1861 Key: &parser.YamlNode{
1862 Lines: diags.LineRange{First: 4, Last: 4},
1863 Value: "labels",
1864 },
1865 },
1866 },
1867 },
1868 },
1869 },
1870 {
1871 content: []byte(`
1872- record: true
1873 expr: bar
1874`),
1875 output: []parser.Rule{
1876 {
1877 Lines: diags.LineRange{First: 2, Last: 3},
1878 Error: parser.ParseError{Err: errors.New("record value must be a string, got bool instead"), Line: 2},
1879 },
1880 },
1881 },
1882 {
1883 content: []byte(`
1884- record:
1885 query: foo
1886 expr: bar
1887`),
1888 output: []parser.Rule{
1889 {
1890 Lines: diags.LineRange{First: 2, Last: 4},
1891 Error: parser.ParseError{Err: errors.New("record value must be a string, got mapping instead"), Line: 3},
1892 },
1893 },
1894 },
1895 {
1896 content: []byte(`
1897- record: foo
1898 expr: bar
1899 labels: some
1900`),
1901 output: []parser.Rule{
1902 {
1903 Lines: diags.LineRange{First: 2, Last: 4},
1904 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got string instead"), Line: 4},
1905 },
1906 },
1907 },
1908 {
1909 content: []byte(`
1910- record: foo
1911 expr: bar
1912 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
1913`),
1914 output: []parser.Rule{
1915 {
1916 Lines: diags.LineRange{First: 2, Last: 4},
1917 Error: parser.ParseError{
1918 Err: errors.New("labels value must be a mapping, got binary data instead"),
1919 Line: 4,
1920 },
1921 },
1922 },
1923 },
1924 {
1925 content: []byte(`
1926- alert: foo
1927 expr: bar
1928 for: 1.23
1929`),
1930 output: []parser.Rule{
1931 {
1932 Lines: diags.LineRange{First: 2, Last: 4},
1933 Error: parser.ParseError{
1934 Err: errors.New("for value must be a string, got float instead"),
1935 Line: 4,
1936 },
1937 },
1938 },
1939 },
1940 {
1941 content: []byte(`
1942- record: foo
1943 expr: bar
1944 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
1945`),
1946 output: []parser.Rule{
1947 {
1948 Lines: diags.LineRange{First: 2, Last: 4},
1949 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got garbage instead"), Line: 4},
1950 },
1951 },
1952 },
1953 {
1954 content: []byte(`
1955- record: foo
1956 expr: bar
1957 labels: !! "SGVsbG8sIFdvcmxkIQ=="
1958`),
1959 err: "error at line 4: did not find expected tag URI",
1960 },
1961 {
1962 content: []byte(`
1963- record: &foo foo
1964 expr: bar
1965 labels: *foo
1966`),
1967 output: []parser.Rule{
1968 {
1969 Lines: diags.LineRange{First: 2, Last: 4},
1970 Error: parser.ParseError{
1971 Err: errors.New("labels value must be a mapping, got string instead"),
1972 Line: 4,
1973 },
1974 },
1975 },
1976 },
1977 // Multi-document tests
1978 {
1979 content: []byte(`---
1980- expr: foo
1981 record: foo
1982---
1983- expr: bar
1984`),
1985 output: []parser.Rule{
1986 {
1987 Lines: diags.LineRange{First: 2, Last: 3},
1988 RecordingRule: &parser.RecordingRule{
1989 Record: parser.YamlNode{
1990 Lines: diags.LineRange{First: 3, Last: 3},
1991 Value: "foo",
1992 Pos: diags.PositionRanges{
1993 {Line: 3, FirstColumn: 11, LastColumn: 13},
1994 },
1995 },
1996 Expr: parser.PromQLExpr{
1997 Value: &parser.YamlNode{
1998 Lines: diags.LineRange{First: 2, Last: 2},
1999 Value: "foo",
2000 Pos: diags.PositionRanges{
2001 {Line: 2, FirstColumn: 9, LastColumn: 11},
2002 },
2003 },
2004 },
2005 },
2006 },
2007 {
2008 Lines: diags.LineRange{First: 5, Last: 5},
2009 Error: parser.ParseError{
2010 Err: errors.New("incomplete rule, no alert or record key"),
2011 Line: 5,
2012 },
2013 },
2014 },
2015 },
2016 {
2017 content: []byte(`---
2018- expr: foo
2019 record: foo
2020---
2021- expr: bar
2022 record: bar
2023 expr: bar
2024`),
2025 output: []parser.Rule{
2026 {
2027 Lines: diags.LineRange{First: 2, Last: 3},
2028 RecordingRule: &parser.RecordingRule{
2029 Record: parser.YamlNode{
2030 Lines: diags.LineRange{First: 3, Last: 3},
2031 Value: "foo",
2032 Pos: diags.PositionRanges{
2033 {Line: 3, FirstColumn: 11, LastColumn: 13},
2034 },
2035 },
2036 Expr: parser.PromQLExpr{
2037 Value: &parser.YamlNode{
2038 Lines: diags.LineRange{First: 2, Last: 2},
2039 Value: "foo",
2040 Pos: diags.PositionRanges{
2041 {Line: 2, FirstColumn: 9, LastColumn: 11},
2042 },
2043 },
2044 },
2045 },
2046 },
2047 {
2048 Lines: diags.LineRange{First: 5, Last: 7},
2049 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 7},
2050 },
2051 },
2052 },
2053 {
2054 content: []byte(`---
2055- expr: foo
2056 record: foo
2057---
2058- expr: bar
2059 alert: foo
2060`),
2061 output: []parser.Rule{
2062 {
2063 Lines: diags.LineRange{First: 2, Last: 3},
2064 RecordingRule: &parser.RecordingRule{
2065 Record: parser.YamlNode{
2066 Lines: diags.LineRange{First: 3, Last: 3},
2067 Value: "foo",
2068 Pos: diags.PositionRanges{
2069 {Line: 3, FirstColumn: 11, LastColumn: 13},
2070 },
2071 },
2072 Expr: parser.PromQLExpr{
2073 Value: &parser.YamlNode{
2074 Lines: diags.LineRange{First: 2, Last: 2},
2075 Value: "foo",
2076 Pos: diags.PositionRanges{
2077 {Line: 2, FirstColumn: 9, LastColumn: 11},
2078 },
2079 },
2080 },
2081 },
2082 },
2083 {
2084 Lines: diags.LineRange{First: 5, Last: 6},
2085 AlertingRule: &parser.AlertingRule{
2086 Alert: parser.YamlNode{
2087 Lines: diags.LineRange{First: 6, Last: 6},
2088 Value: "foo",
2089 Pos: diags.PositionRanges{
2090 {Line: 6, FirstColumn: 10, LastColumn: 12},
2091 },
2092 },
2093 Expr: parser.PromQLExpr{
2094 Value: &parser.YamlNode{
2095 Lines: diags.LineRange{First: 5, Last: 5},
2096 Value: "bar",
2097 Pos: diags.PositionRanges{
2098 {Line: 5, FirstColumn: 9, LastColumn: 11},
2099 },
2100 },
2101 },
2102 },
2103 },
2104 },
2105 },
2106 {
2107 content: []byte(`---
2108groups:
2109- name: v1
2110 rules:
2111 - record: up:count
2112 expr: count(up)
2113 labels:
2114 foo:
2115 bar: foo
2116`),
2117 output: []parser.Rule{
2118 {
2119 Lines: diags.LineRange{First: 5, Last: 9},
2120 Error: parser.ParseError{
2121 Err: errors.New("labels foo value must be a string, got mapping instead"),
2122 Line: 9,
2123 },
2124 },
2125 },
2126 },
2127 {
2128 content: []byte(`
2129groups:
2130- name: v1
2131 rules:
2132 - record: up:count
2133 expr: count(up)
2134`),
2135 strict: true,
2136 output: []parser.Rule{
2137 {
2138 Lines: diags.LineRange{First: 5, Last: 6},
2139 RecordingRule: &parser.RecordingRule{
2140 Record: parser.YamlNode{
2141 Lines: diags.LineRange{First: 5, Last: 5},
2142 Value: "up:count",
2143 Pos: diags.PositionRanges{
2144 {Line: 5, FirstColumn: 13, LastColumn: 20},
2145 },
2146 },
2147 Expr: parser.PromQLExpr{
2148 Value: &parser.YamlNode{
2149 Lines: diags.LineRange{First: 6, Last: 6},
2150 Value: "count(up)",
2151 Pos: diags.PositionRanges{
2152 {Line: 6, FirstColumn: 11, LastColumn: 19},
2153 },
2154 },
2155 },
2156 },
2157 },
2158 },
2159 },
2160 {
2161 content: []byte(`
2162groups:
2163- name: v1
2164 rules:
2165 - record: up:count
2166`),
2167 strict: true,
2168 output: []parser.Rule{
2169 {
2170 Lines: diags.LineRange{First: 5, Last: 5},
2171 Error: parser.ParseError{
2172 Err: errors.New("missing expr key"),
2173 Line: 5,
2174 },
2175 },
2176 },
2177 },
2178 {
2179 content: []byte(`
2180- record: up:count
2181 expr: count(up)
2182`),
2183 strict: true,
2184 err: "error at line 2: top level field must be a groups key, got list",
2185 },
2186 {
2187 content: []byte(`
2188rules:
2189 - record: up:count
2190 expr: count(up)
2191`),
2192 strict: true,
2193 err: "error at line 2: unexpected key rules",
2194 },
2195 {
2196 content: []byte(`
2197groups:
2198 - record: up:count
2199 expr: count(up)
2200`),
2201 strict: true,
2202 err: "error at line 3: invalid group key record",
2203 },
2204 {
2205 content: []byte(`
2206groups:
2207- rules:
2208 - record: up:count
2209 expr: count(up)
2210`),
2211 strict: true,
2212 err: "error at line 3: incomplete group definition, name is required and must be set",
2213 },
2214 {
2215 content: []byte(`
2216groups:
2217- name: foo
2218`),
2219 strict: true,
2220 },
2221 {
2222 content: []byte(`
2223groups: {}
2224`),
2225 strict: true,
2226 err: "error at line 2: groups value must be a list, got mapping",
2227 },
2228 {
2229 content: []byte(`
2230groups:
2231- name: []
2232`),
2233 strict: true,
2234 err: "error at line 3: group name must be a string, got list",
2235 },
2236 {
2237 content: []byte(`
2238groups:
2239- name: foo
2240 name: bar
2241 name: bob
2242`),
2243 strict: true,
2244 err: "error at line 4: duplicated key name",
2245 },
2246 {
2247 content: []byte(`
2248groups:
2249- name: v1
2250 rules:
2251 rules:
2252 - record: up:count
2253 expr: count(up)
2254`),
2255 strict: true,
2256 err: "error at line 4: rules must be a list, got mapping",
2257 },
2258 {
2259 content: []byte(`
2260groups:
2261- name: v1
2262 rules:
2263 - rules:
2264 - record: up:count
2265 expr: count(up)
2266`),
2267 strict: true,
2268 err: "error at line 5: invalid rule key rules",
2269 },
2270 {
2271 content: []byte(`
2272groups:
2273- name: v1
2274 rules:
2275 - rules:
2276 - record: up:count
2277 expr: count(up)
2278`),
2279 strict: true,
2280 err: "error at line 6: found a tab character that violates indentation",
2281 },
2282 {
2283 content: []byte(`
2284---
2285groups:
2286- name: v1
2287 rules:
2288 - record: up:count
2289 expr: count(up)
2290---
2291groups:
2292- name: v1
2293 rules:
2294 - rules:
2295 - record: up:count
2296 expr: count(up)
2297`),
2298 strict: true,
2299 output: []parser.Rule{
2300 {
2301 Lines: diags.LineRange{First: 6, Last: 7},
2302 RecordingRule: &parser.RecordingRule{
2303 Record: parser.YamlNode{
2304 Lines: diags.LineRange{First: 6, Last: 6},
2305 Value: "up:count",
2306 Pos: diags.PositionRanges{
2307 {Line: 6, FirstColumn: 15, LastColumn: 22},
2308 },
2309 },
2310 Expr: parser.PromQLExpr{
2311 Value: &parser.YamlNode{
2312 Lines: diags.LineRange{First: 7, Last: 7},
2313 Value: "count(up)",
2314 Pos: diags.PositionRanges{
2315 {Line: 7, FirstColumn: 13, LastColumn: 21},
2316 },
2317 },
2318 },
2319 },
2320 },
2321 },
2322 err: "error at line 12: invalid rule key rules",
2323 },
2324 {
2325 content: []byte(`
2326---
2327groups: []
2328---
2329groups:
2330- name: foo
2331 rules:
2332 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
2333`),
2334 strict: true,
2335 output: []parser.Rule{
2336 {
2337 Lines: diags.LineRange{First: 8, Last: 8},
2338 Error: parser.ParseError{
2339 Line: 8,
2340 Err: errors.New("labels value must be a mapping, got binary data instead"),
2341 },
2342 },
2343 {
2344 Lines: diags.LineRange{First: 4, Last: 4},
2345 Error: parser.ParseError{
2346 Line: 4,
2347 Err: errors.New("multi-document YAML files are not allowed"),
2348 },
2349 },
2350 },
2351 },
2352 {
2353 content: []byte("[]"),
2354 strict: true,
2355 err: "error at line 1: top level field must be a groups key, got list",
2356 },
2357 {
2358 content: []byte("\n\n[]"),
2359 strict: true,
2360 err: "error at line 3: top level field must be a groups key, got list",
2361 },
2362 {
2363 content: []byte("groups: {}"),
2364 strict: true,
2365 err: "error at line 1: groups value must be a list, got mapping",
2366 },
2367 {
2368 content: []byte("groups: []"),
2369 strict: true,
2370 },
2371 {
2372 content: []byte("xgroups: {}"),
2373 strict: true,
2374 err: "error at line 1: unexpected key xgroups",
2375 },
2376 {
2377 content: []byte("\nbob\n"),
2378 strict: true,
2379 err: "error at line 2: top level field must be a groups key, got string",
2380 },
2381 {
2382 content: []byte(`groups: []
2383
2384rules: []
2385`),
2386 strict: true,
2387 err: "error at line 3: unexpected key rules",
2388 },
2389 {
2390 content: []byte(`
2391groups:
2392- name: foo
2393 rules: []
2394`),
2395 strict: true,
2396 },
2397 {
2398 content: []byte(`
2399groups:
2400- name:
2401 rules: []
2402`),
2403 strict: true,
2404 err: "error at line 3: group name must be a string, got null",
2405 },
2406 {
2407 content: []byte(`
2408groups:
2409- name: foo
2410 rules:
2411 - record: foo
2412 expr: sum(up)
2413 labels:
2414 job: foo
2415`),
2416 strict: true,
2417 output: []parser.Rule{
2418 {
2419 Lines: diags.LineRange{First: 5, Last: 8},
2420 RecordingRule: &parser.RecordingRule{
2421 Record: parser.YamlNode{
2422 Lines: diags.LineRange{First: 5, Last: 5},
2423 Value: "foo",
2424 Pos: diags.PositionRanges{
2425 {Line: 5, FirstColumn: 13, LastColumn: 15},
2426 },
2427 },
2428 Expr: parser.PromQLExpr{
2429 Value: &parser.YamlNode{
2430 Lines: diags.LineRange{First: 6, Last: 6},
2431 Value: "sum(up)",
2432 Pos: diags.PositionRanges{
2433 {Line: 6, FirstColumn: 11, LastColumn: 17},
2434 },
2435 },
2436 },
2437 Labels: &parser.YamlMap{
2438 Lines: diags.LineRange{First: 7, Last: 8},
2439 Key: &parser.YamlNode{
2440 Lines: diags.LineRange{First: 7, Last: 7},
2441 Value: "labels",
2442 },
2443 Items: []*parser.YamlKeyValue{
2444 {
2445 Key: &parser.YamlNode{
2446 Lines: diags.LineRange{First: 8, Last: 8},
2447 Value: "job",
2448 },
2449 Value: &parser.YamlNode{
2450 Lines: diags.LineRange{First: 8, Last: 8},
2451 Value: "foo",
2452 },
2453 },
2454 },
2455 },
2456 },
2457 },
2458 },
2459 },
2460 {
2461 content: []byte(`
2462groups:
2463- name: foo
2464 rules:
2465 - record: foo
2466 expr: sum(up)
2467 xxx: 1
2468 labels:
2469 job: foo
2470`),
2471 strict: true,
2472 err: "error at line 7: invalid rule key xxx",
2473 },
2474 {
2475 content: []byte(`
2476groups:
2477- name: foo
2478 rules:
2479 record: foo
2480 expr: sum(up)
2481 xxx: 1
2482 labels:
2483 job: foo
2484`),
2485 strict: true,
2486 err: "error at line 4: rules must be a list, got mapping",
2487 },
2488 {
2489 content: []byte(`
2490groups:
2491- name: foo
2492 rules:
2493 - record: foo
2494 expr: sum(up)
2495 labels:
2496 job:
2497 foo: bar
2498`),
2499 strict: true,
2500 output: []parser.Rule{
2501 {
2502 Lines: diags.LineRange{First: 5, Last: 9},
2503 Error: parser.ParseError{
2504 Line: 9,
2505 Err: errors.New("labels job value must be a string, got mapping instead"),
2506 },
2507 },
2508 },
2509 },
2510 {
2511 content: []byte(`
2512groups:
2513- name: foo
2514 rules:
2515 - record: foo
2516 expr:
2517 sum: sum(up)
2518`),
2519 strict: true,
2520 output: []parser.Rule{
2521 {
2522 Lines: diags.LineRange{First: 5, Last: 7},
2523 Error: parser.ParseError{
2524 Line: 7,
2525 Err: errors.New("expr value must be a string, got mapping instead"),
2526 },
2527 },
2528 },
2529 },
2530 {
2531 content: []byte(`
2532groups:
2533- name: foo
2534 rules: []
2535- name: foo
2536 rules: []
2537`),
2538 strict: true,
2539 err: "error at line 5: duplicated group name",
2540 },
2541 {
2542 content: []byte(`
2543groups:
2544- name: foo
2545 rules:
2546 - record: foo
2547 expr: sum(up)
2548 labels:
2549 foo: bob
2550 foo: bar
2551`),
2552 strict: true,
2553 output: []parser.Rule{
2554 {
2555 Lines: diags.LineRange{First: 8, Last: 9},
2556 Error: parser.ParseError{
2557 Line: 9,
2558 Err: errors.New("duplicated labels key foo"),
2559 },
2560 },
2561 },
2562 },
2563 {
2564 content: []byte(`
2565groups:
2566- name: v2
2567 rules:
2568 - record: up:count
2569 expr: count(up)
2570 expr: sum(up)`),
2571 strict: true,
2572 output: []parser.Rule{
2573 {
2574 Lines: diags.LineRange{First: 5, Last: 7},
2575 Error: parser.ParseError{
2576 Line: 7,
2577 Err: errors.New("duplicated expr key"),
2578 },
2579 },
2580 },
2581 },
2582 {
2583 content: []byte(`
2584groups:
2585- name: v2
2586 rules:
2587 - record: up:count
2588 expr: count(up)
2589bogus: 1
2590`),
2591 strict: true,
2592 err: "error at line 7: unexpected key bogus",
2593 },
2594 {
2595 content: []byte(`
2596groups:
2597- name: v2
2598 rules:
2599 - record: up:count
2600 expr: count(up)
2601 bogus: 1
2602`),
2603 strict: true,
2604 err: "error at line 7: invalid rule key bogus",
2605 },
2606 {
2607 content: []byte(`
2608groups:
2609- name: v2
2610 rules:
2611 - alert: up:count
2612 for: 5m
2613 keep_firing_for: 5m
2614 expr: count(up)
2615 labels: {}
2616 annotations: {}
2617 bogus: 1
2618`),
2619 strict: true,
2620 err: "error at line 11: invalid rule key bogus",
2621 },
2622 {
2623 content: []byte(`
2624groups:
2625
2626- name: CloudflareKafkaZookeeperExporter
2627
2628 rules:
2629`),
2630 strict: true,
2631 },
2632 {
2633 content: []byte(`
2634groups:
2635- name: foo
2636 rules:
2637 expr: 1
2638`),
2639 strict: true,
2640 err: "error at line 4: rules must be a list, got mapping",
2641 },
2642 {
2643 content: []byte(`
2644groups:
2645- name: foo
2646 rules:
2647 - expr: 1
2648`),
2649 strict: true,
2650 output: []parser.Rule{
2651 {
2652 Lines: diags.LineRange{First: 5, Last: 5},
2653 Error: parser.ParseError{
2654 Line: 5,
2655 Err: errors.New("incomplete rule, no alert or record key"),
2656 },
2657 },
2658 },
2659 },
2660 {
2661 content: []byte(`
2662groups:
2663- name: foo
2664 rules:
2665 - expr: null
2666`),
2667 strict: true,
2668 output: []parser.Rule{
2669 {
2670 Lines: diags.LineRange{First: 5, Last: 5},
2671 Error: parser.ParseError{
2672 Line: 5,
2673 Err: errors.New("incomplete rule, no alert or record key"),
2674 },
2675 },
2676 },
2677 },
2678 {
2679 content: []byte(`
2680groups:
2681- name: foo
2682 rules:
2683 - 1: null
2684`),
2685 strict: true,
2686 err: "error at line 5: invalid rule key 1",
2687 },
2688 {
2689 content: []byte(`
2690groups:
2691- name: foo
2692 rules:
2693 - true: !!binary "SGVsbG8sIFdvcmxkIQ=="
2694`),
2695 strict: true,
2696 err: "error at line 5: invalid rule key true",
2697 },
2698 {
2699 content: []byte(`
2700groups:
2701- name: foo
2702 rules:
2703 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
2704`),
2705 strict: true,
2706 output: []parser.Rule{
2707 {
2708 Lines: diags.LineRange{First: 5, Last: 5},
2709 Error: parser.ParseError{
2710 Line: 5,
2711 Err: errors.New("incomplete rule, no alert or record key"),
2712 },
2713 },
2714 },
2715 },
2716 {
2717 content: []byte(`
2718groups:
2719- name: foo
2720 rules:
2721 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
2722 record: foo
2723`),
2724 strict: true,
2725 output: []parser.Rule{
2726 {
2727 Lines: diags.LineRange{First: 5, Last: 6},
2728 Error: parser.ParseError{
2729 Line: 5,
2730 Err: errors.New("expr value must be a string, got binary data instead"),
2731 },
2732 },
2733 },
2734 },
2735 {
2736 content: []byte(`
2737groups:
2738- name: foo
2739 rules:
2740 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
2741`),
2742 strict: true,
2743 output: []parser.Rule{
2744 {
2745 Lines: diags.LineRange{First: 5, Last: 5},
2746 Error: parser.ParseError{
2747 Line: 5,
2748 Err: errors.New("labels value must be a mapping, got binary data instead"),
2749 },
2750 },
2751 },
2752 },
2753 {
2754 content: []byte(`
2755---
2756groups:
2757- name: foo
2758 rules:
2759 - record: foo
2760 expr: bar
2761---
2762groups:
2763- name: foo
2764 rules:
2765 - record: foo
2766 expr: bar
2767`),
2768 strict: true,
2769 output: []parser.Rule{
2770 {
2771 Lines: diags.LineRange{First: 6, Last: 7},
2772 RecordingRule: &parser.RecordingRule{
2773 Record: parser.YamlNode{
2774 Lines: diags.LineRange{First: 6, Last: 6},
2775 Value: "foo",
2776 Pos: diags.PositionRanges{
2777 {Line: 6, FirstColumn: 15, LastColumn: 17},
2778 },
2779 },
2780 Expr: parser.PromQLExpr{
2781 Value: &parser.YamlNode{
2782 Lines: diags.LineRange{First: 7, Last: 7},
2783 Value: "bar",
2784 Pos: diags.PositionRanges{
2785 {Line: 7, FirstColumn: 13, LastColumn: 15},
2786 },
2787 },
2788 },
2789 },
2790 },
2791 {
2792 Lines: diags.LineRange{First: 12, Last: 13},
2793 RecordingRule: &parser.RecordingRule{
2794 Record: parser.YamlNode{
2795 Lines: diags.LineRange{First: 12, Last: 12},
2796 Value: "foo",
2797 Pos: diags.PositionRanges{
2798 {Line: 12, FirstColumn: 15, LastColumn: 17},
2799 },
2800 },
2801 Expr: parser.PromQLExpr{
2802 Value: &parser.YamlNode{
2803 Lines: diags.LineRange{First: 13, Last: 13},
2804 Value: "bar",
2805 Pos: diags.PositionRanges{
2806 {Line: 13, FirstColumn: 13, LastColumn: 15},
2807 },
2808 },
2809 },
2810 },
2811 },
2812 {
2813 Lines: diags.LineRange{First: 8, Last: 8},
2814 Error: parser.ParseError{
2815 Line: 8,
2816 Err: errors.New("multi-document YAML files are not allowed"),
2817 },
2818 },
2819 },
2820 },
2821 {
2822 content: []byte(`
2823groups:
2824- name: foo
2825 rules:
2826 - record: foo
2827 expr: foo
2828 expr: foo
2829`),
2830 strict: true,
2831 output: []parser.Rule{
2832 {
2833 Lines: diags.LineRange{First: 5, Last: 7},
2834 Error: parser.ParseError{
2835 Line: 7,
2836 Err: errors.New("duplicated expr key"),
2837 },
2838 },
2839 },
2840 },
2841 {
2842 content: []byte(`
2843groups:
2844- name: foo
2845 rules:
2846 - record: foo
2847 keep_firing_for: 1m
2848 keep_firing_for: 2m
2849`),
2850 strict: true,
2851 output: []parser.Rule{
2852 {
2853 Lines: diags.LineRange{First: 5, Last: 7},
2854 Error: parser.ParseError{
2855 Line: 7,
2856 Err: errors.New("duplicated keep_firing_for key"),
2857 },
2858 },
2859 },
2860 },
2861 {
2862 content: []byte(`
2863groups:
2864- name: foo
2865 rules:
2866 - record: foo
2867 keep_firing_for: 1m
2868 record: 2m
2869`),
2870 strict: true,
2871 output: []parser.Rule{
2872 {
2873 Lines: diags.LineRange{First: 5, Last: 7},
2874 Error: parser.ParseError{
2875 Line: 7,
2876 Err: errors.New("duplicated record key"),
2877 },
2878 },
2879 },
2880 },
2881 {
2882 content: []byte(`
2883groups:
2884- name: foo
2885 rules:
2886 - []
2887`),
2888 strict: true,
2889 err: "error at line 5: rule definion must be a mapping, got list",
2890 },
2891 {
2892 content: []byte(`
28931: 0
2894`),
2895 strict: true,
2896 err: "error at line 2: groups key must be a string, got a integer",
2897 },
2898 {
2899 content: []byte(`
2900true: 0
2901`),
2902 strict: true,
2903 err: "error at line 2: groups key must be a string, got a bool",
2904 },
2905 {
2906 content: []byte(`
2907groups: !!binary "SGVsbG8sIFdvcmxkIQ=="
2908`),
2909 strict: true,
2910 err: "error at line 2: groups value must be a list, got binary data",
2911 },
2912 {
2913 content: []byte(`
2914groups:
2915 - true: null"
2916`),
2917 strict: true,
2918 err: "error at line 3: invalid group key true",
2919 },
2920 {
2921 content: []byte(`
2922!!!binary "groups": true"
2923`),
2924 strict: true,
2925 err: "error at line 2: groups key must be a string, got a binary",
2926 },
2927 {
2928 content: []byte("[]"),
2929 strict: true,
2930 err: "error at line 1: top level field must be a groups key, got list",
2931 },
2932 {
2933 content: []byte(`
2934groups:
2935 - true
2936`),
2937 strict: true,
2938 err: "error at line 3: group must be a mapping, got bool",
2939 },
2940 {
2941 content: []byte(`
2942groups:
2943 - name:
2944 rules: []
2945`),
2946 strict: true,
2947 err: "error at line 3: group name must be a string, got null",
2948 },
2949 {
2950 content: []byte(`
2951groups:
2952 - name: ""
2953 rules: []
2954`),
2955 strict: true,
2956 err: "error at line 3: group name cannot be empty",
2957 },
2958 {
2959 content: []byte(`
2960groups:
2961 - name: 1
2962 rules: []
2963`),
2964 strict: true,
2965 err: "error at line 3: group name must be a string, got integer",
2966 },
2967 {
2968 content: []byte(`
2969groups:
2970 - name: foo
2971 interval: 1
2972 rules: []
2973`),
2974 strict: true,
2975 err: "error at line 4: group interval must be a string, got integer",
2976 },
2977 {
2978 content: []byte(`
2979groups:
2980 - name: foo
2981 interval: xxx
2982 rules: []
2983`),
2984 strict: true,
2985 err: `error at line 4: invalid interval value: not a valid duration string: "xxx"`,
2986 },
2987 {
2988 content: []byte(`
2989groups:
2990 - name: foo
2991 query_offset: 1
2992 rules: []
2993`),
2994 strict: true,
2995 err: "error at line 4: group query_offset must be a string, got integer",
2996 },
2997 {
2998 content: []byte(`
2999groups:
3000 - name: foo
3001 query_offset: xxx
3002 rules: []
3003`),
3004 strict: true,
3005 err: `error at line 4: invalid query_offset value: not a valid duration string: "xxx"`,
3006 },
3007 {
3008 content: []byte(`
3009groups:
3010 - name: foo
3011 query_offset: 1m
3012 limit: abc
3013 rules: []
3014`),
3015 strict: true,
3016 err: "error at line 5: group limit must be a integer, got string",
3017 },
3018 {
3019 content: []byte(`
3020groups:
3021- name: v2
3022 rules:
3023 - alert: up:count
3024 for: 5m &timeout
3025 keep_firing_for: **timeout
3026 expr: count(up)
3027 labels: {}
3028 annotations: {}
3029 bogus: 1
3030`),
3031 strict: true,
3032 err: "error at line 7: did not find expected alphabetic or numeric character",
3033 },
3034 {
3035 content: []byte(`
3036groups:
3037- name: v2
3038 rules:
3039 - alert: up:count
3040 for: &for 1
3041 keep_firing_for: *for
3042 expr: count(up)
3043 labels: {}
3044 annotations: {}
3045`),
3046 strict: true,
3047 output: []parser.Rule{
3048 {
3049 Lines: diags.LineRange{First: 5, Last: 10},
3050 Error: parser.ParseError{
3051 Line: 6,
3052 Err: errors.New("for value must be a string, got integer instead"),
3053 },
3054 },
3055 },
3056 },
3057 {
3058 content: []byte(`
3059groups:
3060- name: v2
3061 limit: &for 1
3062 rules:
3063 - alert: up:count
3064 keep_firing_for: *for
3065 expr: count(up)
3066 labels: {}
3067 annotations: {}
3068`),
3069 strict: true,
3070 output: []parser.Rule{
3071 {
3072 Lines: diags.LineRange{First: 6, Last: 10},
3073 Error: parser.ParseError{
3074 Line: 7,
3075 Err: errors.New("keep_firing_for value must be a string, got integer instead"),
3076 },
3077 },
3078 },
3079 },
3080 {
3081 content: []byte(`
3082groups:
3083- name: "{{ source }}"
3084 rules:
3085# pint ignore/begin
3086
3087# pint ignore/end
3088`),
3089 strict: true,
3090 },
3091 {
3092 content: []byte(`
3093groups:
3094- name: foo
3095 rules:
3096 - record: foo
3097 expr: |
3098 {"up"}
3099`),
3100 output: []parser.Rule{
3101 {
3102 Lines: diags.LineRange{First: 5, Last: 7},
3103 RecordingRule: &parser.RecordingRule{
3104 Record: parser.YamlNode{
3105 Lines: diags.LineRange{First: 5, Last: 5},
3106 Value: "foo",
3107 Pos: diags.PositionRanges{
3108 {Line: 5, FirstColumn: 13, LastColumn: 15},
3109 },
3110 },
3111 Expr: parser.PromQLExpr{
3112 Value: &parser.YamlNode{
3113 Lines: diags.LineRange{First: 7, Last: 7},
3114 Value: "{\"up\"}\n",
3115 Pos: diags.PositionRanges{
3116 {Line: 7, FirstColumn: 7, LastColumn: 12},
3117 },
3118 },
3119 },
3120 },
3121 },
3122 },
3123 },
3124 {
3125 content: []byte(`
3126groups:
3127- name: foo
3128 rules:
3129 - record: foo
3130 expr: |
3131 {'up'}
3132`),
3133 output: []parser.Rule{
3134 {
3135 Lines: diags.LineRange{First: 5, Last: 7},
3136 RecordingRule: &parser.RecordingRule{
3137 Record: parser.YamlNode{
3138 Lines: diags.LineRange{First: 5, Last: 5},
3139 Value: "foo",
3140 Pos: diags.PositionRanges{
3141 {Line: 5, FirstColumn: 13, LastColumn: 15},
3142 },
3143 },
3144 Expr: parser.PromQLExpr{
3145 Value: &parser.YamlNode{
3146 Lines: diags.LineRange{First: 7, Last: 7},
3147 Value: "{'up'}\n",
3148 Pos: diags.PositionRanges{
3149 {Line: 7, FirstColumn: 7, LastColumn: 12},
3150 },
3151 },
3152 },
3153 },
3154 },
3155 },
3156 },
3157 {
3158 content: []byte(`
3159groups:
3160- name: foo
3161 rules:
3162 - record: foo
3163 expr: |
3164 {'up' == 1}
3165`),
3166 output: []parser.Rule{
3167 {
3168 Lines: diags.LineRange{First: 5, Last: 7},
3169 RecordingRule: &parser.RecordingRule{
3170 Record: parser.YamlNode{
3171 Lines: diags.LineRange{First: 5, Last: 5},
3172 Value: "foo",
3173 Pos: diags.PositionRanges{
3174 {Line: 5, FirstColumn: 13, LastColumn: 15},
3175 },
3176 },
3177 Expr: parser.PromQLExpr{
3178 Value: &parser.YamlNode{
3179 Lines: diags.LineRange{First: 7, Last: 7},
3180 Value: "{'up' == 1}\n",
3181 Pos: diags.PositionRanges{
3182 {Line: 7, FirstColumn: 7, LastColumn: 17},
3183 },
3184 },
3185 SyntaxError: errors.New(`1:8: parse error: unexpected "=" in label matching, expected string`),
3186 },
3187 },
3188 },
3189 },
3190 },
3191 {
3192 content: []byte(`
3193groups:
3194- name: mygroup
3195 partial_response_strategy: bob
3196 rules:
3197 - record: up:count
3198 expr: count(up)
3199`),
3200 strict: true,
3201 err: "error at line 4: partial_response_strategy is only valid when parser is configured to use the Thanos rule schema",
3202 },
3203 {
3204 content: []byte(`
3205groups:
3206- name: mygroup
3207 partial_response_strategy: warn
3208 rules:
3209 - record: up:count
3210 expr: count(up)
3211`),
3212 strict: true,
3213 schema: parser.ThanosSchema,
3214 output: []parser.Rule{
3215 {
3216 Lines: diags.LineRange{First: 6, Last: 7},
3217 RecordingRule: &parser.RecordingRule{
3218 Record: parser.YamlNode{
3219 Lines: diags.LineRange{First: 6, Last: 6},
3220 Value: "up:count",
3221 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
3222 },
3223 Expr: parser.PromQLExpr{
3224 Value: &parser.YamlNode{
3225 Lines: diags.LineRange{First: 7, Last: 7},
3226 Value: "count(up)",
3227 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
3228 },
3229 },
3230 },
3231 },
3232 },
3233 },
3234 {
3235 content: []byte(`
3236groups:
3237- name: mygroup
3238 partial_response_strategy: abort
3239 rules:
3240 - record: up:count
3241 expr: count(up)
3242`),
3243 strict: true,
3244 schema: parser.ThanosSchema,
3245 output: []parser.Rule{
3246 {
3247 Lines: diags.LineRange{First: 6, Last: 7},
3248 RecordingRule: &parser.RecordingRule{
3249 Record: parser.YamlNode{
3250 Lines: diags.LineRange{First: 6, Last: 6},
3251 Value: "up:count",
3252 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
3253 },
3254 Expr: parser.PromQLExpr{
3255 Value: &parser.YamlNode{
3256 Lines: diags.LineRange{First: 7, Last: 7},
3257 Value: "count(up)",
3258 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
3259 },
3260 },
3261 },
3262 },
3263 },
3264 },
3265 {
3266 content: []byte(`
3267groups:
3268- name: mygroup
3269 partial_response_strategy: abort
3270 rules:
3271 - record: up:count
3272 expr: count(up)
3273`),
3274 strict: false,
3275 schema: parser.PrometheusSchema,
3276 output: []parser.Rule{
3277 {
3278 Lines: diags.LineRange{First: 6, Last: 7},
3279 RecordingRule: &parser.RecordingRule{
3280 Record: parser.YamlNode{
3281 Lines: diags.LineRange{First: 6, Last: 6},
3282 Value: "up:count",
3283 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 13, LastColumn: 20}},
3284 },
3285 Expr: parser.PromQLExpr{
3286 Value: &parser.YamlNode{
3287 Lines: diags.LineRange{First: 7, Last: 7},
3288 Value: "count(up)",
3289 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 11, LastColumn: 19}},
3290 },
3291 },
3292 },
3293 },
3294 },
3295 },
3296 {
3297 content: []byte(`
3298groups:
3299- name: mygroup
3300 partial_response_strategy: bob
3301 rules:
3302 - record: up:count
3303 expr: count(up)
3304`),
3305 strict: true,
3306 schema: parser.ThanosSchema,
3307 err: "error at line 4: invalid partial_response_strategy value: bob",
3308 },
3309 {
3310 content: []byte(`
3311groups:
3312- name: mygroup
3313 partial_response_strategy: 1
3314 rules:
3315 - record: up:count
3316 expr: count(up)
3317`),
3318 strict: true,
3319 schema: parser.ThanosSchema,
3320 err: "error at line 4: partial_response_strategy must be a string, got integer",
3321 },
3322 {
3323 content: []byte(`
3324- alert: Multi Line
3325 expr: foo
3326 AND ON (instance)
3327 bar
3328`),
3329 strict: false,
3330 schema: parser.PrometheusSchema,
3331 output: []parser.Rule{
3332 {
3333 Lines: diags.LineRange{First: 2, Last: 5},
3334 AlertingRule: &parser.AlertingRule{
3335 Alert: parser.YamlNode{
3336 Lines: diags.LineRange{First: 2, Last: 2},
3337 Value: "Multi Line",
3338 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 10, LastColumn: 19}},
3339 },
3340 Expr: parser.PromQLExpr{
3341 Value: &parser.YamlNode{
3342 Lines: diags.LineRange{First: 3, Last: 5},
3343 Value: "foo AND ON (instance) bar",
3344 Pos: diags.PositionRanges{
3345 {Line: 3, FirstColumn: 9, LastColumn: 12},
3346 {Line: 4, FirstColumn: 11, LastColumn: 28},
3347 {Line: 5, FirstColumn: 11, LastColumn: 13},
3348 },
3349 },
3350 },
3351 },
3352 },
3353 },
3354 },
3355 {
3356 content: []byte(`
3357 - alert: FooBar
3358 expr: >-
3359 count(
3360 foo
3361 or
3362 bar
3363 ) > 0
3364`),
3365 strict: false,
3366 schema: parser.PrometheusSchema,
3367 output: []parser.Rule{
3368 {
3369 Lines: diags.LineRange{First: 2, Last: 8},
3370 AlertingRule: &parser.AlertingRule{
3371 Alert: parser.YamlNode{
3372 Lines: diags.LineRange{First: 2, Last: 2},
3373 Value: "FooBar",
3374 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
3375 },
3376 Expr: parser.PromQLExpr{
3377 Value: &parser.YamlNode{
3378 Lines: diags.LineRange{First: 4, Last: 8},
3379 Value: "count(\n foo\n or\n bar\n) > 0",
3380 Pos: diags.PositionRanges{
3381 {Line: 4, FirstColumn: 7, LastColumn: 13},
3382 {Line: 5, FirstColumn: 7, LastColumn: 12},
3383 {Line: 6, FirstColumn: 7, LastColumn: 11},
3384 {Line: 7, FirstColumn: 7, LastColumn: 12},
3385 {Line: 8, FirstColumn: 7, LastColumn: 11},
3386 },
3387 },
3388 },
3389 },
3390 },
3391 },
3392 },
3393 {
3394 content: []byte(`
3395 - alert: FooBar
3396 expr: >-
3397 aaaaaaaaaaaaaaaaaaaaaaaa
3398 AND ON (colo_id) bbbbbbbbbbb
3399 > 2
3400 for: 1m
3401`),
3402 strict: false,
3403 schema: parser.PrometheusSchema,
3404 output: []parser.Rule{
3405 {
3406 Lines: diags.LineRange{First: 2, Last: 7},
3407 AlertingRule: &parser.AlertingRule{
3408 Alert: parser.YamlNode{
3409 Lines: diags.LineRange{First: 2, Last: 2},
3410 Value: "FooBar",
3411 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
3412 },
3413 Expr: parser.PromQLExpr{
3414 Value: &parser.YamlNode{
3415 Lines: diags.LineRange{First: 4, Last: 6},
3416 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
3417 Pos: diags.PositionRanges{
3418 {Line: 4, FirstColumn: 7, LastColumn: 31},
3419 {Line: 5, FirstColumn: 7, LastColumn: 35},
3420 {Line: 6, FirstColumn: 7, LastColumn: 9},
3421 },
3422 },
3423 },
3424 For: &parser.YamlNode{
3425 Lines: diags.LineRange{First: 7, Last: 7},
3426 Value: "1m",
3427 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 10, LastColumn: 11}},
3428 },
3429 },
3430 },
3431 },
3432 },
3433 {
3434 content: []byte(`
3435 - alert: FooBar
3436 expr: 'aaaaaaaaaaaaaaaaaaaaaaaa
3437 AND ON (colo_id) bbbbbbbbbbb
3438 > 2'
3439 for: 1m
3440`),
3441 strict: false,
3442 schema: parser.PrometheusSchema,
3443 output: []parser.Rule{
3444 {
3445 Lines: diags.LineRange{First: 2, Last: 6},
3446 AlertingRule: &parser.AlertingRule{
3447 Alert: parser.YamlNode{
3448 Lines: diags.LineRange{First: 2, Last: 2},
3449 Value: "FooBar",
3450 Pos: diags.PositionRanges{{Line: 2, FirstColumn: 12, LastColumn: 17}},
3451 },
3452 Expr: parser.PromQLExpr{
3453 Value: &parser.YamlNode{
3454 Lines: diags.LineRange{First: 3, Last: 5},
3455 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
3456 Pos: diags.PositionRanges{
3457 {Line: 3, FirstColumn: 12, LastColumn: 36},
3458 {Line: 4, FirstColumn: 11, LastColumn: 39},
3459 {Line: 5, FirstColumn: 11, LastColumn: 13},
3460 },
3461 },
3462 },
3463 For: &parser.YamlNode{
3464 Lines: diags.LineRange{First: 6, Last: 6},
3465 Value: "1m",
3466 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 10, LastColumn: 11}},
3467 },
3468 },
3469 },
3470 },
3471 },
3472 {
3473 content: []byte(`
3474groups:
3475- name: foo
3476 rules:
3477 - record: colo:foo:sum
3478 expr: sum without (instance) ( rate(my_metric[2m]) * on (instance)
3479 group_left (hardware_generation, hms_scope, sliver) (instance:metadata{})
3480 )
3481`),
3482 strict: false,
3483 schema: parser.PrometheusSchema,
3484 output: []parser.Rule{
3485 {
3486 Lines: diags.LineRange{First: 5, Last: 8},
3487 RecordingRule: &parser.RecordingRule{
3488 Record: parser.YamlNode{
3489 Lines: diags.LineRange{First: 5, Last: 5},
3490 Value: "colo:foo:sum",
3491 Pos: diags.PositionRanges{{Line: 5, FirstColumn: 13, LastColumn: 24}},
3492 },
3493 Expr: parser.PromQLExpr{
3494 Value: &parser.YamlNode{
3495 Lines: diags.LineRange{First: 6, Last: 8},
3496 Value: "sum without (instance) ( rate(my_metric[2m]) * on (instance) group_left (hardware_generation, hms_scope, sliver) (instance:metadata{}) )",
3497 Pos: diags.PositionRanges{
3498 {Line: 6, FirstColumn: 11, LastColumn: 71},
3499 {Line: 7, FirstColumn: 7, LastColumn: 80},
3500 {Line: 8, FirstColumn: 7, LastColumn: 7},
3501 },
3502 },
3503 },
3504 },
3505 },
3506 },
3507 },
3508 {
3509 content: []byte(`
3510groups:
3511- name: "{{ source }}"
3512 rules:
3513 # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3514 # BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
3515 - record: some_long_name
3516 labels:
3517 metricssource: receiver
3518 expr:
3519 clamp_max(
3520 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) /
3521 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)),
3522 2
3523 )
3524`),
3525 strict: true,
3526 schema: parser.PrometheusSchema,
3527 output: []parser.Rule{
3528 {
3529 Lines: diags.LineRange{First: 7, Last: 15},
3530 RecordingRule: &parser.RecordingRule{
3531 Record: parser.YamlNode{
3532 Lines: diags.LineRange{First: 7, Last: 7},
3533 Value: "some_long_name",
3534 Pos: diags.PositionRanges{{Line: 7, FirstColumn: 13, LastColumn: 26}},
3535 },
3536 Labels: &parser.YamlMap{
3537 Lines: diags.LineRange{First: 8, Last: 9},
3538
3539 Key: &parser.YamlNode{
3540 Lines: diags.LineRange{First: 8, Last: 8},
3541 Value: "labels",
3542 },
3543 Items: []*parser.YamlKeyValue{
3544 {
3545 Key: &parser.YamlNode{
3546 Lines: diags.LineRange{First: 9, Last: 9},
3547 Value: "metricssource",
3548 },
3549 Value: &parser.YamlNode{
3550 Lines: diags.LineRange{First: 9, Last: 9},
3551 Value: "receiver",
3552 },
3553 },
3554 },
3555 },
3556 Expr: parser.PromQLExpr{
3557 Value: &parser.YamlNode{
3558 Lines: diags.LineRange{First: 11, Last: 15},
3559 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 )`,
3560 Pos: diags.PositionRanges{
3561 {Line: 11, FirstColumn: 7, LastColumn: 17},
3562 {Line: 12, FirstColumn: 9, LastColumn: 129},
3563 {Line: 13, FirstColumn: 9, LastColumn: 139},
3564 {Line: 14, FirstColumn: 9, LastColumn: 10},
3565 {Line: 15, FirstColumn: 7, LastColumn: 7},
3566 },
3567 },
3568 },
3569 },
3570 },
3571 },
3572 },
3573 {
3574 content: []byte(`
3575groups:
3576- name: "{{ source }}"
3577 rules:
3578
3579 - alert: Director_Is_Not_Advertising_Any_Routes
3580 expr: |
3581 sum without (name) (
3582 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
3583 * on (instance) group_left (profile,cluster)
3584 cf_node_role{kubernetes_role="director",role="kubernetes"}
3585 ) <= 0
3586 for: 1m
3587`),
3588 strict: true,
3589 schema: parser.PrometheusSchema,
3590 output: []parser.Rule{
3591 {
3592 Lines: diags.LineRange{First: 6, Last: 13},
3593 AlertingRule: &parser.AlertingRule{
3594 Alert: parser.YamlNode{
3595 Lines: diags.LineRange{First: 6, Last: 6},
3596 Value: "Director_Is_Not_Advertising_Any_Routes",
3597 Pos: diags.PositionRanges{{Line: 6, FirstColumn: 12, LastColumn: 49}},
3598 },
3599 Expr: parser.PromQLExpr{
3600 Value: &parser.YamlNode{
3601 Lines: diags.LineRange{First: 8, Last: 12},
3602 Value: `sum without (name) (
3603 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
3604 * on (instance) group_left (profile,cluster)
3605 cf_node_role{kubernetes_role="director",role="kubernetes"}
3606) <= 0
3607`,
3608 Pos: diags.PositionRanges{
3609 {Line: 8, FirstColumn: 9, LastColumn: 29},
3610 {Line: 9, FirstColumn: 9, LastColumn: 99},
3611 {Line: 10, FirstColumn: 9, LastColumn: 55},
3612 {Line: 11, FirstColumn: 9, LastColumn: 71},
3613 {Line: 12, FirstColumn: 9, LastColumn: 14},
3614 },
3615 },
3616 },
3617 For: &parser.YamlNode{
3618 Lines: diags.LineRange{First: 13, Last: 13},
3619 Value: "1m",
3620 Pos: diags.PositionRanges{{Line: 13, FirstColumn: 10, LastColumn: 11}},
3621 },
3622 },
3623 },
3624 },
3625 },
3626 }
3627
3628 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
3629 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
3630 _, xe := x.(*parser.PromQLNode)
3631 _, ye := y.(*parser.PromQLNode)
3632 return xe || ye
3633 }, alwaysEqual)
3634
3635 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
3636 xe := x.(error)
3637 ye := y.(error)
3638 return xe.Error() == ye.Error()
3639 })
3640 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
3641 _, xe := x.(error)
3642 _, ye := y.(error)
3643 return xe && ye
3644 }, cmpErrorText)
3645
3646 for i, tc := range testCases {
3647 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
3648 t.Logf("\n--- Content ---%s--- END ---", tc.content)
3649
3650 p := parser.NewParser(tc.strict, tc.schema, tc.names)
3651 output, err := p.Parse(tc.content)
3652
3653 if tc.err != "" {
3654 require.EqualError(t, err, tc.err)
3655 } else {
3656 require.NoError(t, err)
3657 }
3658
3659 if diff := cmp.Diff(tc.output, output,
3660 ignorePrometheusExpr,
3661 sameErrorText,
3662 cmpopts.IgnoreFields(parser.YamlNode{}, "Pos"), // FIXME remove?
3663 ); diff != "" {
3664 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
3665 return
3666 }
3667 })
3668 }
3669}
3670