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
pull/49457/head
Alexander Weaver 3 years ago committed by GitHub
parent 2ba4f7ed7d
commit a92f85a87b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      pkg/services/ngalert/provisioning/config.go
  2. 59
      pkg/services/ngalert/provisioning/contactpoints.go
  3. 20
      pkg/services/ngalert/provisioning/mute_timings.go
  4. 2
      pkg/services/ngalert/provisioning/mute_timings_test.go
  5. 22
      pkg/services/ngalert/provisioning/notification_policies.go
  6. 20
      pkg/services/ngalert/provisioning/serialize.go
  7. 42
      pkg/services/ngalert/provisioning/templates.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
}

@ -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

@ -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

@ -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{

@ -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,
}

@ -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)
}

@ -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
}

Loading…
Cancel
Save