grafana/pkg/tests/api/alerting/api_notification_channel_test.go
Matthew Jacobson 15313d53dd
Alerting: Create PostableUserConfig domain layer model AMConfigV1 (#125154)
This pull request contains prep work needed to eventually have separate, distinct types for Alertmanager config in the DB, domain, and API layers.

This PR does not tackle adding UIDs to the domain layer or any structural changes we want to make there. It also does not include separating the DB layer from the API PostableUserConfig.

Included in this PR is is the separation of the API layer from the domain layer. done by creating a copy of PostableUserConfig, named AMConfigV1, as well as some nested types.

These are placed in a new package pkg/services/ngalert/notifier/legacy_storage/v1 to avoid cycles for now, but can eventually live in pkg/services/ngalert/models.

The types are copied verbatim and should not include any functional changes, the intent here is not to start defining our domain layer but to untangle how the codebase references DB/API definitions in places it shouldn't and give us a domain layer type that we can refactor in small iterations without worrying about DB/API serialization.

References to PostableUserConfig and the above nested types and replaced with the domain layer types in as many places as is reasonable, ensuring the boundary between DB <-> Domain <-> API is crossed using newly created compat functions.
2026-05-20 14:20:43 -04:00

2018 lines
61 KiB
Go

package alerting
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"regexp"
"slices"
"strings"
"sync"
"testing"
"time"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
alertingModels "github.com/grafana/alerting/models"
"github.com/grafana/alerting/receivers"
alertingLine "github.com/grafana/alerting/receivers/line/v1"
alertingPushover "github.com/grafana/alerting/receivers/pushover/v1"
alertingSlack "github.com/grafana/alerting/receivers/slack/v1"
alertingTelegram "github.com/grafana/alerting/receivers/telegram/v1"
alertingThreema "github.com/grafana/alerting/receivers/threema/v1"
alertingTemplates "github.com/grafana/alerting/templates"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/server"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
v1 "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage/v1"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/infra/db"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
)
func TestIntegrationNotificationChannels(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
testinfra.SQLiteIntegrationTest(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
mockChannel := newMockNotificationChannel(t, grafanaListedAddr)
amConfig := getAlertmanagerConfig(mockChannel.server.Addr)
mockEmail := &mockEmailHandler{}
// Set up responses
mockChannel.responses["slack_recv1"] = `{"ok": true}`
mockChannel.responses["slack_recvX"] = `{"ok": true}`
// Overriding some URLs to send to the mock channel.
os, ot, opu, ogb, ol, oth := alertingSlack.APIURL,
alertingTelegram.APIURL, alertingPushover.APIURL, receivers.GetBoundary,
alertingLine.APIURL, alertingThreema.APIURL
originalTemplate := alertingTemplates.DefaultTemplateString
t.Cleanup(func() {
alertingSlack.APIURL, alertingTelegram.APIURL, alertingPushover.APIURL, receivers.GetBoundary,
alertingLine.APIURL, alertingThreema.APIURL = os, ot, opu, ogb, ol, oth
alertingTemplates.DefaultTemplateString = originalTemplate
})
alertingTemplates.DefaultTemplateString = alertingTemplates.TemplateForTestsString
alertingSlack.APIURL = fmt.Sprintf("http://%s/slack_recvX/slack_testX", mockChannel.server.Addr)
alertingTelegram.APIURL = fmt.Sprintf("http://%s/telegram_recv/bot%%s/%%s", mockChannel.server.Addr)
alertingPushover.APIURL = fmt.Sprintf("http://%s/pushover_recv/pushover_test", mockChannel.server.Addr)
alertingLine.APIURL = fmt.Sprintf("http://%s/line_recv/line_test", mockChannel.server.Addr)
alertingThreema.APIURL = fmt.Sprintf("http://%s/threema_recv/threema_test", mockChannel.server.Addr)
receivers.GetBoundary = func() string { return "abcd" }
env.NotificationService.EmailHandlerSync = mockEmail.sendEmailCommandHandlerSync
// As we are using a NotificationService mock here, but the test expects real NotificationService -
// we try to issue a real POST request here
env.NotificationService.WebhookHandler = func(_ context.Context, cmd *notifications.SendWebhookSync) error {
if res, err := http.Post(cmd.Url, "", strings.NewReader(cmd.Body)); err == nil {
_ = res.Body.Close()
}
return nil
}
// Create a user to make authenticated requests
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleEditor),
Password: "password",
Login: "grafana",
})
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
{
// There are no notification channel config initially - so it returns the default configuration.
alertsURL := fmt.Sprintf("http://admin:admin@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
resp := getRequest(t, alertsURL, http.StatusOK) // nolint
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
// Admin sees autogenerated routes; strip them before comparing against the user-defined config.
var cfg apimodels.GettableUserConfig
require.NoError(t, json.Unmarshal(b, &cfg))
removeAutogenConfigIfExists(cfg.AlertmanagerConfig.Route)
stripped, err := json.Marshal(cfg)
require.NoError(t, err)
require.JSONEq(t, defaultAlertmanagerConfigJSON, string(stripped))
}
{
// Create the namespace we'll save our alerts to.
apiClient.CreateFolder(t, "default", "default")
// Post the alertmanager config.
saveAndApplyAlertmanagerConfiguration(t, env, 1, amConfig)
// Verifying that all the receivers and routes have been registered.
alertsURL := fmt.Sprintf("http://admin:admin@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
resp := getRequest(t, alertsURL, http.StatusOK) // nolint
b := getBody(t, resp.Body)
// Admin sees autogenerated routes; strip them before comparing against the user-defined config.
var cfg apimodels.GettableUserConfig
require.NoError(t, json.Unmarshal([]byte(b), &cfg))
removeAutogenConfigIfExists(cfg.AlertmanagerConfig.Route)
stripped, err := json.Marshal(cfg)
require.NoError(t, err)
re := regexp.MustCompile(`"uid":"([\w|-]*)"`)
e := getExpAlertmanagerConfigFromAPI(mockChannel.server.Addr)
require.JSONEq(t, e, string(re.ReplaceAll(stripped, []byte(`"uid":""`))))
// Check the receivers API. No errors nor attempts to notify should be registered.
receiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers", grafanaListedAddr)
resp = getRequest(t, receiversURL, http.StatusOK) // nolint
b = getBody(t, resp.Body)
var receivers []alertingModels.ReceiverStatus
err = json.Unmarshal([]byte(b), &receivers)
require.NoError(t, err)
for _, rcv := range receivers {
require.NotNil(t, rcv.Name)
require.NotNil(t, rcv.Active)
require.NotEmpty(t, rcv.Integrations)
for _, integration := range rcv.Integrations {
require.NotNil(t, integration.Name)
require.NotNil(t, integration.SendResolved)
require.Equal(t, "", integration.LastNotifyAttemptError)
require.Zero(t, integration.LastNotifyAttempt)
require.Equal(t, "0s", integration.LastNotifyAttemptDuration)
}
}
}
{
// Create rules that will fire as quickly as possible
originalFunction := store.GenerateNewAlertRuleUID
t.Cleanup(func() {
store.GenerateNewAlertRuleUID = originalFunction
})
store.GenerateNewAlertRuleUID = func(_ *db.Session, _ int64, ruleTitle string) (string, error) {
return "UID_" + ruleTitle, nil
}
rulesConfig := getRulesConfig(t)
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
_ = postRequest(t, u, rulesConfig, http.StatusAccepted) // nolint
}
// Eventually, we'll get all the desired alerts.
// nolint:gosec
require.Eventually(t, func() bool {
return mockChannel.totalNotifications() >= len(nonEmailAlertNames) &&
mockChannel.totalNotificationErrors() >= len(expNotificationErrors) &&
len(mockEmail.emails) >= 1
}, 30*time.Second, 1*time.Second)
mockChannel.matchesExpNotifications(t, expNonEmailNotifications)
require.Equal(t, expEmailNotifications, mockEmail.emails)
require.NoError(t, mockChannel.Close())
// Check the receivers API. Errors and inactive receivers are expected, attempts to deliver notifications should be registered.
receiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers", grafanaListedAddr)
resp := getRequest(t, receiversURL, http.StatusOK) // nolint
b := getBody(t, resp.Body)
var receivers []alertingModels.ReceiverStatus
err := json.Unmarshal([]byte(b), &receivers)
require.NoError(t, err)
for _, rcv := range receivers {
t.Run("Receiver "+rcv.Name, func(t *testing.T) {
var expActive bool
if _, ok := expInactiveReceivers[rcv.Name]; !ok {
expActive = true
}
var expErr bool
if _, ok := expNotificationErrors[rcv.Name]; ok {
expErr = true
}
require.NotEmpty(t, rcv.Integrations)
if expActive {
require.True(t, rcv.Active)
}
// We don't have test alerts for the default notifier, continue iterating.
if rcv.Name == "empty" {
return
}
for _, integration := range rcv.Integrations {
require.NotNil(t, integration.Name)
t.Run("Integration "+integration.Name, func(t *testing.T) {
// If the receiver is not active, no attempts to send notifications should be registered.
if expActive {
// Prometheus' durations get rounded down, so we might end up with "0s" if we have values smaller than 1ms.
require.NotZero(t, integration.LastNotifyAttempt)
} else {
require.Zero(t, integration.LastNotifyAttempt)
require.Equal(t, "0s", integration.LastNotifyAttemptDuration)
}
// Check whether we're expecting an error on this integration.
if expErr {
for _, integration := range rcv.Integrations {
require.Equal(t, expNotificationErrors[rcv.Name], integration.LastNotifyAttemptError)
}
} else {
require.Equal(t, "", integration.LastNotifyAttemptError)
}
})
}
})
}
}
func createAlertmanagerConfig(config string, channelAddr string) string {
return strings.ReplaceAll(config, "CHANNEL_ADDR", channelAddr)
}
func getAlertmanagerConfig(channelAddr string) string {
return createAlertmanagerConfig(alertmanagerConfig, channelAddr)
}
func getExpAlertmanagerConfigFromAPI(channelAddr string) string {
return createAlertmanagerConfig(expAlertmanagerConfigFromAPI, channelAddr)
}
// nonEmailAlertNames are name of alerts to be sent for non-email channels. This should be in sync with
// the routes that we define in Alertmanager config.
var nonEmailAlertNames = []string{
"AlertmanagerAlert",
"OpsGenieAlert",
"VictorOpsAlert",
"ThreemaAlert",
"LineAlert",
"DiscordAlert",
"KafkaAlert",
"GoogleChatAlert",
"PushoverAlert",
"SensuGoAlert",
"TelegramAlert",
"DingDingAlert",
"SlackAlert1",
"SlackAlert2",
"PagerdutyAlert",
"TeamsAlert",
"WebhookAlert",
}
// emailAlertNames are name of alerts to be sent via email. This should be in sync with
// the routes that we define in Alertmanager config.
var emailAlertNames = []string{
"EmailAlert",
}
var failedAlertNames = []string{
"SlackFailedAlert",
}
func getRulesConfig(t *testing.T) string {
t.Helper()
interval, err := model.ParseDuration("10s")
require.NoError(t, err)
rules := apimodels.PostableRuleGroupConfig{
Name: "arulegroup",
Interval: interval,
}
// Create rules that will fire as quickly as possible for all the routes.
rulesToCreate := append(nonEmailAlertNames, emailAlertNames...)
rulesToCreate = append(rulesToCreate, failedAlertNames...)
for _, alertName := range rulesToCreate {
rules.Rules = append(rules.Rules, apimodels.PostableExtendedRuleNode{
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
Title: alertName,
Condition: "A",
Data: []apimodels.AlertQuery{
{
RefID: "A",
RelativeTimeRange: apimodels.RelativeTimeRange{
From: apimodels.Duration(time.Duration(5) * time.Hour),
To: apimodels.Duration(time.Duration(3) * time.Hour),
},
DatasourceUID: expr.DatasourceUID,
Model: json.RawMessage(`{
"type": "math",
"expression": "2 + 3 > 1"
}`),
},
},
},
})
}
b, err := json.Marshal(rules)
require.NoError(t, err)
return string(b)
}
type mockNotificationChannel struct {
t *testing.T
server *http.Server
receivedNotifications map[string][]string
responses map[string]string
notificationErrorCount int
notificationsMtx sync.RWMutex
}
func newMockNotificationChannel(t *testing.T, grafanaListedAddr string) *mockNotificationChannel {
// Spin up a separate webserver to receive notifications emitted by Grafana.
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
nc := &mockNotificationChannel{
// Skip gosec linter since this is in test code.
//
//nolint:gosec
server: &http.Server{
Addr: listener.Addr().String(),
},
receivedNotifications: make(map[string][]string),
responses: make(map[string]string),
t: t,
}
nc.server.Handler = nc
go func() {
require.EqualError(t, nc.server.Serve(listener), http.ErrServerClosed.Error())
}()
return nc
}
func (nc *mockNotificationChannel) ServeHTTP(res http.ResponseWriter, req *http.Request) {
nc.t.Helper()
nc.notificationsMtx.Lock()
defer nc.notificationsMtx.Unlock()
// paths[0] contains the receiver's name
paths := strings.Split(req.URL.Path[1:], "/")
if _, ok := expNotificationErrors[paths[0]]; ok {
nc.notificationErrorCount++
res.WriteHeader(http.StatusInternalServerError)
return
}
key := strings.Join(paths[0:2], "/")
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]])
}
func (nc *mockNotificationChannel) totalNotifications() int {
total := 0
nc.notificationsMtx.RLock()
defer nc.notificationsMtx.RUnlock()
for _, v := range nc.receivedNotifications {
total += len(v)
}
return total
}
func (nc *mockNotificationChannel) totalNotificationErrors() int {
nc.notificationsMtx.RLock()
defer nc.notificationsMtx.RUnlock()
return nc.notificationErrorCount
}
func (nc *mockNotificationChannel) matchesExpNotifications(t *testing.T, exp map[string][]string) {
t.Helper()
nc.notificationsMtx.RLock()
defer nc.notificationsMtx.RUnlock()
require.Len(t, nc.receivedNotifications, len(exp))
for expKey, expVals := range exp {
actVals, ok := nc.receivedNotifications[expKey]
require.True(t, ok)
require.Len(t, actVals, len(expVals))
for i := range expVals {
expVal := expVals[i]
var r1, r2 *regexp.Regexp
switch expKey {
case "webhook_recv/webhook_test":
// It has a time component "startsAt".
r1 = regexp.MustCompile(`.*"startsAt"\s*:\s*"([^"]+)"`)
case "slack_recvX/slack_testX":
fallthrough
case "slack_recv1/slack_test_without_token":
// It has a time component "ts".
r1 = regexp.MustCompile(`.*"ts"\s*:\s*([0-9]+)`)
case "sensugo_recv/sensugo_test":
// It has a time component "ts".
r1 = regexp.MustCompile(`.*"issued"\s*:\s*([0-9]+)`)
case "pagerduty_recvX/pagerduty_testX":
// It has a changing "source".
r1 = regexp.MustCompile(`.*"source"\s*:\s*"([^"]+)"`)
case "googlechat_recv/googlechat_test":
// "Grafana v | 25 May 21 17:44 IST"
r1 = regexp.MustCompile(`.*"text"\s*:\s*"(Grafana v[^"]+)"`)
case "victorops_recv/victorops_test":
// It has a time component "timestamp".
r1 = regexp.MustCompile(`.*"timestamp"\s*:\s*([0-9]+)`)
case "alertmanager_recv/alertmanager_test":
// It has a changing time fields.
r1 = regexp.MustCompile(`.*"startsAt"\s*:\s*"([^"]+)"`)
r2 = regexp.MustCompile(`.*"UpdatedAt"\s*:\s*"([^"]+)"`)
}
if r1 != nil {
parts := r1.FindStringSubmatch(actVals[i])
require.Len(t, parts, 2)
if expKey == "alertmanager_recv/alertmanager_test" {
// 2 fields for Prometheus Alertmanager.
parts2 := r2.FindStringSubmatch(actVals[i])
require.Len(t, parts2, 2)
expVal = fmt.Sprintf(expVal, parts[1], parts2[1])
} else {
expVal = fmt.Sprintf(expVal, parts[1])
}
}
switch expKey {
case "line_recv/line_test", "threema_recv/threema_test":
// POST parameters.
require.Equal(t, expVal, actVals[i])
case "pushover_recv/pushover_test", "telegram_recv/bot6sh027hs034h":
// Multipart data.
multipartEqual(t, expVal, actVals[i])
default:
require.JSONEq(t, expVal, actVals[i])
}
}
}
}
func multipartEqual(t *testing.T, exp, act string) {
t.Helper()
fillMap := func(r *multipart.Reader, m map[string]string) {
for {
part, err := r.NextPart()
if part == nil || errors.Is(err, io.EOF) {
break
}
require.NoError(t, err)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(part)
require.NoError(t, err)
m[part.FormName()] = buf.String()
}
}
expReader := multipart.NewReader(strings.NewReader(exp), receivers.GetBoundary())
actReader := multipart.NewReader(strings.NewReader(act), receivers.GetBoundary())
expMap, actMap := make(map[string]string), make(map[string]string)
fillMap(expReader, expMap)
fillMap(actReader, actMap)
require.Equal(t, expMap, actMap)
}
func (nc *mockNotificationChannel) Close() error {
return nc.server.Close()
}
type mockEmailHandler struct {
emails []*notifications.SendEmailCommandSync
}
func (e *mockEmailHandler) sendEmailCommandHandlerSync(_ context.Context, cmd *notifications.SendEmailCommandSync) error {
// We 0 out the start time since that is a variable that we cannot predict.
alerts := cmd.Data["Alerts"].(alertingTemplates.ExtendedAlerts)
for i := range alerts {
alerts[i].StartsAt = time.Time{}
}
e.emails = append(e.emails, cmd)
return nil
}
// alertmanagerConfig has the config for all the notification channels
// that we want to test. It is recommended to use different URL for each
// channel and have 1 route per channel.
// group_wait 0s means the notification is sent as soon as it is received.
const alertmanagerConfig = `
{
"alertmanager_config": {
"route": {
"receiver": "slack_recv1",
"group_wait": "0s",
"group_by": [
"alertname"
],
"routes": [
{
"receiver": "email_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"EmailAlert\""
]
},
{
"receiver": "slack_recv1",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackAlert1\""
]
},
{
"receiver": "slack_recv2",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackAlert2\""
]
},
{
"receiver": "slack_failed_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackFailedAlert\""
]
},
{
"receiver": "slack_inactive_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"Inactive\""
]
},
{
"receiver": "pagerduty_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"PagerdutyAlert\""
]
},
{
"receiver": "dingding_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"DingDingAlert\""
]
},
{
"receiver": "discord_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"DiscordAlert\""
]
},
{
"receiver": "sensugo_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SensuGoAlert\""
]
},
{
"receiver": "pushover_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"PushoverAlert\""
]
},
{
"receiver": "googlechat_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"GoogleChatAlert\""
]
},
{
"receiver": "kafka_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"KafkaAlert\""
]
},
{
"receiver": "line_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"LineAlert\""
]
},
{
"receiver": "threema_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"ThreemaAlert\""
]
},
{
"receiver": "opsgenie_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"OpsGenieAlert\""
]
},
{
"receiver": "alertmanager_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"AlertmanagerAlert\""
]
},
{
"receiver": "victorops_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"VictorOpsAlert\""
]
},
{
"receiver": "teams_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"TeamsAlert\""
]
},
{
"receiver": "webhook_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"WebhookAlert\""
]
},
{
"receiver": "telegram_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"TelegramAlert\""
]
}
]
},
"receivers": [
{
"name": "email_recv",
"grafana_managed_receiver_configs": [
{
"name": "email_test",
"type": "email",
"settings": {
"addresses": "test@email.com",
"singleEmail": true
}
}
]
},
{
"name": "dingding_recv",
"grafana_managed_receiver_configs": [
{
"name": "dingding_test",
"type": "dingding",
"settings": {
"url": "http://CHANNEL_ADDR/dingding_recv/dingding_test"
}
}
]
},
{
"name": "discord_recv",
"grafana_managed_receiver_configs": [
{
"name": "discord_test",
"type": "discord",
"settings": {
"url": "http://CHANNEL_ADDR/discord_recv/discord_test"
}
}
]
},
{
"name": "googlechat_recv",
"grafana_managed_receiver_configs": [
{
"name": "googlechat_test",
"type": "googlechat",
"settings": {
"url": "http://CHANNEL_ADDR/googlechat_recv/googlechat_test"
}
}
]
},
{
"name": "kafka_recv",
"grafana_managed_receiver_configs": [
{
"name": "kafka_test",
"type": "kafka",
"settings": {
"kafkaRestProxy": "http://CHANNEL_ADDR",
"kafkaTopic": "my_kafka_topic"
}
}
]
},
{
"name": "victorops_recv",
"grafana_managed_receiver_configs": [
{
"name": "victorops_test",
"type": "victorops",
"settings": {
"url": "http://CHANNEL_ADDR/victorops_recv/victorops_test"
}
}
]
},
{
"name": "teams_recv",
"grafana_managed_receiver_configs": [
{
"name": "teams_test",
"type": "teams",
"settings": {
"url": "http://CHANNEL_ADDR/teams_recv/teams_test"
}
}
]
},
{
"name": "webhook_recv",
"grafana_managed_receiver_configs": [
{
"name": "webhook_test",
"type": "webhook",
"settings": {
"url": "http://CHANNEL_ADDR/webhook_recv/webhook_test",
"username": "my_username",
"httpMethod": "POST",
"maxAlerts": "5"
},
"secureSettings": {
"password": "mysecretpassword"
}
}
]
},
{
"name": "sensugo_recv",
"grafana_managed_receiver_configs": [
{
"name": "sensugo_test",
"type": "sensugo",
"settings": {
"url": "http://CHANNEL_ADDR/sensugo_recv/sensugo_test",
"namespace": "sensugo"
},
"secureSettings": {
"apikey": "mysecretkey"
}
}
]
},
{
"name": "pushover_recv",
"grafana_managed_receiver_configs": [
{
"name": "pushover_test",
"type": "pushover",
"settings": {},
"secureSettings": {
"userKey": "mysecretkey",
"apiToken": "mysecrettoken"
}
}
]
},
{
"name": "line_recv",
"grafana_managed_receiver_configs": [
{
"name": "line_test",
"type": "LINE",
"settings": {},
"secureSettings": {
"token": "mysecrettoken"
}
}
]
},
{
"name": "threema_recv",
"grafana_managed_receiver_configs": [
{
"name": "threema_test",
"type": "threema",
"settings": {
"gateway_id": "*1234567",
"recipient_id": "abcdefgh"
},
"secureSettings": {
"api_secret": "myapisecret"
}
}
]
},
{
"name": "opsgenie_recv",
"grafana_managed_receiver_configs": [
{
"name": "opsgenie_test",
"type": "opsgenie",
"settings": {
"apiUrl": "http://CHANNEL_ADDR/opsgenie_recv/opsgenie_test"
},
"secureSettings": {
"apiKey": "mysecretkey"
}
}
]
},
{
"name": "alertmanager_recv",
"grafana_managed_receiver_configs": [
{
"name": "alertmanager_test",
"type": "prometheus-alertmanager",
"settings": {
"url": "http://CHANNEL_ADDR/alertmanager_recv/alertmanager_test"
},
"secureSettings": {}
}
]
},
{
"name": "telegram_recv",
"grafana_managed_receiver_configs": [
{
"name": "telegram_test",
"type": "telegram",
"settings": {
"chatid": "telegram_chat_id"
},
"secureSettings": {
"bottoken": "6sh027hs034h"
}
}
]
},
{
"name": "slack_recv1",
"grafana_managed_receiver_configs": [
{
"name": "slack_test_without_token",
"type": "slack",
"settings": {
"recipient": "#test-channel",
"mentionChannel": "here",
"mentionUsers": "user1, user2",
"mentionGroups": "group1, group2",
"username": "Integration Test",
"icon_emoji": "🚀",
"icon_url": "https://awesomeemoji.com/rocket",
"text": "Integration Test {{ template \"slack.default.text\" . }}",
"title": "Integration Test {{ template \"slack.default.title\" . }}",
"fallback": "Integration Test {{ template \"slack.default.title\" . }}"
},
"secureSettings": {
"url": "http://CHANNEL_ADDR/slack_recv1/slack_test_without_token"
}
}
]
},
{
"name": "slack_recv2",
"grafana_managed_receiver_configs": [
{
"name": "slack_test_with_token",
"type": "slack",
"settings": {
"recipient": "#test-channel",
"mentionUsers": "user1, user2",
"username": "Integration Test"
},
"secureSettings": {
"token": "myfullysecrettoken"
}
}
]
},
{
"name": "slack_failed_recv",
"grafana_managed_receiver_configs": [
{
"name": "slack_failed_test",
"type": "slack",
"settings": {
"recipient": "#test-channel",
"username": "test",
"text": "Integration Test"
},
"secureSettings": {
"url": "http://CHANNEL_ADDR/slack_failed_recv/slack_failed_test"
}
}
]
},
{
"name": "slack_inactive_recv",
"grafana_managed_receiver_configs": [
{
"name": "inactive",
"type": "slack",
"settings": {
"recipient": "#inactive-channel",
"username": "Integration Test"
},
"secureSettings": {
"token": "myfullysecrettoken"
}
}
]
},
{
"name": "pagerduty_recv",
"grafana_managed_receiver_configs": [
{
"name": "pagerduty_test",
"type": "pagerduty",
"settings": {
"severity": "warning",
"class": "testclass",
"component": "Integration Test",
"group": "testgroup",
"summary": "Integration Test {{ template \"pagerduty.default.description\" . }}",
"url": "http://CHANNEL_ADDR/pagerduty_recvX/pagerduty_testX"
},
"secureSettings": {
"integrationKey": "pagerduty_recv/pagerduty_test"
}
}
]
}
]
}
}
`
var expAlertmanagerConfigFromAPI = `
{
"template_files": null,
"alertmanager_config": {
"route": {
"receiver": "slack_recv1",
"group_wait": "0s",
"group_by": [
"alertname"
],
"routes": [
{
"receiver": "email_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"EmailAlert\""
]
},
{
"receiver": "slack_recv1",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackAlert1\""
]
},
{
"receiver": "slack_recv2",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackAlert2\""
]
},
{
"receiver": "slack_failed_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SlackFailedAlert\""
]
},
{
"receiver": "slack_inactive_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"Inactive\""
]
}, {
"receiver": "pagerduty_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"PagerdutyAlert\""
]
},
{
"receiver": "dingding_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"DingDingAlert\""
]
},
{
"receiver": "discord_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"DiscordAlert\""
]
},
{
"receiver": "sensugo_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"SensuGoAlert\""
]
},
{
"receiver": "pushover_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"PushoverAlert\""
]
},
{
"receiver": "googlechat_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"GoogleChatAlert\""
]
},
{
"receiver": "kafka_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"KafkaAlert\""
]
},
{
"receiver": "line_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"LineAlert\""
]
},
{
"receiver": "threema_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"ThreemaAlert\""
]
},
{
"receiver": "opsgenie_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"OpsGenieAlert\""
]
},
{
"receiver": "alertmanager_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"AlertmanagerAlert\""
]
},
{
"receiver": "victorops_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"VictorOpsAlert\""
]
},
{
"receiver": "teams_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"TeamsAlert\""
]
},
{
"receiver": "webhook_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"WebhookAlert\""
]
},
{
"receiver": "telegram_recv",
"group_wait": "0s",
"group_by": [
"alertname"
],
"matchers": [
"alertname=\"TelegramAlert\""
]
}
]
},
"receivers": [
{
"name": "email_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "email_test",
"type": "email",
"disableResolveMessage": false,
"settings": {
"addresses": "test@email.com",
"singleEmail": true
},
"secureFields": {}
}
]
},
{
"name": "dingding_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "dingding_test",
"type": "dingding",
"disableResolveMessage": false,
"settings": {},
"secureFields": {"url": true}
}
]
},
{
"name": "discord_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "discord_test",
"type": "discord",
"disableResolveMessage": false,
"settings": {},
"secureFields": {"url": true}
}
]
},
{
"name": "googlechat_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "googlechat_test",
"type": "googlechat",
"disableResolveMessage": false,
"settings": {},
"secureFields": {"url": true}
}
]
},
{
"name": "kafka_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "kafka_test",
"type": "kafka",
"disableResolveMessage": false,
"settings": {
"kafkaRestProxy": "http://CHANNEL_ADDR",
"kafkaTopic": "my_kafka_topic"
},
"secureFields": {}
}
]
},
{
"name": "victorops_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "victorops_test",
"type": "victorops",
"disableResolveMessage": false,
"settings": {},
"secureFields": {"url": true}
}
]
},
{
"name": "teams_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "teams_test",
"type": "teams",
"disableResolveMessage": false,
"settings": {
"url": "http://CHANNEL_ADDR/teams_recv/teams_test"
},
"secureFields": {}
}
]
},
{
"name": "webhook_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "webhook_test",
"type": "webhook",
"disableResolveMessage": false,
"settings": {
"url": "http://CHANNEL_ADDR/webhook_recv/webhook_test",
"username": "my_username",
"httpMethod": "POST",
"maxAlerts": "5"
},
"secureFields": {
"password": true
}
}
]
},
{
"name": "sensugo_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "sensugo_test",
"type": "sensugo",
"disableResolveMessage": false,
"settings": {
"url": "http://CHANNEL_ADDR/sensugo_recv/sensugo_test",
"namespace": "sensugo"
},
"secureFields": {
"apikey": true
}
}
]
},
{
"name": "pushover_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "pushover_test",
"type": "pushover",
"disableResolveMessage": false,
"settings": {},
"secureFields": {
"userKey": true,
"apiToken": true
}
}
]
},
{
"name": "line_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "line_test",
"type": "LINE",
"disableResolveMessage": false,
"settings": {},
"secureFields": {
"token": true
}
}
]
},
{
"name": "threema_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "threema_test",
"type": "threema",
"disableResolveMessage": false,
"settings": {
"gateway_id": "*1234567",
"recipient_id": "abcdefgh"
},
"secureFields": {
"api_secret": true
}
}
]
},
{
"name": "opsgenie_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "opsgenie_test",
"type": "opsgenie",
"disableResolveMessage": false,
"settings": {
"apiUrl": "http://CHANNEL_ADDR/opsgenie_recv/opsgenie_test"
},
"secureFields": {
"apiKey": true
}
}
]
},
{
"name": "alertmanager_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "alertmanager_test",
"type": "prometheus-alertmanager",
"disableResolveMessage": false,
"settings": {
"url": "http://CHANNEL_ADDR/alertmanager_recv/alertmanager_test"
},
"secureFields": {}
}
]
},
{
"name": "telegram_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "telegram_test",
"type": "telegram",
"disableResolveMessage": false,
"settings": {
"chatid": "telegram_chat_id"
},
"secureFields": {
"bottoken": true
}
}
]
},
{
"name": "slack_recv1",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "slack_test_without_token",
"type": "slack",
"disableResolveMessage": false,
"settings": {
"fallback": "Integration Test {{ template \"slack.default.title\" . }}",
"icon_emoji": "🚀",
"icon_url": "https://awesomeemoji.com/rocket",
"mentionChannel": "here",
"mentionGroups": "group1, group2",
"mentionUsers": "user1, user2",
"recipient": "#test-channel",
"text": "Integration Test {{ template \"slack.default.text\" . }}",
"title": "Integration Test {{ template \"slack.default.title\" . }}",
"username": "Integration Test"
},
"secureFields": {
"url": true
}
}
]
},
{
"name": "slack_recv2",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "slack_test_with_token",
"type": "slack",
"disableResolveMessage": false,
"settings": {
"mentionUsers": "user1, user2",
"recipient": "#test-channel",
"username": "Integration Test"
},
"secureFields": {
"token": true
}
}
]
},
{
"name": "slack_failed_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "slack_failed_test",
"type": "slack",
"disableResolveMessage": false,
"settings": {
"recipient": "#test-channel",
"username": "test",
"text": "Integration Test"
},
"secureFields": {
"url": true
}
}
]
},
{
"name": "slack_inactive_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "inactive",
"type": "slack",
"disableResolveMessage": false,
"settings": {
"recipient": "#inactive-channel",
"username": "Integration Test"
},
"secureFields": {
"token": true
}
}
]
},
{
"name": "pagerduty_recv",
"grafana_managed_receiver_configs": [
{
"uid": "",
"name": "pagerduty_test",
"type": "pagerduty",
"disableResolveMessage": false,
"settings": {
"class": "testclass",
"component": "Integration Test",
"group": "testgroup",
"severity": "warning",
"summary": "Integration Test {{ template \"pagerduty.default.description\" . }}",
"url": "http://CHANNEL_ADDR/pagerduty_recvX/pagerduty_testX"
},
"secureFields": {
"integrationKey": true
}
}
]
}
]
}
}
`
var expEmailNotifications = []*notifications.SendEmailCommandSync{
{
SendEmailCommand: notifications.SendEmailCommand{
To: []string{"test@email.com"},
SingleEmail: true,
Template: "ng_alert_notification",
Subject: "[FIRING:1] EmailAlert (default)",
Data: map[string]interface{}{
"Title": "[FIRING:1] EmailAlert (default)",
"Message": "",
"Status": "firing",
"Alerts": alertingTemplates.ExtendedAlerts{
alertingTemplates.ExtendedAlert{
Status: "firing",
Labels: template.KV{"alertname": "EmailAlert", "grafana_folder": "default"},
Annotations: template.KV{},
StartsAt: time.Time{},
EndsAt: time.Time{},
GeneratorURL: "http://localhost:3000/alerting/grafana/UID_EmailAlert/view?orgId=1",
Fingerprint: "1e8f5e886dc14813",
SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_EmailAlert&orgId=1",
DashboardURL: "",
PanelURL: "",
RuleUID: "UID_EmailAlert",
OrgID: new(int64(1)),
Values: map[string]float64{"A": 1},
ValueString: "[ var='A' labels={} type='math' value=1 ]",
},
},
"GroupLabels": template.KV{"alertname": "EmailAlert"},
"CommonLabels": template.KV{"alertname": "EmailAlert", "grafana_folder": "default"},
"CommonAnnotations": template.KV{},
"ExternalURL": "http://localhost:3000/",
"RuleUrl": "http://localhost:3000/alerting/list",
"AlertPageUrl": "http://localhost:3000/alerting/list?alertState=firing&view=state",
},
},
},
}
// expNonEmailNotifications is all the expected notifications (except email).
// The key for the map is taken from the URL. The last 2 components of URL
// split with "/" forms the key for that route.
var expNonEmailNotifications = map[string][]string{
"slack_recv1/slack_test_without_token": {
`{
"channel": "#test-channel",
"username": "Integration Test",
"icon_emoji": "🚀",
"icon_url": "https://awesomeemoji.com/rocket",
"attachments": [
{
"title": "Integration Test [FIRING:1] SlackAlert1 (default)",
"title_link": "http://localhost:3000/alerting/grafana/UID_SlackAlert1/view?orgId=1",
"text": "Integration Test ",
"fallback": "Integration Test [FIRING:1] SlackAlert1 (default)",
"footer": "Grafana",
"footer_icon": "https://grafana.com/static/assets/img/fav32.png",
"color": "#D63232",
"ts": %s,
"mrkdwn_in": ["pretext"],
"pretext": "<!here|here> <!subteam^group1><!subteam^group2> <@user1><@user2>"
}
]
}`,
},
"slack_recvX/slack_testX": {
`{
"channel": "#test-channel",
"username": "Integration Test",
"attachments": [
{
"title": "[FIRING:1] SlackAlert2 (default)",
"title_link": "http://localhost:3000/alerting/grafana/UID_SlackAlert2/view?orgId=1",
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SlackAlert2\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_SlackAlert2&orgId=1\n",
"fallback": "[FIRING:1] SlackAlert2 (default)",
"footer": "Grafana",
"footer_icon": "https://grafana.com/static/assets/img/fav32.png",
"color": "#D63232",
"ts": %s,
"mrkdwn_in": ["pretext"],
"pretext": "<@user1><@user2>"
}
]
}`,
},
"pagerduty_recvX/pagerduty_testX": {
`{
"routing_key": "pagerduty_recv/pagerduty_test",
"dedup_key": "234edb34441f942f713f3c2ccf58b1d719d921b4cbe34e57a1630f1dee847e3b",
"event_action": "trigger",
"payload": {
"summary": "Integration Test [FIRING:1] PagerdutyAlert (default)",
"source": "%s",
"severity": "warning",
"class": "testclass",
"component": "Integration Test",
"group": "testgroup",
"custom_details": {
"firing": "\nValue: A=1\nLabels:\n - alertname = PagerdutyAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_PagerdutyAlert&orgId=1\n",
"num_firing": "1",
"num_resolved": "0",
"resolved": ""
}
},
"client": "Grafana",
"client_url": "http://localhost:3000/",
"links": [
{
"href": "http://localhost:3000/",
"text": "External URL"
}
]
}`,
},
"dingding_recv/dingding_test": {
`{
"link": {
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist",
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DingDingAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_DingDingAlert&orgId=1\n",
"title": "[FIRING:1] DingDingAlert (default)"
},
"msgtype": "link"
}`,
},
"teams_recv/teams_test": {
`{
"attachments": [
{
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"body": [
{
"color": "attention",
"size": "large",
"text": "[FIRING:1] TeamsAlert (default)",
"type": "TextBlock",
"weight": "bolder",
"wrap": true
}, {
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = TeamsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=__alert_rule_uid__%3DUID_TeamsAlert&orgId=1\n",
"type": "TextBlock",
"wrap": true
}, {
"actions": [
{
"title": "View URL",
"type": "Action.OpenUrl",
"url": "http://localhost:3000/alerting/list"
}
],
"type": "ActionSet"
}
],
"type": "AdaptiveCard",
"version": "1.4",
"msTeams": {
"width": "Full"
}
},
"contentType": "application/vnd.microsoft.card.adaptive"
}
],
"summary": "[FIRING:1] TeamsAlert (default)",
"type": "message"
}`,
},
"webhook_recv/webhook_test": {
`{
"receiver": "webhook_recv",
"status": "firing",
"orgId": 1,
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "WebhookAlert",
"grafana_folder": "default"
},
"annotations": {},
"startsAt": "%s",
"values": {"A": 1},
"valueString": "[ var='A' labels={} type='math' value=1 ]",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view?orgId=1",
"fingerprint": "15c59b0a380bd9f1",
"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_WebhookAlert&orgId=1",
"dashboardURL": "",
"panelURL": "",
"orgId": 1,
"ruleUID": "UID_WebhookAlert"
}
],
"groupLabels": {
"alertname": "WebhookAlert"
},
"commonLabels": {
"alertname": "WebhookAlert",
"grafana_folder": "default"
},
"commonAnnotations": {},
"externalURL": "http://localhost:3000/",
"version": "1",
"groupKey": "{}/{alertname=\"WebhookAlert\"}:{alertname=\"WebhookAlert\"}",
"truncatedAlerts": 0,
"title": "[FIRING:1] WebhookAlert (default)",
"state": "alerting",
"message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = WebhookAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_WebhookAlert&orgId=1\n"
}`,
},
"discord_recv/discord_test": {
`{
"content": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DiscordAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_DiscordAlert&orgId=1\n",
"embeds": [
{
"color": 14037554,
"footer": {
"icon_url": "https://grafana.com/static/assets/img/fav32.png",
"text": "Grafana v"
},
"title": "[FIRING:1] DiscordAlert (default)",
"type": "rich",
"url": "http://localhost:3000/alerting/list"
}
],
"username": "Grafana"
}`,
},
"sensugo_recv/sensugo_test": {
`{
"check": {
"handlers": null,
"interval": 86400,
"issued": %s,
"metadata": {
"labels": {
"ruleURL": "http://localhost:3000/alerting/list"
},
"name": "default"
},
"output": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SensuGoAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_SensuGoAlert&orgId=1\n",
"status": 2
},
"entity": {
"metadata": {
"name": "default",
"namespace": "sensugo"
}
},
"ruleUrl": "http://localhost:3000/alerting/list"
}`,
},
"pushover_recv/pushover_test": {
"--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert (default)\r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = PushoverAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_PushoverAlert&orgId=1\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n",
},
"telegram_recv/bot6sh027hs034h": {
"--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nHTML\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = TelegramAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_TelegramAlert&orgId=1\n\r\n--abcd--\r\n",
},
"googlechat_recv/googlechat_test": {
`{
"previewText": "[FIRING:1] GoogleChatAlert (default)",
"fallbackText": "[FIRING:1] GoogleChatAlert (default)",
"cards": [
{
"header": {
"title": "[FIRING:1] GoogleChatAlert (default)"
},
"sections": [
{
"widgets": [
{
"textParagraph": {
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = GoogleChatAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_GoogleChatAlert&orgId=1\n"
}
},
{
"buttons": [
{
"textButton": {
"text": "OPEN IN GRAFANA",
"onClick": {
"openLink": {
"url": "http://localhost:3000/alerting/list"
}
}
}
}
]
},
{
"textParagraph": {
"text": "%s"
}
}
]
}
]
}
]
}`,
},
"topics/my_kafka_topic": {
`{
"records": [
{
"value": {
"alert_state": "alerting",
"client": "Grafana",
"client_url": "http://localhost:3000/alerting/list",
"description": "[FIRING:1] KafkaAlert (default)",
"details": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = KafkaAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_KafkaAlert&orgId=1\n",
"incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f"
}
}
]
}`,
},
"line_recv/line_test": {
`message=%5BFIRING%3A1%5D+LineAlert+%28default%29%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%3ForgId%3D1%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3D__alert_rule_uid__%253DUID_LineAlert%26orgId%3D1%0A`,
},
"threema_recv/threema_test": {
`from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%28default%29%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%3ForgId%3D1%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3D__alert_rule_uid__%253DUID_ThreemaAlert%26orgId%3D1%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`,
},
"victorops_recv/victorops_test": {
`{
"alert_url": "http://localhost:3000/alerting/list",
"entity_display_name": "[FIRING:1] VictorOpsAlert (default)",
"entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae",
"message_type": "CRITICAL",
"monitoring_tool": "Grafana v",
"state_message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = VictorOpsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_VictorOpsAlert&orgId=1\n",
"timestamp": %s
}`,
},
"opsgenie_recv/opsgenie_test": {
`{
"alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e",
"description": "[FIRING:1] OpsGenieAlert (default)\nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = OpsGenieAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_OpsGenieAlert&orgId=1\n",
"details": {
"url": "http://localhost:3000/alerting/list"
},
"message": "[FIRING:1] OpsGenieAlert (default)",
"source": "Grafana",
"tags": ["alertname:OpsGenieAlert","grafana_folder:default"]
}`,
},
// Prometheus Alertmanager.
"alertmanager_recv/alertmanager_test": {
`[
{
"labels": {
"__alert_rule_uid__": "UID_AlertmanagerAlert",
"alertname": "AlertmanagerAlert",
"grafana_folder": "default"
},
"annotations": {
"__alert_rule_namespace_uid__":"default",
"__orgId__":"1",
"__values__": "{\"A\":1}",
"__value_string__": "[ var='A' labels={} type='math' value=1 ]"
},
"startsAt": "%s",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://localhost:3000/alerting/grafana/UID_AlertmanagerAlert/view",
"UpdatedAt": "%s",
"Timeout": false
}
]`,
},
}
// expNotificationErrors maps a receiver name with its expected error string.
var expNotificationErrors = map[string]string{
"slack_failed_recv": "failed to send Slack message: unexpected 5xx status code: 500",
}
// expInactiveReceivers is a set of receivers expected to be unused.
var expInactiveReceivers = map[string]struct{}{
"slack_inactive_recv": {},
}
// Drop-in replacement for removed api.
func saveAndApplyAlertmanagerConfiguration(t *testing.T, env *server.TestEnv, org int64, rawConfig string) {
t.Helper()
config, err := notifier.Load([]byte(rawConfig))
require.NoError(t, err)
err = notifier.EncryptReceiverConfigs(config.AlertmanagerConfig.Receivers, func(ctx context.Context, payload []byte) ([]byte, error) {
return env.Server.HTTPServer.AlertNG.MultiOrgAlertmanager.Crypto.Encrypt(ctx, payload, secrets.WithoutScope())
})
require.NoError(t, err)
cfgToSave, err := legacy_storage.SerializeAlertmanagerConfig(*config)
require.NoError(t, err)
err = env.Server.HTTPServer.AlertNG.Api.AlertingStore.SaveAlertmanagerConfiguration(context.Background(), &models.SaveAlertmanagerConfigurationCmd{
AlertmanagerConfiguration: string(cfgToSave),
ConfigurationVersion: "v1",
Default: false,
OrgID: org,
})
require.NoError(t, err)
_, err = env.Server.HTTPServer.AlertNG.MultiOrgAlertmanager.ApplyConfig(context.Background(), org, &models.AlertConfiguration{
AlertmanagerConfiguration: string(cfgToSave),
})
require.NoError(t, err)
}
func isAutogenRoute(route *apimodels.Route) bool {
return notifier.IsAutogeneratedRoot(v1.RouteToModel(route))
}
func removeAutogenConfigIfExists(route *apimodels.Route) {
route.Routes = slices.DeleteFunc(route.Routes, func(route *apimodels.Route) bool {
return isAutogenRoute(route)
})
}