2021-05-14 19:49:54 +01:00
package alerting
import (
2022-11-29 09:59:03 +01:00
"context"
2021-05-18 17:31:00 +03:00
"encoding/json"
2021-05-14 19:49:54 +01:00
"fmt"
2022-08-10 13:37:51 +00:00
"io"
2021-05-14 19:49:54 +01:00
"net/http"
2021-08-12 16:04:09 +03:00
"regexp"
2021-05-14 19:49:54 +01:00
"testing"
2021-08-24 03:28:09 -07:00
"time"
2024-02-06 08:33:47 +00:00
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
2024-02-22 15:58:56 +00:00
"github.com/prometheus/alertmanager/timeinterval"
2024-02-06 08:33:47 +00:00
"github.com/prometheus/common/model"
2023-01-30 09:55:35 +01:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2024-02-06 08:33:47 +00:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2022-08-10 11:56:48 +02:00
"github.com/grafana/grafana/pkg/services/org"
2022-11-29 09:59:03 +01:00
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
2022-06-28 14:32:25 +02:00
"github.com/grafana/grafana/pkg/services/user"
2021-05-14 19:49:54 +01:00
"github.com/grafana/grafana/pkg/tests/testinfra"
)
2024-02-06 08:33:47 +00:00
func TestIntegrationAlertmanagerConfiguration ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
AppModeProduction : true ,
})
2024-04-04 15:04:47 +02:00
grafanaListedAddr , env := testinfra . StartGrafanaEnv ( t , dir , path )
createUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
2024-02-06 08:33:47 +00:00
DefaultOrgRole : string ( org . RoleAdmin ),
Password : "admin" ,
Login : "admin" ,
})
client := newAlertingApiClient ( grafanaListedAddr , "admin" , "admin" )
cases := [] struct {
name string
cfg apimodels . PostableUserConfig
expErr string
}{{
name : "configuration with default route" ,
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
}, {
name : "configuration with UTF-8 matchers" ,
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
Routes : [] * apimodels . Route {{
GroupBy : [] model . LabelName { "foo🙂" },
Matchers : config . Matchers {{
Type : labels . MatchEqual ,
Name : "foo🙂" ,
Value : "bar" ,
}, {
Type : labels . MatchNotEqual ,
Name : "_bar1" ,
Value : "baz🙂" ,
}, {
Type : labels . MatchRegexp ,
Name : "0baz" ,
Value : "[a-zA-Z0-9]+,?" ,
}, {
Type : labels . MatchNotRegexp ,
Name : "corge" ,
Value : "^[0-9]+((,[0-9]{3})*(,[0-9]{0,3})?)?$" ,
}, {
Type : labels . MatchEqual ,
Name : "Προμηθέας" , // Prometheus in Greek
Value : "Prom" ,
}, {
Type : labels . MatchNotEqual ,
Name : "犬" , // Dog in Japanese
Value : "Shiba Inu" ,
}},
}},
},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
}, {
name : "configuration with UTF-8 object matchers" ,
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
Routes : [] * apimodels . Route {{
GroupBy : [] model . LabelName { "foo🙂" },
ObjectMatchers : apimodels . ObjectMatchers {{
Type : labels . MatchEqual ,
Name : "foo🙂" ,
Value : "bar" ,
}, {
Type : labels . MatchNotEqual ,
Name : "_bar1" ,
Value : "baz🙂" ,
}, {
Type : labels . MatchRegexp ,
Name : "0baz" ,
Value : "[a-zA-Z0-9]+,?" ,
}, {
Type : labels . MatchNotRegexp ,
Name : "corge" ,
Value : "^[0-9]+((,[0-9]{3})*(,[0-9]{0,3})?)?$" ,
}, {
Type : labels . MatchEqual ,
Name : "Προμηθέας" , // Prometheus in Greek
Value : "Prom" ,
}, {
Type : labels . MatchNotEqual ,
Name : "犬" , // Dog in Japanese
Value : "Shiba Inu" ,
}},
}},
},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
}, {
name : "configuration with UTF-8 in both matchers and object matchers" ,
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
Routes : [] * apimodels . Route {{
GroupBy : [] model . LabelName { "foo🙂" },
Matchers : config . Matchers {{
Type : labels . MatchEqual ,
Name : "foo🙂" ,
Value : "bar" ,
}, {
Type : labels . MatchNotEqual ,
Name : "_bar1" ,
Value : "baz🙂" ,
}, {
Type : labels . MatchRegexp ,
Name : "0baz" ,
Value : "[a-zA-Z0-9]+,?" ,
}, {
Type : labels . MatchNotRegexp ,
Name : "corge" ,
Value : "^[0-9]+((,[0-9]{3})*(,[0-9]{0,3})?)?$" ,
}},
ObjectMatchers : apimodels . ObjectMatchers {{
Type : labels . MatchEqual ,
Name : "Προμηθέας" , // Prometheus in Greek
Value : "Prom" ,
}, {
Type : labels . MatchNotEqual ,
Name : "犬" , // Dog in Japanese
Value : "Shiba Inu" ,
}},
}},
},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
2024-02-22 15:58:56 +00:00
}, {
2024-02-22 16:57:20 +00:00
// TODO: Mute time intervals is deprecated in Alertmanager and scheduled to be
// removed before version 1.0. Remove this test when support for mute time
// intervals is removed.
name : "configuration with mute time intervals" ,
2024-02-22 15:58:56 +00:00
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
Routes : [] * apimodels . Route {{
MuteTimeIntervals : [] string { "weekends" },
}},
},
2024-02-22 16:57:20 +00:00
MuteTimeIntervals : [] config . MuteTimeInterval {{
2024-02-22 15:58:56 +00:00
Name : "weekends" ,
TimeIntervals : [] timeinterval . TimeInterval {{
Weekdays : [] timeinterval . WeekdayRange {{
InclusiveRange : timeinterval . InclusiveRange {
Begin : 1 ,
End : 5 ,
},
}},
}},
}},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
}, {
2024-02-22 16:57:20 +00:00
name : "configuration with time intervals" ,
2024-02-22 15:58:56 +00:00
cfg : apimodels . PostableUserConfig {
AlertmanagerConfig : apimodels . PostableApiAlertingConfig {
Config : apimodels . Config {
Route : & apimodels . Route {
Receiver : "test" ,
Routes : [] * apimodels . Route {{
MuteTimeIntervals : [] string { "weekends" },
}},
},
2024-02-22 16:57:20 +00:00
TimeIntervals : [] config . TimeInterval {{
2024-02-22 15:58:56 +00:00
Name : "weekends" ,
TimeIntervals : [] timeinterval . TimeInterval {{
Weekdays : [] timeinterval . WeekdayRange {{
InclusiveRange : timeinterval . InclusiveRange {
Begin : 1 ,
End : 5 ,
},
}},
}},
}},
},
Receivers : [] * apimodels . PostableApiReceiver {{
Receiver : config . Receiver {
Name : "test" ,
},
}},
},
},
2024-02-06 08:33:47 +00:00
}}
for _ , tc := range cases {
t . Run ( tc . name , func ( t * testing . T ) {
ok , err := client . PostConfiguration ( t , tc . cfg )
if tc . expErr != "" {
require . EqualError ( t , err , tc . expErr )
require . False ( t , ok )
} else {
require . NoError ( t , err )
require . True ( t , ok )
}
})
}
}
2022-12-09 08:11:56 +01:00
func TestIntegrationAlertmanagerConfigurationIsTransactional ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-05-14 19:49:54 +01:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 17:16:40 +03:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
2021-09-16 15:33:51 +01:00
NGAlertAlertmanagerConfigPollInterval : 2 * time . Second ,
DisableAnonymous : true ,
2022-02-09 10:26:06 +01:00
AppModeProduction : true ,
2021-05-14 19:49:54 +01:00
})
2024-04-04 15:04:47 +02:00
grafanaListedAddr , env := testinfra . StartGrafanaEnv ( t , dir , path )
2021-08-25 15:11:22 +02:00
2024-04-04 15:04:47 +02:00
orgService , err := orgimpl . ProvideService ( env . SQLStore , env . Cfg , quotatest . New ( false , nil ))
2022-11-29 09:59:03 +01:00
require . NoError ( t , err )
2021-08-25 15:11:22 +02:00
// editor from main organisation requests configuration
alertConfigURL := fmt . Sprintf ( "http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts" , grafanaListedAddr )
2021-08-12 16:04:09 +03:00
// create user under main organisation
2024-04-04 15:04:47 +02:00
userID := createUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2021-08-12 16:04:09 +03:00
Password : "editor" ,
Login : "editor" ,
})
// create another organisation
2022-11-29 09:59:03 +01:00
newOrg , err := orgService . CreateWithMember ( context . Background (), & org . CreateOrgCommand { Name : "another org" , UserID : userID })
require . NoError ( t , err )
orgID := newOrg . ID
2021-08-12 16:04:09 +03:00
// create user under different organisation
2024-04-04 15:04:47 +02:00
createUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2021-08-12 16:04:09 +03:00
Password : "editor-42" ,
Login : "editor-42" ,
2022-06-28 14:32:25 +02:00
OrgID : orgID ,
2021-08-12 16:04:09 +03:00
})
2021-05-14 19:49:54 +01:00
// On a blank start with no configuration, it saves and delivers the default configuration.
{
resp := getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
require . JSONEq ( t , defaultAlertmanagerConfigJSON , getBody ( t , resp . Body ))
}
// When creating new configuration, if it fails to apply - it does not save it.
{
payload := `
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "slack.receiver"
},
"receivers": [{
"name": "slack.receiver",
"grafana_managed_receiver_configs": [{
"settings": {
"iconEmoji": "",
"iconUrl": "",
"mentionGroups": "",
"mentionUsers": "",
"recipient": "#unified-alerting-test",
"username": ""
},
"secureSettings": {},
"type": "slack",
"name": "slack.receiver",
"disableResolveMessage": false,
"uid": ""
}]
}]
}
}
`
resp := postRequest ( t , alertConfigURL , payload , http . StatusBadRequest ) // nolint
2022-08-10 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2022-04-14 17:54:49 +02:00
require . NoError ( t , err )
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-04-14 17:54:49 +02:00
require . NoError ( t , json . Unmarshal ( b , & res ))
2023-04-28 10:56:59 -04:00
require . Regexp ( t , `^failed to save and apply Alertmanager configuration: failed to validate integration "slack.receiver" \(UID [^\)]+\) of type "slack": token must be specified when using the Slack chat API` , res [ "message" ])
2021-05-14 19:49:54 +01:00
resp = getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
2022-04-14 17:54:49 +02:00
2021-05-14 19:49:54 +01:00
require . JSONEq ( t , defaultAlertmanagerConfigJSON , getBody ( t , resp . Body ))
}
2021-08-12 16:04:09 +03:00
// editor42 from organisation 42 posts configuration
alertConfigURL = fmt . Sprintf ( "http://editor-42:editor-42@%s/api/alertmanager/grafana/config/api/v1/alerts" , grafanaListedAddr )
2021-08-24 03:28:09 -07:00
// Before we start operating, make sure we've synced this org.
require . Eventually ( t , func () bool {
resp , err := http . Get ( alertConfigURL ) // nolint
require . NoError ( t , err )
return resp . StatusCode == http . StatusOK
}, 10 * time . Second , 2 * time . Second )
2021-08-12 16:04:09 +03:00
// Post the alertmanager config.
{
mockChannel := newMockNotificationChannel ( t , grafanaListedAddr )
amConfig := getAlertmanagerConfig ( mockChannel . server . Addr )
postRequest ( t , alertConfigURL , amConfig , http . StatusAccepted ) // nolint
// Verifying that the new configuration is returned
resp := getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
b := getBody ( t , resp . Body )
re := regexp . MustCompile ( `"uid":"([\w|-]*)"` )
e := getExpAlertmanagerConfigFromAPI ( mockChannel . server . Addr )
require . JSONEq ( t , e , string ( re . ReplaceAll ([] byte ( b ), [] byte ( `"uid":""` ))))
}
// verify that main organisation still gets the default configuration
alertConfigURL = fmt . Sprintf ( "http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts" , grafanaListedAddr )
{
resp := getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
require . JSONEq ( t , defaultAlertmanagerConfigJSON , getBody ( t , resp . Body ))
}
2021-05-14 19:49:54 +01:00
}
2022-12-09 08:11:56 +01:00
func TestIntegrationAlertmanagerConfigurationPersistSecrets ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-05-14 19:49:54 +01:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 17:16:40 +03:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
2022-02-09 10:26:06 +01:00
AppModeProduction : true ,
2021-05-14 19:49:54 +01:00
})
2024-04-04 15:04:47 +02:00
grafanaListedAddr , env := testinfra . StartGrafanaEnv ( t , dir , path )
2021-08-25 15:11:22 +02:00
alertConfigURL := fmt . Sprintf ( "http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts" , grafanaListedAddr )
2024-04-04 15:04:47 +02:00
createUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2021-08-12 16:04:09 +03:00
Password : "editor" ,
Login : "editor" ,
})
2021-05-18 17:31:00 +03:00
generatedUID := ""
2021-05-14 19:49:54 +01:00
// create a new configuration that has a secret
{
payload := `
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "slack.receiver"
},
"receivers": [{
"name": "slack.receiver",
"grafana_managed_receiver_configs": [{
"settings": {
"recipient": "#unified-alerting-test"
},
"secureSettings": {
"url": "http://averysecureurl.com/webhook"
},
"type": "slack",
"name": "slack.receiver",
"disableResolveMessage": false
}]
}]
}
}
`
resp := postRequest ( t , alertConfigURL , payload , http . StatusAccepted ) // nolint
require . JSONEq ( t , `{"message":"configuration created"}` , getBody ( t , resp . Body ))
}
2021-05-18 17:31:00 +03:00
// Try to update a receiver with unknown UID
2021-05-14 19:49:54 +01:00
{
2021-05-18 17:31:00 +03:00
// Then, update the recipient
2021-05-14 19:49:54 +01:00
payload := `
2021-05-18 17:31:00 +03:00
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "slack.receiver"
},
"receivers": [{
"name": "slack.receiver",
"grafana_managed_receiver_configs": [{
"settings": {
"recipient": "#unified-alerting-test-but-updated"
},
"secureFields": {
"url": true
},
"type": "slack",
"name": "slack.receiver",
"disableResolveMessage": false,
"uid": "invalid"
}]
}]
}
}
`
resp := postRequest ( t , alertConfigURL , payload , http . StatusBadRequest ) // nolint
2022-04-14 17:54:49 +02:00
s := getBody ( t , resp . Body )
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-04-14 17:54:49 +02:00
require . NoError ( t , json . Unmarshal ([] byte ( s ), & res ))
require . Equal ( t , "unknown receiver: invalid" , res [ "message" ])
2021-05-18 17:31:00 +03:00
}
// The secure settings must be present
{
resp := getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
2024-02-06 08:33:47 +00:00
var c apimodels . GettableUserConfig
2021-05-18 17:31:00 +03:00
bb := getBody ( t , resp . Body )
err := json . Unmarshal ([] byte ( bb ), & c )
require . NoError ( t , err )
m := c . GetGrafanaReceiverMap ()
assert . Len ( t , m , 1 )
for k := range m {
generatedUID = m [ k ]. UID
}
// Then, update the recipient
payload := fmt . Sprintf ( `
2021-05-14 19:49:54 +01:00
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "slack.receiver"
},
"receivers": [{
"name": "slack.receiver",
"grafana_managed_receiver_configs": [{
"settings": {
"recipient": "#unified-alerting-test-but-updated"
},
"secureFields": {
"url": true
},
"type": "slack",
"name": "slack.receiver",
2021-05-18 17:31:00 +03:00
"disableResolveMessage": false,
"uid": %q
2021-05-14 19:49:54 +01:00
}]
}]
}
}
2021-05-18 17:31:00 +03:00
` , generatedUID )
resp = postRequest ( t , alertConfigURL , payload , http . StatusAccepted ) // nolint
2021-05-14 19:49:54 +01:00
require . JSONEq ( t , `{"message": "configuration created"}` , getBody ( t , resp . Body ))
}
// The secure settings must be present
{
resp := getRequest ( t , alertConfigURL , http . StatusOK ) // nolint
2021-05-18 17:31:00 +03:00
require . JSONEq ( t , fmt . Sprintf ( `
2021-05-14 19:49:54 +01:00
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "slack.receiver"
},
"receivers": [{
"name": "slack.receiver",
"grafana_managed_receiver_configs": [{
2021-05-18 17:31:00 +03:00
"uid": %q,
2021-05-14 19:49:54 +01:00
"name": "slack.receiver",
"type": "slack",
"disableResolveMessage": false,
"settings": {
"recipient": "#unified-alerting-test-but-updated"
},
"secureFields": {
"url": true
}
}]
}]
}
}
2021-05-18 17:31:00 +03:00
` , generatedUID ), getBody ( t , resp . Body ))
2021-05-14 19:49:54 +01:00
}
}