diff --git a/go.mod b/go.mod index dea70c0cdda..355a0e5944f 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/google/uuid v1.3.0 github.com/google/wire v0.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-azure-sdk-go v1.6.0 github.com/grafana/grafana-plugin-sdk-go v0.159.0 diff --git a/go.sum b/go.sum index 01a5de63f60..e9f1b8a0af6 100644 --- a/go.sum +++ b/go.sum @@ -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/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-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/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/cuetsy v0.1.8 h1:l0AKXfHr0clu6qPirirDzNC/W5mqq5gG7iruOVolG34= diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 88dd9eb96f9..3c3c18b6fd0 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -406,8 +406,8 @@ func statusForTestReceivers(v []notifier.TestReceiverResult) int { for _, next := range receiver.Configs { if next.Error != nil { var ( - invalidReceiverErr alertingNotify.InvalidReceiverError - receiverTimeoutErr alertingNotify.ReceiverTimeoutError + invalidReceiverErr alertingNotify.IntegrationValidationError + receiverTimeoutErr alertingNotify.IntegrationTimeoutError ) if errors.As(next.Error, &invalidReceiverErr) { numBadRequests += 1 diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index 5fed1c2c87f..94c29dfbf2d 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -108,7 +108,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test1", UID: "uid1", Status: "failed", - Error: alertingNotify.InvalidReceiverError{}, + Error: alertingNotify.IntegrationValidationError{}, }}, }, { Name: "test2", @@ -116,7 +116,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test2", UID: "uid2", Status: "failed", - Error: alertingNotify.InvalidReceiverError{}, + Error: alertingNotify.IntegrationValidationError{}, }}, }})) }) @@ -128,7 +128,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test1", UID: "uid1", Status: "failed", - Error: alertingNotify.ReceiverTimeoutError{}, + Error: alertingNotify.IntegrationTimeoutError{}, }}, }, { Name: "test2", @@ -136,7 +136,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test2", UID: "uid2", Status: "failed", - Error: alertingNotify.ReceiverTimeoutError{}, + Error: alertingNotify.IntegrationTimeoutError{}, }}, }})) }) @@ -148,7 +148,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test1", UID: "uid1", Status: "failed", - Error: alertingNotify.InvalidReceiverError{}, + Error: alertingNotify.IntegrationValidationError{}, }}, }, { Name: "test2", @@ -156,7 +156,7 @@ func TestStatusForTestReceivers(t *testing.T) { Name: "test2", UID: "uid2", Status: "failed", - Error: alertingNotify.ReceiverTimeoutError{}, + Error: alertingNotify.IntegrationTimeoutError{}, }}, }})) }) diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index 1f26f219888..e5304520110 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -3,9 +3,7 @@ package notifier import ( "context" "crypto/md5" - "encoding/base64" "encoding/json" - "errors" "fmt" "path/filepath" "strconv" @@ -54,7 +52,7 @@ type Alertmanager struct { fileStore *FileStore NotificationService notifications.Service - decryptFn receivers.GetDecryptedValueFn + decryptFn alertingNotify.GetDecryptedValueFn 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, - peer alertingNotify.ClusterPeer, decryptFn receivers.GetDecryptedValueFn, ns notifications.Service, + peer alertingNotify.ClusterPeer, decryptFn alertingNotify.GetDecryptedValueFn, ns notifications.Service, m *metrics.Alertmanager) (*Alertmanager, error) { workingPath := filepath.Join(cfg.DataPath, workingDir, strconv.Itoa(int(orgID))) 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. -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)) for _, receiver := range receivers { 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. -func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableApiReceiver, tmpl *alertingNotify.Template) ([]*alertingNotify.Integration, error) { - integrations := make([]*alertingNotify.Integration, 0, len(receiver.GrafanaManagedReceivers)) - for i, r := range receiver.GrafanaManagedReceivers { - n, err := am.buildReceiverIntegration(PostableGrafanaReceiverToGrafanaReceiver(r), tmpl) - if err != nil { - return nil, err - } - integrations = append(integrations, alertingNotify.NewIntegration(n, n, r.Type, i)) +func (am *Alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } + s := &sender{am.NotificationService} + img := newImageStore(am.Store) + 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 } -func (am *Alertmanager) buildReceiverIntegration(r *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.NotificationChannel, error) { - // secure settings are already encrypted at this point - secureSettings := make(map[string][]byte, len(r.SecureSettings)) - - 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 +func (am *Alertmanager) buildReceiverIntegration(r *alertingNotify.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (*alertingNotify.Integration, error) { + apiReceiver := &alertingNotify.APIReceiver{ + GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ + Integrations: []*alertingNotify.GrafanaIntegrationConfig{r}, + }, } - - 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) + integrations, err := am.buildReceiverIntegrations(apiReceiver, tmpl) if err != nil { - return nil, alertingNotify.InvalidReceiverError{ - Receiver: r, - Err: err, - } + return nil, err } - receiverFactory, exists := alertingNotify.Factory(r.Type) - if !exists { - return nil, alertingNotify.InvalidReceiverError{ - Receiver: r, - Err: fmt.Errorf("notifier %s is not supported", r.Type), - } + if len(integrations) == 0 { + // This should not happen, but it is better to return some error rather than having a panic. + return nil, fmt.Errorf("failed to build integration") } - n, err := receiverFactory(factoryConfig) - if err != nil { - return nil, alertingNotify.InvalidReceiverError{ - Receiver: r, - Err: err, - } - } - return n, nil + return integrations[0], nil } // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not diff --git a/pkg/services/ngalert/notifier/compat.go b/pkg/services/ngalert/notifier/compat.go index 90c4807d8f3..41bcb1cab87 100644 --- a/pkg/services/ngalert/notifier/compat.go +++ b/pkg/services/ngalert/notifier/compat.go @@ -8,8 +8,8 @@ import ( apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ) -func PostableGrafanaReceiverToGrafanaReceiver(p *apimodels.PostableGrafanaReceiver) *alertingNotify.GrafanaReceiver { - return &alertingNotify.GrafanaReceiver{ +func PostableGrafanaReceiverToGrafanaIntegrationConfig(p *apimodels.PostableGrafanaReceiver) *alertingNotify.GrafanaIntegrationConfig { + return &alertingNotify.GrafanaIntegrationConfig{ UID: p.UID, Name: p.Name, Type: p.Type, @@ -20,16 +20,16 @@ func PostableGrafanaReceiverToGrafanaReceiver(p *apimodels.PostableGrafanaReceiv } func PostableApiReceiverToApiReceiver(r *apimodels.PostableApiReceiver) *alertingNotify.APIReceiver { - receivers := alertingNotify.GrafanaReceivers{ - Receivers: make([]*alertingNotify.GrafanaReceiver, 0, len(r.GrafanaManagedReceivers)), + integrations := alertingNotify.GrafanaIntegrations{ + Integrations: make([]*alertingNotify.GrafanaIntegrationConfig, 0, len(r.GrafanaManagedReceivers)), } - for _, receiver := range r.GrafanaManagedReceivers { - receivers.Receivers = append(receivers.Receivers, PostableGrafanaReceiverToGrafanaReceiver(receiver)) + for _, cfg := range r.GrafanaManagedReceivers { + integrations.Integrations = append(integrations.Integrations, PostableGrafanaReceiverToGrafanaIntegrationConfig(cfg)) } return &alertingNotify.APIReceiver{ - ConfigReceiver: r.Receiver, - GrafanaReceivers: receivers, + ConfigReceiver: r.Receiver, + GrafanaIntegrations: integrations, } } diff --git a/pkg/services/ngalert/notifier/compat_test.go b/pkg/services/ngalert/notifier/compat_test.go index 46d95b37405..b3a2ff8508a 100644 --- a/pkg/services/ngalert/notifier/compat_test.go +++ b/pkg/services/ngalert/notifier/compat_test.go @@ -11,7 +11,7 @@ import ( apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ) -func TestPostableGrafanaReceiverToGrafanaReceiver(t *testing.T) { +func TestPostableGrafanaReceiverToGrafanaIntegrationConfig(t *testing.T) { r := &apimodels.PostableGrafanaReceiver{ UID: "test-uid", Name: "test-name", @@ -22,8 +22,8 @@ func TestPostableGrafanaReceiverToGrafanaReceiver(t *testing.T) { "test": "data", }, } - actual := PostableGrafanaReceiverToGrafanaReceiver(r) - require.Equal(t, alertingNotify.GrafanaReceiver{ + actual := PostableGrafanaReceiverToGrafanaIntegrationConfig(r) + require.Equal(t, alertingNotify.GrafanaIntegrationConfig{ UID: "test-uid", Name: "test-name", Type: "slack", @@ -43,7 +43,7 @@ func TestPostableApiReceiverToApiReceiver(t *testing.T) { }, } actual := PostableApiReceiverToApiReceiver(r) - require.Empty(t, actual.Receivers) + require.Empty(t, actual.Integrations) require.Equal(t, r.Receiver, actual.ConfigReceiver) }) t.Run("converts receivers", func(t *testing.T) { @@ -77,10 +77,10 @@ func TestPostableApiReceiverToApiReceiver(t *testing.T) { }, } 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, *PostableGrafanaReceiverToGrafanaReceiver(r.GrafanaManagedReceivers[0]), *actual.Receivers[0]) - require.Equal(t, *PostableGrafanaReceiverToGrafanaReceiver(r.GrafanaManagedReceivers[1]), *actual.Receivers[1]) + require.Equal(t, *PostableGrafanaReceiverToGrafanaIntegrationConfig(r.GrafanaManagedReceivers[0]), *actual.Integrations[0]) + require.Equal(t, *PostableGrafanaReceiverToGrafanaIntegrationConfig(r.GrafanaManagedReceivers[1]), *actual.Integrations[1]) }) } diff --git a/pkg/services/ngalert/notifier/config.go b/pkg/services/ngalert/notifier/config.go index 01ecab34017..5acb73c390d 100644 --- a/pkg/services/ngalert/notifier/config.go +++ b/pkg/services/ngalert/notifier/config.go @@ -8,6 +8,7 @@ import ( "path/filepath" alertingNotify "github.com/grafana/alerting/notify" + alertingTemplates "github.com/grafana/alerting/templates" "github.com/grafana/grafana/pkg/infra/log" api "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -94,14 +95,14 @@ type AlertingConfiguration struct { AlertmanagerConfig api.PostableApiAlertingConfig RawAlertmanagerConfig []byte - AlertmanagerTemplates *alertingNotify.Template + AlertmanagerTemplates *alertingTemplates.Template - IntegrationsFunc func(receivers []*api.PostableApiReceiver, templates *alertingNotify.Template) (map[string][]*alertingNotify.Integration, error) - ReceiverIntegrationsFunc func(r *alertingNotify.GrafanaReceiver, tmpl *alertingNotify.Template) (alertingNotify.NotificationChannel, error) + IntegrationsFunc func(receivers []*alertingNotify.APIReceiver, templates *alertingTemplates.Template) (map[string][]*alertingNotify.Integration, 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) { - return 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.GrafanaIntegrationConfig, tmpl *alertingTemplates.Template) (alertingNotify.Notifier, error) { return a.ReceiverIntegrationsFunc(next, tmpl) } } @@ -119,14 +120,14 @@ func (a AlertingConfiguration) MuteTimeIntervals() []alertingNotify.MuteTimeInte } 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 { return a.AlertmanagerConfig.Route.AsAMRoute() } -func (a AlertingConfiguration) Templates() *alertingNotify.Template { +func (a AlertingConfiguration) Templates() *alertingTemplates.Template { return a.AlertmanagerTemplates } diff --git a/pkg/services/ngalert/notifier/email_test.go b/pkg/services/ngalert/notifier/email_test.go index 7bcd66a10a2..1fe36a4a3bc 100644 --- a/pkg/services/ngalert/notifier/email_test.go +++ b/pkg/services/ngalert/notifier/email_test.go @@ -2,7 +2,6 @@ package notifier import ( "context" - "encoding/json" "net/url" "os" "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() - - jsonData := map[string]interface{}{ - "addresses": "someops@example.com;somedev@example.com", - "singleEmail": true, + if subjectTmpl == "" { + subjectTmpl = alertingTemplates.DefaultMessageTitleEmbed } - if messageTmpl != "" { - jsonData["message"] = messageTmpl - } - - if subjectTmpl != "" { - 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), + return alertingEmail.New(alertingEmail.Config{ + SingleEmail: true, + Addresses: []string{ + "someops@example.com", + "somedev@example.com", }, - NotificationService: ns, - DecryptFunc: func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string { - return fallback - }, - ImageStore: &images.UnavailableImageStore{}, - Template: emailTmpl, - Logger: &alertingLogging.FakeLogger{}, - } - emailNotifier, err := alertingEmail.New(fc) - require.NoError(t, err) - return emailNotifier + Message: messageTmpl, + Subject: subjectTmpl, + }, receivers.Metadata{}, emailTmpl, ns, &images.UnavailableImageStore{}, &alertingLogging.FakeLogger{}) } func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message { diff --git a/pkg/services/ngalert/notifier/log.go b/pkg/services/ngalert/notifier/log.go index a48477d8abe..aed0addd87f 100644 --- a/pkg/services/ngalert/notifier/log.go +++ b/pkg/services/ngalert/notifier/log.go @@ -6,8 +6,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" ) -var LoggerFactory alertingLogging.LoggerFactory = func(ctx ...interface{}) alertingLogging.Logger { - return &logWrapper{log.New(ctx...)} +var LoggerFactory alertingLogging.LoggerFactory = func(logger string, ctx ...interface{}) alertingLogging.Logger { + return &logWrapper{log.New(append([]interface{}{logger}, ctx...)...)} } type logWrapper struct { diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 1713a118468..8b687c2d9ac 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -13,7 +13,6 @@ import ( "github.com/prometheus/client_golang/prometheus" alertingNotify "github.com/grafana/alerting/notify" - "github.com/grafana/alerting/receivers" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" @@ -49,14 +48,14 @@ type MultiOrgAlertmanager struct { orgStore store.OrgStore kvStore kvstore.KVStore - decryptFn receivers.GetDecryptedValueFn + decryptFn alertingNotify.GetDecryptedValueFn metrics *metrics.MultiOrgAlertmanager ns notifications.Service } 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, ) (*MultiOrgAlertmanager, error) { moa := &MultiOrgAlertmanager{ diff --git a/pkg/services/ngalert/notifier/receivers.go b/pkg/services/ngalert/notifier/receivers.go index f473a035a33..5cc12f15f86 100644 --- a/pkg/services/ngalert/notifier/receivers.go +++ b/pkg/services/ngalert/notifier/receivers.go @@ -35,9 +35,9 @@ type TestReceiverConfigResult struct { func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) { receivers := make([]*alertingNotify.APIReceiver, 0, len(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 { - greceivers = append(greceivers, &alertingNotify.GrafanaReceiver{ + integrations = append(integrations, &alertingNotify.GrafanaIntegrationConfig{ UID: gr.UID, Name: gr.Name, Type: gr.Type, @@ -48,8 +48,8 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei } receivers = append(receivers, &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, - GrafanaReceivers: alertingNotify.GrafanaReceivers{ - Receivers: greceivers, + GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ + Integrations: integrations, }, }) } diff --git a/pkg/services/ngalert/notifier/receivers_test.go b/pkg/services/ngalert/notifier/receivers_test.go index 2c3b3102946..6ee01b9b9fe 100644 --- a/pkg/services/ngalert/notifier/receivers_test.go +++ b/pkg/services/ngalert/notifier/receivers_test.go @@ -11,19 +11,20 @@ import ( ) func TestInvalidReceiverError_Error(t *testing.T) { - e := alertingNotify.InvalidReceiverError{ - Receiver: &alertingNotify.GrafanaReceiver{ + e := alertingNotify.IntegrationValidationError{ + Integration: &alertingNotify.GrafanaIntegrationConfig{ Name: "test", + Type: "test-type", UID: "uid", }, 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) { - e := alertingNotify.ReceiverTimeoutError{ - Receiver: &alertingNotify.GrafanaReceiver{ + e := alertingNotify.IntegrationTimeoutError{ + Integration: &alertingNotify.GrafanaIntegrationConfig{ Name: "test", UID: "uid", }, @@ -44,18 +45,18 @@ func (e timeoutError) Timeout() bool { func TestProcessNotifierError(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", UID: "uid", } - require.Equal(t, alertingNotify.ReceiverTimeoutError{ - Receiver: r, - Err: context.DeadlineExceeded, - }, alertingNotify.ProcessNotifierError(r, context.DeadlineExceeded)) + require.Equal(t, alertingNotify.IntegrationTimeoutError{ + Integration: r, + Err: context.DeadlineExceeded, + }, alertingNotify.ProcessIntegrationError(r, context.DeadlineExceeded)) }) t.Run("assert ReceiverTimeoutError is returned for *url.Error timeout", func(t *testing.T) { - r := &alertingNotify.GrafanaReceiver{ + r := &alertingNotify.GrafanaIntegrationConfig{ Name: "test", UID: "uid", } @@ -64,18 +65,18 @@ func TestProcessNotifierError(t *testing.T) { URL: "https://grafana.net", Err: timeoutError{}, } - require.Equal(t, alertingNotify.ReceiverTimeoutError{ - Receiver: r, - Err: urlError, - }, alertingNotify.ProcessNotifierError(r, urlError)) + require.Equal(t, alertingNotify.IntegrationTimeoutError{ + Integration: r, + Err: urlError, + }, alertingNotify.ProcessIntegrationError(r, urlError)) }) t.Run("assert unknown error is returned unmodified", func(t *testing.T) { - r := &alertingNotify.GrafanaReceiver{ + r := &alertingNotify.GrafanaIntegrationConfig{ Name: "test", UID: "uid", } err := errors.New("this is an error") - require.Equal(t, err, alertingNotify.ProcessNotifierError(r, err)) + require.Equal(t, err, alertingNotify.ProcessIntegrationError(r, err)) }) } diff --git a/pkg/services/ngalert/notifier/sender.go b/pkg/services/ngalert/notifier/sender.go index 13fcebba592..11c17c12d8a 100644 --- a/pkg/services/ngalert/notifier/sender.go +++ b/pkg/services/ngalert/notifier/sender.go @@ -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} -} diff --git a/pkg/services/ngalert/provisioning/compat.go b/pkg/services/ngalert/provisioning/compat.go new file mode 100644 index 00000000000..edd9a23bc15 --- /dev/null +++ b/pkg/services/ngalert/provisioning/compat.go @@ -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 +} diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 64d238f4093..13183c298f8 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -7,9 +7,7 @@ import ( "fmt" "sort" - "github.com/grafana/alerting/logging" alertingNotify "github.com/grafana/alerting/notify" - "github.com/grafana/alerting/receivers" "github.com/prometheus/alertmanager/config" "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/notifier/channels_config" "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/setting" "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, 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()) } @@ -257,7 +254,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in } // 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()) } @@ -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 == "" { return fmt.Errorf("type should not be an empty string") } if e.Settings == nil { return fmt.Errorf("settings should not be empty") } - factory, exists := alertingNotify.Factory(e.Type) - if !exists { - return fmt.Errorf("unknown type '%s'", e.Type) - } - jsonBytes, err := e.Settings.MarshalJSON() + integration, err := EmbeddedContactPointToGrafanaIntegrationConfig(e) if err != nil { return err } - cfg, _ := receivers.NewFactoryConfig(&receivers.NotificationChannelConfig{ - Settings: jsonBytes, - Type: e.Type, - }, nil, decryptFunc, nil, nil, func(ctx ...interface{}) logging.Logger { - return &logging.FakeLogger{} - }, setting.BuildVersion) - if _, err := factory(cfg); err != nil { + _, err = alertingNotify.BuildReceiverConfiguration(ctx, &alertingNotify.APIReceiver{ + GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ + Integrations: []*alertingNotify.GrafanaIntegrationConfig{&integration}, + }, + }, decryptFunc) + if err != nil { return err } return nil diff --git a/pkg/services/provisioning/alerting/contact_point_types.go b/pkg/services/provisioning/alerting/contact_point_types.go index 8fe5d2ac3db..391c9ff1fc2 100644 --- a/pkg/services/provisioning/alerting/contact_point_types.go +++ b/pkg/services/provisioning/alerting/contact_point_types.go @@ -95,7 +95,7 @@ func (config *ReceiverV1) mapToModel(name string) (definitions.EmbeddedContactPo } // As the values are not encrypted when coming from disk files, // 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 }) if err != nil { diff --git a/pkg/services/sqlstore/migrations/ualert/ualert.go b/pkg/services/sqlstore/migrations/ualert/ualert.go index 535c6395006..a7a4a83a759 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert.go @@ -480,7 +480,7 @@ func (m *migration) validateAlertmanagerConfig(config *PostableUserConfig) error return err } var ( - cfg = &alertingNotify.GrafanaReceiver{ + cfg = &alertingNotify.GrafanaIntegrationConfig{ UID: gr.UID, Name: gr.Name, Type: gr.Type, @@ -504,7 +504,7 @@ func (m *migration) validateAlertmanagerConfig(config *PostableUserConfig) error return fallback } _, err = alertingNotify.BuildReceiverConfiguration(context.Background(), &alertingNotify.APIReceiver{ - GrafanaReceivers: alertingNotify.GrafanaReceivers{Receivers: []*alertingNotify.GrafanaReceiver{cfg}}, + GrafanaIntegrations: alertingNotify.GrafanaIntegrations{Integrations: []*alertingNotify.GrafanaIntegrationConfig{cfg}}, }, decryptFunc) if err != nil { return err diff --git a/pkg/services/sqlstore/migrations/ualert/ualert_test.go b/pkg/services/sqlstore/migrations/ualert/ualert_test.go index ef2086ccb54..98a56c07c6f 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert_test.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert_test.go @@ -72,7 +72,7 @@ func Test_validateAlertmanagerConfig(t *testing.T) { 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", diff --git a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go index e6eb902872c..9ab9a901f6f 100644 --- a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go @@ -102,7 +102,7 @@ func TestIntegrationAlertmanagerConfigurationIsTransactional(t *testing.T) { require.NoError(t, err) var res map[string]interface{} 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 require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body)) diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index c6bd1f5bb4e..2990e0d21ee 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -205,32 +205,34 @@ func TestIntegrationTestReceivers(t *testing.T) { require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 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(`{ - "alert": { - "annotations": { - "summary": "Notification test", - "__value_string__": "[ metric='foo' labels={instance=bar} value=10 ]" - }, - "labels": { - "alertname": "TestAlert", - "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" + "alert": { + "annotations": { + "summary": "Notification test", + "__value_string__": "[ metric='foo' labels={instance=bar} value=10 ]" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" } - ] - }], - "notified_at": "%s" - }`, + }, + "receivers": [{ + "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].Error, result.NotifiedAt.Format(time.RFC3339Nano)) 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[0].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(`{ "alert": { @@ -411,7 +414,7 @@ func TestIntegrationTestReceivers(t *testing.T) { "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" + "error": %q } ] }, { @@ -428,6 +431,7 @@ func TestIntegrationTestReceivers(t *testing.T) { "notified_at": "%s" }`, result.Receivers[0].Configs[0].UID, + result.Receivers[0].Configs[0].Error, result.Receivers[1].Configs[0].UID, result.NotifiedAt.Format(time.RFC3339Nano)) 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) nc.receivedNotifications[key] = append(nc.receivedNotifications[key], body) + res.Header().Set("Content-Type", "application/json") res.WriteHeader(http.StatusOK) fmt.Fprint(res, nc.responses[paths[0]]) } diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index ce6eaf000ab..ee759178674 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -10,11 +10,12 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/expr" "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/expr" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "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 { b, err := io.ReadAll(resp.Body) require.NoError(t, err) - t.Fatal(string(b)) + t.Log(string(b)) + require.Equal(t, expStatusCode, resp.StatusCode) } return resp }