From a92f85a87bc0c78ae41a2b6138246c5d65f7eb5b Mon Sep 17 00:00:00 2001 From: Alexander Weaver Date: Mon, 23 May 2022 18:16:03 -0500 Subject: [PATCH] Alerting: Factor out shared code for working with configs in provisioning package (#49419) * Factor out shared code for working with configs * Refactor in notification policies and contact points * Better file ordering * Address feedback --- pkg/services/ngalert/provisioning/config.go | 53 +++++++++++++++++ .../ngalert/provisioning/contactpoints.go | 59 +++++++------------ .../ngalert/provisioning/mute_timings.go | 20 ++----- .../ngalert/provisioning/mute_timings_test.go | 2 +- .../provisioning/notification_policies.go | 22 ++----- .../ngalert/provisioning/serialize.go | 20 ------- .../ngalert/provisioning/templates.go | 42 ++----------- 7 files changed, 92 insertions(+), 126 deletions(-) create mode 100644 pkg/services/ngalert/provisioning/config.go delete mode 100644 pkg/services/ngalert/provisioning/serialize.go diff --git a/pkg/services/ngalert/provisioning/config.go b/pkg/services/ngalert/provisioning/config.go new file mode 100644 index 00000000000..15ebde4a392 --- /dev/null +++ b/pkg/services/ngalert/provisioning/config.go @@ -0,0 +1,53 @@ +package provisioning + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" +) + +func deserializeAlertmanagerConfig(config []byte) (*definitions.PostableUserConfig, error) { + result := definitions.PostableUserConfig{} + if err := json.Unmarshal(config, &result); err != nil { + return nil, fmt.Errorf("failed to deserialize alertmanager configuration: %w", err) + } + return &result, nil +} + +func serializeAlertmanagerConfig(config definitions.PostableUserConfig) ([]byte, error) { + return json.Marshal(config) +} + +type cfgRevision struct { + cfg *definitions.PostableUserConfig + concurrencyToken string + version string +} + +func getLastConfiguration(ctx context.Context, orgID int64, store AMConfigStore) (*cfgRevision, error) { + q := models.GetLatestAlertmanagerConfigurationQuery{ + OrgID: orgID, + } + if err := store.GetLatestAlertmanagerConfiguration(ctx, &q); err != nil { + return nil, err + } + + if q.Result == nil { + return nil, fmt.Errorf("no alertmanager configuration present in this org") + } + + concurrencyToken := q.Result.ConfigurationHash + cfg, err := deserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) + if err != nil { + return nil, err + } + + return &cfgRevision{ + cfg: cfg, + concurrencyToken: concurrencyToken, + version: q.Result.ConfigurationVersion, + }, nil +} diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 1bd94d74b78..a1f63f7fe47 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -36,7 +36,7 @@ func NewContactPointService(store store.AlertingStore, encryptionService secrets } func (ecp *ContactPointService) GetContactPoints(ctx context.Context, orgID int64) ([]apimodels.EmbeddedContactPoint, error) { - cfg, _, err := ecp.getCurrentConfig(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, ecp.amStore) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func (ecp *ContactPointService) GetContactPoints(ctx context.Context, orgID int6 return nil, err } contactPoints := []apimodels.EmbeddedContactPoint{} - for _, contactPoint := range cfg.GetGrafanaReceiverMap() { + for _, contactPoint := range revision.cfg.GetGrafanaReceiverMap() { embeddedContactPoint := apimodels.EmbeddedContactPoint{ UID: contactPoint.UID, Type: contactPoint.Type, @@ -77,11 +77,11 @@ func (ecp *ContactPointService) GetContactPoints(ctx context.Context, orgID int6 // internal only func (ecp *ContactPointService) getContactPointDecrypted(ctx context.Context, orgID int64, uid string) (apimodels.EmbeddedContactPoint, error) { - cfg, _, err := ecp.getCurrentConfig(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, ecp.amStore) if err != nil { return apimodels.EmbeddedContactPoint{}, err } - for _, receiver := range cfg.GetGrafanaReceiverMap() { + for _, receiver := range revision.cfg.GetGrafanaReceiverMap() { if receiver.UID != uid { continue } @@ -114,7 +114,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in return apimodels.EmbeddedContactPoint{}, fmt.Errorf("contact point is not valid: %w", err) } - cfg, fetchedHash, err := ecp.getCurrentConfig(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, ecp.amStore) if err != nil { return apimodels.EmbeddedContactPoint{}, err } @@ -143,7 +143,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in } receiverFound := false - for _, receiver := range cfg.AlertmanagerConfig.Receivers { + for _, receiver := range revision.cfg.AlertmanagerConfig.Receivers { if receiver.Name == contactPoint.Name { receiver.PostableGrafanaReceivers.GrafanaManagedReceivers = append(receiver.PostableGrafanaReceivers.GrafanaManagedReceivers, grafanaReceiver) receiverFound = true @@ -151,7 +151,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in } if !receiverFound { - cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers, &apimodels.PostableApiReceiver{ + revision.cfg.AlertmanagerConfig.Receivers = append(revision.cfg.AlertmanagerConfig.Receivers, &apimodels.PostableApiReceiver{ Receiver: config.Receiver{ Name: grafanaReceiver.Name, }, @@ -161,7 +161,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in }) } - data, err := json.Marshal(cfg) + data, err := json.Marshal(revision.cfg) if err != nil { return apimodels.EmbeddedContactPoint{}, err } @@ -169,8 +169,8 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in err = ecp.xact.InTransaction(ctx, func(ctx context.Context) error { err = ecp.amStore.UpdateAlertmanagerConfiguration(ctx, &models.SaveAlertmanagerConfigurationCmd{ AlertmanagerConfiguration: string(data), - FetchedConfigurationHash: fetchedHash, - ConfigurationVersion: "v1", + FetchedConfigurationHash: revision.concurrencyToken, + ConfigurationVersion: revision.version, Default: false, OrgID: orgID, }) @@ -242,11 +242,11 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in SecureSettings: extractedSecrets, } // save to store - cfg, fetchedHash, err := ecp.getCurrentConfig(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, ecp.amStore) if err != nil { return err } - for _, receiver := range cfg.AlertmanagerConfig.Receivers { + for _, receiver := range revision.cfg.AlertmanagerConfig.Receivers { if receiver.Name == contactPoint.Name { receiverNotFound := true for i, grafanaReceiver := range receiver.GrafanaManagedReceivers { @@ -261,15 +261,15 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in } } } - data, err := json.Marshal(cfg) + data, err := json.Marshal(revision.cfg) if err != nil { return err } return ecp.xact.InTransaction(ctx, func(ctx context.Context) error { err = ecp.amStore.UpdateAlertmanagerConfiguration(ctx, &models.SaveAlertmanagerConfigurationCmd{ AlertmanagerConfiguration: string(data), - FetchedConfigurationHash: fetchedHash, - ConfigurationVersion: "v1", + FetchedConfigurationHash: revision.concurrencyToken, + ConfigurationVersion: revision.version, Default: false, OrgID: orgID, }) @@ -286,7 +286,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in } func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID int64, uid string) error { - cfg, fetchedHash, err := ecp.getCurrentConfig(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, ecp.amStore) if err != nil { return err } @@ -297,7 +297,7 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in // Name of the contact point that will be removed, might be used if a // full removal is done to check if it's referenced in any route. name := "" - for i, receiver := range cfg.AlertmanagerConfig.Receivers { + for i, receiver := range revision.cfg.AlertmanagerConfig.Receivers { for j, grafanaReceiver := range receiver.GrafanaManagedReceivers { if grafanaReceiver.UID == uid { name = grafanaReceiver.Name @@ -305,16 +305,16 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in // if this was the last receiver we removed, we remove the whole receiver if len(receiver.GrafanaManagedReceivers) == 0 { fullRemoval = true - cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers[:i], cfg.AlertmanagerConfig.Receivers[i+1:]...) + revision.cfg.AlertmanagerConfig.Receivers = append(revision.cfg.AlertmanagerConfig.Receivers[:i], revision.cfg.AlertmanagerConfig.Receivers[i+1:]...) } break } } } - if fullRemoval && isContactPointInUse(name, []*apimodels.Route{cfg.AlertmanagerConfig.Route}) { + if fullRemoval && isContactPointInUse(name, []*apimodels.Route{revision.cfg.AlertmanagerConfig.Route}) { return fmt.Errorf("contact point '%s' is currently used by a notification policy", name) } - data, err := json.Marshal(cfg) + data, err := json.Marshal(revision.cfg) if err != nil { return err } @@ -328,29 +328,14 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in } return ecp.amStore.UpdateAlertmanagerConfiguration(ctx, &models.SaveAlertmanagerConfigurationCmd{ AlertmanagerConfiguration: string(data), - FetchedConfigurationHash: fetchedHash, - ConfigurationVersion: "v1", + FetchedConfigurationHash: revision.concurrencyToken, + ConfigurationVersion: revision.version, Default: false, OrgID: orgID, }) }) } -func (ecp *ContactPointService) getCurrentConfig(ctx context.Context, orgID int64) (*apimodels.PostableUserConfig, string, error) { - query := &models.GetLatestAlertmanagerConfigurationQuery{ - OrgID: orgID, - } - err := ecp.amStore.GetLatestAlertmanagerConfiguration(ctx, query) - if err != nil { - return nil, "", err - } - cfg, err := DeserializeAlertmanagerConfig([]byte(query.Result.AlertmanagerConfiguration)) - if err != nil { - return nil, "", err - } - return cfg, query.Result.ConfigurationHash, nil -} - func isContactPointInUse(name string, routes []*apimodels.Route) bool { if len(routes) == 0 { return false diff --git a/pkg/services/ngalert/provisioning/mute_timings.go b/pkg/services/ngalert/provisioning/mute_timings.go index 97fa455d6af..a2f08683d70 100644 --- a/pkg/services/ngalert/provisioning/mute_timings.go +++ b/pkg/services/ngalert/provisioning/mute_timings.go @@ -2,11 +2,9 @@ package provisioning import ( "context" - "fmt" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - "github.com/grafana/grafana/pkg/services/ngalert/models" ) type MuteTimingService struct { @@ -26,25 +24,17 @@ func NewMuteTimingService(config AMConfigStore, prov ProvisioningStore, xact Tra } func (m *MuteTimingService) GetMuteTimings(ctx context.Context, orgID int64) ([]definitions.MuteTiming, error) { - q := models.GetLatestAlertmanagerConfigurationQuery{ - OrgID: orgID, - } - err := m.config.GetLatestAlertmanagerConfiguration(ctx, &q) + rev, err := getLastConfiguration(ctx, orgID, m.config) if err != nil { return nil, err } - if q.Result == nil { - return nil, fmt.Errorf("no alertmanager configuration present in this org") - } - - cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) - if err != nil { - return nil, err + if rev.cfg.AlertmanagerConfig.MuteTimeIntervals == nil { + return []definitions.MuteTiming{}, nil } - result := make([]definitions.MuteTiming, 0, len(cfg.AlertmanagerConfig.MuteTimeIntervals)) - for _, interval := range cfg.AlertmanagerConfig.MuteTimeIntervals { + result := make([]definitions.MuteTiming, 0, len(rev.cfg.AlertmanagerConfig.MuteTimeIntervals)) + for _, interval := range rev.cfg.AlertmanagerConfig.MuteTimeIntervals { result = append(result, definitions.MuteTiming{MuteTimeInterval: interval}) } return result, nil diff --git a/pkg/services/ngalert/provisioning/mute_timings_test.go b/pkg/services/ngalert/provisioning/mute_timings_test.go index 266edc5dd53..6a1a9be329c 100644 --- a/pkg/services/ngalert/provisioning/mute_timings_test.go +++ b/pkg/services/ngalert/provisioning/mute_timings_test.go @@ -26,7 +26,7 @@ func TestMuteTimingService(t *testing.T) { require.Equal(t, "asdf", result[0].Name) }) - t.Run("service returns empty map when config file contains no templates", func(t *testing.T) { + t.Run("service returns empty list when config file contains no mute timings", func(t *testing.T) { sut := createMuteTimingSvcSut() sut.config.(*MockAMConfigStore).EXPECT(). getsConfig(models.AlertConfiguration{ diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index dc213934695..f03611fc86d 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -38,7 +38,7 @@ func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID i return definitions.Route{}, err } - cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) + cfg, err := deserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) if err != nil { return definitions.Route{}, err } @@ -63,31 +63,21 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI if err != nil { return fmt.Errorf("%w: %s", ErrValidation, err.Error()) } - - q := models.GetLatestAlertmanagerConfigurationQuery{ - OrgID: orgID, - } - err = nps.amStore.GetLatestAlertmanagerConfiguration(ctx, &q) - if err != nil { - return err - } - - concurrencyToken := q.Result.ConfigurationHash - cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) + revision, err := getLastConfiguration(ctx, orgID, nps.amStore) if err != nil { return err } - cfg.AlertmanagerConfig.Config.Route = &tree + revision.cfg.AlertmanagerConfig.Config.Route = &tree - serialized, err := SerializeAlertmanagerConfig(*cfg) + serialized, err := serializeAlertmanagerConfig(*revision.cfg) if err != nil { return err } cmd := models.SaveAlertmanagerConfigurationCmd{ AlertmanagerConfiguration: string(serialized), - ConfigurationVersion: q.Result.ConfigurationVersion, - FetchedConfigurationHash: concurrencyToken, + ConfigurationVersion: revision.version, + FetchedConfigurationHash: revision.concurrencyToken, Default: false, OrgID: orgID, } diff --git a/pkg/services/ngalert/provisioning/serialize.go b/pkg/services/ngalert/provisioning/serialize.go deleted file mode 100644 index db73440bdb8..00000000000 --- a/pkg/services/ngalert/provisioning/serialize.go +++ /dev/null @@ -1,20 +0,0 @@ -package provisioning - -import ( - "encoding/json" - "fmt" - - "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" -) - -func DeserializeAlertmanagerConfig(config []byte) (*definitions.PostableUserConfig, error) { - result := definitions.PostableUserConfig{} - if err := json.Unmarshal(config, &result); err != nil { - return nil, fmt.Errorf("failed to deserialize alertmanager configuration: %w", err) - } - return &result, nil -} - -func SerializeAlertmanagerConfig(config definitions.PostableUserConfig) ([]byte, error) { - return json.Marshal(config) -} diff --git a/pkg/services/ngalert/provisioning/templates.go b/pkg/services/ngalert/provisioning/templates.go index 93be3732a36..97903a567cb 100644 --- a/pkg/services/ngalert/provisioning/templates.go +++ b/pkg/services/ngalert/provisioning/templates.go @@ -26,7 +26,7 @@ func NewTemplateService(config AMConfigStore, prov ProvisioningStore, xact Trans } func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) (map[string]string, error) { - revision, err := t.getLastConfiguration(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, t.config) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl def return definitions.MessageTemplate{}, fmt.Errorf("%w: %s", ErrValidation, err.Error()) } - revision, err := t.getLastConfiguration(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, t.config) if err != nil { return definitions.MessageTemplate{}, err } @@ -54,7 +54,7 @@ func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl def } revision.cfg.TemplateFiles[tmpl.Name] = tmpl.Template - serialized, err := SerializeAlertmanagerConfig(*revision.cfg) + serialized, err := serializeAlertmanagerConfig(*revision.cfg) if err != nil { return definitions.MessageTemplate{}, err } @@ -84,14 +84,14 @@ func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl def } func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, name string) error { - revision, err := t.getLastConfiguration(ctx, orgID) + revision, err := getLastConfiguration(ctx, orgID, t.config) if err != nil { return err } delete(revision.cfg.TemplateFiles, name) - serialized, err := SerializeAlertmanagerConfig(*revision.cfg) + serialized, err := serializeAlertmanagerConfig(*revision.cfg) if err != nil { return err } @@ -123,35 +123,3 @@ func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, name return nil } - -func (t *TemplateService) getLastConfiguration(ctx context.Context, orgID int64) (*cfgRevision, error) { - q := models.GetLatestAlertmanagerConfigurationQuery{ - OrgID: orgID, - } - err := t.config.GetLatestAlertmanagerConfiguration(ctx, &q) - if err != nil { - return nil, err - } - - if q.Result == nil { - return nil, fmt.Errorf("no alertmanager configuration present in this org") - } - - concurrencyToken := q.Result.ConfigurationHash - cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) - if err != nil { - return nil, err - } - - return &cfgRevision{ - cfg: cfg, - concurrencyToken: concurrencyToken, - version: q.Result.ConfigurationVersion, - }, nil -} - -type cfgRevision struct { - cfg *definitions.PostableUserConfig - concurrencyToken string - version string -}