diff --git a/pkg/api/alertmanager.go b/pkg/api/alertmanager.go index 5177aeea43c..89f2cf63e4c 100644 --- a/pkg/api/alertmanager.go +++ b/pkg/api/alertmanager.go @@ -174,10 +174,68 @@ type AlertingConfigResponse struct { type ApiAlertingConfig struct { config.Config + // Override/eliminate Routes tree in AM config - we'll set it via new fields + Route struct{} `yaml:"route,omitempty" json:"route,omitempty"` + + AlertManagerRoute *config.Route `yaml:"alertmanager_route,omitempty" json:"alertmanager_route,omitempty"` + GrafanaManagedRoute *config.Route `yaml:"grafana_managed_route,omitempty" json:"grafana_managed_route,omitempty"` + // Override with our superset receiver type Receivers []*ApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"` } +func (c *ApiAlertingConfig) UnmarshalJSON(b []byte) error { + type plain ApiAlertingConfig + if err := json.Unmarshal(b, (*plain)(c)); err != nil { + return err + } + + return c.validate() +} + +// validate ensures that the two routing trees use the correct receiver types. +func (c *ApiAlertingConfig) validate() error { + receivers := make(map[string]ReceiverType, len(c.Receivers)) + + for _, r := range c.Receivers { + receivers[r.Name] = r.Type() + } + + for _, receiver := range AllReceivers(c.GrafanaManagedRoute) { + t, ok := receivers[receiver] + if !ok { + return fmt.Errorf("unexpected receiver (%s) is undefined", receiver) + } + if t != GrafanaReceiverType { + return fmt.Errorf("unexpected receiver (%s): cannot use Alertmanager receiver types in Grafana managed routes", receiver) + } + + } + + for _, receiver := range AllReceivers(c.AlertManagerRoute) { + t, ok := receivers[receiver] + if !ok { + return fmt.Errorf("unexpected receiver (%s) is undefined", receiver) + } + if t != AlertmanagerReceiverType { + return fmt.Errorf("unexpected receiver (%s): cannot use Grafana receiver types in non-Grafana managed routes", receiver) + } + + } + + return nil +} + +// AllReceivers will recursively walk a routing tree and return a list of all the +// referenced receiver names. +func AllReceivers(route *config.Route) (res []string) { + res = append(res, route.Receiver) + for _, subRoute := range route.Routes { + res = append(res, AllReceivers(subRoute)...) + } + return res +} + type GrafanaReceiver models.CreateAlertNotificationCommand type ReceiverType int @@ -227,6 +285,7 @@ func (r *ApiReceiver) UnmarshalJSON(b []byte) error { } } + return nil } diff --git a/pkg/api/alertmanager_test.go b/pkg/api/alertmanager_test.go index 1a47516d581..a551e3775d3 100644 --- a/pkg/api/alertmanager_test.go +++ b/pkg/api/alertmanager_test.go @@ -63,3 +63,241 @@ func Test_ApiReceiver_Marshaling(t *testing.T) { }) } } + +func Test_AllReceivers(t *testing.T) { + input := &config.Route{ + Receiver: "foo", + Routes: []*config.Route{ + { + Receiver: "bar", + Routes: []*config.Route{ + { + Receiver: "bazz", + }, + }, + }, + { + Receiver: "buzz", + }, + }, + } + + require.Equal(t, []string{"foo", "bar", "bazz", "buzz"}, AllReceivers(input)) +} + +func Test_ApiAlertingConfig_Marshaling(t *testing.T) { + for _, tc := range []struct { + desc string + input ApiAlertingConfig + err bool + }{ + { + desc: "success", + input: ApiAlertingConfig{ + Config: config.Config{}, + AlertManagerRoute: &config.Route{ + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + GrafanaManagedRoute: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + Receivers: []*ApiReceiver{ + { + Receiver: config.Receiver{ + Name: "am", + EmailConfigs: []*config.EmailConfig{{}}, + }, + }, + { + Receiver: config.Receiver{ + Name: "graf", + }, + GrafanaReceivers: GrafanaReceivers{ + GrafanaManagedReceivers: []*GrafanaReceiver{{}}, + }, + }, + }, + }, + }, + { + desc: "failure undefined am receiver", + input: ApiAlertingConfig{ + Config: config.Config{}, + AlertManagerRoute: &config.Route{ + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "unmentioned", + }, + }, + }, + GrafanaManagedRoute: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + Receivers: []*ApiReceiver{ + { + Receiver: config.Receiver{ + Name: "am", + EmailConfigs: []*config.EmailConfig{{}}, + }, + }, + { + Receiver: config.Receiver{ + Name: "graf", + }, + GrafanaReceivers: GrafanaReceivers{ + GrafanaManagedReceivers: []*GrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure undefined graf receiver", + input: ApiAlertingConfig{ + Config: config.Config{}, + AlertManagerRoute: &config.Route{ + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + GrafanaManagedRoute: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "unmentioned", + }, + }, + }, + Receivers: []*ApiReceiver{ + { + Receiver: config.Receiver{ + Name: "am", + EmailConfigs: []*config.EmailConfig{{}}, + }, + }, + { + Receiver: config.Receiver{ + Name: "graf", + }, + GrafanaReceivers: GrafanaReceivers{ + GrafanaManagedReceivers: []*GrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure mixed AM in Grafana", + input: ApiAlertingConfig{ + Config: config.Config{}, + AlertManagerRoute: &config.Route{ + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + GrafanaManagedRoute: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + Receivers: []*ApiReceiver{ + { + Receiver: config.Receiver{ + Name: "am", + EmailConfigs: []*config.EmailConfig{{}}, + }, + }, + { + Receiver: config.Receiver{ + Name: "graf", + }, + GrafanaReceivers: GrafanaReceivers{ + GrafanaManagedReceivers: []*GrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure mixed Grafana in AM", + input: ApiAlertingConfig{ + Config: config.Config{}, + AlertManagerRoute: &config.Route{ + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + GrafanaManagedRoute: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + Receivers: []*ApiReceiver{ + { + Receiver: config.Receiver{ + Name: "am", + EmailConfigs: []*config.EmailConfig{{}}, + }, + }, + { + Receiver: config.Receiver{ + Name: "graf", + }, + GrafanaReceivers: GrafanaReceivers{ + GrafanaManagedReceivers: []*GrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + encoded, err := json.Marshal(tc.input) + require.Nil(t, err) + + var out ApiAlertingConfig + err = json.Unmarshal(encoded, &out) + + if tc.err { + require.Error(t, err) + } else { + require.Equal(t, tc.input, out) + } + }) + } +}