The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/notifier/alertmanager_test.go

259 lines
7.3 KiB

package notifier
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/secrets/database"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func setupAMTest(t *testing.T) *alertmanager {
dir := t.TempDir()
cfg := &setting.Cfg{
DataPath: dir,
AppURL: "http://localhost:9093",
}
l := log.New("alertmanager-test")
m := metrics.NewAlertmanagerMetrics(prometheus.NewRegistry(), l)
sqlStore := db.InitTestDB(t)
s := &store.DBstore{
Cfg: setting.UnifiedAlertingSettings{
BaseInterval: 10 * time.Second,
DefaultRuleEvaluationInterval: time.Minute,
},
SQLStore: sqlStore,
Logger: l,
DashboardService: dashboards.NewFakeDashboardService(t),
}
kvStore := fakes.NewFakeKVStore(t)
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
decryptFn := secretsService.GetDecryptedValue
orgID := 1
stateStore := NewFileStore(int64(orgID), kvStore)
crypto := NewCrypto(secretsService, s, l)
am, err := NewAlertmanager(context.Background(), 1, cfg, s, stateStore, &NilPeer{}, decryptFn, nil, m, featuremgmt.WithFeatures(), crypto, nil)
require.NoError(t, err)
return am
}
func TestIntegrationAlertmanager_newAlertmanager(t *testing.T) {
am := setupAMTest(t)
require.False(t, am.Ready())
}
func TestAlertmanager_SaveAndApplyConfig_WithExternalSecrets(t *testing.T) {
am := setupAMTest(t)
cfg := &definitions.PostableUserConfig{
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
Config: definitions.Config{
Route: &definitions.Route{
Receiver: "default-receiver",
},
},
Receivers: []*definitions.PostableApiReceiver{
{
Receiver: config.Receiver{Name: "default-receiver"},
},
},
},
ExtraConfigs: []definitions.ExtraConfiguration{
{
Identifier: "external-prometheus",
MergeMatchers: []*labels.Matcher{{Type: labels.MatchEqual, Name: "cluster", Value: "prod"}},
AlertmanagerConfig: `
route:
receiver: webhook-receiver
receivers:
- name: webhook-receiver
webhook_configs:
- url: 'https://webhook.example.com/alerts'
http_config:
basic_auth:
username: 'admin'
password: 'super-secret-password'
- url: 'https://slack.com/webhook/ABC123'
send_resolved: true
- name: email-receiver
email_configs:
- to: 'alerts@example.com'
from: 'grafana@example.com'
smarthost: 'smtp.gmail.com:587'
auth_username: 'grafana@example.com'
auth_password: 'another-secret-password'`,
},
},
}
err := am.SaveAndApplyConfig(context.Background(), cfg)
require.NoError(t, err)
savedConfig, err := am.Store.GetLatestAlertmanagerConfiguration(context.Background(), am.Base.TenantID())
require.NoError(t, err)
// Verify secrets are encrypted in stored config
var savedUserConfig definitions.PostableUserConfig
err = json.Unmarshal([]byte(savedConfig.AlertmanagerConfiguration), &savedUserConfig)
require.NoError(t, err)
require.Len(t, savedUserConfig.ExtraConfigs, 1)
extraConfig := savedUserConfig.ExtraConfigs[0]
require.Equal(t, "external-prometheus", extraConfig.Identifier)
require.NotContains(t, extraConfig.AlertmanagerConfig, "super-secret-password")
require.NotContains(t, extraConfig.AlertmanagerConfig, "another-secret-password")
require.NotContains(t, extraConfig.AlertmanagerConfig, "ABC123")
// Apply the saved configuration again and check that it is applied without errors
err = am.ApplyConfig(context.Background(), savedConfig)
require.NoError(t, err)
require.True(t, am.Ready())
}
func TestAlertmanager_ApplyConfig(t *testing.T) {
basicConfig := func() definitions.PostableApiAlertingConfig {
return definitions.PostableApiAlertingConfig{
Config: definitions.Config{
Route: &definitions.Route{
Receiver: "default-receiver",
ObjectMatchers: definitions.ObjectMatchers{
&labels.Matcher{
Type: labels.MatchEqual,
Name: "__grafana_autogenerated__",
Value: "true",
},
},
},
},
Receivers: []*definitions.PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "default-receiver",
},
},
},
}
}
testCases := []struct {
name string
config *definitions.PostableUserConfig
expectedError string
skipInvalid bool
}{
{
name: "basic config",
config: &definitions.PostableUserConfig{
AlertmanagerConfig: basicConfig(),
TemplateFiles: map[string]string{
"grafana-template": "{{ define \"grafana.title\" }}Alert{{ end }}",
},
},
skipInvalid: false,
},
{
name: "with mimir config",
config: &definitions.PostableUserConfig{
AlertmanagerConfig: basicConfig(),
TemplateFiles: map[string]string{
"grafana-template": "{{ define \"grafana.title\" }}Grafana Alert{{ end }}",
},
ExtraConfigs: []definitions.ExtraConfiguration{
{
Identifier: "mimir-prod",
MergeMatchers: config.Matchers{
{
Type: labels.MatchEqual,
Name: "__mimir__",
Value: "true",
},
},
TemplateFiles: map[string]string{
"mimir-template": "{{ define \"mimir.title\" }}Mimir Alert{{ end }}",
},
AlertmanagerConfig: `route:
receiver: mimir-webhook
group_by:
- alertname
- cluster
receivers:
- name: mimir-webhook
webhook_configs:
- url: https://webhook.example.com/alerts
send_resolved: true
http_config: {}`,
},
},
},
skipInvalid: false,
},
{
name: "invalid config fails",
config: &definitions.PostableUserConfig{
AlertmanagerConfig: basicConfig(),
ExtraConfigs: []definitions.ExtraConfiguration{
{
Identifier: "", // invalid: empty identifier
MergeMatchers: config.Matchers{},
AlertmanagerConfig: `route:
receiver: test-receiver
receivers:
- name: test-receiver`,
},
},
},
expectedError: "failed to get full alertmanager configuration",
skipInvalid: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
am := setupAMTest(t)
ctx := context.Background()
err := am.SaveAndApplyConfig(ctx, tc.config)
if tc.expectedError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tc.expectedError)
} else {
require.NoError(t, err)
templateDefs := tc.config.GetMergedTemplateDefinitions()
expectedTemplateCount := len(tc.config.TemplateFiles)
if len(tc.config.ExtraConfigs) > 0 {
expectedTemplateCount += len(tc.config.ExtraConfigs[0].TemplateFiles)
}
require.Len(t, templateDefs, expectedTemplateCount)
}
})
}
}