diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 25dce197f52..b701f74a3bb 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -746,6 +746,9 @@ func (c *Config) UnmarshalJSON(b []byte) error { if len(c.Route.Match) > 0 || len(c.Route.MatchRE) > 0 { return fmt.Errorf("root route must not have any matchers") } + if len(c.Route.MuteTimeIntervals) > 0 { + return fmt.Errorf("root route must not have any mute time intervals") + } for _, r := range c.InhibitRules { if err := r.UnmarshalYAML(noopUnmarshal); err != nil { @@ -753,6 +756,33 @@ func (c *Config) UnmarshalJSON(b []byte) error { } } + tiNames := make(map[string]struct{}) + for _, mt := range c.MuteTimeIntervals { + if mt.Name == "" { + return fmt.Errorf("missing name in mute time interval") + } + if _, ok := tiNames[mt.Name]; ok { + return fmt.Errorf("mute time interval %q is not unique", mt.Name) + } + tiNames[mt.Name] = struct{}{} + } + return checkTimeInterval(c.Route, tiNames) +} + +func checkTimeInterval(r *Route, timeIntervals map[string]struct{}) error { + for _, sr := range r.Routes { + if err := checkTimeInterval(sr, timeIntervals); err != nil { + return err + } + } + if len(r.MuteTimeIntervals) == 0 { + return nil + } + for _, mt := range r.MuteTimeIntervals { + if _, ok := timeIntervals[mt]; !ok { + return fmt.Errorf("undefined time interval %q used in route", mt) + } + } return nil } diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go index d6be3625476..a5c88d01621 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go @@ -2,6 +2,7 @@ package definitions import ( "encoding/json" + "errors" "io/ioutil" "strings" "testing" @@ -405,6 +406,283 @@ email_configs: } } +func Test_ConfigUnmashaling(t *testing.T) { + for _, tc := range []struct { + desc, input string + err error + }{ + { + desc: "empty mute time name should error", + err: errors.New("missing name in mute time interval"), + input: ` + { + "route": { + "receiver": "grafana-default-email" + }, + "mute_time_intervals": [ + { + "name": "", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + } + ], + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "uxwfZvtnz", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "" + }, + "secureFields": {} + } + ] + } + ] + } + `, + }, + { + desc: "not unique mute time names should error", + err: errors.New("mute time interval \"test1\" is not unique"), + input: ` + { + "route": { + "receiver": "grafana-default-email" + }, + "mute_time_intervals": [ + { + "name": "test1", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + }, + { + "name": "test1", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + } + ], + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "uxwfZvtnz", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "" + }, + "secureFields": {} + } + ] + } + ] + } + `, + }, + { + desc: "mute time intervals on root route should error", + err: errors.New("root route must not have any mute time intervals"), + input: ` + { + "route": { + "receiver": "grafana-default-email", + "mute_time_intervals": ["test1"] + }, + "mute_time_intervals": [ + { + "name": "test1", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + } + ], + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "uxwfZvtnz", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "" + }, + "secureFields": {} + } + ] + } + ] + } + `, + }, + { + desc: "undefined mute time names in routes should error", + err: errors.New("undefined time interval \"test2\" used in route"), + input: ` + { + "route": { + "receiver": "grafana-default-email", + "routes": [ + { + "receiver": "grafana-default-email", + "object_matchers": [ + [ + "a", + "=", + "b" + ] + ], + "mute_time_intervals": [ + "test2" + ] + } + ] + }, + "mute_time_intervals": [ + { + "name": "test1", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + } + ], + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "uxwfZvtnz", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "" + }, + "secureFields": {} + } + ] + } + ] + } + `, + }, + { + desc: "valid config should not error", + input: ` + { + "route": { + "receiver": "grafana-default-email", + "routes": [ + { + "receiver": "grafana-default-email", + "object_matchers": [ + [ + "a", + "=", + "b" + ] + ], + "mute_time_intervals": [ + "test1" + ] + } + ] + }, + "mute_time_intervals": [ + { + "name": "test1", + "time_intervals": [ + { + "times": [ + { + "start_time": "00:00", + "end_time": "12:00" + } + ] + } + ] + } + ], + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "uxwfZvtnz", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "" + }, + "secureFields": {} + } + ] + } + ] + } + `, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + var out Config + err := json.Unmarshal([]byte(tc.input), &out) + require.Equal(t, tc.err, err) + }) + } +} + func Test_GettableUserConfigUnmarshaling(t *testing.T) { for _, tc := range []struct { desc, input string