diff --git a/config/config.go b/config/config.go index d6ad565d..29981d22 100644 --- a/config/config.go +++ b/config/config.go @@ -579,7 +579,7 @@ type Route struct { GroupByAll bool `yaml:"-" json:"-"` Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"` - MatchRE map[string]Regexp `yaml:"match_re,omitempty" json:"match_re,omitempty"` + MatchRE MatchRegexps `yaml:"match_re,omitempty" json:"match_re,omitempty"` Continue bool `yaml:"continue,omitempty" json:"continue,omitempty"` Routes []*Route `yaml:"routes,omitempty" json:"routes,omitempty"` @@ -601,11 +601,6 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error { } } - for k := range r.MatchRE { - if !model.LabelNameRE.MatchString(k) { - return fmt.Errorf("invalid label name %q", k) - } - } for _, l := range r.GroupByStr { if l == "..." { r.GroupByAll = true @@ -650,13 +645,13 @@ type InhibitRule struct { SourceMatch map[string]string `yaml:"source_match,omitempty" json:"source_match,omitempty"` // SourceMatchRE defines pairs like SourceMatch but does regular expression // matching. - SourceMatchRE map[string]Regexp `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"` + SourceMatchRE MatchRegexps `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"` // TargetMatch defines a set of labels that have to equal the given // value for target alerts. TargetMatch map[string]string `yaml:"target_match,omitempty" json:"target_match,omitempty"` // TargetMatchRE defines pairs like TargetMatch but does regular expression // matching. - TargetMatchRE map[string]Regexp `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"` + TargetMatchRE MatchRegexps `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"` // A set of labels that must be equal between the source and target alert // for them to be a match. Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"` @@ -675,24 +670,12 @@ func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error { } } - for k := range r.SourceMatchRE { - if !model.LabelNameRE.MatchString(k) { - return fmt.Errorf("invalid label name %q", k) - } - } - for k := range r.TargetMatch { if !model.LabelNameRE.MatchString(k) { return fmt.Errorf("invalid label name %q", k) } } - for k := range r.TargetMatchRE { - if !model.LabelNameRE.MatchString(k) { - return fmt.Errorf("invalid label name %q", k) - } - } - return nil } @@ -724,6 +707,26 @@ func (c *Receiver) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +// MatchRegexps represents a map of Regexp. +type MatchRegexps map[string]Regexp + +// UnmarshalYAML implements the yaml.Unmarshaler interface for MatchRegexps. +func (m *MatchRegexps) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain MatchRegexps + if err := unmarshal((*plain)(m)); err != nil { + return err + } + for k, v := range *m { + if !model.LabelNameRE.MatchString(k) { + return fmt.Errorf("invalid label name %q", k) + } + if v.Regexp == nil { + return fmt.Errorf("invalid regexp value for %q", k) + } + } + return nil +} + // Regexp encapsulates a regexp.Regexp and makes it YAML marshalable. type Regexp struct { *regexp.Regexp diff --git a/config/config_test.go b/config/config_test.go index 0f4814ba..0305ab2b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -16,6 +16,7 @@ package config import ( "encoding/json" "net/url" + "os" "reflect" "regexp" "strings" @@ -776,3 +777,32 @@ func TestUnmarshalHostPort(t *testing.T) { }) } } + +func TestNilRegexp(t *testing.T) { + for _, tc := range []struct { + file string + errMsg string + }{ + { + file: "testdata/conf.nil-match_re-route.yml", + errMsg: "invalid_label", + }, + { + file: "testdata/conf.nil-source_match_re-inhibitiion.yml", + errMsg: "invalid_source_label", + }, + { + file: "testdata/conf.nil-target_match_re-inhibitiion.yml", + errMsg: "invalid_target_label", + }, + } { + t.Run(tc.file, func(t *testing.T) { + _, err := os.Stat(tc.file) + require.NoError(t, err) + + _, err = LoadFile(tc.file) + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMsg) + }) + } +} diff --git a/config/testdata/conf.nil-match_re-route.yml b/config/testdata/conf.nil-match_re-route.yml new file mode 100644 index 00000000..78e8c66f --- /dev/null +++ b/config/testdata/conf.nil-match_re-route.yml @@ -0,0 +1,10 @@ +route: + receiver: empty + + routes: + - match_re: + invalid_label: + receiver: empty + +receivers: +- name: empty diff --git a/config/testdata/conf.nil-source_match_re-inhibitiion.yml b/config/testdata/conf.nil-source_match_re-inhibitiion.yml new file mode 100644 index 00000000..5fdffffc --- /dev/null +++ b/config/testdata/conf.nil-source_match_re-inhibitiion.yml @@ -0,0 +1,11 @@ +route: + receiver: empty + +receivers: +- name: empty + +inhibit_rules: +- source_match_re: + invalid_source_label: + target_match_re: + severity: critical diff --git a/config/testdata/conf.nil-target_match_re-inhibitiion.yml b/config/testdata/conf.nil-target_match_re-inhibitiion.yml new file mode 100644 index 00000000..66067cca --- /dev/null +++ b/config/testdata/conf.nil-target_match_re-inhibitiion.yml @@ -0,0 +1,11 @@ +route: + receiver: empty + +receivers: +- name: empty + +inhibit_rules: +- source_match: + severity: critical + target_match_re: + invalid_target_label: