Alerting: Update alerting module to 20230418161049-5f374e58cb32 + refactoring (#66622)

* update to alerting 20230418161049-5f374e58cb32
* rename renamed structs in https://github.com/grafana/alerting/pull/73
* update ValidateContactPoint to use BuildReceiverConfiguration
* update logger factory according to changes
* rewrite integration builder
Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>
This commit is contained in:
Yuri Tseretyan 2023-04-25 13:39:46 -04:00 committed by GitHub
parent 12e5101b91
commit a8b4a4bb45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 179 additions and 198 deletions

2
go.mod
View File

@ -63,7 +63,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/google/wire v0.5.0 github.com/google/wire v0.5.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/grafana/alerting v0.0.0-20230315185333-d1e3c68ac064 github.com/grafana/alerting v0.0.0-20230418161049-5f374e58cb32
github.com/grafana/grafana-aws-sdk v0.12.0 github.com/grafana/grafana-aws-sdk v0.12.0
github.com/grafana/grafana-azure-sdk-go v1.6.0 github.com/grafana/grafana-azure-sdk-go v1.6.0
github.com/grafana/grafana-plugin-sdk-go v0.159.0 github.com/grafana/grafana-plugin-sdk-go v0.159.0

4
go.sum
View File

@ -1272,6 +1272,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20230315185333-d1e3c68ac064 h1:MtsWzSTav7NGKolO+TaJQUcyR7VY0YpUROVsJX8ktIU= github.com/grafana/alerting v0.0.0-20230315185333-d1e3c68ac064 h1:MtsWzSTav7NGKolO+TaJQUcyR7VY0YpUROVsJX8ktIU=
github.com/grafana/alerting v0.0.0-20230315185333-d1e3c68ac064/go.mod h1:nHfrSTdV7/l74N5/ezqlQ+JwSvIChhN3G5+PjCfwG/E= github.com/grafana/alerting v0.0.0-20230315185333-d1e3c68ac064/go.mod h1:nHfrSTdV7/l74N5/ezqlQ+JwSvIChhN3G5+PjCfwG/E=
github.com/grafana/alerting v0.0.0-20230410151633-4a7ecc241d72 h1:WuQGIUeDIyPviylMaMD1d2nEYIiD/icHYO0rc/AH8kQ=
github.com/grafana/alerting v0.0.0-20230410151633-4a7ecc241d72/go.mod h1:nHfrSTdV7/l74N5/ezqlQ+JwSvIChhN3G5+PjCfwG/E=
github.com/grafana/alerting v0.0.0-20230418161049-5f374e58cb32 h1:LdPoVBj+CA5oHLeUejDzqy8/c4Fa0UfTtCcOHka0Jws=
github.com/grafana/alerting v0.0.0-20230418161049-5f374e58cb32/go.mod h1:nHfrSTdV7/l74N5/ezqlQ+JwSvIChhN3G5+PjCfwG/E=
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cuetsy v0.1.8 h1:l0AKXfHr0clu6qPirirDzNC/W5mqq5gG7iruOVolG34= github.com/grafana/cuetsy v0.1.8 h1:l0AKXfHr0clu6qPirirDzNC/W5mqq5gG7iruOVolG34=

View File

@ -406,8 +406,8 @@ func statusForTestReceivers(v []notifier.TestReceiverResult) int {
for _, next := range receiver.Configs { for _, next := range receiver.Configs {
if next.Error != nil { if next.Error != nil {
var ( var (
invalidReceiverErr alertingNotify.InvalidReceiverError invalidReceiverErr alertingNotify.IntegrationValidationError
receiverTimeoutErr alertingNotify.ReceiverTimeoutError receiverTimeoutErr alertingNotify.IntegrationTimeoutError
) )
if errors.As(next.Error, &invalidReceiverErr) { if errors.As(next.Error, &invalidReceiverErr) {
numBadRequests += 1 numBadRequests += 1

View File

@ -108,7 +108,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test1", Name: "test1",
UID: "uid1", UID: "uid1",
Status: "failed", Status: "failed",
Error: alertingNotify.InvalidReceiverError{}, Error: alertingNotify.IntegrationValidationError{},
}}, }},
}, { }, {
Name: "test2", Name: "test2",
@ -116,7 +116,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test2", Name: "test2",
UID: "uid2", UID: "uid2",
Status: "failed", Status: "failed",
Error: alertingNotify.InvalidReceiverError{}, Error: alertingNotify.IntegrationValidationError{},
}}, }},
}})) }}))
}) })
@ -128,7 +128,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test1", Name: "test1",
UID: "uid1", UID: "uid1",
Status: "failed", Status: "failed",
Error: alertingNotify.ReceiverTimeoutError{}, Error: alertingNotify.IntegrationTimeoutError{},
}}, }},
}, { }, {
Name: "test2", Name: "test2",
@ -136,7 +136,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test2", Name: "test2",
UID: "uid2", UID: "uid2",
Status: "failed", Status: "failed",
Error: alertingNotify.ReceiverTimeoutError{}, Error: alertingNotify.IntegrationTimeoutError{},
}}, }},
}})) }}))
}) })
@ -148,7 +148,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test1", Name: "test1",
UID: "uid1", UID: "uid1",
Status: "failed", Status: "failed",
Error: alertingNotify.InvalidReceiverError{}, Error: alertingNotify.IntegrationValidationError{},
}}, }},
}, { }, {
Name: "test2", Name: "test2",
@ -156,7 +156,7 @@ func TestStatusForTestReceivers(t *testing.T) {
Name: "test2", Name: "test2",
UID: "uid2", UID: "uid2",
Status: "failed", Status: "failed",
Error: alertingNotify.ReceiverTimeoutError{}, Error: alertingNotify.IntegrationTimeoutError{},
}}, }},
}})) }}))
}) })

View File

@ -3,9 +3,7 @@ package notifier
import ( import (
"context" "context"
"crypto/md5" "crypto/md5"
"encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -54,7 +52,7 @@ type Alertmanager struct {
fileStore *FileStore fileStore *FileStore
NotificationService notifications.Service NotificationService notifications.Service
decryptFn receivers.GetDecryptedValueFn decryptFn alertingNotify.GetDecryptedValueFn
orgID int64 orgID int64
} }
@ -84,7 +82,7 @@ func (m maintenanceOptions) MaintenanceFunc(state alertingNotify.State) (int64,
} }
func newAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store AlertingStore, kvStore kvstore.KVStore, func newAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store AlertingStore, kvStore kvstore.KVStore,
peer alertingNotify.ClusterPeer, decryptFn receivers.GetDecryptedValueFn, ns notifications.Service, peer alertingNotify.ClusterPeer, decryptFn alertingNotify.GetDecryptedValueFn, ns notifications.Service,
m *metrics.Alertmanager) (*Alertmanager, error) { m *metrics.Alertmanager) (*Alertmanager, error) {
workingPath := filepath.Join(cfg.DataPath, workingDir, strconv.Itoa(int(orgID))) workingPath := filepath.Join(cfg.DataPath, workingDir, strconv.Itoa(int(orgID)))
fileStore := NewFileStore(orgID, kvStore, workingPath) fileStore := NewFileStore(orgID, kvStore, workingPath)
@ -317,7 +315,7 @@ func (am *Alertmanager) WorkingDirPath() string {
} }
// buildIntegrationsMap builds a map of name to the list of Grafana integration notifiers off of a list of receiver config. // buildIntegrationsMap builds a map of name to the list of Grafana integration notifiers off of a list of receiver config.
func (am *Alertmanager) buildIntegrationsMap(receivers []*apimodels.PostableApiReceiver, templates *alertingNotify.Template) (map[string][]*alertingNotify.Integration, error) { func (am *Alertmanager) buildIntegrationsMap(receivers []*alertingNotify.APIReceiver, templates *alertingTemplates.Template) (map[string][]*alertingNotify.Integration, error) {
integrationsMap := make(map[string][]*alertingNotify.Integration, len(receivers)) integrationsMap := make(map[string][]*alertingNotify.Integration, len(receivers))
for _, receiver := range receivers { for _, receiver := range receivers {
integrations, err := am.buildReceiverIntegrations(receiver, templates) integrations, err := am.buildReceiverIntegrations(receiver, templates)
@ -331,66 +329,48 @@ func (am *Alertmanager) buildIntegrationsMap(receivers []*apimodels.PostableApiR
} }
// buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config.
func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableApiReceiver, tmpl *alertingNotify.Template) ([]*alertingNotify.Integration, error) { func (am *Alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) {
integrations := make([]*alertingNotify.Integration, 0, len(receiver.GrafanaManagedReceivers)) receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn)
for i, r := range receiver.GrafanaManagedReceivers { if err != nil {
n, err := am.buildReceiverIntegration(PostableGrafanaReceiverToGrafanaReceiver(r), tmpl) return nil, err
if err != nil { }
return nil, err s := &sender{am.NotificationService}
} img := newImageStore(am.Store)
integrations = append(integrations, alertingNotify.NewIntegration(n, n, r.Type, i)) integrations, err := alertingNotify.BuildReceiverIntegrations(
receiverCfg,
tmpl,
img,
LoggerFactory,
func(n receivers.Metadata) (receivers.WebhookSender, error) {
return s, nil
},
func(n receivers.Metadata) (receivers.EmailSender, error) {
return s, nil
},
am.orgID,
setting.BuildVersion,
)
if err != nil {
return nil, err
} }
return integrations, nil return integrations, nil
} }
func (am *Alertmanager) buildReceiverIntegration(r *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.NotificationChannel, error) { func (am *Alertmanager) buildReceiverIntegration(r *alertingNotify.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (*alertingNotify.Integration, error) {
// secure settings are already encrypted at this point apiReceiver := &alertingNotify.APIReceiver{
secureSettings := make(map[string][]byte, len(r.SecureSettings)) GrafanaIntegrations: alertingNotify.GrafanaIntegrations{
Integrations: []*alertingNotify.GrafanaIntegrationConfig{r},
for k, v := range r.SecureSettings { },
d, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, alertingNotify.InvalidReceiverError{
Receiver: r,
Err: errors.New("failed to decode secure setting"),
}
}
secureSettings[k] = d
} }
integrations, err := am.buildReceiverIntegrations(apiReceiver, tmpl)
var (
cfg = &receivers.NotificationChannelConfig{
UID: r.UID,
OrgID: am.orgID,
Name: r.Name,
Type: r.Type,
DisableResolveMessage: r.DisableResolveMessage,
Settings: r.Settings,
SecureSettings: secureSettings,
}
)
factoryConfig, err := receivers.NewFactoryConfig(cfg, NewNotificationSender(am.NotificationService), am.decryptFn, tmpl, newImageStore(am.Store), LoggerFactory, setting.BuildVersion)
if err != nil { if err != nil {
return nil, alertingNotify.InvalidReceiverError{ return nil, err
Receiver: r,
Err: err,
}
} }
receiverFactory, exists := alertingNotify.Factory(r.Type) if len(integrations) == 0 {
if !exists { // This should not happen, but it is better to return some error rather than having a panic.
return nil, alertingNotify.InvalidReceiverError{ return nil, fmt.Errorf("failed to build integration")
Receiver: r,
Err: fmt.Errorf("notifier %s is not supported", r.Type),
}
} }
n, err := receiverFactory(factoryConfig) return integrations[0], nil
if err != nil {
return nil, alertingNotify.InvalidReceiverError{
Receiver: r,
Err: err,
}
}
return n, nil
} }
// PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not

