1
2
3
4
5
6
7
8
9
10
11
12
13
14 package config
15
16 import (
17 "encoding/json"
18 "fmt"
19 "net"
20 "net/url"
21 "os"
22 "path/filepath"
23 "regexp"
24 "sort"
25 "strings"
26 "time"
27
28 "github.com/pkg/errors"
29 commoncfg "github.com/prometheus/common/config"
30 "github.com/prometheus/common/model"
31 "gopkg.in/yaml.v2"
32
33 "github.com/prometheus/alertmanager/pkg/labels"
34 "github.com/prometheus/alertmanager/timeinterval"
35 )
36
37 const secretToken = "<secret>"
38
39 var secretTokenJSON string
40
41 func init() {
42 b, err := json.Marshal(secretToken)
43 if err != nil {
44 panic(err)
45 }
46 secretTokenJSON = string(b)
47 }
48
49
50 type Secret string
51
52
53 func (s Secret) MarshalYAML() (interface{}, error) {
54 if s != "" {
55 return secretToken, nil
56 }
57 return nil, nil
58 }
59
60
61 func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
62 type plain Secret
63 return unmarshal((*plain)(s))
64 }
65
66
67 func (s Secret) MarshalJSON() ([]byte, error) {
68 return json.Marshal(secretToken)
69 }
70
71
72 type URL struct {
73 *url.URL
74 }
75
76
77 func (u *URL) Copy() *URL {
78 v := *u.URL
79 return &URL{&v}
80 }
81
82
83 func (u URL) MarshalYAML() (interface{}, error) {
84 if u.URL != nil {
85 return u.URL.String(), nil
86 }
87 return nil, nil
88 }
89
90
91 func (u *URL) UnmarshalYAML(unmarshal func(interface{}) error) error {
92 var s string
93 if err := unmarshal(&s); err != nil {
94 return err
95 }
96 urlp, err := parseURL(s)
97 if err != nil {
98 return err
99 }
100 u.URL = urlp.URL
101 return nil
102 }
103
104
105 func (u URL) MarshalJSON() ([]byte, error) {
106 if u.URL != nil {
107 return json.Marshal(u.URL.String())
108 }
109 return []byte("null"), nil
110 }
111
112
113 func (u *URL) UnmarshalJSON(data []byte) error {
114 var s string
115 if err := json.Unmarshal(data, &s); err != nil {
116 return err
117 }
118 urlp, err := parseURL(s)
119 if err != nil {
120 return err
121 }
122 u.URL = urlp.URL
123 return nil
124 }
125
126
127 type SecretURL URL
128
129
130 func (s SecretURL) MarshalYAML() (interface{}, error) {
131 if s.URL != nil {
132 return secretToken, nil
133 }
134 return nil, nil
135 }
136
137
138 func (s *SecretURL) UnmarshalYAML(unmarshal func(interface{}) error) error {
139 var str string
140 if err := unmarshal(&str); err != nil {
141 return err
142 }
143
144
145
146 if str == secretToken {
147 s.URL = &url.URL{}
148 return nil
149 }
150 return unmarshal((*URL)(s))
151 }
152
153
154 func (s SecretURL) MarshalJSON() ([]byte, error) {
155 return json.Marshal(secretToken)
156 }
157
158
159 func (s *SecretURL) UnmarshalJSON(data []byte) error {
160
161
162
163 if string(data) == secretToken || string(data) == secretTokenJSON {
164 s.URL = &url.URL{}
165 return nil
166 }
167 return json.Unmarshal(data, (*URL)(s))
168 }
169
170
171 func Load(s string) (*Config, error) {
172 cfg := &Config{}
173 err := yaml.UnmarshalStrict([]byte(s), cfg)
174 if err != nil {
175 return nil, err
176 }
177
178
179
180 if cfg.Route == nil {
181 return nil, errors.New("no route provided in config")
182 }
183
184
185 if cfg.Route.Continue {
186 return nil, errors.New("cannot have continue in root route")
187 }
188
189 cfg.original = s
190 return cfg, nil
191 }
192
193
194 func LoadFile(filename string) (*Config, error) {
195 content, err := os.ReadFile(filename)
196 if err != nil {
197 return nil, err
198 }
199 cfg, err := Load(string(content))
200 if err != nil {
201 return nil, err
202 }
203
204 resolveFilepaths(filepath.Dir(filename), cfg)
205 return cfg, nil
206 }
207
208
209
210 func resolveFilepaths(baseDir string, cfg *Config) {
211 join := func(fp string) string {
212 if len(fp) > 0 && !filepath.IsAbs(fp) {
213 fp = filepath.Join(baseDir, fp)
214 }
215 return fp
216 }
217
218 for i, tf := range cfg.Templates {
219 cfg.Templates[i] = join(tf)
220 }
221
222 cfg.Global.HTTPConfig.SetDirectory(baseDir)
223 for _, receiver := range cfg.Receivers {
224 for _, cfg := range receiver.OpsGenieConfigs {
225 cfg.HTTPConfig.SetDirectory(baseDir)
226 }
227 for _, cfg := range receiver.PagerdutyConfigs {
228 cfg.HTTPConfig.SetDirectory(baseDir)
229 }
230 for _, cfg := range receiver.PushoverConfigs {
231 cfg.HTTPConfig.SetDirectory(baseDir)
232 }
233 for _, cfg := range receiver.SlackConfigs {
234 cfg.HTTPConfig.SetDirectory(baseDir)
235 }
236 for _, cfg := range receiver.VictorOpsConfigs {
237 cfg.HTTPConfig.SetDirectory(baseDir)
238 }
239 for _, cfg := range receiver.WebhookConfigs {
240 cfg.HTTPConfig.SetDirectory(baseDir)
241 }
242 for _, cfg := range receiver.WechatConfigs {
243 cfg.HTTPConfig.SetDirectory(baseDir)
244 }
245 for _, cfg := range receiver.SNSConfigs {
246 cfg.HTTPConfig.SetDirectory(baseDir)
247 }
248 for _, cfg := range receiver.TelegramConfigs {
249 cfg.HTTPConfig.SetDirectory(baseDir)
250 }
251 for _, cfg := range receiver.DiscordConfigs {
252 cfg.HTTPConfig.SetDirectory(baseDir)
253 }
254 for _, cfg := range receiver.WebexConfigs {
255 cfg.HTTPConfig.SetDirectory(baseDir)
256 }
257 }
258 }
259
260
261 type MuteTimeInterval struct {
262 Name string `yaml:"name" json:"name"`
263 TimeIntervals []timeinterval.TimeInterval `yaml:"time_intervals" json:"time_intervals"`
264 }
265
266
267 func (mt *MuteTimeInterval) UnmarshalYAML(unmarshal func(interface{}) error) error {
268 type plain MuteTimeInterval
269 if err := unmarshal((*plain)(mt)); err != nil {
270 return err
271 }
272 if mt.Name == "" {
273 return fmt.Errorf("missing name in mute time interval")
274 }
275 return nil
276 }
277
278
279 type TimeInterval struct {
280 Name string `yaml:"name" json:"name"`
281 TimeIntervals []timeinterval.TimeInterval `yaml:"time_intervals" json:"time_intervals"`
282 }
283
284
285 func (ti *TimeInterval) UnmarshalYAML(unmarshal func(interface{}) error) error {
286 type plain TimeInterval
287 if err := unmarshal((*plain)(ti)); err != nil {
288 return err
289 }
290 if ti.Name == "" {
291 return fmt.Errorf("missing name in time interval")
292 }
293 return nil
294 }
295
296
297 type Config struct {
298 Global *GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
299 Route *Route `yaml:"route,omitempty" json:"route,omitempty"`
300 InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"`
301 Receivers []*Receiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
302 Templates []string `yaml:"templates" json:"templates"`
303
304 MuteTimeIntervals []MuteTimeInterval `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
305 TimeIntervals []TimeInterval `yaml:"time_intervals,omitempty" json:"time_intervals,omitempty"`
306
307
308 original string
309 }
310
311 func (c Config) String() string {
312 b, err := yaml.Marshal(c)
313 if err != nil {
314 return fmt.Sprintf("<error creating config string: %s>", err)
315 }
316 return string(b)
317 }
318
319
320 func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
321
322
323
324 type plain Config
325 if err := unmarshal((*plain)(c)); err != nil {
326 return err
327 }
328
329
330
331 if c.Global == nil {
332 c.Global = &GlobalConfig{}
333 *c.Global = DefaultGlobalConfig()
334 }
335
336 if c.Global.SlackAPIURL != nil && len(c.Global.SlackAPIURLFile) > 0 {
337 return fmt.Errorf("at most one of slack_api_url & slack_api_url_file must be configured")
338 }
339
340 if c.Global.OpsGenieAPIKey != "" && len(c.Global.OpsGenieAPIKeyFile) > 0 {
341 return fmt.Errorf("at most one of opsgenie_api_key & opsgenie_api_key_file must be configured")
342 }
343
344 if c.Global.VictorOpsAPIKey != "" && len(c.Global.VictorOpsAPIKeyFile) > 0 {
345 return fmt.Errorf("at most one of victorops_api_key & victorops_api_key_file must be configured")
346 }
347
348 if len(c.Global.SMTPAuthPassword) > 0 && len(c.Global.SMTPAuthPasswordFile) > 0 {
349 return fmt.Errorf("at most one of smtp_auth_password & smtp_auth_password_file must be configured")
350 }
351
352 names := map[string]struct{}{}
353
354 for _, rcv := range c.Receivers {
355 if _, ok := names[rcv.Name]; ok {
356 return fmt.Errorf("notification config name %q is not unique", rcv.Name)
357 }
358 for _, wh := range rcv.WebhookConfigs {
359 if wh.HTTPConfig == nil {
360 wh.HTTPConfig = c.Global.HTTPConfig
361 }
362 }
363 for _, ec := range rcv.EmailConfigs {
364 if ec.Smarthost.String() == "" {
365 if c.Global.SMTPSmarthost.String() == "" {
366 return fmt.Errorf("no global SMTP smarthost set")
367 }
368 ec.Smarthost = c.Global.SMTPSmarthost
369 }
370 if ec.From == "" {
371 if c.Global.SMTPFrom == "" {
372 return fmt.Errorf("no global SMTP from set")
373 }
374 ec.From = c.Global.SMTPFrom
375 }
376 if ec.Hello == "" {
377 ec.Hello = c.Global.SMTPHello
378 }
379 if ec.AuthUsername == "" {
380 ec.AuthUsername = c.Global.SMTPAuthUsername
381 }
382 if ec.AuthPassword == "" && ec.AuthPasswordFile == "" {
383 ec.AuthPassword = c.Global.SMTPAuthPassword
384 ec.AuthPasswordFile = c.Global.SMTPAuthPasswordFile
385 }
386 if ec.AuthSecret == "" {
387 ec.AuthSecret = c.Global.SMTPAuthSecret
388 }
389 if ec.AuthIdentity == "" {
390 ec.AuthIdentity = c.Global.SMTPAuthIdentity
391 }
392 if ec.RequireTLS == nil {
393 ec.RequireTLS = new(bool)
394 *ec.RequireTLS = c.Global.SMTPRequireTLS
395 }
396 }
397 for _, sc := range rcv.SlackConfigs {
398 if sc.HTTPConfig == nil {
399 sc.HTTPConfig = c.Global.HTTPConfig
400 }
401 if sc.APIURL == nil && len(sc.APIURLFile) == 0 {
402 if c.Global.SlackAPIURL == nil && len(c.Global.SlackAPIURLFile) == 0 {
403 return fmt.Errorf("no global Slack API URL set either inline or in a file")
404 }
405 sc.APIURL = c.Global.SlackAPIURL
406 sc.APIURLFile = c.Global.SlackAPIURLFile
407 }
408 }
409 for _, poc := range rcv.PushoverConfigs {
410 if poc.HTTPConfig == nil {
411 poc.HTTPConfig = c.Global.HTTPConfig
412 }
413 }
414 for _, pdc := range rcv.PagerdutyConfigs {
415 if pdc.HTTPConfig == nil {
416 pdc.HTTPConfig = c.Global.HTTPConfig
417 }
418 if pdc.URL == nil {
419 if c.Global.PagerdutyURL == nil {
420 return fmt.Errorf("no global PagerDuty URL set")
421 }
422 pdc.URL = c.Global.PagerdutyURL
423 }
424 }
425 for _, ogc := range rcv.OpsGenieConfigs {
426 if ogc.HTTPConfig == nil {
427 ogc.HTTPConfig = c.Global.HTTPConfig
428 }
429 if ogc.APIURL == nil {
430 if c.Global.OpsGenieAPIURL == nil {
431 return fmt.Errorf("no global OpsGenie URL set")
432 }
433 ogc.APIURL = c.Global.OpsGenieAPIURL
434 }
435 if !strings.HasSuffix(ogc.APIURL.Path, "/") {
436 ogc.APIURL.Path += "/"
437 }
438 if ogc.APIKey == "" && len(ogc.APIKeyFile) == 0 {
439 if c.Global.OpsGenieAPIKey == "" && len(c.Global.OpsGenieAPIKeyFile) == 0 {
440 return fmt.Errorf("no global OpsGenie API Key set either inline or in a file")
441 }
442 ogc.APIKey = c.Global.OpsGenieAPIKey
443 ogc.APIKeyFile = c.Global.OpsGenieAPIKeyFile
444 }
445 }
446 for _, wcc := range rcv.WechatConfigs {
447 if wcc.HTTPConfig == nil {
448 wcc.HTTPConfig = c.Global.HTTPConfig
449 }
450
451 if wcc.APIURL == nil {
452 if c.Global.WeChatAPIURL == nil {
453 return fmt.Errorf("no global Wechat URL set")
454 }
455 wcc.APIURL = c.Global.WeChatAPIURL
456 }
457
458 if wcc.APISecret == "" {
459 if c.Global.WeChatAPISecret == "" {
460 return fmt.Errorf("no global Wechat ApiSecret set")
461 }
462 wcc.APISecret = c.Global.WeChatAPISecret
463 }
464
465 if wcc.CorpID == "" {
466 if c.Global.WeChatAPICorpID == "" {
467 return fmt.Errorf("no global Wechat CorpID set")
468 }
469 wcc.CorpID = c.Global.WeChatAPICorpID
470 }
471
472 if !strings.HasSuffix(wcc.APIURL.Path, "/") {
473 wcc.APIURL.Path += "/"
474 }
475 }
476 for _, voc := range rcv.VictorOpsConfigs {
477 if voc.HTTPConfig == nil {
478 voc.HTTPConfig = c.Global.HTTPConfig
479 }
480 if voc.APIURL == nil {
481 if c.Global.VictorOpsAPIURL == nil {
482 return fmt.Errorf("no global VictorOps URL set")
483 }
484 voc.APIURL = c.Global.VictorOpsAPIURL
485 }
486 if !strings.HasSuffix(voc.APIURL.Path, "/") {
487 voc.APIURL.Path += "/"
488 }
489 if voc.APIKey == "" && len(voc.APIKeyFile) == 0 {
490 if c.Global.VictorOpsAPIKey == "" && len(c.Global.VictorOpsAPIKeyFile) == 0 {
491 return fmt.Errorf("no global VictorOps API Key set")
492 }
493 voc.APIKey = c.Global.VictorOpsAPIKey
494 voc.APIKeyFile = c.Global.VictorOpsAPIKeyFile
495 }
496 }
497 for _, sns := range rcv.SNSConfigs {
498 if sns.HTTPConfig == nil {
499 sns.HTTPConfig = c.Global.HTTPConfig
500 }
501 }
502
503 for _, telegram := range rcv.TelegramConfigs {
504 if telegram.HTTPConfig == nil {
505 telegram.HTTPConfig = c.Global.HTTPConfig
506 }
507 if telegram.APIUrl == nil {
508 telegram.APIUrl = c.Global.TelegramAPIUrl
509 }
510 }
511 for _, discord := range rcv.DiscordConfigs {
512 if discord.HTTPConfig == nil {
513 discord.HTTPConfig = c.Global.HTTPConfig
514 }
515 if discord.WebhookURL == nil {
516 return fmt.Errorf("no discord webhook URL provided")
517 }
518 }
519 for _, webex := range rcv.WebexConfigs {
520 if webex.HTTPConfig == nil {
521 webex.HTTPConfig = c.Global.HTTPConfig
522 }
523 if webex.APIURL == nil {
524 if c.Global.WebexAPIURL == nil {
525 return fmt.Errorf("no global Webex URL set")
526 }
527
528 webex.APIURL = c.Global.WebexAPIURL
529 }
530 }
531
532 names[rcv.Name] = struct{}{}
533 }
534
535
536
537 if c.Route == nil {
538 return fmt.Errorf("no routes provided")
539 }
540 if len(c.Route.Receiver) == 0 {
541 return fmt.Errorf("root route must specify a default receiver")
542 }
543 if len(c.Route.Match) > 0 || len(c.Route.MatchRE) > 0 || len(c.Route.Matchers) > 0 {
544 return fmt.Errorf("root route must not have any matchers")
545 }
546 if len(c.Route.MuteTimeIntervals) > 0 {
547 return fmt.Errorf("root route must not have any mute time intervals")
548 }
549
550 if len(c.Route.ActiveTimeIntervals) > 0 {
551 return fmt.Errorf("root route must not have any active time intervals")
552 }
553
554
555 if err := checkReceiver(c.Route, names); err != nil {
556 return err
557 }
558
559 tiNames := make(map[string]struct{})
560
561
562 for _, mt := range c.MuteTimeIntervals {
563 if _, ok := tiNames[mt.Name]; ok {
564 return fmt.Errorf("mute time interval %q is not unique", mt.Name)
565 }
566 tiNames[mt.Name] = struct{}{}
567 }
568
569 for _, mt := range c.TimeIntervals {
570 if _, ok := tiNames[mt.Name]; ok {
571 return fmt.Errorf("time interval %q is not unique", mt.Name)
572 }
573 tiNames[mt.Name] = struct{}{}
574 }
575
576 return checkTimeInterval(c.Route, tiNames)
577 }
578
579
580
581 func checkReceiver(r *Route, receivers map[string]struct{}) error {
582 for _, sr := range r.Routes {
583 if err := checkReceiver(sr, receivers); err != nil {
584 return err
585 }
586 }
587 if r.Receiver == "" {
588 return nil
589 }
590 if _, ok := receivers[r.Receiver]; !ok {
591 return fmt.Errorf("undefined receiver %q used in route", r.Receiver)
592 }
593 return nil
594 }
595
596 func checkTimeInterval(r *Route, timeIntervals map[string]struct{}) error {
597 for _, sr := range r.Routes {
598 if err := checkTimeInterval(sr, timeIntervals); err != nil {
599 return err
600 }
601 }
602
603 for _, ti := range r.ActiveTimeIntervals {
604 if _, ok := timeIntervals[ti]; !ok {
605 return fmt.Errorf("undefined time interval %q used in route", ti)
606 }
607 }
608
609 for _, tm := range r.MuteTimeIntervals {
610 if _, ok := timeIntervals[tm]; !ok {
611 return fmt.Errorf("undefined time interval %q used in route", tm)
612 }
613 }
614 return nil
615 }
616
617
618 func DefaultGlobalConfig() GlobalConfig {
619 defaultHTTPConfig := commoncfg.DefaultHTTPClientConfig
620 return GlobalConfig{
621 ResolveTimeout: model.Duration(5 * time.Minute),
622 HTTPConfig: &defaultHTTPConfig,
623
624 SMTPHello: "localhost",
625 SMTPRequireTLS: true,
626 PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"),
627 OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"),
628 WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
629 VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
630 TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
631 WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
632 }
633 }
634
635 func mustParseURL(s string) *URL {
636 u, err := parseURL(s)
637 if err != nil {
638 panic(err)
639 }
640 return u
641 }
642
643 func parseURL(s string) (*URL, error) {
644 u, err := url.Parse(s)
645 if err != nil {
646 return nil, err
647 }
648 if u.Scheme != "http" && u.Scheme != "https" {
649 return nil, fmt.Errorf("unsupported scheme %q for URL", u.Scheme)
650 }
651 if u.Host == "" {
652 return nil, fmt.Errorf("missing host for URL")
653 }
654 return &URL{u}, nil
655 }
656
657
658 type HostPort struct {
659 Host string
660 Port string
661 }
662
663
664 func (hp *HostPort) UnmarshalYAML(unmarshal func(interface{}) error) error {
665 var (
666 s string
667 err error
668 )
669 if err = unmarshal(&s); err != nil {
670 return err
671 }
672 if s == "" {
673 return nil
674 }
675 hp.Host, hp.Port, err = net.SplitHostPort(s)
676 if err != nil {
677 return err
678 }
679 if hp.Port == "" {
680 return errors.Errorf("address %q: port cannot be empty", s)
681 }
682 return nil
683 }
684
685
686 func (hp *HostPort) UnmarshalJSON(data []byte) error {
687 var (
688 s string
689 err error
690 )
691 if err = json.Unmarshal(data, &s); err != nil {
692 return err
693 }
694 if s == "" {
695 return nil
696 }
697 hp.Host, hp.Port, err = net.SplitHostPort(s)
698 if err != nil {
699 return err
700 }
701 if hp.Port == "" {
702 return errors.Errorf("address %q: port cannot be empty", s)
703 }
704 return nil
705 }
706
707
708 func (hp HostPort) MarshalYAML() (interface{}, error) {
709 return hp.String(), nil
710 }
711
712
713 func (hp HostPort) MarshalJSON() ([]byte, error) {
714 return json.Marshal(hp.String())
715 }
716
717 func (hp HostPort) String() string {
718 if hp.Host == "" && hp.Port == "" {
719 return ""
720 }
721 return fmt.Sprintf("%s:%s", hp.Host, hp.Port)
722 }
723
724
725
726 type GlobalConfig struct {
727
728
729 ResolveTimeout model.Duration `yaml:"resolve_timeout" json:"resolve_timeout"`
730
731 HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
732
733 SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
734 SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
735 SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
736 SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
737 SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
738 SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"`
739 SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
740 SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
741 SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"`
742 SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
743 SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"`
744 PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
745 OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
746 OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
747 OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"`
748 WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
749 WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
750 WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
751 VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
752 VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
753 VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"`
754 TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
755 WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"`
756 }
757
758
759 func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
760 *c = DefaultGlobalConfig()
761 type plain GlobalConfig
762 return unmarshal((*plain)(c))
763 }
764
765
766 type Route struct {
767 Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty"`
768
769 GroupByStr []string `yaml:"group_by,omitempty" json:"group_by,omitempty"`
770 GroupBy []model.LabelName `yaml:"-" json:"-"`
771 GroupByAll bool `yaml:"-" json:"-"`
772
773 Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"`
774
775 MatchRE MatchRegexps `yaml:"match_re,omitempty" json:"match_re,omitempty"`
776 Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
777 MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
778 ActiveTimeIntervals []string `yaml:"active_time_intervals,omitempty" json:"active_time_intervals,omitempty"`
779 Continue bool `yaml:"continue" json:"continue,omitempty"`
780 Routes []*Route `yaml:"routes,omitempty" json:"routes,omitempty"`
781
782 GroupWait *model.Duration `yaml:"group_wait,omitempty" json:"group_wait,omitempty"`
783 GroupInterval *model.Duration `yaml:"group_interval,omitempty" json:"group_interval,omitempty"`
784 RepeatInterval *model.Duration `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty"`
785 }
786
787
788 func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
789 type plain Route
790 if err := unmarshal((*plain)(r)); err != nil {
791 return err
792 }
793
794 for k := range r.Match {
795 if !model.LabelNameRE.MatchString(k) {
796 return fmt.Errorf("invalid label name %q", k)
797 }
798 }
799
800 for _, l := range r.GroupByStr {
801 if l == "..." {
802 r.GroupByAll = true
803 } else {
804 labelName := model.LabelName(l)
805 if !labelName.IsValid() {
806 return fmt.Errorf("invalid label name %q in group_by list", l)
807 }
808 r.GroupBy = append(r.GroupBy, labelName)
809 }
810 }
811
812 if len(r.GroupBy) > 0 && r.GroupByAll {
813 return fmt.Errorf("cannot have wildcard group_by (`...`) and other other labels at the same time")
814 }
815
816 groupBy := map[model.LabelName]struct{}{}
817
818 for _, ln := range r.GroupBy {
819 if _, ok := groupBy[ln]; ok {
820 return fmt.Errorf("duplicated label %q in group_by", ln)
821 }
822 groupBy[ln] = struct{}{}
823 }
824
825 if r.GroupInterval != nil && time.Duration(*r.GroupInterval) == time.Duration(0) {
826 return fmt.Errorf("group_interval cannot be zero")
827 }
828 if r.RepeatInterval != nil && time.Duration(*r.RepeatInterval) == time.Duration(0) {
829 return fmt.Errorf("repeat_interval cannot be zero")
830 }
831
832 return nil
833 }
834
835
836
837
838 type InhibitRule struct {
839
840
841 SourceMatch map[string]string `yaml:"source_match,omitempty" json:"source_match,omitempty"`
842
843
844 SourceMatchRE MatchRegexps `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"`
845
846 SourceMatchers Matchers `yaml:"source_matchers,omitempty" json:"source_matchers,omitempty"`
847
848
849 TargetMatch map[string]string `yaml:"target_match,omitempty" json:"target_match,omitempty"`
850
851
852 TargetMatchRE MatchRegexps `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"`
853
854 TargetMatchers Matchers `yaml:"target_matchers,omitempty" json:"target_matchers,omitempty"`
855
856
857 Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"`
858 }
859
860
861 func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
862 type plain InhibitRule
863 if err := unmarshal((*plain)(r)); err != nil {
864 return err
865 }
866
867 for k := range r.SourceMatch {
868 if !model.LabelNameRE.MatchString(k) {
869 return fmt.Errorf("invalid label name %q", k)
870 }
871 }
872
873 for k := range r.TargetMatch {
874 if !model.LabelNameRE.MatchString(k) {
875 return fmt.Errorf("invalid label name %q", k)
876 }
877 }
878
879 return nil
880 }
881
882
883 type Receiver struct {
884
885 Name string `yaml:"name" json:"name"`
886
887 DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"`
888 EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
889 PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
890 SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
891 WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
892 OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
893 WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
894 PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
895 VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
896 SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
897 TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"`
898 WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"`
899 }
900
901
902 func (c *Receiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
903 type plain Receiver
904 if err := unmarshal((*plain)(c)); err != nil {
905 return err
906 }
907 if c.Name == "" {
908 return fmt.Errorf("missing name in receiver")
909 }
910 return nil
911 }
912
913
914 type MatchRegexps map[string]Regexp
915
916
917 func (m *MatchRegexps) UnmarshalYAML(unmarshal func(interface{}) error) error {
918 type plain MatchRegexps
919 if err := unmarshal((*plain)(m)); err != nil {
920 return err
921 }
922 for k, v := range *m {
923 if !model.LabelNameRE.MatchString(k) {
924 return fmt.Errorf("invalid label name %q", k)
925 }
926 if v.Regexp == nil {
927 return fmt.Errorf("invalid regexp value for %q", k)
928 }
929 }
930 return nil
931 }
932
933
934 type Regexp struct {
935 *regexp.Regexp
936 original string
937 }
938
939
940 func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
941 var s string
942 if err := unmarshal(&s); err != nil {
943 return err
944 }
945 regex, err := regexp.Compile("^(?:" + s + ")$")
946 if err != nil {
947 return err
948 }
949 re.Regexp = regex
950 re.original = s
951 return nil
952 }
953
954
955 func (re Regexp) MarshalYAML() (interface{}, error) {
956 if re.original != "" {
957 return re.original, nil
958 }
959 return nil, nil
960 }
961
962
963 func (re *Regexp) UnmarshalJSON(data []byte) error {
964 var s string
965 if err := json.Unmarshal(data, &s); err != nil {
966 return err
967 }
968 regex, err := regexp.Compile("^(?:" + s + ")$")
969 if err != nil {
970 return err
971 }
972 re.Regexp = regex
973 re.original = s
974 return nil
975 }
976
977
978 func (re Regexp) MarshalJSON() ([]byte, error) {
979 if re.original != "" {
980 return json.Marshal(re.original)
981 }
982 return []byte("null"), nil
983 }
984
985
986
987 type Matchers labels.Matchers
988
989
990 func (m *Matchers) UnmarshalYAML(unmarshal func(interface{}) error) error {
991 var lines []string
992 if err := unmarshal(&lines); err != nil {
993 return err
994 }
995 for _, line := range lines {
996 pm, err := labels.ParseMatchers(line)
997 if err != nil {
998 return err
999 }
1000 *m = append(*m, pm...)
1001 }
1002 sort.Sort(labels.Matchers(*m))
1003 return nil
1004 }
1005
1006
1007 func (m Matchers) MarshalYAML() (interface{}, error) {
1008 result := make([]string, len(m))
1009 for i, matcher := range m {
1010 result[i] = matcher.String()
1011 }
1012 return result, nil
1013 }
1014
1015
1016 func (m *Matchers) UnmarshalJSON(data []byte) error {
1017 var lines []string
1018 if err := json.Unmarshal(data, &lines); err != nil {
1019 return err
1020 }
1021 for _, line := range lines {
1022 pm, err := labels.ParseMatchers(line)
1023 if err != nil {
1024 return err
1025 }
1026 *m = append(*m, pm...)
1027 }
1028 sort.Sort(labels.Matchers(*m))
1029 return nil
1030 }
1031
1032
1033 func (m Matchers) MarshalJSON() ([]byte, error) {
1034 if len(m) == 0 {
1035 return []byte("[]"), nil
1036 }
1037 result := make([]string, len(m))
1038 for i, matcher := range m {
1039 result[i] = matcher.String()
1040 }
1041 return json.Marshal(result)
1042 }
1043
View as plain text