cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.69.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

3048lines · modecode

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