View File

@ -8,8 +8,8 @@ import (
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
) )
func PostableGrafanaReceiverToGrafanaReceiver(p *apimodels.PostableGrafanaReceiver) *alertingNotify.GrafanaReceiver { func PostableGrafanaReceiverToGrafanaIntegrationConfig(p *apimodels.PostableGrafanaReceiver) *alertingNotify.GrafanaIntegrationConfig {
return &alertingNotify.GrafanaReceiver{ return &alertingNotify.GrafanaIntegrationConfig{
UID: p.UID, UID: p.UID,
Name: p.Name, Name: p.Name,
Type: p.Type, Type: p.Type,
@ -20,16 +20,16 @@ func PostableGrafanaReceiverToGrafanaReceiver(p *apimodels.PostableGrafanaReceiv
} }
func PostableApiReceiverToApiReceiver(r *apimodels.PostableApiReceiver) *alertingNotify.APIReceiver { func PostableApiReceiverToApiReceiver(r *apimodels.PostableApiReceiver) *alertingNotify.APIReceiver {
receivers := alertingNotify.GrafanaReceivers{ integrations := alertingNotify.GrafanaIntegrations{
Receivers: make([]*alertingNotify.GrafanaReceiver, 0, len(r.GrafanaManagedReceivers)), Integrations: make([]*alertingNotify.GrafanaIntegrationConfig, 0, len(r.GrafanaManagedReceivers)),
} }
for _, receiver := range r.GrafanaManagedReceivers { for _, cfg := range r.GrafanaManagedReceivers {
receivers.Receivers = append(receivers.Receivers, PostableGrafanaReceiverToGrafanaReceiver(receiver)) integrations.Integrations = append(integrations.Integrations, PostableGrafanaReceiverToGrafanaIntegrationConfig(cfg))
} }
return &alertingNotify.APIReceiver{ return &alertingNotify.APIReceiver{
ConfigReceiver: r.Receiver, ConfigReceiver: r.Receiver,
GrafanaReceivers: receivers, GrafanaIntegrations: integrations,
} }
} }

View File

@ -11,7 +11,7 @@ import (
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
) )
func TestPostableGrafanaReceiverToGrafanaReceiver(t *testing.T) { func TestPostableGrafanaReceiverToGrafanaIntegrationConfig(t *testing.T) {
r := &apimodels.PostableGrafanaReceiver{ r := &apimodels.PostableGrafanaReceiver{
UID: "test-uid", UID: "test-uid",
Name: "test-name", Name: "test-name",
@ -22,8 +22,8 @@ func TestPostableGrafanaReceiverToGrafanaReceiver(t *testing.T) {
"test": "data", "test": "data",
}, },
} }
actual := PostableGrafanaReceiverToGrafanaReceiver(r) actual := PostableGrafanaReceiverToGrafanaIntegrationConfig(r)
require.Equal(t, alertingNotify.GrafanaReceiver{ require.Equal(t, alertingNotify.GrafanaIntegrationConfig{
UID: "test-uid", UID: "test-uid",
Name: "test-name", Name: "test-name",
Type: "slack", Type: "slack",
@ -43,7 +43,7 @@ func TestPostableApiReceiverToApiReceiver(t *testing.T) {
}, },
} }
actual := PostableApiReceiverToApiReceiver(r) actual := PostableApiReceiverToApiReceiver(r)
require.Empty(t, actual.Receivers) require.Empty(t, actual.Integrations)
require.Equal(t, r.Receiver, actual.ConfigReceiver) require.Equal(t, r.Receiver, actual.ConfigReceiver)
}) })
t.Run("converts receivers", func(t *testing.T) { t.Run("converts receivers", func(t *testing.T) {
@ -77,10 +77,10 @@ func TestPostableApiReceiverToApiReceiver(t *testing.T) {
}, },
} }
actual := PostableApiReceiverToApiReceiver(r) actual := PostableApiReceiverToApiReceiver(r)
require.Len(t, actual.Receivers, 2) require.Len(t, actual.Integrations, 2)
require.Equal(t, r.Receiver, actual.ConfigReceiver) require.Equal(t, r.Receiver, actual.ConfigReceiver)
require.Equal(t, *PostableGrafanaReceiverToGrafanaReceiver(r.GrafanaManagedReceivers[0]), *actual.Receivers[0]) require.Equal(t, *PostableGrafanaReceiverToGrafanaIntegrationConfig(r.GrafanaManagedReceivers[0]), *actual.Integrations[0])
require.Equal(t, *PostableGrafanaReceiverToGrafanaReceiver(r.GrafanaManagedReceivers[1]), *actual.Receivers[1]) require.Equal(t, *PostableGrafanaReceiverToGrafanaIntegrationConfig(r.GrafanaManagedReceivers[1]), *actual.Integrations[1])
}) })
} }

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
alertingNotify "github.com/grafana/alerting/notify" alertingNotify "github.com/grafana/alerting/notify"
alertingTemplates "github.com/grafana/alerting/templates"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
api "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" api "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@ -94,14 +95,14 @@ type AlertingConfiguration struct {
AlertmanagerConfig api.PostableApiAlertingConfig AlertmanagerConfig api.PostableApiAlertingConfig
RawAlertmanagerConfig []byte RawAlertmanagerConfig []byte
AlertmanagerTemplates *alertingNotify.Template AlertmanagerTemplates *alertingTemplates.Template
IntegrationsFunc func(receivers []*api.PostableApiReceiver, templates *alertingNotify.Template) (map[string][]*alertingNotify.Integration, error) IntegrationsFunc func(receivers []*alertingNotify.APIReceiver, templates *alertingTemplates.Template) (map[string][]*alertingNotify.Integration, error)
ReceiverIntegrationsFunc func(r *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.NotificationChannel, error) ReceiverIntegrationsFunc func(r *alertingNotify.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (*alertingNotify.Integration, error)
} }
func (a AlertingConfiguration) BuildReceiverIntegrationsFunc() func(next *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.Notifier, error) { func (a AlertingConfiguration) BuildReceiverIntegrationsFunc() func(next *alertingNotify.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (alertingNotify.Notifier, error) {
return func(next *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.Notifier, error) { return func(next *alertingNotify.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (alertingNotify.Notifier, error) {
return a.ReceiverIntegrationsFunc(next, tmpl) return a.ReceiverIntegrationsFunc(next, tmpl)
} }
} }
@ -119,14 +120,14 @@ func (a AlertingConfiguration) MuteTimeIntervals() []alertingNotify.MuteTimeInte
} }
func (a AlertingConfiguration) ReceiverIntegrations() (map[string][]*alertingNotify.Integration, error) { func (a AlertingConfiguration) ReceiverIntegrations() (map[string][]*alertingNotify.Integration, error) {
return a.IntegrationsFunc(a.AlertmanagerConfig.Receivers, a.AlertmanagerTemplates) return a.IntegrationsFunc(PostableApiAlertingConfigToApiReceivers(a.AlertmanagerConfig), a.AlertmanagerTemplates)
} }
func (a AlertingConfiguration) RoutingTree() *alertingNotify.Route { func (a AlertingConfiguration) RoutingTree() *alertingNotify.Route {
return a.AlertmanagerConfig.Route.AsAMRoute() return a.AlertmanagerConfig.Route.AsAMRoute()
} }
func (a AlertingConfiguration) Templates() *alertingNotify.Template { func (a AlertingConfiguration) Templates() *alertingTemplates.Template {
return a.AlertmanagerTemplates return a.AlertmanagerTemplates
} }

