1
2
3
4
5
6
7
8
9
10
11
12
13
14 package config
15
16 import (
17 "encoding/json"
18 "net/url"
19 "os"
20 "reflect"
21 "regexp"
22 "strings"
23 "testing"
24 "time"
25
26 commoncfg "github.com/prometheus/common/config"
27 "github.com/prometheus/common/model"
28 "github.com/stretchr/testify/require"
29 "gopkg.in/yaml.v2"
30 )
31
32 func TestLoadEmptyString(t *testing.T) {
33 var in string
34 _, err := Load(in)
35
36 expected := "no route provided in config"
37
38 if err == nil {
39 t.Fatalf("no error returned, expected:\n%v", expected)
40 }
41 if err.Error() != expected {
42 t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
43 }
44 }
45
46 func TestDefaultReceiverExists(t *testing.T) {
47 in := `
48 route:
49 group_wait: 30s
50 `
51 _, err := Load(in)
52
53 expected := "root route must specify a default receiver"
54
55 if err == nil {
56 t.Fatalf("no error returned, expected:\n%v", expected)
57 }
58 if err.Error() != expected {
59 t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
60 }
61 }
62
63 func TestReceiverNameIsUnique(t *testing.T) {
64 in := `
65 route:
66 receiver: team-X
67
68 receivers:
69 - name: 'team-X'
70 - name: 'team-X'
71 `
72 _, err := Load(in)
73
74 expected := "notification config name \"team-X\" is not unique"
75
76 if err == nil {
77 t.Fatalf("no error returned, expected:\n%q", expected)
78 }
79 if err.Error() != expected {
80 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
81 }
82 }
83
84 func TestReceiverExists(t *testing.T) {
85 in := `
86 route:
87 receiver: team-X
88
89 receivers:
90 - name: 'team-Y'
91 `
92 _, err := Load(in)
93
94 expected := "undefined receiver \"team-X\" used in route"
95
96 if err == nil {
97 t.Fatalf("no error returned, expected:\n%q", expected)
98 }
99 if err.Error() != expected {
100 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
101 }
102 }
103
104 func TestReceiverExistsForDeepSubRoute(t *testing.T) {
105 in := `
106 route:
107 receiver: team-X
108 routes:
109 - match:
110 foo: bar
111 routes:
112 - match:
113 foo: bar
114 receiver: nonexistent
115
116 receivers:
117 - name: 'team-X'
118 `
119 _, err := Load(in)
120
121 expected := "undefined receiver \"nonexistent\" used in route"
122
123 if err == nil {
124 t.Fatalf("no error returned, expected:\n%q", expected)
125 }
126 if err.Error() != expected {
127 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
128 }
129 }
130
131 func TestReceiverHasName(t *testing.T) {
132 in := `
133 route:
134
135 receivers:
136 - name: ''
137 `
138 _, err := Load(in)
139
140 expected := "missing name in receiver"
141
142 if err == nil {
143 t.Fatalf("no error returned, expected:\n%q", expected)
144 }
145 if err.Error() != expected {
146 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
147 }
148 }
149
150 func TestMuteTimeExists(t *testing.T) {
151 in := `
152 route:
153 receiver: team-Y
154 routes:
155 - match:
156 severity: critical
157 mute_time_intervals:
158 - business_hours
159
160 receivers:
161 - name: 'team-Y'
162 `
163 _, err := Load(in)
164
165 expected := "undefined time interval \"business_hours\" used in route"
166
167 if err == nil {
168 t.Fatalf("no error returned, expected:\n%q", expected)
169 }
170 if err.Error() != expected {
171 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
172 }
173 }
174
175 func TestActiveTimeExists(t *testing.T) {
176 in := `
177 route:
178 receiver: team-Y
179 routes:
180 - match:
181 severity: critical
182 active_time_intervals:
183 - business_hours
184
185 receivers:
186 - name: 'team-Y'
187 `
188 _, err := Load(in)
189
190 expected := "undefined time interval \"business_hours\" used in route"
191
192 if err == nil {
193 t.Fatalf("no error returned, expected:\n%q", expected)
194 }
195 if err.Error() != expected {
196 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
197 }
198 }
199
200 func TestTimeIntervalHasName(t *testing.T) {
201 in := `
202 time_intervals:
203 - name:
204 time_intervals:
205 - times:
206 - start_time: '09:00'
207 end_time: '17:00'
208
209 receivers:
210 - name: 'team-X-mails'
211
212 route:
213 receiver: 'team-X-mails'
214 routes:
215 - match:
216 severity: critical
217 mute_time_intervals:
218 - business_hours
219 `
220 _, err := Load(in)
221
222 expected := "missing name in time interval"
223
224 if err == nil {
225 t.Fatalf("no error returned, expected:\n%q", expected)
226 }
227 if err.Error() != expected {
228 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
229 }
230 }
231
232 func TestMuteTimeNoDuplicates(t *testing.T) {
233 in := `
234 mute_time_intervals:
235 - name: duplicate
236 time_intervals:
237 - times:
238 - start_time: '09:00'
239 end_time: '17:00'
240 - name: duplicate
241 time_intervals:
242 - times:
243 - start_time: '10:00'
244 end_time: '14:00'
245
246 receivers:
247 - name: 'team-X-mails'
248
249 route:
250 receiver: 'team-X-mails'
251 routes:
252 - match:
253 severity: critical
254 mute_time_intervals:
255 - business_hours
256 `
257 _, err := Load(in)
258
259 expected := "mute time interval \"duplicate\" is not unique"
260
261 if err == nil {
262 t.Fatalf("no error returned, expected:\n%q", expected)
263 }
264 if err.Error() != expected {
265 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
266 }
267 }
268
269 func TestGroupByHasNoDuplicatedLabels(t *testing.T) {
270 in := `
271 route:
272 group_by: ['alertname', 'cluster', 'service', 'cluster']
273
274 receivers:
275 - name: 'team-X-mails'
276 `
277 _, err := Load(in)
278
279 expected := "duplicated label \"cluster\" in group_by"
280
281 if err == nil {
282 t.Fatalf("no error returned, expected:\n%q", expected)
283 }
284 if err.Error() != expected {
285 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
286 }
287 }
288
289 func TestWildcardGroupByWithOtherGroupByLabels(t *testing.T) {
290 in := `
291 route:
292 group_by: ['alertname', 'cluster', '...']
293 receiver: team-X-mails
294 receivers:
295 - name: 'team-X-mails'
296 `
297 _, err := Load(in)
298
299 expected := "cannot have wildcard group_by (`...`) and other other labels at the same time"
300
301 if err == nil {
302 t.Fatalf("no error returned, expected:\n%q", expected)
303 }
304 if err.Error() != expected {
305 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
306 }
307 }
308
309 func TestGroupByInvalidLabel(t *testing.T) {
310 in := `
311 route:
312 group_by: ['-invalid-']
313 receiver: team-X-mails
314 receivers:
315 - name: 'team-X-mails'
316 `
317 _, err := Load(in)
318
319 expected := "invalid label name \"-invalid-\" in group_by list"
320
321 if err == nil {
322 t.Fatalf("no error returned, expected:\n%q", expected)
323 }
324 if err.Error() != expected {
325 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
326 }
327 }
328
329 func TestRootRouteExists(t *testing.T) {
330 in := `
331 receivers:
332 - name: 'team-X-mails'
333 `
334 _, err := Load(in)
335
336 expected := "no routes provided"
337
338 if err == nil {
339 t.Fatalf("no error returned, expected:\n%q", expected)
340 }
341 if err.Error() != expected {
342 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
343 }
344 }
345
346 func TestRootRouteNoMuteTimes(t *testing.T) {
347 in := `
348 mute_time_intervals:
349 - name: my_mute_time
350 time_intervals:
351 - times:
352 - start_time: '09:00'
353 end_time: '17:00'
354
355 receivers:
356 - name: 'team-X-mails'
357
358 route:
359 receiver: 'team-X-mails'
360 mute_time_intervals:
361 - my_mute_time
362 `
363 _, err := Load(in)
364
365 expected := "root route must not have any mute time intervals"
366
367 if err == nil {
368 t.Fatalf("no error returned, expected:\n%q", expected)
369 }
370 if err.Error() != expected {
371 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
372 }
373 }
374
375 func TestRootRouteNoActiveTimes(t *testing.T) {
376 in := `
377 time_intervals:
378 - name: my_active_time
379 time_intervals:
380 - times:
381 - start_time: '09:00'
382 end_time: '17:00'
383
384 receivers:
385 - name: 'team-X-mails'
386
387 route:
388 receiver: 'team-X-mails'
389 active_time_intervals:
390 - my_active_time
391 `
392 _, err := Load(in)
393
394 expected := "root route must not have any active time intervals"
395
396 if err == nil {
397 t.Fatalf("no error returned, expected:\n%q", expected)
398 }
399 if err.Error() != expected {
400 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
401 }
402 }
403
404 func TestRootRouteHasNoMatcher(t *testing.T) {
405 testCases := []struct {
406 name string
407 in string
408 }{
409 {
410 name: "Test deprecated matchers on root route not allowed",
411 in: `
412 route:
413 receiver: 'team-X'
414 match:
415 severity: critical
416 receivers:
417 - name: 'team-X'
418 `,
419 },
420 {
421 name: "Test matchers not allowed on root route",
422 in: `
423 route:
424 receiver: 'team-X'
425 matchers:
426 - severity=critical
427 receivers:
428 - name: 'team-X'
429 `,
430 },
431 }
432 expected := "root route must not have any matchers"
433
434 for _, tc := range testCases {
435 t.Run(tc.name, func(t *testing.T) {
436 _, err := Load(tc.in)
437
438 if err == nil {
439 t.Fatalf("no error returned, expected:\n%q", expected)
440 }
441 if err.Error() != expected {
442 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
443 }
444 })
445 }
446 }
447
448 func TestContinueErrorInRouteRoot(t *testing.T) {
449 in := `
450 route:
451 receiver: team-X-mails
452 continue: true
453
454 receivers:
455 - name: 'team-X-mails'
456 `
457 _, err := Load(in)
458
459 expected := "cannot have continue in root route"
460
461 if err == nil {
462 t.Fatalf("no error returned, expected:\n%q", expected)
463 }
464 if err.Error() != expected {
465 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
466 }
467 }
468
469 func TestGroupIntervalIsGreaterThanZero(t *testing.T) {
470 in := `
471 route:
472 receiver: team-X-mails
473 group_interval: 0s
474
475 receivers:
476 - name: 'team-X-mails'
477 `
478 _, err := Load(in)
479
480 expected := "group_interval cannot be zero"
481
482 if err == nil {
483 t.Fatalf("no error returned, expected:\n%q", expected)
484 }
485 if err.Error() != expected {
486 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
487 }
488 }
489
490 func TestRepeatIntervalIsGreaterThanZero(t *testing.T) {
491 in := `
492 route:
493 receiver: team-X-mails
494 repeat_interval: 0s
495
496 receivers:
497 - name: 'team-X-mails'
498 `
499 _, err := Load(in)
500
501 expected := "repeat_interval cannot be zero"
502
503 if err == nil {
504 t.Fatalf("no error returned, expected:\n%q", expected)
505 }
506 if err.Error() != expected {
507 t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
508 }
509 }
510
511 func TestHideConfigSecrets(t *testing.T) {
512 c, err := LoadFile("testdata/conf.good.yml")
513 if err != nil {
514 t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
515 }
516
517
518 s := c.String()
519 if strings.Count(s, "<secret>") != 13 || strings.Contains(s, "mysecret") {
520 t.Fatal("config's String method reveals authentication credentials.")
521 }
522 }
523
524 func TestJSONMarshal(t *testing.T) {
525 c, err := LoadFile("testdata/conf.good.yml")
526 if err != nil {
527 t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
528 }
529
530 _, err = json.Marshal(c)
531 if err != nil {
532 t.Fatal("JSON Marshaling failed:", err)
533 }
534 }
535
536 func TestJSONMarshalSecret(t *testing.T) {
537 test := struct {
538 S Secret
539 }{
540 S: Secret("test"),
541 }
542
543 c, err := json.Marshal(test)
544 if err != nil {
545 t.Fatal(err)
546 }
547
548
549
550 require.Equal(t, "{\"S\":\"\\u003csecret\\u003e\"}", string(c), "Secret not properly elided.")
551 }
552
553 func TestMarshalSecretURL(t *testing.T) {
554 urlp, err := url.Parse("http://example.com/")
555 if err != nil {
556 t.Fatal(err)
557 }
558 u := &SecretURL{urlp}
559
560 c, err := json.Marshal(u)
561 if err != nil {
562 t.Fatal(err)
563 }
564
565
566 require.Equal(t, "\"\\u003csecret\\u003e\"", string(c), "SecretURL not properly elided in JSON.")
567
568 out := &SecretURL{}
569 err = json.Unmarshal(c, out)
570 if err != nil {
571 t.Fatal(err)
572 }
573
574 c, err = yaml.Marshal(u)
575 if err != nil {
576 t.Fatal(err)
577 }
578 require.Equal(t, "<secret>\n", string(c), "SecretURL not properly elided in YAML.")
579
580 out = &SecretURL{}
581 err = yaml.Unmarshal(c, &out)
582 if err != nil {
583 t.Fatal(err)
584 }
585 }
586
587 func TestUnmarshalSecretURL(t *testing.T) {
588 b := []byte(`"http://example.com/se cret"`)
589 var u SecretURL
590
591 err := json.Unmarshal(b, &u)
592 if err != nil {
593 t.Fatal(err)
594 }
595 require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in JSON.")
596
597 err = yaml.Unmarshal(b, &u)
598 if err != nil {
599 t.Fatal(err)
600 }
601
602 require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.")
603 }
604
605 func TestMarshalURL(t *testing.T) {
606 for name, tc := range map[string]struct {
607 input *URL
608 expectedJSON string
609 expectedYAML string
610 }{
611 "url": {
612 input: mustParseURL("http://example.com/"),
613 expectedJSON: "\"http://example.com/\"",
614 expectedYAML: "http://example.com/\n",
615 },
616
617 "wrapped nil value": {
618 input: &URL{},
619 expectedJSON: "null",
620 expectedYAML: "null\n",
621 },
622
623 "wrapped empty URL": {
624 input: &URL{&url.URL{}},
625 expectedJSON: "\"\"",
626 expectedYAML: "\"\"\n",
627 },
628 } {
629 t.Run(name, func(t *testing.T) {
630 j, err := json.Marshal(tc.input)
631 require.NoError(t, err)
632 require.Equal(t, tc.expectedJSON, string(j), "URL not properly marshaled into JSON.")
633
634 y, err := yaml.Marshal(tc.input)
635 require.NoError(t, err)
636 require.Equal(t, tc.expectedYAML, string(y), "URL not properly marshaled into YAML.")
637 })
638 }
639 }
640
641 func TestUnmarshalNilURL(t *testing.T) {
642 b := []byte(`null`)
643
644 {
645 var u URL
646 err := json.Unmarshal(b, &u)
647 require.Error(t, err, "unsupported scheme \"\" for URL")
648 require.Nil(t, nil, u.URL)
649 }
650
651 {
652 var u URL
653 err := yaml.Unmarshal(b, &u)
654 require.NoError(t, err)
655 require.Nil(t, nil, u.URL)
656 }
657 }
658
659 func TestUnmarshalEmptyURL(t *testing.T) {
660 b := []byte(`""`)
661
662 {
663 var u URL
664 err := json.Unmarshal(b, &u)
665 require.Error(t, err, "unsupported scheme \"\" for URL")
666 require.Equal(t, (*url.URL)(nil), u.URL)
667 }
668
669 {
670 var u URL
671 err := yaml.Unmarshal(b, &u)
672 require.Error(t, err, "unsupported scheme \"\" for URL")
673 require.Equal(t, (*url.URL)(nil), u.URL)
674 }
675 }
676
677 func TestUnmarshalURL(t *testing.T) {
678 b := []byte(`"http://example.com/a b"`)
679 var u URL
680
681 err := json.Unmarshal(b, &u)
682 if err != nil {
683 t.Fatal(err)
684 }
685 require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in JSON.")
686
687 err = yaml.Unmarshal(b, &u)
688 if err != nil {
689 t.Fatal(err)
690 }
691 require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in YAML.")
692 }
693
694 func TestUnmarshalInvalidURL(t *testing.T) {
695 for _, b := range [][]byte{
696 []byte(`"://example.com"`),
697 []byte(`"http:example.com"`),
698 []byte(`"telnet://example.com"`),
699 } {
700 var u URL
701
702 err := json.Unmarshal(b, &u)
703 if err == nil {
704 t.Errorf("Expected an error unmarshaling %q from JSON", string(b))
705 }
706
707 err = yaml.Unmarshal(b, &u)
708 if err == nil {
709 t.Errorf("Expected an error unmarshaling %q from YAML", string(b))
710 }
711 t.Logf("%s", err)
712 }
713 }
714
715 func TestUnmarshalRelativeURL(t *testing.T) {
716 b := []byte(`"/home"`)
717 var u URL
718
719 err := json.Unmarshal(b, &u)
720 if err == nil {
721 t.Errorf("Expected an error parsing URL")
722 }
723
724 err = yaml.Unmarshal(b, &u)
725 if err == nil {
726 t.Errorf("Expected an error parsing URL")
727 }
728 }
729
730 func TestMarshalRegexpWithNilValue(t *testing.T) {
731 r := &Regexp{}
732
733 out, err := json.Marshal(r)
734 require.NoError(t, err)
735 require.Equal(t, "null", string(out))
736
737 out, err = yaml.Marshal(r)
738 require.NoError(t, err)
739 require.Equal(t, "null\n", string(out))
740 }
741
742 func TestUnmarshalEmptyRegexp(t *testing.T) {
743 b := []byte(`""`)
744
745 {
746 var re Regexp
747 err := json.Unmarshal(b, &re)
748 require.NoError(t, err)
749 require.Equal(t, regexp.MustCompile("^(?:)$"), re.Regexp)
750 require.Equal(t, "", re.original)
751 }
752
753 {
754 var re Regexp
755 err := yaml.Unmarshal(b, &re)
756 require.NoError(t, err)
757 require.Equal(t, regexp.MustCompile("^(?:)$"), re.Regexp)
758 require.Equal(t, "", re.original)
759 }
760 }
761
762 func TestUnmarshalNullRegexp(t *testing.T) {
763 input := []byte(`null`)
764
765 {
766 var re Regexp
767 err := json.Unmarshal(input, &re)
768 require.NoError(t, err)
769 require.Nil(t, nil, re.Regexp)
770 require.Equal(t, "", re.original)
771 }
772
773 {
774 var re Regexp
775 err := yaml.Unmarshal(input, &re)
776 require.NoError(t, err)
777 require.Nil(t, re.Regexp)
778 require.Equal(t, "", re.original)
779 }
780 }
781
782 func TestMarshalEmptyMatchers(t *testing.T) {
783 r := Matchers{}
784
785 out, err := json.Marshal(r)
786 require.NoError(t, err)
787 require.Equal(t, "[]", string(out))
788
789 out, err = yaml.Marshal(r)
790 require.NoError(t, err)
791 require.Equal(t, "[]\n", string(out))
792 }
793
794 func TestJSONUnmarshal(t *testing.T) {
795 c, err := LoadFile("testdata/conf.good.yml")
796 if err != nil {
797 t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
798 }
799
800 _, err = json.Marshal(c)
801 if err != nil {
802 t.Fatal("JSON Marshaling failed:", err)
803 }
804 }
805
806 func TestMarshalIdempotency(t *testing.T) {
807 c, err := LoadFile("testdata/conf.good.yml")
808 if err != nil {
809 t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
810 }
811
812 marshaled, err := yaml.Marshal(c)
813 if err != nil {
814 t.Fatal("YAML Marshaling failed:", err)
815 }
816
817 c = new(Config)
818 if err := yaml.Unmarshal(marshaled, c); err != nil {
819 t.Fatal("YAML Unmarshaling failed:", err)
820 }
821 }
822
823 func TestGroupByAllNotMarshaled(t *testing.T) {
824 in := `
825 route:
826 receiver: team-X-mails
827 group_by: [...]
828
829 receivers:
830 - name: 'team-X-mails'
831 `
832 c, err := Load(in)
833 if err != nil {
834 t.Fatal("load failed:", err)
835 }
836
837 dat, err := yaml.Marshal(c)
838 if err != nil {
839 t.Fatal("YAML Marshaling failed:", err)
840 }
841
842 if strings.Contains(string(dat), "groupbyall") {
843 t.Fatal("groupbyall found in config file")
844 }
845 }
846
847 func TestEmptyFieldsAndRegex(t *testing.T) {
848 boolFoo := true
849 regexpFoo := Regexp{
850 Regexp: regexp.MustCompile("^(?:^(foo1|foo2|baz)$)$"),
851 original: "^(foo1|foo2|baz)$",
852 }
853
854 expectedConf := Config{
855 Global: &GlobalConfig{
856 HTTPConfig: &commoncfg.HTTPClientConfig{
857 FollowRedirects: true,
858 EnableHTTP2: true,
859 },
860 ResolveTimeout: model.Duration(5 * time.Minute),
861 SMTPSmarthost: HostPort{Host: "localhost", Port: "25"},
862 SMTPFrom: "alertmanager@example.org",
863 SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")),
864 SMTPRequireTLS: true,
865 PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"),
866 OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"),
867 WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
868 VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
869 TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
870 WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
871 },
872
873 Templates: []string{
874 "/etc/alertmanager/template/*.tmpl",
875 },
876 Route: &Route{
877 Receiver: "team-X-mails",
878 GroupBy: []model.LabelName{
879 "alertname",
880 "cluster",
881 "service",
882 },
883 GroupByStr: []string{
884 "alertname",
885 "cluster",
886 "service",
887 },
888 GroupByAll: false,
889 Routes: []*Route{
890 {
891 Receiver: "team-X-mails",
892 MatchRE: map[string]Regexp{
893 "service": regexpFoo,
894 },
895 },
896 },
897 },
898 Receivers: []*Receiver{
899 {
900 Name: "team-X-mails",
901 EmailConfigs: []*EmailConfig{
902 {
903 To: "team-X+alerts@example.org",
904 From: "alertmanager@example.org",
905 Smarthost: HostPort{Host: "localhost", Port: "25"},
906 HTML: "{{ template \"email.default.html\" . }}",
907 RequireTLS: &boolFoo,
908 },
909 },
910 },
911 },
912 }
913
914
915
916 _, err := LoadFile("testdata/conf.good.yml")
917 if err != nil {
918 t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
919 }
920
921 config, err := LoadFile("testdata/conf.empty-fields.yml")
922 if err != nil {
923 t.Errorf("Error parsing %s: %s", "testdata/conf.empty-fields.yml", err)
924 }
925
926 configGot, err := yaml.Marshal(config)
927 if err != nil {
928 t.Fatal("YAML Marshaling failed:", err)
929 }
930
931 configExp, err := yaml.Marshal(expectedConf)
932 if err != nil {
933 t.Fatalf("%s", err)
934 }
935
936 if !reflect.DeepEqual(configGot, configExp) {
937 t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", "testdata/conf.empty-fields.yml", configGot, configExp)
938 }
939 }
940
941 func TestGlobalAndLocalHTTPConfig(t *testing.T) {
942 config, err := LoadFile("testdata/conf.http-config.good.yml")
943 if err != nil {
944 t.Fatalf("Error parsing %s: %s", "testdata/conf-http-config.good.yml", err)
945 }
946
947 if config.Global.HTTPConfig.FollowRedirects {
948 t.Fatalf("global HTTP config should not follow redirects")
949 }
950
951 if !config.Receivers[0].SlackConfigs[0].HTTPConfig.FollowRedirects {
952 t.Fatalf("global HTTP config should follow redirects")
953 }
954 }
955
956 func TestSMTPHello(t *testing.T) {
957 c, err := LoadFile("testdata/conf.good.yml")
958 if err != nil {
959 t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
960 }
961
962 const refValue = "host.example.org"
963 hostName := c.Global.SMTPHello
964 if hostName != refValue {
965 t.Errorf("Invalid SMTP Hello hostname: %s\nExpected: %s", hostName, refValue)
966 }
967 }
968
969 func TestSMTPBothPasswordAndFile(t *testing.T) {
970 _, err := LoadFile("testdata/conf.smtp-both-password-and-file.yml")
971 if err == nil {
972 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.smtp-both-password-and-file.yml", err)
973 }
974 if err.Error() != "at most one of smtp_auth_password & smtp_auth_password_file must be configured" {
975 t.Errorf("Expected: %s\nGot: %s", "at most one of auth_password & auth_password_file must be configured", err.Error())
976 }
977 }
978
979 func TestSMTPNoUsernameOrPassword(t *testing.T) {
980 _, err := LoadFile("testdata/conf.smtp-no-username-or-password.yml")
981 if err != nil {
982 t.Fatalf("Error parsing %s: %s", "testdata/conf.smtp-no-username-or-password.yml", err)
983 }
984 }
985
986 func TestGlobalAndLocalSMTPPassword(t *testing.T) {
987 config, err := LoadFile("testdata/conf.smtp-password-global-and-local.yml")
988 if err != nil {
989 t.Fatalf("Error parsing %s: %s", "testdata/conf.smtp-password-global-and-local.yml", err)
990 }
991
992 require.Equal(t, "/tmp/globaluserpassword", config.Receivers[0].EmailConfigs[0].AuthPasswordFile, "first email should use password file /tmp/globaluserpassword")
993 require.Emptyf(t, config.Receivers[0].EmailConfigs[0].AuthPassword, "password field should be empty when file provided")
994
995 require.Equal(t, "/tmp/localuser1password", config.Receivers[0].EmailConfigs[1].AuthPasswordFile, "second email should use password file /tmp/localuser1password")
996 require.Emptyf(t, config.Receivers[0].EmailConfigs[1].AuthPassword, "password field should be empty when file provided")
997
998 require.Equal(t, Secret("mysecret"), config.Receivers[0].EmailConfigs[2].AuthPassword, "third email should use password mysecret")
999 require.Emptyf(t, config.Receivers[0].EmailConfigs[2].AuthPasswordFile, "file field should be empty when password provided")
1000 }
1001
1002 func TestGroupByAll(t *testing.T) {
1003 c, err := LoadFile("testdata/conf.group-by-all.yml")
1004 if err != nil {
1005 t.Fatalf("Error parsing %s: %s", "testdata/conf.group-by-all.yml", err)
1006 }
1007
1008 if !c.Route.GroupByAll {
1009 t.Errorf("Invalid group by all param: expected to by true")
1010 }
1011 }
1012
1013 func TestVictorOpsDefaultAPIKey(t *testing.T) {
1014 conf, err := LoadFile("testdata/conf.victorops-default-apikey.yml")
1015 if err != nil {
1016 t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey.yml", err)
1017 }
1018
1019 defaultKey := conf.Global.VictorOpsAPIKey
1020 overrideKey := Secret("qwe456")
1021 if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKey {
1022 t.Fatalf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, defaultKey)
1023 }
1024 if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKey {
1025 t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, string(overrideKey))
1026 }
1027 }
1028
1029 func TestVictorOpsDefaultAPIKeyFile(t *testing.T) {
1030 conf, err := LoadFile("testdata/conf.victorops-default-apikey-file.yml")
1031 if err != nil {
1032 t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey-file.yml", err)
1033 }
1034
1035 defaultKey := conf.Global.VictorOpsAPIKeyFile
1036 overrideKey := "/override_file"
1037 if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile {
1038 t.Fatalf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, defaultKey)
1039 }
1040 if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKeyFile {
1041 t.Errorf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, overrideKey)
1042 }
1043 }
1044
1045 func TestVictorOpsBothAPIKeyAndFile(t *testing.T) {
1046 _, err := LoadFile("testdata/conf.victorops-both-file-and-apikey.yml")
1047 if err == nil {
1048 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-both-file-and-apikey.yml", err)
1049 }
1050 if err.Error() != "at most one of victorops_api_key & victorops_api_key_file must be configured" {
1051 t.Errorf("Expected: %s\nGot: %s", "at most one of victorops_api_key & victorops_api_key_file must be configured", err.Error())
1052 }
1053 }
1054
1055 func TestVictorOpsNoAPIKey(t *testing.T) {
1056 _, err := LoadFile("testdata/conf.victorops-no-apikey.yml")
1057 if err == nil {
1058 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-no-apikey.yml", err)
1059 }
1060 if err.Error() != "no global VictorOps API Key set" {
1061 t.Errorf("Expected: %s\nGot: %s", "no global VictorOps API Key set", err.Error())
1062 }
1063 }
1064
1065 func TestOpsGenieDefaultAPIKey(t *testing.T) {
1066 conf, err := LoadFile("testdata/conf.opsgenie-default-apikey.yml")
1067 if err != nil {
1068 t.Fatalf("Error parsing %s: %s", "testdata/conf.opsgenie-default-apikey.yml", err)
1069 }
1070
1071 defaultKey := conf.Global.OpsGenieAPIKey
1072 if defaultKey != conf.Receivers[0].OpsGenieConfigs[0].APIKey {
1073 t.Fatalf("Invalid OpsGenie key: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKey, defaultKey)
1074 }
1075 if defaultKey == conf.Receivers[1].OpsGenieConfigs[0].APIKey {
1076 t.Errorf("Invalid OpsGenie key: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKey, "qwe456")
1077 }
1078 }
1079
1080 func TestOpsGenieDefaultAPIKeyFile(t *testing.T) {
1081 conf, err := LoadFile("testdata/conf.opsgenie-default-apikey-file.yml")
1082 if err != nil {
1083 t.Fatalf("Error parsing %s: %s", "testdata/conf.opsgenie-default-apikey-file.yml", err)
1084 }
1085
1086 defaultKey := conf.Global.OpsGenieAPIKeyFile
1087 if defaultKey != conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile {
1088 t.Fatalf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, defaultKey)
1089 }
1090 if defaultKey == conf.Receivers[1].OpsGenieConfigs[0].APIKeyFile {
1091 t.Errorf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, "/override_file")
1092 }
1093 }
1094
1095 func TestOpsGenieBothAPIKeyAndFile(t *testing.T) {
1096 _, err := LoadFile("testdata/conf.opsgenie-both-file-and-apikey.yml")
1097 if err == nil {
1098 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-both-file-and-apikey.yml", err)
1099 }
1100 if err.Error() != "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured" {
1101 t.Errorf("Expected: %s\nGot: %s", "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured", err.Error())
1102 }
1103 }
1104
1105 func TestOpsGenieNoAPIKey(t *testing.T) {
1106 _, err := LoadFile("testdata/conf.opsgenie-no-apikey.yml")
1107 if err == nil {
1108 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-no-apikey.yml", err)
1109 }
1110 if err.Error() != "no global OpsGenie API Key set either inline or in a file" {
1111 t.Errorf("Expected: %s\nGot: %s", "no global OpsGenie API Key set either inline or in a file", err.Error())
1112 }
1113 }
1114
1115 func TestOpsGenieDeprecatedTeamSpecified(t *testing.T) {
1116 _, err := LoadFile("testdata/conf.opsgenie-default-apikey-old-team.yml")
1117 if err == nil {
1118 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-default-apikey-old-team.yml", err)
1119 }
1120
1121 const expectedErr = `yaml: unmarshal errors:
1122 line 16: field teams not found in type config.plain`
1123 if err.Error() != expectedErr {
1124 t.Errorf("Expected: %s\nGot: %s", expectedErr, err.Error())
1125 }
1126 }
1127
1128 func TestSlackBothAPIURLAndFile(t *testing.T) {
1129 _, err := LoadFile("testdata/conf.slack-both-file-and-url.yml")
1130 if err == nil {
1131 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.slack-both-file-and-url.yml", err)
1132 }
1133 if err.Error() != "at most one of slack_api_url & slack_api_url_file must be configured" {
1134 t.Errorf("Expected: %s\nGot: %s", "at most one of slack_api_url & slack_api_url_file must be configured", err.Error())
1135 }
1136 }
1137
1138 func TestSlackNoAPIURL(t *testing.T) {
1139 _, err := LoadFile("testdata/conf.slack-no-api-url.yml")
1140 if err == nil {
1141 t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.slack-no-api-url.yml", err)
1142 }
1143 if err.Error() != "no global Slack API URL set either inline or in a file" {
1144 t.Errorf("Expected: %s\nGot: %s", "no global Slack API URL set either inline or in a file", err.Error())
1145 }
1146 }
1147
1148 func TestSlackGlobalAPIURLFile(t *testing.T) {
1149 conf, err := LoadFile("testdata/conf.slack-default-api-url-file.yml")
1150 if err != nil {
1151 t.Fatalf("Error parsing %s: %s", "testdata/conf.slack-default-api-url-file.yml", err)
1152 }
1153
1154
1155 firstConfig := conf.Receivers[0].SlackConfigs[0]
1156 if firstConfig.APIURLFile != "/global_file" || firstConfig.APIURL != nil {
1157 t.Fatalf("Invalid Slack URL file: %s\nExpected: %s", firstConfig.APIURLFile, "/global_file")
1158 }
1159
1160
1161 secondConfig := conf.Receivers[0].SlackConfigs[1]
1162 if secondConfig.APIURLFile != "/override_file" || secondConfig.APIURL != nil {
1163 t.Fatalf("Invalid Slack URL file: %s\nExpected: %s", secondConfig.APIURLFile, "/override_file")
1164 }
1165
1166
1167 thirdConfig := conf.Receivers[0].SlackConfigs[2]
1168 if thirdConfig.APIURL.String() != "http://mysecret.example.com/" || thirdConfig.APIURLFile != "" {
1169 t.Fatalf("Invalid Slack URL: %s\nExpected: %s", thirdConfig.APIURL.String(), "http://mysecret.example.com/")
1170 }
1171 }
1172
1173 func TestValidSNSConfig(t *testing.T) {
1174 _, err := LoadFile("testdata/conf.sns-topic-arn.yml")
1175 if err != nil {
1176 t.Fatalf("Error parsing %s: %s", "testdata/conf.sns-topic-arn.yml\"", err)
1177 }
1178 }
1179
1180 func TestInvalidSNSConfig(t *testing.T) {
1181 _, err := LoadFile("testdata/conf.sns-invalid.yml")
1182 if err == nil {
1183 t.Fatalf("expected error with missing fields on SNS config")
1184 }
1185 const expectedErr = `must provide either a Target ARN, Topic ARN, or Phone Number for SNS config`
1186 if err.Error() != expectedErr {
1187 t.Errorf("Expected: %s\nGot: %s", expectedErr, err.Error())
1188 }
1189 }
1190
1191 func TestUnmarshalHostPort(t *testing.T) {
1192 for _, tc := range []struct {
1193 in string
1194
1195 exp HostPort
1196 jsonOut string
1197 yamlOut string
1198 err bool
1199 }{
1200 {
1201 in: `""`,
1202 exp: HostPort{},
1203 yamlOut: `""
1204 `,
1205 jsonOut: `""`,
1206 },
1207 {
1208 in: `"localhost:25"`,
1209 exp: HostPort{Host: "localhost", Port: "25"},
1210 yamlOut: `localhost:25
1211 `,
1212 jsonOut: `"localhost:25"`,
1213 },
1214 {
1215 in: `":25"`,
1216 exp: HostPort{Host: "", Port: "25"},
1217 yamlOut: `:25
1218 `,
1219 jsonOut: `":25"`,
1220 },
1221 {
1222 in: `"localhost"`,
1223 err: true,
1224 },
1225 {
1226 in: `"localhost:"`,
1227 err: true,
1228 },
1229 } {
1230 tc := tc
1231 t.Run(tc.in, func(t *testing.T) {
1232 hp := HostPort{}
1233 err := yaml.Unmarshal([]byte(tc.in), &hp)
1234 if tc.err {
1235 require.Error(t, err)
1236 return
1237 }
1238 require.NoError(t, err)
1239 require.Equal(t, tc.exp, hp)
1240
1241 b, err := yaml.Marshal(&hp)
1242 require.NoError(t, err)
1243 require.Equal(t, tc.yamlOut, string(b))
1244
1245 b, err = json.Marshal(&hp)
1246 require.NoError(t, err)
1247 require.Equal(t, tc.jsonOut, string(b))
1248 })
1249 }
1250 }
1251
1252 func TestNilRegexp(t *testing.T) {
1253 for _, tc := range []struct {
1254 file string
1255 errMsg string
1256 }{
1257 {
1258 file: "testdata/conf.nil-match_re-route.yml",
1259 errMsg: "invalid_label",
1260 },
1261 {
1262 file: "testdata/conf.nil-source_match_re-inhibition.yml",
1263 errMsg: "invalid_source_label",
1264 },
1265 {
1266 file: "testdata/conf.nil-target_match_re-inhibition.yml",
1267 errMsg: "invalid_target_label",
1268 },
1269 } {
1270 t.Run(tc.file, func(t *testing.T) {
1271 _, err := os.Stat(tc.file)
1272 require.NoError(t, err)
1273
1274 _, err = LoadFile(tc.file)
1275 require.Error(t, err)
1276 require.Contains(t, err.Error(), tc.errMsg)
1277 })
1278 }
1279 }
1280
View as plain text