Alerting: Separate configuration model for remote Alertmanager Mimir client (#107741)
* replace PostableUserConfig with GrafanaAlertmanagerConfig to decouple from internal Grafana models * update alertmanager + tests * calculate hash of the GrafanaAlertmanagerConfig
This commit is contained in:
@@ -293,18 +293,20 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config
|
||||
if err := am.mergeExtraConfigs(ctx, decryptedCfg); err != nil {
|
||||
return fmt.Errorf("unable to merge extra configurations: %w", err)
|
||||
}
|
||||
rawDecrypted, err := json.Marshal(decryptedCfg)
|
||||
payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg)
|
||||
rawPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal decrypted configuration: %w", err)
|
||||
}
|
||||
configHash := md5.Sum(rawDecrypted)
|
||||
|
||||
configHash := md5.Sum(rawPayload)
|
||||
|
||||
// Send the configuration only if we need to.
|
||||
if !am.shouldSendConfig(ctx, configHash) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return am.sendConfiguration(ctx, decryptedCfg, fmt.Sprintf("%x", configHash), config.CreatedAt, am.isDefaultConfiguration(configHash))
|
||||
return am.sendConfiguration(ctx, payload, fmt.Sprintf("%x", configHash), config.CreatedAt, am.isDefaultConfiguration(configHash))
|
||||
}
|
||||
|
||||
func (am *Alertmanager) isDefaultConfiguration(configHash [16]byte) bool {
|
||||
@@ -370,11 +372,11 @@ func (am *Alertmanager) mergeExtraConfigs(ctx context.Context, config *apimodels
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *Alertmanager) sendConfiguration(ctx context.Context, decrypted *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error {
|
||||
func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg *remoteClient.GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error {
|
||||
am.metrics.ConfigSyncsTotal.Inc()
|
||||
if err := am.mimirClient.CreateGrafanaAlertmanagerConfig(
|
||||
ctx,
|
||||
decrypted,
|
||||
cfg,
|
||||
hash,
|
||||
createdAt,
|
||||
isDefault,
|
||||
@@ -420,14 +422,14 @@ func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.P
|
||||
if err := am.mergeExtraConfigs(ctx, decryptedCfg); err != nil {
|
||||
return fmt.Errorf("unable to merge extra configurations: %w", err)
|
||||
}
|
||||
|
||||
rawCfg, err := json.Marshal(decryptedCfg)
|
||||
payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg)
|
||||
rawCfg, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := fmt.Sprintf("%x", md5.Sum(rawCfg))
|
||||
|
||||
return am.sendConfiguration(ctx, decryptedCfg, hash, time.Now().Unix(), false)
|
||||
return am.sendConfiguration(ctx, payload, hash, time.Now().Unix(), false)
|
||||
}
|
||||
|
||||
// SaveAndApplyDefaultConfig sends the default Grafana Alertmanager configuration to the remote Alertmanager.
|
||||
@@ -446,10 +448,17 @@ func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg)
|
||||
rawCfg, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := fmt.Sprintf("%x", md5.Sum(rawCfg))
|
||||
|
||||
return am.sendConfiguration(
|
||||
ctx,
|
||||
decryptedCfg,
|
||||
am.defaultConfigHash,
|
||||
payload,
|
||||
hash,
|
||||
time.Now().Unix(),
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package remote
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -47,6 +49,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
//go:embed test-data/*.*
|
||||
var testData embed.FS
|
||||
|
||||
var (
|
||||
defaultGrafanaConfig = setting.GetAlertmanagerDefaultConfiguration()
|
||||
errTest = errors.New("test")
|
||||
@@ -357,12 +362,14 @@ func TestCompareAndSendConfiguration(t *testing.T) {
|
||||
testGrafanaConfigWithBadEncryption, err := json.Marshal(inputCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgWithDecryptedSecret, err := notifier.Load([]byte(testGrafanaConfigWithSecret))
|
||||
test, err := notifier.Load([]byte(testGrafanaConfigWithSecret))
|
||||
require.NoError(t, err)
|
||||
cfgWithDecryptedSecret := PostableUserConfigToGrafanaAlertmanagerConfig(test)
|
||||
|
||||
cfgWithAutogenRoutes, err := notifier.Load([]byte(testGrafanaConfigWithSecret))
|
||||
testAutogenRoutes, err := notifier.Load([]byte(testGrafanaConfigWithSecret))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testAutogenFn(nil, nil, 0, &cfgWithAutogenRoutes.AlertmanagerConfig, false))
|
||||
require.NoError(t, testAutogenFn(nil, nil, 0, &testAutogenRoutes.AlertmanagerConfig, false))
|
||||
cfgWithAutogenRoutes := PostableUserConfigToGrafanaAlertmanagerConfig(testAutogenRoutes)
|
||||
|
||||
// Calculate hashes for expected configurations
|
||||
cfgWithDecryptedSecretBytes, err := json.Marshal(cfgWithDecryptedSecret)
|
||||
@@ -373,6 +380,20 @@ func TestCompareAndSendConfiguration(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
cfgWithAutogenRoutesHash := fmt.Sprintf("%x", md5.Sum(cfgWithAutogenRoutesBytes))
|
||||
|
||||
cfgWithExtraUnmergedBytes, err := testData.ReadFile(path.Join("test-data", "config-with-extra.json"))
|
||||
require.NoError(t, err)
|
||||
cfgWithExtraUnmerged, err := notifier.Load(cfgWithExtraUnmergedBytes)
|
||||
require.NoError(t, err)
|
||||
r, err := cfgWithExtraUnmerged.GetMergedAlertmanagerConfig()
|
||||
require.NoError(t, err)
|
||||
cfgWithExtraMerged := &client.GrafanaAlertmanagerConfig{
|
||||
TemplateFiles: cfgWithExtraUnmerged.TemplateFiles,
|
||||
AlertmanagerConfig: r.Config,
|
||||
}
|
||||
cfgWithExtraMergedBytes, err := json.Marshal(cfgWithExtraMerged)
|
||||
require.NoError(t, err)
|
||||
cfgWithExtraMergedHash := fmt.Sprintf("%x", md5.Sum(cfgWithExtraMergedBytes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
@@ -428,6 +449,15 @@ func TestCompareAndSendConfiguration(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
name: "no error, with extra configurations",
|
||||
config: string(cfgWithExtraUnmergedBytes),
|
||||
autogenFn: NoopAutogenFn,
|
||||
expCfg: &client.UserGrafanaConfig{
|
||||
GrafanaAlertmanagerConfig: cfgWithExtraMerged,
|
||||
Hash: cfgWithExtraMergedHash,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -677,7 +707,7 @@ func TestCompareAndSendConfigurationWithExtraConfigs(t *testing.T) {
|
||||
// Return an empty config to ensure it gets replaced
|
||||
w.Header().Add("content-type", "application/json")
|
||||
require.NoError(t, json.NewEncoder(w).Encode(client.UserGrafanaConfig{
|
||||
GrafanaAlertmanagerConfig: &apimodels.PostableUserConfig{},
|
||||
GrafanaAlertmanagerConfig: &client.GrafanaAlertmanagerConfig{},
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
@@ -15,14 +16,25 @@ const (
|
||||
grafanaAlertmanagerReceiversPath = "/api/v1/grafana/receivers"
|
||||
)
|
||||
|
||||
type GrafanaAlertmanagerConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig definition.PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
func (u *GrafanaAlertmanagerConfig) MarshalJSON() ([]byte, error) {
|
||||
// this is special marshaling that makes sure that secrets are not masked
|
||||
type cfg GrafanaAlertmanagerConfig
|
||||
return definition.MarshalJSONWithSecrets((*cfg)(u))
|
||||
}
|
||||
|
||||
type UserGrafanaConfig struct {
|
||||
GrafanaAlertmanagerConfig *apimodels.PostableUserConfig `json:"configuration"`
|
||||
Hash string `json:"configuration_hash"`
|
||||
CreatedAt int64 `json:"created"`
|
||||
Default bool `json:"default"`
|
||||
Promoted bool `json:"promoted"`
|
||||
ExternalURL string `json:"external_url"`
|
||||
SmtpConfig SmtpConfig `json:"smtp_config"`
|
||||
GrafanaAlertmanagerConfig *GrafanaAlertmanagerConfig `json:"configuration"`
|
||||
Hash string `json:"configuration_hash"`
|
||||
CreatedAt int64 `json:"created"`
|
||||
Default bool `json:"default"`
|
||||
Promoted bool `json:"promoted"`
|
||||
ExternalURL string `json:"external_url"`
|
||||
SmtpConfig SmtpConfig `json:"smtp_config"`
|
||||
|
||||
// TODO: Remove once everything can be sent in the 'SmtpConfig' field.
|
||||
SmtpFrom string `json:"smtp_from"`
|
||||
@@ -52,7 +64,7 @@ func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafana
|
||||
return gc, nil
|
||||
}
|
||||
|
||||
func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error {
|
||||
func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg *GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error {
|
||||
payload, err := definition.MarshalJSONWithSecrets(&UserGrafanaConfig{
|
||||
GrafanaAlertmanagerConfig: cfg,
|
||||
Hash: hash,
|
||||
|
||||
@@ -29,7 +29,7 @@ type MimirClient interface {
|
||||
DeleteGrafanaAlertmanagerState(ctx context.Context) error
|
||||
|
||||
GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error)
|
||||
CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error
|
||||
CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration *GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error
|
||||
DeleteGrafanaAlertmanagerConfig(ctx context.Context) error
|
||||
|
||||
TestTemplate(ctx context.Context, c alertingNotify.TestTemplatesConfigBodyParams) (*alertingNotify.TestTemplatesResults, error)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package remote
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/remote/client"
|
||||
)
|
||||
|
||||
func PostableUserConfigToGrafanaAlertmanagerConfig(config *definitions.PostableUserConfig) *client.GrafanaAlertmanagerConfig {
|
||||
return &client.GrafanaAlertmanagerConfig{
|
||||
TemplateFiles: config.TemplateFiles,
|
||||
AlertmanagerConfig: config.AlertmanagerConfig,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"template_files": {
|
||||
"test": "{{ define \"my_templ\" }}TEST{{ end }}"
|
||||
},
|
||||
"alertmanager_config": {
|
||||
"route": {
|
||||
"receiver": "grafana-default-email",
|
||||
"group_by": [
|
||||
"grafana_folder",
|
||||
"alertname"
|
||||
]
|
||||
},
|
||||
"receivers": [
|
||||
{
|
||||
"name": "grafana-default-email",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid": "",
|
||||
"name": "some other name",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {
|
||||
"addresses": "<example@email.com>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"extra_config": [
|
||||
{
|
||||
"identifier": "imported",
|
||||
"merge_matchers": ["imported=\"true\""],
|
||||
"template_files":
|
||||
{
|
||||
"extra_template": "{{ define \"my_message\" }}TEST{{ end }}"
|
||||
},
|
||||
"alertmanager_config": "{\"receivers\":[{\"webhook_configs\":[{\"url\":\"http://localhost\"}],\"name\":\"webhook\"}],\"route\":{\"receiver\":\"webhook\"}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user