diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index 83569bee7ae..3a82b923b83 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -47,7 +47,8 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - [CHANGE] Prometheus Compatible API: Use float-like values for `api/prometheus/grafana/api/v1/alerts` and `api/prometheus/grafana/api/v1/rules` instead of the evaluation string #47216 - [CHANGE] Notification URL points to alert view page instead of alert edit page. #47752 -- [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857 +- [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857 +- [FEATURE] Indicate whether contact point is provisioned when GETting Alertmanager configuration #48323 - [BUGFIX] (Legacy) Templates: Parse notification templates using all the matches of the alert rule when going from `Alerting` to `OK` in legacy alerting #47355 - [BUGFIX] Scheduler: Fix state manager to support OK option of `AlertRule.ExecErrState` #47670 - [ENHANCEMENT] Templates: Enable the use of classic condition values in templates #46971 @@ -56,3 +57,4 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - `grafana_alerting_ticker_next_tick_timestamp_seconds` - `grafana_alerting_ticker_interval_seconds` - [ENHANCEMENT] Migration: Migrate each legacy notification channel to its own contact point, use nested routes to reproduce multi-channel alerts #47291 + diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index d1358d9e06d..51f36db2e40 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -223,6 +223,15 @@ func TestAlertmanagerConfig(t *testing.T) { body := asGettableUserConfig(t, response) require.Equal(t, ngmodels.ProvenanceNone, body.AlertmanagerConfig.Route.Provenance) }) + t.Run("contact point from GET config has no provenance", func(t *testing.T) { + sut := createSut(t, nil) + rc := createRequestCtxInOrg(1) + + response := sut.RouteGetAlertingConfig(rc) + + body := asGettableUserConfig(t, response) + require.Equal(t, ngmodels.ProvenanceNone, body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance) + }) }) t.Run("when objects are provisioned", func(t *testing.T) { @@ -236,6 +245,26 @@ func TestAlertmanagerConfig(t *testing.T) { body := asGettableUserConfig(t, response) require.Equal(t, ngmodels.ProvenanceAPI, body.AlertmanagerConfig.Route.Provenance) }) + t.Run("contact point from GET config has expected provenance", func(t *testing.T) { + sut := createSut(t, nil) + rc := createRequestCtxInOrg(1) + request := createAmConfigRequest(t) + + _ = sut.RoutePostAlertingConfig(rc, request) + + response := sut.RouteGetAlertingConfig(rc) + body := asGettableUserConfig(t, response) + + cpUID := body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].UID + require.NotEmpty(t, cpUID) + + setContactPointProvenance(t, 1, cpUID, sut.mam.ProvStore) + + response = sut.RouteGetAlertingConfig(rc) + body = asGettableUserConfig(t, response) + + require.Equal(t, ngmodels.ProvenanceAPI, body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance) + }) }) } @@ -563,9 +592,16 @@ func createRequestCtxInOrg(org int64) *models.ReqContext { } // setRouteProvenance marks an org's routing tree as provisioned. -func setRouteProvenance(t *testing.T, org int64, ps provisioning.ProvisioningStore) { +func setRouteProvenance(t *testing.T, orgID int64, ps provisioning.ProvisioningStore) { t.Helper() - err := ps.SetProvenance(context.Background(), &apimodels.Route{}, org, ngmodels.ProvenanceAPI) + err := ps.SetProvenance(context.Background(), &apimodels.Route{}, orgID, ngmodels.ProvenanceAPI) + require.NoError(t, err) +} + +// setContactPointProvenance marks a contact point as provisioned. +func setContactPointProvenance(t *testing.T, orgID int64, UID string, ps provisioning.ProvisioningStore) { + t.Helper() + err := ps.SetProvenance(context.Background(), &apimodels.EmbeddedContactPoint{UID: UID}, orgID, ngmodels.ProvenanceAPI) require.NoError(t, err) } diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index a2cd6802df3..8f7bc2b84ce 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -1000,12 +1000,13 @@ func AllReceivers(route *config.Route) (res []string) { } type GettableGrafanaReceiver struct { - UID string `json:"uid"` - Name string `json:"name"` - Type string `json:"type"` - DisableResolveMessage bool `json:"disableResolveMessage"` - Settings *simplejson.Json `json:"settings"` - SecureFields map[string]bool `json:"secureFields"` + UID string `json:"uid"` + Name string `json:"name"` + Type string `json:"type"` + DisableResolveMessage bool `json:"disableResolveMessage"` + Settings *simplejson.Json `json:"settings"` + SecureFields map[string]bool `json:"secureFields"` + Provenance models.Provenance `json:"provenance,omitempty"` } type PostableGrafanaReceiver struct { diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 7dd70647f2d..a39beea1f38 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -134,5 +134,17 @@ func (moa *MultiOrgAlertmanager) mergeProvenance(ctx context.Context, config def } config.Route.Provenance = provenance } + cp := definitions.EmbeddedContactPoint{} + cpProvs, err := moa.ProvStore.GetProvenances(ctx, org, cp.ResourceType()) + if err != nil { + return definitions.GettableApiAlertingConfig{}, err + } + for _, receiver := range config.Receivers { + for _, contactPoint := range receiver.GrafanaManagedReceivers { + if provenance, exists := cpProvs[contactPoint.UID]; exists { + contactPoint.Provenance = provenance + } + } + } return config, nil }