Alerting: Support for Mimir configuration in Grafana Alertmanager (#106402)
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
@@ -314,11 +315,43 @@ func (am *alertmanager) aggregateInhibitMatchers(rules []config.InhibitRule, amu
|
||||
}
|
||||
}
|
||||
|
||||
func logMergeResult(l log.Logger, m apimodels.MergeResult) {
|
||||
if len(m.RenamedReceivers) == 0 && len(m.RenamedTimeIntervals) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
logCtx := make([]any, 0, 4)
|
||||
if len(m.RenamedTimeIntervals) > 0 {
|
||||
rcvBuilder := strings.Builder{}
|
||||
for from, to := range m.RenamedReceivers {
|
||||
rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to))
|
||||
}
|
||||
logCtx = append(logCtx, "renamedReceivers", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1]))
|
||||
}
|
||||
if len(m.RenamedTimeIntervals) > 0 {
|
||||
rcvBuilder := strings.Builder{}
|
||||
for from, to := range m.RenamedTimeIntervals {
|
||||
rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to))
|
||||
}
|
||||
logCtx = append(logCtx, "renamedTimeIntervals", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1]))
|
||||
}
|
||||
l.Info("Configurations merged successfully but some resources were renamed", logCtx...)
|
||||
}
|
||||
|
||||
// applyConfig applies a new configuration by re-initializing all components using the configuration provided.
|
||||
// It returns a boolean indicating whether the user config was changed and an error.
|
||||
// It is not safe to call concurrently.
|
||||
func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.PostableUserConfig, skipInvalid bool) (bool, error) {
|
||||
err := AddAutogenConfig(ctx, am.logger, am.Store, am.Base.TenantID(), &cfg.AlertmanagerConfig, skipInvalid)
|
||||
mergeResult, err := cfg.GetMergedAlertmanagerConfig()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get full alertmanager configuration: %w", err)
|
||||
}
|
||||
logMergeResult(am.logger, mergeResult)
|
||||
amConfig := mergeResult.Config
|
||||
templates := cfg.GetMergedTemplateDefinitions()
|
||||
|
||||
// Now add autogenerated config to the route.
|
||||
err = AddAutogenConfig(ctx, am.logger, am.Store, am.Base.TenantID(), &amConfig, skipInvalid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -337,7 +370,7 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable
|
||||
return false, nil
|
||||
}
|
||||
|
||||
receivers := PostableApiAlertingConfigToApiReceivers(cfg.AlertmanagerConfig)
|
||||
receivers := PostableApiAlertingConfigToApiReceivers(amConfig)
|
||||
for _, recv := range receivers {
|
||||
err = patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn)
|
||||
if err != nil {
|
||||
@@ -347,11 +380,11 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable
|
||||
|
||||
am.logger.Info("Applying new configuration to Alertmanager", "configHash", fmt.Sprintf("%x", configHash))
|
||||
err = am.Base.ApplyConfig(alertingNotify.NotificationsConfiguration{
|
||||
RoutingTree: cfg.AlertmanagerConfig.Route.AsAMRoute(),
|
||||
InhibitRules: cfg.AlertmanagerConfig.InhibitRules,
|
||||
MuteTimeIntervals: cfg.AlertmanagerConfig.MuteTimeIntervals,
|
||||
TimeIntervals: cfg.AlertmanagerConfig.TimeIntervals,
|
||||
Templates: ToTemplateDefinitions(cfg),
|
||||
RoutingTree: amConfig.Route.AsAMRoute(),
|
||||
InhibitRules: amConfig.InhibitRules,
|
||||
MuteTimeIntervals: amConfig.MuteTimeIntervals,
|
||||
TimeIntervals: amConfig.TimeIntervals,
|
||||
Templates: templates,
|
||||
Receivers: receivers,
|
||||
DispatcherLimits: &nilLimits{},
|
||||
Raw: rawConfig,
|
||||
|
||||
@@ -2,16 +2,22 @@ package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
promcfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"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"
|
||||
@@ -62,3 +68,145 @@ func TestAlertmanager_newAlertmanager(t *testing.T) {
|
||||
am := setupAMTest(t)
|
||||
require.False(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: definitions.PostableApiAlertingConfig{
|
||||
Config: definitions.Config{
|
||||
Route: &definitions.Route{
|
||||
Receiver: "mimir-webhook",
|
||||
GroupBy: []model.LabelName{"alertname", "cluster"},
|
||||
},
|
||||
},
|
||||
Receivers: []*definitions.PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "mimir-webhook",
|
||||
WebhookConfigs: []*config.WebhookConfig{
|
||||
{
|
||||
URL: &config.SecretURL{
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "webhook.example.com",
|
||||
Path: "/alerts",
|
||||
},
|
||||
},
|
||||
HTTPConfig: &promcfg.DefaultHTTPClientConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skipInvalid: false,
|
||||
},
|
||||
{
|
||||
name: "invalid config fails",
|
||||
config: &definitions.PostableUserConfig{
|
||||
AlertmanagerConfig: basicConfig(),
|
||||
ExtraConfigs: []definitions.ExtraConfiguration{
|
||||
{
|
||||
Identifier: "", // invalid: empty identifier
|
||||
MergeMatchers: config.Matchers{},
|
||||
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
||||
Config: definitions.Config{
|
||||
Route: &definitions.Route{
|
||||
Receiver: "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()
|
||||
|
||||
changed, err := am.applyConfig(ctx, tc.config, false)
|
||||
|
||||
if tc.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.expectedError)
|
||||
require.False(t, changed)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
alertingTemplates "github.com/grafana/alerting/templates"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@@ -132,19 +131,6 @@ func PostableApiAlertingConfigToApiReceivers(c apimodels.PostableApiAlertingConf
|
||||
return apiReceivers
|
||||
}
|
||||
|
||||
// ToTemplateDefinitions converts the given PostableUserConfig's TemplateFiles to a slice of TemplateDefinitions.
|
||||
func ToTemplateDefinitions(cfg *apimodels.PostableUserConfig) []alertingTemplates.TemplateDefinition {
|
||||
out := make([]alertingTemplates.TemplateDefinition, 0, len(cfg.TemplateFiles))
|
||||
for name, tmpl := range cfg.TemplateFiles {
|
||||
out = append(out, alertingTemplates.TemplateDefinition{
|
||||
Name: name,
|
||||
Template: tmpl,
|
||||
Kind: alertingTemplates.GrafanaKind,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Silence-specific compat functions to convert between grafana/alerting and model types.
|
||||
|
||||
func GettableSilenceToSilence(s alertingNotify.GettableSilence) *models.Silence {
|
||||
|
||||
Reference in New Issue
Block a user