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/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

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/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=

View File

@ -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

View File

@ -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{},
}},
}}))
})

View File

@ -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

View File

@ -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,
}
}

View File

@ -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])
})
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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{

View File

@ -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,
},
})
}

View File

@ -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))
})
}

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"
"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

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,
// 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 {

View File

@ -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

View File

@ -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",

View File

@ -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))

View File

@ -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]])
}

View File

@ -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
}