cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

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