diff --git a/pkg/services/ngalert/notifier/channels/discord.go b/pkg/services/ngalert/notifier/channels/discord.go index 89b8e463dd6..eef72a81d70 100644 --- a/pkg/services/ngalert/notifier/channels/discord.go +++ b/pkg/services/ngalert/notifier/channels/discord.go @@ -50,7 +50,7 @@ func NewDiscordConfig(config *NotificationChannelConfig) (*DiscordConfig, error) }, nil } -func DiscrodFactory(fc FactoryConfig) (NotificationChannel, error) { +func DiscordFactory(fc FactoryConfig) (NotificationChannel, error) { cfg, err := NewDiscordConfig(fc.Config) if err != nil { return nil, receiverInitError{ @@ -95,10 +95,20 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, if d.Content != "" { bodyJSON.Set("content", tmpl(d.Content)) + if tmplErr != nil { + d.log.Warn("failed to template Discord notification content", "err", tmplErr.Error()) + // Reset tmplErr for templating other fields. + tmplErr = nil + } } if d.AvatarURL != "" { bodyJSON.Set("avatar_url", tmpl(d.AvatarURL)) + if tmplErr != nil { + d.log.Warn("failed to template Discord Avatar URL", "err", tmplErr.Error(), "fallback", d.AvatarURL) + bodyJSON.Set("avatar_url", d.AvatarURL) + tmplErr = nil + } } footer := map[string]interface{}{ @@ -119,10 +129,15 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, bodyJSON.Set("embeds", []interface{}{embed}) - u := tmpl(d.WebhookURL) if tmplErr != nil { d.log.Warn("failed to template Discord message", "err", tmplErr.Error()) - return false, tmplErr + tmplErr = nil + } + + u := tmpl(d.WebhookURL) + if tmplErr != nil { + d.log.Warn("failed to template Discord URL", "err", tmplErr.Error(), "fallback", d.WebhookURL) + u = d.WebhookURL } body, err := json.Marshal(bodyJSON) diff --git a/pkg/services/ngalert/notifier/channels/discord_test.go b/pkg/services/ngalert/notifier/channels/discord_test.go index e94cee2cbe2..43999597c5f 100644 --- a/pkg/services/ngalert/notifier/channels/discord_test.go +++ b/pkg/services/ngalert/notifier/channels/discord_test.go @@ -3,7 +3,6 @@ package channels import ( "context" "encoding/json" - "errors" "net/url" "testing" @@ -58,6 +57,134 @@ func TestDiscordNotifier(t *testing.T) { }, expMsgError: nil, }, + { + name: "Missing field in template", + settings: `{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "url": "http://localhost", + "message": "I'm a custom template {{ .NotAField }} bad template" + }`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1"}, + }, + }, + }, + expMsg: map[string]interface{}{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "content": "I'm a custom template ", + "embeds": []interface{}{map[string]interface{}{ + "color": 1.4037554e+07, + "footer": map[string]interface{}{ + "icon_url": "https://grafana.com/assets/img/fav32.png", + "text": "Grafana v" + setting.BuildVersion, + }, + "title": "[FIRING:1] (val1)", + "url": "http://localhost/alerting/list", + "type": "rich", + }}, + "username": "Grafana", + }, + expMsgError: nil, + }, + { + name: "Invalid message template", + settings: `{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "url": "http://localhost", + "message": "{{ template \"invalid.template\" }}" + }`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1"}, + }, + }, + }, + expMsg: map[string]interface{}{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "content": "", + "embeds": []interface{}{map[string]interface{}{ + "color": 1.4037554e+07, + "footer": map[string]interface{}{ + "icon_url": "https://grafana.com/assets/img/fav32.png", + "text": "Grafana v" + setting.BuildVersion, + }, + "title": "[FIRING:1] (val1)", + "url": "http://localhost/alerting/list", + "type": "rich", + }}, + "username": "Grafana", + }, + expMsgError: nil, + }, + { + name: "Invalid avatar URL template", + settings: `{ + "avatar_url": "{{ invalid } }}", + "url": "http://localhost", + "message": "valid message" + }`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1"}, + }, + }, + }, + expMsg: map[string]interface{}{ + "avatar_url": "{{ invalid } }}", + "content": "valid message", + "embeds": []interface{}{map[string]interface{}{ + "color": 1.4037554e+07, + "footer": map[string]interface{}{ + "icon_url": "https://grafana.com/assets/img/fav32.png", + "text": "Grafana v" + setting.BuildVersion, + }, + "title": "[FIRING:1] (val1)", + "url": "http://localhost/alerting/list", + "type": "rich", + }}, + "username": "Grafana", + }, + expMsgError: nil, + }, + { + name: "Invalid URL template", + settings: `{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "url": "http://localhost?q={{invalid }}}", + "message": "valid message" + }`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1"}, + }, + }, + }, + expMsg: map[string]interface{}{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "content": "valid message", + "embeds": []interface{}{map[string]interface{}{ + "color": 1.4037554e+07, + "footer": map[string]interface{}{ + "icon_url": "https://grafana.com/assets/img/fav32.png", + "text": "Grafana v" + setting.BuildVersion, + }, + "title": "[FIRING:1] (val1)", + "url": "http://localhost/alerting/list", + "type": "rich", + }}, + "username": "Grafana", + }, + expMsgError: nil, + }, { name: "Custom config with multiple alerts", settings: `{ @@ -100,14 +227,6 @@ func TestDiscordNotifier(t *testing.T) { settings: `{}`, expInitError: `could not find webhook url property in settings`, }, - { - name: "Invalid template returns error", - settings: `{ - "url": "http://localhost", - "message": "{{ template \"invalid.template\" }}" - }`, - expMsgError: errors.New("template: :1:12: executing \"\" at <{{template \"invalid.template\"}}>: template \"invalid.template\" not defined"), - }, { name: "Default config with one alert, use default discord username", settings: `{ diff --git a/pkg/services/ngalert/notifier/channels/factory.go b/pkg/services/ngalert/notifier/channels/factory.go index ed5d9804724..03059480a6b 100644 --- a/pkg/services/ngalert/notifier/channels/factory.go +++ b/pkg/services/ngalert/notifier/channels/factory.go @@ -36,7 +36,7 @@ func NewFactoryConfig(config *NotificationChannelConfig, notificationService not var receiverFactories = map[string]func(FactoryConfig) (NotificationChannel, error){ "prometheus-alertmanager": AlertmanagerFactory, "dingding": DingDingFactory, - "discord": DiscrodFactory, + "discord": DiscordFactory, "email": EmailFactory, "googlechat": GoogleChatFactory, "kafka": KafkaFactory,