View File

@ -2,7 +2,6 @@ package notifier
import ( import (
"context" "context"
"encoding/json"
"net/url" "net/url"
"os" "os"
"testing" "testing"
@ -188,40 +187,20 @@ func TestEmailNotifierIntegration(t *testing.T) {
} }
} }
func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *template.Template, ns receivers.NotificationSender) *alertingEmail.Notifier { func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *template.Template, ns receivers.EmailSender) *alertingEmail.Notifier {
t.Helper() t.Helper()
if subjectTmpl == "" {
jsonData := map[string]interface{}{ subjectTmpl = alertingTemplates.DefaultMessageTitleEmbed
"addresses": "someops@example.com;somedev@example.com",
"singleEmail": true,
} }
if messageTmpl != "" { return alertingEmail.New(alertingEmail.Config{
jsonData["message"] = messageTmpl SingleEmail: true,
} Addresses: []string{
"someops@example.com",
if subjectTmpl != "" { "somedev@example.com",
jsonData["subject"] = subjectTmpl
}
bytes, err := json.Marshal(jsonData)
require.NoError(t, err)
fc := receivers.FactoryConfig{
Config: &receivers.NotificationChannelConfig{
Name: "ops",
Type: "alertingEmail",
Settings: json.RawMessage(bytes),
}, },
NotificationService: ns, Message: messageTmpl,
DecryptFunc: func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string { Subject: subjectTmpl,
return fallback }, receivers.Metadata{}, emailTmpl, ns, &images.UnavailableImageStore{}, &alertingLogging.FakeLogger{})
},
ImageStore: &images.UnavailableImageStore{},
Template: emailTmpl,
Logger: &alertingLogging.FakeLogger{},
}
emailNotifier, err := alertingEmail.New(fc)
require.NoError(t, err)
return emailNotifier
} }
func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message { func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message {

View File

@ -6,8 +6,8 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
) )
var LoggerFactory alertingLogging.LoggerFactory = func(ctx ...interface{}) alertingLogging.Logger { var LoggerFactory alertingLogging.LoggerFactory = func(logger string, ctx ...interface{}) alertingLogging.Logger {
return &logWrapper{log.New(ctx...)} return &logWrapper{log.New(append([]interface{}{logger}, ctx...)...)}
} }
type logWrapper struct { type logWrapper struct {

View File

@ -13,7 +13,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
alertingNotify "github.com/grafana/alerting/notify" alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers"
"github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -49,14 +48,14 @@ type MultiOrgAlertmanager struct {
orgStore store.OrgStore orgStore store.OrgStore
kvStore kvstore.KVStore kvStore kvstore.KVStore
decryptFn receivers.GetDecryptedValueFn decryptFn alertingNotify.GetDecryptedValueFn
metrics *metrics.MultiOrgAlertmanager metrics *metrics.MultiOrgAlertmanager
ns notifications.Service ns notifications.Service
} }
func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore AlertingStore, orgStore store.OrgStore, func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore AlertingStore, orgStore store.OrgStore,
kvStore kvstore.KVStore, provStore provisioning.ProvisioningStore, decryptFn receivers.GetDecryptedValueFn, kvStore kvstore.KVStore, provStore provisioning.ProvisioningStore, decryptFn alertingNotify.GetDecryptedValueFn,
m *metrics.MultiOrgAlertmanager, ns notifications.Service, l log.Logger, s secrets.Service, m *metrics.MultiOrgAlertmanager, ns notifications.Service, l log.Logger, s secrets.Service,
) (*MultiOrgAlertmanager, error) { ) (*MultiOrgAlertmanager, error) {
moa := &MultiOrgAlertmanager{ moa := &MultiOrgAlertmanager{

View File

@ -35,9 +35,9 @@ type TestReceiverConfigResult struct {
func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) { func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) {
receivers := make([]*alertingNotify.APIReceiver, 0, len(c.Receivers)) receivers := make([]*alertingNotify.APIReceiver, 0, len(c.Receivers))
for _, r := range c.Receivers { for _, r := range c.Receivers {
greceivers := make([]*alertingNotify.GrafanaReceiver, 0, len(r.GrafanaManagedReceivers)) integrations := make([]*alertingNotify.GrafanaIntegrationConfig, 0, len(r.GrafanaManagedReceivers))
for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers {
greceivers = append(greceivers, &alertingNotify.GrafanaReceiver{ integrations = append(integrations, &alertingNotify.GrafanaIntegrationConfig{
UID: gr.UID, UID: gr.UID,
Name: gr.Name, Name: gr.Name,
Type: gr.Type, Type: gr.Type,
@ -48,8 +48,8 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
} }
receivers = append(receivers, &alertingNotify.APIReceiver{ receivers = append(receivers, &alertingNotify.APIReceiver{
ConfigReceiver: r.Receiver, ConfigReceiver: r.Receiver,
GrafanaReceivers: alertingNotify.GrafanaReceivers{ GrafanaIntegrations: alertingNotify.GrafanaIntegrations{
Receivers: greceivers, Integrations: integrations,
}, },
}) })
} }

View File

@ -11,19 +11,20 @@ import (
) )
func TestInvalidReceiverError_Error(t *testing.T) { func TestInvalidReceiverError_Error(t *testing.T) {
e := alertingNotify.InvalidReceiverError{ e := alertingNotify.IntegrationValidationError{
Receiver: &alertingNotify.GrafanaReceiver{ Integration: &alertingNotify.GrafanaIntegrationConfig{
Name: "test", Name: "test",
Type: "test-type",
UID: "uid", UID: "uid",
}, },
Err: errors.New("this is an error"), Err: errors.New("this is an error"),
} }
require.Equal(t, "the receiver is invalid: this is an error", e.Error()) require.Equal(t, `failed to validate integration "test" (UID uid) of type "test-type": this is an error`, e.Error())
} }
func TestReceiverTimeoutError_Error(t *testing.T) { func TestReceiverTimeoutError_Error(t *testing.T) {
e := alertingNotify.ReceiverTimeoutError{ e := alertingNotify.IntegrationTimeoutError{
Receiver: &alertingNotify.GrafanaReceiver{ Integration: &alertingNotify.GrafanaIntegrationConfig{
Name: "test", Name: "test",
UID: "uid", UID: "uid",
}, },
@ -44,18 +45,18 @@ func (e timeoutError) Timeout() bool {
func TestProcessNotifierError(t *testing.T) { func TestProcessNotifierError(t *testing.T) {
t.Run("assert ReceiverTimeoutError is returned for context deadline exceeded", func(t *testing.T) { t.Run("assert ReceiverTimeoutError is returned for context deadline exceeded", func(t *testing.T) {
r := &alertingNotify.GrafanaReceiver{ r := &alertingNotify.GrafanaIntegrationConfig{
Name: "test", Name: "test",
UID: "uid", UID: "uid",
} }
require.Equal(t, alertingNotify.ReceiverTimeoutError{ require.Equal(t, alertingNotify.IntegrationTimeoutError{
Receiver: r, Integration: r,
Err: context.DeadlineExceeded, Err: context.DeadlineExceeded,
}, alertingNotify.ProcessNotifierError(r, context.DeadlineExceeded)) }, alertingNotify.ProcessIntegrationError(r, context.DeadlineExceeded))
}) })
t.Run("assert ReceiverTimeoutError is returned for *url.Error timeout", func(t *testing.T) { t.Run("assert ReceiverTimeoutError is returned for *url.Error timeout", func(t *testing.T) {
r := &alertingNotify.GrafanaReceiver{ r := &alertingNotify.GrafanaIntegrationConfig{
Name: "test", Name: "test",
UID: "uid", UID: "uid",
} }
@ -64,18 +65,18 @@ func TestProcessNotifierError(t *testing.T) {
URL: "https://grafana.net", URL: "https://grafana.net",
Err: timeoutError{}, Err: timeoutError{},
} }
require.Equal(t, alertingNotify.ReceiverTimeoutError{ require.Equal(t, alertingNotify.IntegrationTimeoutError{
Receiver: r, Integration: r,
Err: urlError, Err: urlError,
}, alertingNotify.ProcessNotifierError(r, urlError)) }, alertingNotify.ProcessIntegrationError(r, urlError))
}) })
t.Run("assert unknown error is returned unmodified", func(t *testing.T) { t.Run("assert unknown error is returned unmodified", func(t *testing.T) {
r := &alertingNotify.GrafanaReceiver{ r := &alertingNotify.GrafanaIntegrationConfig{
Name: "test", Name: "test",
UID: "uid", UID: "uid",
} }
err := errors.New("this is an error") err := errors.New("this is an error")
require.Equal(t, err, alertingNotify.ProcessNotifierError(r, err)) require.Equal(t, err, alertingNotify.ProcessIntegrationError(r, err))
}) })
} }

View File

@ -50,7 +50,3 @@ func (s sender) SendEmail(ctx context.Context, cmd *receivers.SendEmailSettings)
}, },
}) })
} }
func NewNotificationSender(ns notifications.Service) receivers.NotificationSender {
return &sender{ns: ns}
}

View File

@ -0,0 +1,22 @@
package provisioning
import (
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
)
func EmbeddedContactPointToGrafanaIntegrationConfig(e definitions.EmbeddedContactPoint) (alertingNotify.GrafanaIntegrationConfig, error) {
data, err := e.Settings.MarshalJSON()
if err != nil {
return alertingNotify.GrafanaIntegrationConfig{}, err
}
return alertingNotify.GrafanaIntegrationConfig{
UID: e.UID,
Name: e.Name,
Type: e.Type,
DisableResolveMessage: e.DisableResolveMessage,
Settings: data,
SecureSettings: nil,
}, nil
}

View File

@ -7,9 +7,7 @@ import (
"fmt" "fmt"
"sort" "sort"
"github.com/grafana/alerting/logging"
alertingNotify "github.com/grafana/alerting/notify" alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers"
"github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/config"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
@ -18,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config" "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -136,7 +133,7 @@ func (ecp *ContactPointService) getContactPointDecrypted(ctx context.Context, or
func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID int64, func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID int64,
contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) (apimodels.EmbeddedContactPoint, error) { contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) (apimodels.EmbeddedContactPoint, error) {
if err := ValidateContactPoint(contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil { if err := ValidateContactPoint(ctx, contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil {
return apimodels.EmbeddedContactPoint{}, fmt.Errorf("%w: %s", ErrValidation, err.Error()) return apimodels.EmbeddedContactPoint{}, fmt.Errorf("%w: %s", ErrValidation, err.Error())
} }
@ -257,7 +254,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in
} }
// validate merged values // validate merged values
if err := ValidateContactPoint(contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil { if err := ValidateContactPoint(ctx, contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil {
return fmt.Errorf("%w: %s", ErrValidation, err.Error()) return fmt.Errorf("%w: %s", ErrValidation, err.Error())
} }
@ -500,28 +497,23 @@ func replaceReferences(oldName, newName string, routes ...*apimodels.Route) {
} }
} }
func ValidateContactPoint(e apimodels.EmbeddedContactPoint, decryptFunc receivers.GetDecryptedValueFn) error { func ValidateContactPoint(ctx context.Context, e apimodels.EmbeddedContactPoint, decryptFunc alertingNotify.GetDecryptedValueFn) error {
if e.Type == "" { if e.Type == "" {
return fmt.Errorf("type should not be an empty string") return fmt.Errorf("type should not be an empty string")
} }
if e.Settings == nil { if e.Settings == nil {
return fmt.Errorf("settings should not be empty") return fmt.Errorf("settings should not be empty")
} }
factory, exists := alertingNotify.Factory(e.Type) integration, err := EmbeddedContactPointToGrafanaIntegrationConfig(e)
if !exists {
return fmt.Errorf("unknown type '%s'", e.Type)
}
jsonBytes, err := e.Settings.MarshalJSON()
if err != nil { if err != nil {
return err return err
} }
cfg, _ := receivers.NewFactoryConfig(&receivers.NotificationChannelConfig{ _, err = alertingNotify.BuildReceiverConfiguration(ctx, &alertingNotify.APIReceiver{
Settings: jsonBytes, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{
Type: e.Type, Integrations: []*alertingNotify.GrafanaIntegrationConfig{&integration},
}, nil, decryptFunc, nil, nil, func(ctx ...interface{}) logging.Logger { },
return &logging.FakeLogger{} }, decryptFunc)
}, setting.BuildVersion) if err != nil {
if _, err := factory(cfg); err != nil {
return err return err
} }
return nil return nil

View File

@ -95,7 +95,7 @@ func (config *ReceiverV1) mapToModel(name string) (definitions.EmbeddedContactPo
} }
// As the values are not encrypted when coming from disk files, // As the values are not encrypted when coming from disk files,
// we can simply return the fallback for validation. // we can simply return the fallback for validation.
err := provisioning.ValidateContactPoint(cp, func(_ context.Context, _ map[string][]byte, _, fallback string) string { err := provisioning.ValidateContactPoint(context.Background(), cp, func(_ context.Context, _ map[string][]byte, _, fallback string) string {
return fallback return fallback
}) })
if err != nil { if err != nil {

View File

@ -480,7 +480,7 @@ func (m *migration) validateAlertmanagerConfig(config *PostableUserConfig) error
return err return err
} }
var ( var (
cfg = &alertingNotify.GrafanaReceiver{ cfg = &alertingNotify.GrafanaIntegrationConfig{
UID: gr.UID, UID: gr.UID,
Name: gr.Name, Name: gr.Name,
Type: gr.Type, Type: gr.Type,
@ -504,7 +504,7 @@ func (m *migration) validateAlertmanagerConfig(config *PostableUserConfig) error
return fallback return fallback
} }
_, err = alertingNotify.BuildReceiverConfiguration(context.Background(), &alertingNotify.APIReceiver{ _, err = alertingNotify.BuildReceiverConfiguration(context.Background(), &alertingNotify.APIReceiver{
GrafanaReceivers: alertingNotify.GrafanaReceivers{Receivers: []*alertingNotify.GrafanaReceiver{cfg}}, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{Integrations: []*alertingNotify.GrafanaIntegrationConfig{cfg}},
}, decryptFunc) }, decryptFunc)
if err != nil { if err != nil {
return err return err

View File

@ -72,7 +72,7 @@ func Test_validateAlertmanagerConfig(t *testing.T) {
SecureSettings: map[string]string{"url": invalidUri}, SecureSettings: map[string]string{"url": invalidUri},
}, },
}, },
err: fmt.Errorf("failed to validate receiver \"SlackWithBadURL\" of type \"slack\": failed to parse notifier SlackWithBadURL (UID: test-uid): invalid URL %q", invalidUri), err: fmt.Errorf("failed to validate integration \"SlackWithBadURL\" (UID test-uid) of type \"slack\": invalid URL %q", invalidUri),
}, },
{ {
name: "when a slack receiver has an invalid recipient - it should not error", name: "when a slack receiver has an invalid recipient - it should not error",

View File

@ -102,7 +102,7 @@ func TestIntegrationAlertmanagerConfigurationIsTransactional(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var res map[string]interface{} var res map[string]interface{}
require.NoError(t, json.Unmarshal(b, &res)) require.NoError(t, json.Unmarshal(b, &res))
require.Equal(t, `failed to save and apply Alertmanager configuration: failed to build integration map: the receiver is invalid: failed to validate receiver "slack.receiver" of type "slack": token must be specified when using the Slack chat API`, res["message"]) require.Regexp(t, `^failed to save and apply Alertmanager configuration: failed to build integration map: failed to validate integration "slack.receiver" \(UID [^\)]+\) of type "slack": token must be specified when using the Slack chat API`, res["message"])
resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint
require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body)) require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body))

View File

@ -205,32 +205,34 @@ func TestIntegrationTestReceivers(t *testing.T) {
require.NoError(t, json.Unmarshal(b, &result)) require.NoError(t, json.Unmarshal(b, &result))
require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers, 1)
require.Len(t, result.Receivers[0].Configs, 1) require.Len(t, result.Receivers[0].Configs, 1)
require.Regexp(t, `failed to validate integration "receiver-1" \(UID[^\)]+\) of type "email": could not find addresses in settings`, result.Receivers[0].Configs[0].Error)
expectedJSON := fmt.Sprintf(`{ expectedJSON := fmt.Sprintf(`{
"alert": { "alert": {
"annotations": { "annotations": {
"summary": "Notification test", "summary": "Notification test",
"__value_string__": "[ metric='foo' labels={instance=bar} value=10 ]" "__value_string__": "[ metric='foo' labels={instance=bar} value=10 ]"
}, },
"labels": { "labels": {
"alertname": "TestAlert", "alertname": "TestAlert",
"instance": "Grafana" "instance": "Grafana"
}
},
"receivers": [{
"name":"receiver-1",
"grafana_managed_receiver_configs": [
{
"name": "receiver-1",
"uid": "%s",
"status": "failed",
"error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings"
} }
] },
}], "receivers": [{
"notified_at": "%s" "name":"receiver-1",
}`, "grafana_managed_receiver_configs": [
{
"name": "receiver-1",
"uid": "%s",
"status": "failed",
"error": %q
}
]
}],
"notified_at": "%s"
}`,
result.Receivers[0].Configs[0].UID, result.Receivers[0].Configs[0].UID,
result.Receivers[0].Configs[0].Error,
result.NotifiedAt.Format(time.RFC3339Nano)) result.NotifiedAt.Format(time.RFC3339Nano))
require.JSONEq(t, expectedJSON, string(b)) require.JSONEq(t, expectedJSON, string(b))
}) })
@ -392,6 +394,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
require.Len(t, result.Receivers, 2) require.Len(t, result.Receivers, 2)
require.Len(t, result.Receivers[0].Configs, 1) require.Len(t, result.Receivers[0].Configs, 1)
require.Len(t, result.Receivers[1].Configs, 1) require.Len(t, result.Receivers[1].Configs, 1)
require.Regexp(t, `failed to validate integration "receiver-1" \(UID[^\)]+\) of type "email": could not find addresses in settings`, result.Receivers[0].Configs[0].Error)
expectedJSON := fmt.Sprintf(`{ expectedJSON := fmt.Sprintf(`{
"alert": { "alert": {
@ -411,7 +414,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
"name": "receiver-1", "name": "receiver-1",
"uid": "%s", "uid": "%s",
"status": "failed", "status": "failed",
"error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings" "error": %q
} }
] ]
}, { }, {
@ -428,6 +431,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
"notified_at": "%s" "notified_at": "%s"
}`, }`,
result.Receivers[0].Configs[0].UID, result.Receivers[0].Configs[0].UID,
result.Receivers[0].Configs[0].Error,
result.Receivers[1].Configs[0].UID, result.Receivers[1].Configs[0].UID,
result.NotifiedAt.Format(time.RFC3339Nano)) result.NotifiedAt.Format(time.RFC3339Nano))
require.JSONEq(t, expectedJSON, string(b)) require.JSONEq(t, expectedJSON, string(b))
@ -1056,6 +1060,7 @@ func (nc *mockNotificationChannel) ServeHTTP(res http.ResponseWriter, req *http.
body := getBody(nc.t, req.Body) body := getBody(nc.t, req.Body)
nc.receivedNotifications[key] = append(nc.receivedNotifications[key], body) nc.receivedNotifications[key] = append(nc.receivedNotifications[key], body)
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusOK) res.WriteHeader(http.StatusOK)
fmt.Fprint(res, nc.responses[paths[0]]) fmt.Fprint(res, nc.responses[paths[0]])
} }

View File

@ -10,11 +10,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/expr"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/expr"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
@ -75,7 +76,8 @@ func postRequest(t *testing.T, url string, body string, expStatusCode int) *http
if expStatusCode != resp.StatusCode { if expStatusCode != resp.StatusCode {
b, err := io.ReadAll(resp.Body) b, err := io.ReadAll(resp.Body)
require.NoError(t, err) require.NoError(t, err)
t.Fatal(string(b)) t.Log(string(b))
require.Equal(t, expStatusCode, resp.StatusCode)
} }
return resp return resp
} }