Alerting: Remove dependency on Grafana notifications package in alerting notifiers (#60271)

* create sender service interface and bridge to grafana notifier service
* update notifiers to use local sender interface
This commit is contained in:
Yuri Tseretyan 2022-12-14 10:59:37 -05:00 committed by GitHub
parent 07b5043222
commit 7c3ab4a715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 238 additions and 192 deletions

View File

@ -514,7 +514,7 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
SecureSettings: secureSettings,
}
)
factoryConfig, err := channels.NewFactoryConfig(cfg, am.NotificationService, am.decryptFn, tmpl, am.Store)
factoryConfig, err := channels.NewFactoryConfig(cfg, NewNotificationSender(am.NotificationService), am.decryptFn, tmpl, am.Store)
if err != nil {
return nil, InvalidReceiverError{
Receiver: r,

View File

@ -12,7 +12,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const defaultDingdingMsgType = "link"
@ -73,7 +72,7 @@ func newDingDingNotifier(fc FactoryConfig) (*DingDingNotifier, error) {
type DingDingNotifier struct {
*Base
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings dingDingSettings
}
@ -107,9 +106,9 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
u = dd.settings.URL
}
cmd := &models.SendWebhookSync{Url: u, Body: b}
cmd := &SendWebhookSettings{Url: u, Body: b}
if err := dd.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := dd.ns.SendWebhook(ctx, cmd); err != nil {
return false, fmt.Errorf("send notification to dingding: %w", err)
}

View File

@ -20,14 +20,13 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
type DiscordNotifier struct {
*Base
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
tmpl *template.Template
settings discordSettings
@ -179,7 +178,7 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
return false, err
}
if err := d.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := d.ns.SendWebhook(ctx, cmd); err != nil {
d.log.Error("failed to send notification to Discord", "error", err)
return false, err
}
@ -236,8 +235,8 @@ func (d DiscordNotifier) constructAttachments(ctx context.Context, as []*types.A
return attachments
}
func (d DiscordNotifier) buildRequest(url string, body []byte, attachments []discordAttachment) (*models.SendWebhookSync, error) {
cmd := &models.SendWebhookSync{
func (d DiscordNotifier) buildRequest(url string, body []byte, attachments []discordAttachment) (*SendWebhookSettings, error) {
cmd := &SendWebhookSettings{
Url: url,
HttpMethod: "POST",
}

View File

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/util"
)
@ -27,7 +26,7 @@ type EmailNotifier struct {
Message string
Subject string
log log.Logger
ns notifications.EmailSender
ns EmailSender
images ImageStore
tmpl *template.Template
}
@ -69,7 +68,7 @@ func NewEmailConfig(config *NotificationChannelConfig) (*EmailConfig, error) {
// NewEmailNotifier is the constructor function
// for the EmailNotifier.
func NewEmailNotifier(config *EmailConfig, ns notifications.EmailSender, images ImageStore, t *template.Template) *EmailNotifier {
func NewEmailNotifier(config *EmailConfig, ns EmailSender, images ImageStore, t *template.Template) *EmailNotifier {
return &EmailNotifier{
Base: NewBase(&models.AlertNotification{
Uid: config.UID,
@ -126,33 +125,31 @@ func (en *EmailNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bo
return nil
}, alerts...)
cmd := &models.SendEmailCommandSync{
SendEmailCommand: models.SendEmailCommand{
Subject: subject,
Data: map[string]interface{}{
"Title": subject,
"Message": tmpl(en.Message),
"Status": data.Status,
"Alerts": data.Alerts,
"GroupLabels": data.GroupLabels,
"CommonLabels": data.CommonLabels,
"CommonAnnotations": data.CommonAnnotations,
"ExternalURL": data.ExternalURL,
"RuleUrl": ruleURL,
"AlertPageUrl": alertPageURL,
},
EmbeddedFiles: embeddedFiles,
To: en.Addresses,
SingleEmail: en.SingleEmail,
Template: "ng_alert_notification",
cmd := &SendEmailSettings{
Subject: subject,
Data: map[string]interface{}{
"Title": subject,
"Message": tmpl(en.Message),
"Status": data.Status,
"Alerts": data.Alerts,
"GroupLabels": data.GroupLabels,
"CommonLabels": data.CommonLabels,
"CommonAnnotations": data.CommonAnnotations,
"ExternalURL": data.ExternalURL,
"RuleUrl": ruleURL,
"AlertPageUrl": alertPageURL,
},
EmbeddedFiles: embeddedFiles,
To: en.Addresses,
SingleEmail: en.SingleEmail,
Template: "ng_alert_notification",
}
if tmplErr != nil {
en.log.Warn("failed to template email message", "error", tmplErr.Error())
}
if err := en.ns.SendEmailCommandHandlerSync(ctx, cmd); err != nil {
if err := en.ns.SendEmail(ctx, cmd); err != nil {
return false, err
}

View File

@ -104,7 +104,7 @@ func TestEmailNotifier(t *testing.T) {
}
func TestEmailNotifierIntegration(t *testing.T) {
ns := CreateNotificationService(t)
ns := createEmailSender(t)
emailTmpl := templateForTests(t)
externalURL, err := url.Parse("http://localhost/base")
@ -267,7 +267,7 @@ func TestEmailNotifierIntegration(t *testing.T) {
}
}
func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *template.Template, ns notifications.EmailSender) *EmailNotifier {
func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *template.Template, ns *emailSender) *EmailNotifier {
t.Helper()
json := `{
@ -295,10 +295,10 @@ func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *
return emailNotifier
}
func getSingleSentMessage(t *testing.T, ns *notifications.NotificationService) *notifications.Message {
func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message {
t.Helper()
mailer := ns.GetMailer().(*notifications.FakeMailer)
mailer := ns.ns.GetMailer().(*notifications.FakeMailer)
require.Len(t, mailer.Sent, 1)
sent := mailer.Sent[0]
mailer.Sent = []*notifications.Message{}

View File

@ -8,12 +8,11 @@ import (
"github.com/prometheus/alertmanager/template"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
type FactoryConfig struct {
Config *NotificationChannelConfig
NotificationService notifications.Service
NotificationService NotificationSender
DecryptFunc GetDecryptedValueFn
ImageStore ImageStore
// Used to retrieve image URLs for messages, or data for uploads.
@ -24,7 +23,7 @@ type ImageStore interface {
GetImage(ctx context.Context, token string) (*models.Image, error)
}
func NewFactoryConfig(config *NotificationChannelConfig, notificationService notifications.Service,
func NewFactoryConfig(config *NotificationChannelConfig, notificationService NotificationSender,
decryptFunc GetDecryptedValueFn, template *template.Template, imageStore ImageStore) (FactoryConfig, error) {
if config.Settings == nil {
return FactoryConfig{}, errors.New("no settings supplied")

View File

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
@ -23,7 +22,7 @@ import (
type GoogleChatNotifier struct {
*Base
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
tmpl *template.Template
settings googleChatSettings
@ -160,7 +159,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
return false, fmt.Errorf("marshal json: %w", err)
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: u,
HttpMethod: "POST",
HttpHeader: map[string]string{
@ -169,7 +168,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
Body: string(body),
}
if err := gcn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := gcn.ns.SendWebhook(ctx, cmd); err != nil {
gcn.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", gcn.Name)
return false, err
}

View File

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
// KafkaNotifier is responsible for sending
@ -23,7 +22,7 @@ type KafkaNotifier struct {
*Base
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings kafkaSettings
}
@ -91,7 +90,7 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
kn.log.Warn("failed to template Kafka message", "error", tmplErr.Error())
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: topicURL,
Body: body,
HttpMethod: "POST",
@ -101,7 +100,7 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
},
}
if err = kn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err = kn.ns.SendWebhook(ctx, cmd); err != nil {
kn.log.Error("Failed to send notification to Kafka", "error", err, "body", body)
return false, err
}

View File

@ -12,7 +12,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
var (
@ -24,7 +23,7 @@ var (
type LineNotifier struct {
*Base
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings lineSettings
}
@ -79,7 +78,7 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
form := url.Values{}
form.Add("message", body)
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: LineNotifyURL,
HttpMethod: "POST",
HttpHeader: map[string]string{
@ -89,7 +88,7 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
Body: form.Encode(),
}
if err := ln.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := ln.ns.SendWebhook(ctx, cmd); err != nil {
ln.log.Error("failed to send notification to LINE", "error", err, "body", body)
return false, err
}

View File

@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const (
@ -39,7 +38,7 @@ type OpsgenieNotifier struct {
*Base
tmpl *template.Template
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
settings *opsgenieSettings
}
@ -163,7 +162,7 @@ func (on *OpsgenieNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
return true, nil
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: url,
Body: string(body),
HttpMethod: http.MethodPost,
@ -173,7 +172,7 @@ func (on *OpsgenieNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
},
}
if err := on.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := on.ns.SendWebhook(ctx, cmd); err != nil {
return false, fmt.Errorf("send notification to Opsgenie: %w", err)
}

View File

@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const (
@ -45,7 +44,7 @@ type PagerdutyNotifier struct {
*Base
tmpl *template.Template
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
settings *pagerdutySettings
}
@ -166,7 +165,7 @@ func (pn *PagerdutyNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
}
pn.log.Info("notifying Pagerduty", "event_type", eventType)
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: PagerdutyEventAPIURL,
Body: string(body),
HttpMethod: "POST",
@ -174,7 +173,7 @@ func (pn *PagerdutyNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
"Content-Type": "application/json",
},
}
if err := pn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := pn.ns.SendWebhook(ctx, cmd); err != nil {
return false, fmt.Errorf("send notification to Pagerduty: %w", err)
}

View File

@ -20,7 +20,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const (
@ -44,7 +43,7 @@ type PushoverNotifier struct {
tmpl *template.Template
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
settings pushoverSettings
}
@ -173,14 +172,14 @@ func (pn *PushoverNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
return false, err
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: PushoverEndpoint,
HttpMethod: "POST",
HttpHeader: headers,
Body: uploadBody.String(),
}
if err := pn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := pn.ns.SendWebhook(ctx, cmd); err != nil {
pn.log.Error("failed to send pushover notification", "error", err, "webhook", pn.Name)
return false, err
}

View File

@ -0,0 +1,46 @@
package channels
import "context"
type SendWebhookSettings struct {
Url string
User string
Password string
Body string
HttpMethod string
HttpHeader map[string]string
ContentType string
Validation func(body []byte, statusCode int) error
}
// SendEmailSettings is the command for sending emails
type SendEmailSettings struct {
To []string
SingleEmail bool
Template string
Subject string
Data map[string]interface{}
Info string
ReplyTo []string
EmbeddedFiles []string
AttachedFiles []*SendEmailAttachFile
}
// SendEmailAttachFile is a definition of the attached files without path
type SendEmailAttachFile struct {
Name string
Content []byte
}
type WebhookSender interface {
SendWebhook(ctx context.Context, cmd *SendWebhookSettings) error
}
type EmailSender interface {
SendEmail(ctx context.Context, cmd *SendEmailSettings) error
}
type NotificationSender interface {
WebhookSender
EmailSender
}

View File

@ -14,14 +14,13 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
type SensuGoNotifier struct {
*Base
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings sensuGoSettings
}
@ -172,7 +171,7 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
return false, err
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: fmt.Sprintf("%s/api/core/v2/namespaces/%s/events", strings.TrimSuffix(sn.settings.URL, "/"), namespace),
Body: string(body),
HttpMethod: "POST",
@ -181,7 +180,7 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
"Authorization": fmt.Sprintf("Key %s", sn.settings.APIKey),
},
}
if err := sn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := sn.ns.SendWebhook(ctx, cmd); err != nil {
sn.log.Error("failed to send Sensu Go event", "error", err, "sensugo", sn.Name)
return false, err
}

View File

@ -25,7 +25,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
@ -67,7 +66,7 @@ type SlackNotifier struct {
log log.Logger
tmpl *template.Template
images ImageStore
webhookSender notifications.WebhookSender
webhookSender WebhookSender
sendFn sendFunc
settings slackSettings
}

View File

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const (
@ -253,7 +252,7 @@ type TeamsNotifier struct {
*Base
tmpl *template.Template
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
settings teamsSettings
}
@ -355,25 +354,27 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
return false, fmt.Errorf("failed to marshal JSON: %w", err)
}
cmd := &models.SendWebhookSync{Url: u, Body: string(b)}
cmd := &SendWebhookSettings{Url: u, Body: string(b)}
// Teams sometimes does not use status codes to show when a request has failed. Instead, the
// response can contain an error message, irrespective of status code (i.e. https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#rate-limiting-for-connectors)
cmd.Validation = func(b []byte, statusCode int) error {
// The request succeeded if the response is "1"
// https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#send-messages-using-curl-and-powershell
if !bytes.Equal(b, []byte("1")) {
return errors.New(string(b))
}
return nil
}
cmd.Validation = validateResponse
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := tn.ns.SendWebhook(ctx, cmd); err != nil {
return false, errors.Wrap(err, "send notification to Teams")
}
return true, nil
}
func validateResponse(b []byte, statusCode int) error {
// The request succeeded if the response is "1"
// https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#send-messages-using-curl-and-powershell
if !bytes.Equal(b, []byte("1")) {
return errors.New(string(b))
}
return nil
}
func (tn *TeamsNotifier) SendResolved() bool {
return !tn.GetDisableResolveMessage()
}

View File

@ -3,11 +3,8 @@ package channels
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"math/rand"
"net/url"
"strings"
"testing"
"github.com/prometheus/alertmanager/notify"
@ -16,7 +13,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/notifications"
)
func TestTeamsNotifier(t *testing.T) {
@ -30,7 +26,6 @@ func TestTeamsNotifier(t *testing.T) {
name string
settings string
alerts []*types.Alert
response *mockResponse
expMsg map[string]interface{}
expInitError string
expMsgError error
@ -250,23 +245,6 @@ func TestTeamsNotifier(t *testing.T) {
name: "Error in initing",
settings: `{}`,
expInitError: `could not find url property in settings`,
}, {
name: "webhook returns error message in body with 200",
settings: `{"url": "http://localhost"}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
response: &mockResponse{
status: 200,
body: "some error message",
error: nil,
},
expMsgError: errors.New("send notification to Teams: webhook failed validation: some error message"),
}}
for _, c := range cases {
@ -280,14 +258,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON,
}
webhookSender := CreateNotificationService(t)
originalClient := notifications.NetClient
defer func() {
notifications.SetWebhookClient(*originalClient)
}()
clientStub := newMockClient(c.response)
notifications.SetWebhookClient(clientStub)
webhookSender := mockNotificationService()
fc := FactoryConfig{
Config: m,
@ -317,54 +288,24 @@ func TestTeamsNotifier(t *testing.T) {
require.True(t, ok)
require.NoError(t, err)
require.NotEmpty(t, clientStub.lastRequest.URL.String())
require.NotNil(t, webhookSender.Webhook)
lastRequest := webhookSender.Webhook
require.NotEmpty(t, lastRequest.Url)
expBody, err := json.Marshal(c.expMsg)
require.NoError(t, err)
body, err := io.ReadAll(clientStub.lastRequest.Body)
require.NoError(t, err)
require.JSONEq(t, string(expBody), string(body))
require.JSONEq(t, string(expBody), lastRequest.Body)
require.NotNil(t, lastRequest.Validation)
})
}
}
type mockClient struct {
response mockResponse
lastRequest *http.Request
}
type mockResponse struct {
status int
body string
error error
}
func (c *mockClient) Do(req *http.Request) (*http.Response, error) {
// Do Nothing
c.lastRequest = req
return makeResponse(c.response.status, c.response.body), c.response.error
}
func newMockClient(resp *mockResponse) *mockClient {
client := &mockClient{}
if resp != nil {
client.response = *resp
} else {
client.response = mockResponse{
status: 200,
body: "1",
error: nil,
}
}
return client
}
func makeResponse(status int, body string) *http.Response {
return &http.Response{
StatusCode: status,
Body: io.NopCloser(strings.NewReader(body)),
}
func Test_ValidateResponse(t *testing.T) {
require.NoError(t, validateResponse([]byte("1"), rand.Int()))
err := validateResponse([]byte("some error message"), rand.Int())
require.Error(t, err)
require.Equal(t, "some error message", err.Error())
}

View File

@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
var (
@ -38,7 +37,7 @@ type TelegramNotifier struct {
*Base
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings telegramSettings
}
@ -140,7 +139,7 @@ func (tn *TelegramNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
if err != nil {
return false, fmt.Errorf("failed to create telegram message: %w", err)
}
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := tn.ns.SendWebhook(ctx, cmd); err != nil {
return false, fmt.Errorf("failed to send telegram message: %w", err)
}
@ -168,7 +167,7 @@ func (tn *TelegramNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
if err != nil {
return fmt.Errorf("failed to create image: %w", err)
}
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := tn.ns.SendWebhook(ctx, cmd); err != nil {
return fmt.Errorf("failed to upload image to telegram: %w", err)
}
return nil
@ -207,7 +206,7 @@ func (tn *TelegramNotifier) buildTelegramMessage(ctx context.Context, as []*type
return m, nil
}
func (tn *TelegramNotifier) newWebhookSyncCmd(action string, fn func(writer *multipart.Writer) error) (*models.SendWebhookSync, error) {
func (tn *TelegramNotifier) newWebhookSyncCmd(action string, fn func(writer *multipart.Writer) error) (*SendWebhookSettings, error) {
b := bytes.Buffer{}
w := multipart.NewWriter(&b)
@ -234,7 +233,7 @@ func (tn *TelegramNotifier) newWebhookSyncCmd(action string, fn func(writer *mul
return nil, fmt.Errorf("failed to close multipart: %w", err)
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: fmt.Sprintf(TelegramAPIURL, tn.settings.BotToken, action),
Body: b.String(),
HttpMethod: "POST",

View File

@ -129,28 +129,50 @@ func resetTimeNow() {
}
type notificationServiceMock struct {
Webhook models.SendWebhookSync
EmailSync models.SendEmailCommandSync
Emailx models.SendEmailCommand
Webhook SendWebhookSettings
EmailSync SendEmailSettings
ShouldError error
}
func (ns *notificationServiceMock) SendWebhookSync(ctx context.Context, cmd *models.SendWebhookSync) error {
func (ns *notificationServiceMock) SendWebhook(ctx context.Context, cmd *SendWebhookSettings) error {
ns.Webhook = *cmd
return ns.ShouldError
}
func (ns *notificationServiceMock) SendEmailCommandHandlerSync(ctx context.Context, cmd *models.SendEmailCommandSync) error {
func (ns *notificationServiceMock) SendEmail(ctx context.Context, cmd *SendEmailSettings) error {
ns.EmailSync = *cmd
return ns.ShouldError
}
func (ns *notificationServiceMock) SendEmailCommandHandler(ctx context.Context, cmd *models.SendEmailCommand) error {
ns.Emailx = *cmd
return ns.ShouldError
}
func mockNotificationService() *notificationServiceMock { return &notificationServiceMock{} }
func CreateNotificationService(t *testing.T) *notifications.NotificationService {
type emailSender struct {
ns *notifications.NotificationService
}
func (e emailSender) SendEmail(ctx context.Context, cmd *SendEmailSettings) error {
attached := make([]*models.SendEmailAttachFile, 0, len(cmd.AttachedFiles))
for _, file := range cmd.AttachedFiles {
attached = append(attached, &models.SendEmailAttachFile{
Name: file.Name,
Content: file.Content,
})
}
return e.ns.SendEmailCommandHandlerSync(ctx, &models.SendEmailCommandSync{
SendEmailCommand: models.SendEmailCommand{
To: cmd.To,
SingleEmail: cmd.SingleEmail,
Template: cmd.Template,
Subject: cmd.Subject,
Data: cmd.Data,
Info: cmd.Info,
ReplyTo: cmd.ReplyTo,
EmbeddedFiles: cmd.EmbeddedFiles,
AttachedFiles: attached,
},
})
}
func createEmailSender(t *testing.T) *emailSender {
t.Helper()
tracer := tracing.InitializeTracerForTest()
@ -170,5 +192,5 @@ func CreateNotificationService(t *testing.T) *notifications.NotificationService
ns, err := notifications.ProvideService(bus, cfg, mailer, nil)
require.NoError(t, err)
return ns
return &emailSender{ns: ns}
}

View File

@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
var (
@ -28,7 +27,7 @@ type ThreemaNotifier struct {
*Base
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings threemaSettings
}
@ -123,7 +122,7 @@ func (tn *ThreemaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
data.Set("secret", tn.settings.APISecret)
data.Set("text", tn.buildMessage(ctx, as...))
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: ThreemaGwBaseURL,
Body: data.Encode(),
HttpMethod: "POST",
@ -131,7 +130,7 @@ func (tn *ThreemaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
"Content-Type": "application/x-www-form-urlencoded",
},
}
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := tn.ns.SendWebhook(ctx, cmd); err != nil {
tn.log.Error("Failed to send threema notification", "error", err, "webhook", tn.Name)
return false, err
}

View File

@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
@ -100,7 +99,7 @@ type VictoropsNotifier struct {
*Base
log log.Logger
images ImageStore
ns notifications.WebhookSender
ns WebhookSender
tmpl *template.Template
settings victorOpsSettings
}
@ -161,12 +160,12 @@ func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
if err != nil {
return false, err
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: u,
Body: string(b),
}
if err := vn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := vn.ns.SendWebhook(ctx, cmd); err != nil {
vn.log.Error("failed to send notification", "error", err, "webhook", vn.Name)
return false, err
}

View File

@ -13,7 +13,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
const webexAPIURL = "https://webexapis.com/v1/messages"
@ -21,7 +20,7 @@ const webexAPIURL = "https://webexapis.com/v1/messages"
// WebexNotifier is responsible for sending alert notifications as webex messages.
type WebexNotifier struct {
*Base
ns notifications.WebhookSender
ns WebhookSender
log log.Logger
images ImageStore
tmpl *template.Template
@ -152,7 +151,7 @@ func (wn *WebexNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
return false, tmplErr
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: parsedURL,
Body: string(body),
HttpMethod: http.MethodPost,
@ -164,7 +163,7 @@ func (wn *WebexNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
cmd.HttpHeader = headers
}
if err := wn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := wn.ns.SendWebhook(ctx, cmd); err != nil {
return false, err
}

View File

@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
// WebhookNotifier is responsible for sending
@ -24,7 +23,7 @@ import (
type WebhookNotifier struct {
*Base
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
images ImageStore
tmpl *template.Template
orgID int64
@ -204,7 +203,7 @@ func (wn *WebhookNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
return false, tmplErr
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: parsedURL,
User: wn.settings.User,
Password: wn.settings.Password,
@ -213,7 +212,7 @@ func (wn *WebhookNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
HttpHeader: headers,
}
if err := wn.ns.SendWebhookSync(ctx, cmd); err != nil {
if err := wn.ns.SendWebhook(ctx, cmd); err != nil {
return false, err
}

View File

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/notifications"
)
var weComEndpoint = "https://qyapi.weixin.qq.com"
@ -130,7 +129,7 @@ type WeComNotifier struct {
*Base
tmpl *template.Template
log log.Logger
ns notifications.WebhookSender
ns WebhookSender
settings wecomSettings
tok *WeComAccessToken
tokExpireAt time.Time
@ -183,12 +182,12 @@ func (w *WeComNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
w.log.Warn("failed to template WeCom message", "error", tmplErr.Error())
}
cmd := &models.SendWebhookSync{
cmd := &SendWebhookSettings{
Url: url,
Body: string(body),
}
if err = w.ns.SendWebhookSync(ctx, cmd); err != nil {
if err = w.ns.SendWebhook(ctx, cmd); err != nil {
w.log.Error("failed to send WeCom webhook", "error", err, "notification", w.Name)
return false, err
}

View File

@ -0,0 +1,56 @@
package notifier
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
"github.com/grafana/grafana/pkg/services/notifications"
)
type sender struct {
ns notifications.Service
}
func (s sender) SendWebhook(ctx context.Context, cmd *channels.SendWebhookSettings) error {
return s.ns.SendWebhookSync(ctx, &models.SendWebhookSync{
Url: cmd.Url,
User: cmd.User,
Password: cmd.Password,
Body: cmd.Body,
HttpMethod: cmd.HttpMethod,
HttpHeader: cmd.HttpHeader,
ContentType: cmd.ContentType,
Validation: cmd.Validation,
})
}
func (s sender) SendEmail(ctx context.Context, cmd *channels.SendEmailSettings) error {
var attached []*models.SendEmailAttachFile
if cmd.AttachedFiles != nil {
attached = make([]*models.SendEmailAttachFile, 0, len(cmd.AttachedFiles))
for _, file := range cmd.AttachedFiles {
attached = append(attached, &models.SendEmailAttachFile{
Name: file.Name,
Content: file.Content,
})
}
}
return s.ns.SendEmailCommandHandlerSync(ctx, &models.SendEmailCommandSync{
SendEmailCommand: models.SendEmailCommand{
To: cmd.To,
SingleEmail: cmd.SingleEmail,
Template: cmd.Template,
Subject: cmd.Subject,
Data: cmd.Data,
Info: cmd.Info,
ReplyTo: cmd.ReplyTo,
EmbeddedFiles: cmd.EmbeddedFiles,
AttachedFiles: attached,
},
})
}
func NewNotificationSender(ns notifications.Service) channels.NotificationSender {
return &sender{ns: ns}
}