From c08c14f8dd81a4ad354d415cc2ef5cf707379305 Mon Sep 17 00:00:00 2001 From: Alex Moreno Date: Thu, 27 Oct 2022 19:07:01 +0200 Subject: [PATCH] Alerting: Add custom title to pushover contact point (#57530) * Add custom title to pushover contact point * Update pkg/services/ngalert/notifier/channels/pushover.go Co-authored-by: Santiago * Use simplejson * Use more verbose variable names Co-authored-by: Santiago --- .../ngalert/notifier/channels/pushover.go | 158 ++++++++---------- .../notifier/channels/pushover_test.go | 52 +++++- .../channels_config/available_channels.go | 7 + 3 files changed, 118 insertions(+), 99 deletions(-) diff --git a/pkg/services/ngalert/notifier/channels/pushover.go b/pkg/services/ngalert/notifier/channels/pushover.go index 3f85bebd44b..0df40f44a7f 100644 --- a/pkg/services/ngalert/notifier/channels/pushover.go +++ b/pkg/services/ngalert/notifier/channels/pushover.go @@ -32,112 +32,90 @@ var ( // alert notifications to Pushover type PushoverNotifier struct { *Base - UserKey string - APIToken string - AlertingPriority int - OKPriority int - Retry int - Expire int - Device string - AlertingSound string - OKSound string - Upload bool - Message string - tmpl *template.Template - log log.Logger - images ImageStore - ns notifications.WebhookSender + tmpl *template.Template + log log.Logger + images ImageStore + ns notifications.WebhookSender + settings pushoverSettings } -type PushoverConfig struct { - *NotificationChannelConfig - UserKey string - APIToken string - AlertingPriority int - OKPriority int - Retry int - Expire int - Device string - AlertingSound string - OKSound string - Upload bool - Message string +type pushoverSettings struct { + userKey string + apiToken string + alertingPriority int + okPriority int + retry int + expire int + device string + alertingSound string + okSound string + upload bool + title string + message string } func PushoverFactory(fc FactoryConfig) (NotificationChannel, error) { - cfg, err := NewPushoverConfig(fc.Config, fc.DecryptFunc) + pn, err := newPushoverNotifier(fc) if err != nil { return nil, receiverInitError{ Reason: err.Error(), Cfg: *fc.Config, } } - return NewPushoverNotifier(cfg, fc.ImageStore, fc.NotificationService, fc.Template), nil + return pn, nil } -func NewPushoverConfig(config *NotificationChannelConfig, decryptFunc GetDecryptedValueFn) (*PushoverConfig, error) { - userKey := decryptFunc(context.Background(), config.SecureSettings, "userKey", config.Settings.Get("userKey").MustString()) +// newPushoverNotifier is the constructor for the Pushover notifier +func newPushoverNotifier(fc FactoryConfig) (*PushoverNotifier, error) { + decryptFunc := fc.DecryptFunc + userKey := decryptFunc(context.Background(), fc.Config.SecureSettings, "userKey", fc.Config.Settings.Get("userKey").MustString()) if userKey == "" { return nil, errors.New("user key not found") } - APIToken := decryptFunc(context.Background(), config.SecureSettings, "apiToken", config.Settings.Get("apiToken").MustString()) - if APIToken == "" { + apiToken := decryptFunc(context.Background(), fc.Config.SecureSettings, "apiToken", fc.Config.Settings.Get("apiToken").MustString()) + if apiToken == "" { return nil, errors.New("API token not found") } - alertingPriority, err := strconv.Atoi(config.Settings.Get("priority").MustString("0")) // default Normal + + alertingPriority, err := strconv.Atoi(fc.Config.Settings.Get("priority").MustString("0")) // default Normal if err != nil { return nil, fmt.Errorf("failed to convert alerting priority to integer: %w", err) } - okPriority, err := strconv.Atoi(config.Settings.Get("okPriority").MustString("0")) // default Normal + okPriority, err := strconv.Atoi(fc.Config.Settings.Get("okPriority").MustString("0")) // default Normal if err != nil { return nil, fmt.Errorf("failed to convert OK priority to integer: %w", err) } - retry, _ := strconv.Atoi(config.Settings.Get("retry").MustString()) - expire, _ := strconv.Atoi(config.Settings.Get("expire").MustString()) - return &PushoverConfig{ - NotificationChannelConfig: config, - APIToken: APIToken, - UserKey: userKey, - AlertingPriority: alertingPriority, - OKPriority: okPriority, - Retry: retry, - Expire: expire, - Device: config.Settings.Get("device").MustString(), - AlertingSound: config.Settings.Get("sound").MustString(), - OKSound: config.Settings.Get("okSound").MustString(), - Upload: config.Settings.Get("uploadImage").MustBool(true), - Message: config.Settings.Get("message").MustString(DefaultMessageEmbed), - }, nil -} + retry, _ := strconv.Atoi(fc.Config.Settings.Get("retry").MustString()) + expire, _ := strconv.Atoi(fc.Config.Settings.Get("expire").MustString()) -// NewSlackNotifier is the constructor for the Slack notifier -func NewPushoverNotifier(config *PushoverConfig, images ImageStore, - ns notifications.WebhookSender, t *template.Template) *PushoverNotifier { return &PushoverNotifier{ Base: NewBase(&models.AlertNotification{ - Uid: config.UID, - Name: config.Name, - Type: config.Type, - DisableResolveMessage: config.DisableResolveMessage, - Settings: config.Settings, - SecureSettings: config.SecureSettings, + Uid: fc.Config.UID, + Name: fc.Config.Name, + Type: fc.Config.Type, + DisableResolveMessage: fc.Config.DisableResolveMessage, + Settings: fc.Config.Settings, + SecureSettings: fc.Config.SecureSettings, }), - UserKey: config.UserKey, - APIToken: config.APIToken, - AlertingPriority: config.AlertingPriority, - OKPriority: config.OKPriority, - Retry: config.Retry, - Expire: config.Expire, - Device: config.Device, - AlertingSound: config.AlertingSound, - OKSound: config.OKSound, - Upload: config.Upload, - Message: config.Message, - tmpl: t, - log: log.New("alerting.notifier.pushover"), - images: images, - ns: ns, - } + tmpl: fc.Template, + log: log.New("alerting.notifier.pushover"), + images: fc.ImageStore, + ns: fc.NotificationService, + settings: pushoverSettings{ + userKey: userKey, + apiToken: apiToken, + alertingPriority: alertingPriority, + okPriority: okPriority, + retry: retry, + expire: expire, + device: fc.Config.Settings.Get("device").MustString(), + alertingSound: fc.Config.Settings.Get("sound").MustString(), + okSound: fc.Config.Settings.Get("okSound").MustString(), + upload: fc.Config.Settings.Get("uploadImage").MustBool(true), + title: fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed), + message: fc.Config.Settings.Get("message").MustString(DefaultMessageEmbed), + }, + }, nil } // Notify sends an alert notification to Slack. @@ -181,40 +159,40 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al var tmplErr error tmpl, _ := TmplText(ctx, pn.tmpl, as, pn.log, &tmplErr) - if err := w.WriteField("user", tmpl(pn.UserKey)); err != nil { + if err := w.WriteField("user", tmpl(pn.settings.userKey)); err != nil { return nil, b, fmt.Errorf("failed to write the user: %w", err) } - if err := w.WriteField("token", pn.APIToken); err != nil { + if err := w.WriteField("token", pn.settings.apiToken); err != nil { return nil, b, fmt.Errorf("failed to write the token: %w", err) } status := types.Alerts(as...).Status() - priority := pn.AlertingPriority + priority := pn.settings.alertingPriority if status == model.AlertResolved { - priority = pn.OKPriority + priority = pn.settings.okPriority } if err := w.WriteField("priority", strconv.Itoa(priority)); err != nil { return nil, b, fmt.Errorf("failed to write the priority: %w", err) } if priority == 2 { - if err := w.WriteField("retry", strconv.Itoa(pn.Retry)); err != nil { + if err := w.WriteField("retry", strconv.Itoa(pn.settings.retry)); err != nil { return nil, b, fmt.Errorf("failed to write retry: %w", err) } - if err := w.WriteField("expire", strconv.Itoa(pn.Expire)); err != nil { + if err := w.WriteField("expire", strconv.Itoa(pn.settings.expire)); err != nil { return nil, b, fmt.Errorf("failed to write expire: %w", err) } } - if pn.Device != "" { - if err := w.WriteField("device", tmpl(pn.Device)); err != nil { + if pn.settings.device != "" { + if err := w.WriteField("device", tmpl(pn.settings.device)); err != nil { return nil, b, fmt.Errorf("failed to write the device: %w", err) } } - if err := w.WriteField("title", tmpl(DefaultMessageTitleEmbed)); err != nil { + if err := w.WriteField("title", tmpl(pn.settings.title)); err != nil { return nil, b, fmt.Errorf("failed to write the title: %w", err) } @@ -227,7 +205,7 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al return nil, b, fmt.Errorf("failed to write the URL title: %w", err) } - if err := w.WriteField("message", tmpl(pn.Message)); err != nil { + if err := w.WriteField("message", tmpl(pn.settings.message)); err != nil { return nil, b, fmt.Errorf("failed write the message: %w", err) } @@ -267,9 +245,9 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al var sound string if status == model.AlertResolved { - sound = tmpl(pn.OKSound) + sound = tmpl(pn.settings.okSound) } else { - sound = tmpl(pn.AlertingSound) + sound = tmpl(pn.settings.alertingSound) } if sound != "default" { if err := w.WriteField("sound", sound); err != nil { diff --git a/pkg/services/ngalert/notifier/channels/pushover_test.go b/pkg/services/ngalert/notifier/channels/pushover_test.go index ae8cd654dd5..f9c0daa9e9e 100644 --- a/pkg/services/ngalert/notifier/channels/pushover_test.go +++ b/pkg/services/ngalert/notifier/channels/pushover_test.go @@ -67,6 +67,35 @@ func TestPushoverNotifier(t *testing.T) { }, expMsgError: nil, }, + { + name: "Custom title", + settings: `{ + "userKey": "", + "apiToken": "", + "title": "Alerts firing: {{ len .Alerts.Firing }}" + }`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertImageToken__": "test-image-1"}, + }, + }, + }, + expMsg: map[string]string{ + "user": "", + "token": "", + "priority": "0", + "sound": "", + "title": "Alerts firing: 1", + "url": "http://localhost/alerting/list", + "url_title": "Show alert rule", + "message": "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n", + "attachment": "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\b\x04\x00\x00\x00\xb5\x1c\f\x02\x00\x00\x00\vIDATx\xdacd`\x00\x00\x00\x06\x00\x020\x81\xd0/\x00\x00\x00\x00IEND\xaeB`\x82", + "html": "1", + }, + expMsgError: nil, + }, { name: "Custom config with multiple alerts", settings: `{ @@ -141,17 +170,23 @@ func TestPushoverNotifier(t *testing.T) { require.NoError(t, err) secureSettings := make(map[string][]byte) - m := &NotificationChannelConfig{ - Name: "pushover_testing", - Type: "pushover", - Settings: settingsJSON, - SecureSettings: secureSettings, - } - webhookSender := mockNotificationService() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue - cfg, err := NewPushoverConfig(m, decryptFn) + + fc := FactoryConfig{ + Config: &NotificationChannelConfig{ + Name: "pushover_testing", + Type: "pushover", + Settings: settingsJSON, + SecureSettings: secureSettings, + }, + ImageStore: images, + NotificationService: webhookSender, + DecryptFunc: decryptFn, + Template: tmpl, + } + pn, err := newPushoverNotifier(fc) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) @@ -161,7 +196,6 @@ func TestPushoverNotifier(t *testing.T) { ctx := notify.WithGroupKey(context.Background(), "alertname") ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""}) - pn := NewPushoverNotifier(cfg, images, webhookSender, tmpl) ok, err := pn.Notify(ctx, c.alerts...) if c.expMsgError != nil { require.Error(t, err) diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 8c6b8c12187..cdf843aab62 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -410,6 +410,13 @@ func GetAvailableNotifiers() []*NotifierPlugin { SelectOptions: pushoverSoundOptions, PropertyName: "okSound", }, + { // New in 9.3. + Label: "Title", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: channels.DefaultMessageTitleEmbed, + PropertyName: "title", + }, { // New in 8.0. Label: "Message", Element: ElementTypeTextArea,