...

Source file src/github.com/prometheus/alertmanager/config/config.go

Documentation: github.com/prometheus/alertmanager/config

     1  // Copyright 2015 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    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  // Secret is a string that must not be revealed on marshaling.
    50  type Secret string
    51  
    52  // MarshalYAML implements the yaml.Marshaler interface for Secret.
    53  func (s Secret) MarshalYAML() (interface{}, error) {
    54  	if s != "" {
    55  		return secretToken, nil
    56  	}
    57  	return nil, nil
    58  }
    59  
    60  // UnmarshalYAML implements the yaml.Unmarshaler interface for Secret.
    61  func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
    62  	type plain Secret
    63  	return unmarshal((*plain)(s))
    64  }
    65  
    66  // MarshalJSON implements the json.Marshaler interface for Secret.
    67  func (s Secret) MarshalJSON() ([]byte, error) {
    68  	return json.Marshal(secretToken)
    69  }
    70  
    71  // URL is a custom type that represents an HTTP or HTTPS URL and allows validation at configuration load time.
    72  type URL struct {
    73  	*url.URL
    74  }
    75  
    76  // Copy makes a deep-copy of the struct.
    77  func (u *URL) Copy() *URL {
    78  	v := *u.URL
    79  	return &URL{&v}
    80  }
    81  
    82  // MarshalYAML implements the yaml.Marshaler interface for URL.
    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  // UnmarshalYAML implements the yaml.Unmarshaler interface for URL.
    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  // MarshalJSON implements the json.Marshaler interface for URL.
   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  // UnmarshalJSON implements the json.Marshaler interface for URL.
   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  // SecretURL is a URL that must not be revealed on marshaling.
   127  type SecretURL URL
   128  
   129  // MarshalYAML implements the yaml.Marshaler interface for SecretURL.
   130  func (s SecretURL) MarshalYAML() (interface{}, error) {
   131  	if s.URL != nil {
   132  		return secretToken, nil
   133  	}
   134  	return nil, nil
   135  }
   136  
   137  // UnmarshalYAML implements the yaml.Unmarshaler interface for SecretURL.
   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  	// In order to deserialize a previously serialized configuration (eg from
   144  	// the Alertmanager API with amtool), `<secret>` needs to be treated
   145  	// specially, as it isn't a valid URL.
   146  	if str == secretToken {
   147  		s.URL = &url.URL{}
   148  		return nil
   149  	}
   150  	return unmarshal((*URL)(s))
   151  }
   152  
   153  // MarshalJSON implements the json.Marshaler interface for SecretURL.
   154  func (s SecretURL) MarshalJSON() ([]byte, error) {
   155  	return json.Marshal(secretToken)
   156  }
   157  
   158  // UnmarshalJSON implements the json.Marshaler interface for SecretURL.
   159  func (s *SecretURL) UnmarshalJSON(data []byte) error {
   160  	// In order to deserialize a previously serialized configuration (eg from
   161  	// the Alertmanager API with amtool), `<secret>` needs to be treated
   162  	// specially, as it isn't a valid URL.
   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  // Load parses the YAML input s into a Config.
   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  	// Check if we have a root route. We cannot check for it in the
   178  	// UnmarshalYAML method because it won't be called if the input is empty
   179  	// (e.g. the config file is empty or only contains whitespace).
   180  	if cfg.Route == nil {
   181  		return nil, errors.New("no route provided in config")
   182  	}
   183  
   184  	// Check if continue in root route.
   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  // LoadFile parses the given YAML file into a Config.
   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  // resolveFilepaths joins all relative paths in a configuration
   209  // with a given base directory.
   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  // MuteTimeInterval represents a named set of time intervals for which a route should be muted.
   261  type MuteTimeInterval struct {
   262  	Name          string                      `yaml:"name" json:"name"`
   263  	TimeIntervals []timeinterval.TimeInterval `yaml:"time_intervals" json:"time_intervals"`
   264  }
   265  
   266  // UnmarshalYAML implements the yaml.Unmarshaler interface for MuteTimeInterval.
   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  // TimeInterval represents a named set of time intervals for which a route should be muted.
   279  type TimeInterval struct {
   280  	Name          string                      `yaml:"name" json:"name"`
   281  	TimeIntervals []timeinterval.TimeInterval `yaml:"time_intervals" json:"time_intervals"`
   282  }
   283  
   284  // UnmarshalYAML implements the yaml.Unmarshaler interface for MuteTimeInterval.
   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  // Config is the top-level configuration for Alertmanager's config files.
   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  	// Deprecated. Remove before v1.0 release.
   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  	// original is the input from which the config was parsed.
   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  // UnmarshalYAML implements the yaml.Unmarshaler interface for Config.
   320  func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
   321  	// We want to set c to the defaults and then overwrite it with the input.
   322  	// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
   323  	// again, we have to hide it using a type indirection.
   324  	type plain Config
   325  	if err := unmarshal((*plain)(c)); err != nil {
   326  		return err
   327  	}
   328  
   329  	// If a global block was open but empty the default global config is overwritten.
   330  	// We have to restore it here.
   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  	// The root route must not have any matchers as it is the fallback node
   536  	// for all alerts.
   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  	// Validate that all receivers used in the routing tree are defined.
   555  	if err := checkReceiver(c.Route, names); err != nil {
   556  		return err
   557  	}
   558  
   559  	tiNames := make(map[string]struct{})
   560  
   561  	// read mute time intervals until deprecated
   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  // checkReceiver returns an error if a node in the routing tree
   580  // references a receiver not in the given map.
   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  // DefaultGlobalConfig returns GlobalConfig with default values.
   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  // HostPort represents a "host:port" network address.
   658  type HostPort struct {
   659  	Host string
   660  	Port string
   661  }
   662  
   663  // UnmarshalYAML implements the yaml.Unmarshaler interface for HostPort.
   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  // UnmarshalJSON implements the json.Unmarshaler interface for HostPort.
   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  // MarshalYAML implements the yaml.Marshaler interface for HostPort.
   708  func (hp HostPort) MarshalYAML() (interface{}, error) {
   709  	return hp.String(), nil
   710  }
   711  
   712  // MarshalJSON implements the json.Marshaler interface for HostPort.
   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  // GlobalConfig defines configuration parameters that are valid globally
   725  // unless overwritten.
   726  type GlobalConfig struct {
   727  	// ResolveTimeout is the time after which an alert is declared resolved
   728  	// if it has not been updated.
   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  // UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig.
   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  // A Route is a node that contains definitions of how to handle alerts.
   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  	// Deprecated. Remove before v1.0 release.
   773  	Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"`
   774  	// Deprecated. Remove before v1.0 release.
   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  // UnmarshalYAML implements the yaml.Unmarshaler interface for Route.
   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  // InhibitRule defines an inhibition rule that mutes alerts that match the
   836  // target labels if an alert matching the source labels exists.
   837  // Both alerts have to have a set of labels being equal.
   838  type InhibitRule struct {
   839  	// SourceMatch defines a set of labels that have to equal the given
   840  	// value for source alerts. Deprecated. Remove before v1.0 release.
   841  	SourceMatch map[string]string `yaml:"source_match,omitempty" json:"source_match,omitempty"`
   842  	// SourceMatchRE defines pairs like SourceMatch but does regular expression
   843  	// matching. Deprecated. Remove before v1.0 release.
   844  	SourceMatchRE MatchRegexps `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"`
   845  	// SourceMatchers defines a set of label matchers that have to be fulfilled for source alerts.
   846  	SourceMatchers Matchers `yaml:"source_matchers,omitempty" json:"source_matchers,omitempty"`
   847  	// TargetMatch defines a set of labels that have to equal the given
   848  	// value for target alerts. Deprecated. Remove before v1.0 release.
   849  	TargetMatch map[string]string `yaml:"target_match,omitempty" json:"target_match,omitempty"`
   850  	// TargetMatchRE defines pairs like TargetMatch but does regular expression
   851  	// matching. Deprecated. Remove before v1.0 release.
   852  	TargetMatchRE MatchRegexps `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"`
   853  	// TargetMatchers defines a set of label matchers that have to be fulfilled for target alerts.
   854  	TargetMatchers Matchers `yaml:"target_matchers,omitempty" json:"target_matchers,omitempty"`
   855  	// A set of labels that must be equal between the source and target alert
   856  	// for them to be a match.
   857  	Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"`
   858  }
   859  
   860  // UnmarshalYAML implements the yaml.Unmarshaler interface for InhibitRule.
   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  // Receiver configuration provides configuration on how to contact a receiver.
   883  type Receiver struct {
   884  	// A unique identifier for this receiver.
   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  // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.
   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  // MatchRegexps represents a map of Regexp.
   914  type MatchRegexps map[string]Regexp
   915  
   916  // UnmarshalYAML implements the yaml.Unmarshaler interface for MatchRegexps.
   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  // Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
   934  type Regexp struct {
   935  	*regexp.Regexp
   936  	original string
   937  }
   938  
   939  // UnmarshalYAML implements the yaml.Unmarshaler interface for Regexp.
   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  // MarshalYAML implements the yaml.Marshaler interface for Regexp.
   955  func (re Regexp) MarshalYAML() (interface{}, error) {
   956  	if re.original != "" {
   957  		return re.original, nil
   958  	}
   959  	return nil, nil
   960  }
   961  
   962  // UnmarshalJSON implements the json.Unmarshaler interface for Regexp
   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  // MarshalJSON implements the json.Marshaler interface for Regexp.
   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  // Matchers is label.Matchers with an added UnmarshalYAML method to implement the yaml.Unmarshaler interface
   986  // and MarshalYAML to implement the yaml.Marshaler interface.
   987  type Matchers labels.Matchers
   988  
   989  // UnmarshalYAML implements the yaml.Unmarshaler interface for Matchers.
   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  // MarshalYAML implements the yaml.Marshaler interface for Matchers.
  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  // UnmarshalJSON implements the json.Unmarshaler interface for Matchers.
  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  // MarshalJSON implements the json.Marshaler interface for Matchers.
  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