mirror of
https://github.com/grafana/grafana.git
synced 2025-09-27 01:53:57 +08:00
Alerting: Support for Mimir configuration in Grafana Alertmanager (#106402)
This commit is contained in:
@ -3,13 +3,17 @@ package definitions
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
alertingTemplates "github.com/grafana/alerting/templates"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
@ -263,6 +267,7 @@ type (
|
||||
PostableApiReceiver = definition.PostableApiReceiver
|
||||
PostableGrafanaReceivers = definition.PostableGrafanaReceivers
|
||||
ReceiverType = definition.ReceiverType
|
||||
MergeResult = definition.MergeResult
|
||||
)
|
||||
|
||||
const (
|
||||
@ -643,13 +648,82 @@ type DatasourceUIDReference struct {
|
||||
DatasourceUID string
|
||||
}
|
||||
|
||||
type ExtraConfiguration struct {
|
||||
Identifier string `yaml:"identifier" json:"identifier"`
|
||||
MergeMatchers config.Matchers `yaml:"merge_matchers" json:"merge_matchers"`
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
func (c ExtraConfiguration) Validate() error {
|
||||
if c.Identifier == "" {
|
||||
return errors.New("identifier is required")
|
||||
}
|
||||
if len(c.MergeMatchers) == 0 {
|
||||
return errors.New("at least one matcher is required")
|
||||
}
|
||||
for _, m := range c.MergeMatchers {
|
||||
if m.Type != labels.MatchEqual {
|
||||
return errors.New("only matchers with type equal are supported")
|
||||
}
|
||||
}
|
||||
err := c.AlertmanagerConfig.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid alertmanager configuration: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type PostableUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
ExtraConfigs []ExtraConfiguration `yaml:"extra_config,omitempty" json:"extra_config,omitempty"`
|
||||
amSimple map[string]interface{} `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
func (c *PostableUserConfig) GetMergedAlertmanagerConfig() (MergeResult, error) {
|
||||
if len(c.ExtraConfigs) == 0 {
|
||||
return MergeResult{
|
||||
Config: c.AlertmanagerConfig,
|
||||
}, nil
|
||||
}
|
||||
// support only one config for now
|
||||
mimirCfg := c.ExtraConfigs[0]
|
||||
opts := definition.MergeOpts{
|
||||
DedupSuffix: mimirCfg.Identifier,
|
||||
SubtreeMatchers: mimirCfg.MergeMatchers,
|
||||
}
|
||||
if err := opts.Validate(); err != nil {
|
||||
return MergeResult{}, fmt.Errorf("invalid merge options: %w", err)
|
||||
}
|
||||
return definition.Merge(c.AlertmanagerConfig, mimirCfg.AlertmanagerConfig, opts) // for now support only the first extra config
|
||||
}
|
||||
|
||||
// GetMergedTemplateDefinitions converts the given PostableUserConfig's TemplateFiles to a slice of TemplateDefinitions.
|
||||
func (c *PostableUserConfig) GetMergedTemplateDefinitions() []alertingTemplates.TemplateDefinition {
|
||||
out := make([]alertingTemplates.TemplateDefinition, 0, len(c.TemplateFiles))
|
||||
for name, tmpl := range c.TemplateFiles {
|
||||
out = append(out, alertingTemplates.TemplateDefinition{
|
||||
Name: name,
|
||||
Template: tmpl,
|
||||
Kind: alertingTemplates.GrafanaKind,
|
||||
})
|
||||
}
|
||||
if len(c.ExtraConfigs) == 0 {
|
||||
return out
|
||||
}
|
||||
// support only one config for now
|
||||
for name, tmpl := range c.ExtraConfigs[0].TemplateFiles {
|
||||
out = append(out, alertingTemplates.TemplateDefinition{
|
||||
Name: name,
|
||||
Template: tmpl,
|
||||
Kind: alertingTemplates.MimirKind,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *PostableUserConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableUserConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
@ -661,6 +735,15 @@ func (c *PostableUserConfig) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(c.ExtraConfigs) > 1 {
|
||||
return errors.New("only one extra config is supported")
|
||||
}
|
||||
for _, extraConfig := range c.ExtraConfigs {
|
||||
if err := extraConfig.Validate(); err != nil {
|
||||
return fmt.Errorf("extra configuration is invalid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
type intermediate struct {
|
||||
AlertmanagerConfig map[string]interface{} `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
alertingTemplates "github.com/grafana/alerting/templates"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -215,3 +217,201 @@ func Test_RawMessageMarshaling(t *testing.T) {
|
||||
assert.Equal(t, RawMessage(`{"data":"test"}`), n.Field)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostableUserConfig_GetMergedAlertmanagerConfig(t *testing.T) {
|
||||
alertmanagerCfg := PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &Route{
|
||||
Receiver: "default",
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config PostableUserConfig
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "no extra configs",
|
||||
config: PostableUserConfig{
|
||||
AlertmanagerConfig: alertmanagerCfg,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid mimir config",
|
||||
config: PostableUserConfig{
|
||||
AlertmanagerConfig: alertmanagerCfg,
|
||||
ExtraConfigs: []ExtraConfiguration{
|
||||
{
|
||||
Identifier: "mimir-1",
|
||||
MergeMatchers: config.Matchers{
|
||||
{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "cluster",
|
||||
Value: "prod",
|
||||
},
|
||||
},
|
||||
AlertmanagerConfig: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &Route{
|
||||
Receiver: "mimir-receiver",
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "mimir-receiver",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty identifier",
|
||||
config: PostableUserConfig{
|
||||
AlertmanagerConfig: alertmanagerCfg,
|
||||
ExtraConfigs: []ExtraConfiguration{
|
||||
{
|
||||
Identifier: "",
|
||||
MergeMatchers: config.Matchers{},
|
||||
AlertmanagerConfig: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &Route{
|
||||
Receiver: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "invalid merge options",
|
||||
},
|
||||
{
|
||||
name: "bad matcher type",
|
||||
config: PostableUserConfig{
|
||||
AlertmanagerConfig: alertmanagerCfg,
|
||||
ExtraConfigs: []ExtraConfiguration{
|
||||
{
|
||||
Identifier: "test",
|
||||
MergeMatchers: config.Matchers{
|
||||
{
|
||||
Type: labels.MatchNotEqual,
|
||||
Name: "cluster",
|
||||
Value: "prod",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "only equality matchers are allowed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := tc.config.GetMergedAlertmanagerConfig()
|
||||
if tc.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result.Config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostableUserConfig_GetMergedTemplateDefinitions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config PostableUserConfig
|
||||
expectedTemplates int
|
||||
}{
|
||||
{
|
||||
name: "no templates",
|
||||
config: PostableUserConfig{
|
||||
TemplateFiles: map[string]string{},
|
||||
ExtraConfigs: []ExtraConfiguration{},
|
||||
},
|
||||
expectedTemplates: 0,
|
||||
},
|
||||
{
|
||||
name: "grafana templates only",
|
||||
config: PostableUserConfig{
|
||||
TemplateFiles: map[string]string{
|
||||
"grafana-template1": "{{ define \"test\" }}Hello{{ end }}",
|
||||
"grafana-template2": "{{ define \"test2\" }}World{{ end }}",
|
||||
},
|
||||
ExtraConfigs: []ExtraConfiguration{},
|
||||
},
|
||||
expectedTemplates: 2,
|
||||
},
|
||||
{
|
||||
name: "mimir templates only",
|
||||
config: PostableUserConfig{
|
||||
TemplateFiles: map[string]string{},
|
||||
ExtraConfigs: []ExtraConfiguration{
|
||||
{
|
||||
TemplateFiles: map[string]string{
|
||||
"mimir-template": "{{ define \"mimir\" }}Mimir{{ end }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedTemplates: 1,
|
||||
},
|
||||
{
|
||||
name: "mixed templates",
|
||||
config: PostableUserConfig{
|
||||
TemplateFiles: map[string]string{
|
||||
"grafana-template": "{{ define \"grafana\" }}Grafana{{ end }}",
|
||||
},
|
||||
ExtraConfigs: []ExtraConfiguration{
|
||||
{
|
||||
TemplateFiles: map[string]string{
|
||||
"mimir-template": "{{ define \"mimir\" }}Mimir{{ end }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedTemplates: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := tc.config.GetMergedTemplateDefinitions()
|
||||
require.Len(t, result, tc.expectedTemplates)
|
||||
|
||||
templateMap := make(map[string]string)
|
||||
kindMap := make(map[string]alertingTemplates.Kind)
|
||||
for _, tmpl := range result {
|
||||
templateMap[tmpl.Name] = tmpl.Template
|
||||
kindMap[tmpl.Name] = tmpl.Kind
|
||||
}
|
||||
|
||||
for name, content := range tc.config.TemplateFiles {
|
||||
require.Equal(t, content, templateMap[name])
|
||||
require.Equal(t, alertingTemplates.GrafanaKind, kindMap[name])
|
||||
}
|
||||
|
||||
if len(tc.config.ExtraConfigs) > 0 {
|
||||
for name, content := range tc.config.ExtraConfigs[0].TemplateFiles {
|
||||
require.Equal(t, content, templateMap[name])
|
||||
require.Equal(t, alertingTemplates.MimirKind, kindMap[name])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -111,6 +111,11 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
||||
|
||||
revision.Config.AlertmanagerConfig.Route = &tree
|
||||
|
||||
_, err = revision.Config.GetMergedAlertmanagerConfig()
|
||||
if err != nil {
|
||||
return definitions.Route{}, "", fmt.Errorf("new routing tree is not compatible with extra configuration: %w", err)
|
||||
}
|
||||
|
||||
err = nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
if err := nps.configStore.Save(ctx, revision, orgID); err != nil {
|
||||
return err
|
||||
|
Reference in New Issue
Block a user