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) { if testing.Short() { t.Skip("skipping integration test in short mode") } 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) } }) } }