mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 16:15:42 -06:00
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:
parent
07b5043222
commit
7c3ab4a715
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
46
pkg/services/ngalert/notifier/channels/sender.go
Normal file
46
pkg/services/ngalert/notifier/channels/sender.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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 ¬ificationServiceMock{} }
|
||||
|
||||
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}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
56
pkg/services/ngalert/notifier/sender.go
Normal file
56
pkg/services/ngalert/notifier/sender.go
Normal 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}
|
||||
}
|
Loading…
Reference in New Issue
Block a user