Alerting: Support simplified routing receivers in Prometheus conversion API (#105135)

Adds ability to set notifications settings using the Prometheus conversion API.

The API now supports a new optional header: X-Grafana-Alerting-Notification-Settings which can be used to specify notification settings.

The value of the header is the AlertRuleNotificationSettings structure in JSON:
mimirtool rules load alerts.yaml --extra-headers 'X-Grafana-Alerting-Notification-Settings: {"receiver": "my-webhook", "group_by": ["cluster", "pod"]}'
This commit is contained in:
Alexander Akhmetov
2025-05-12 22:07:02 +02:00
committed by GitHub
parent e965b85e19
commit c17b019ab1
12 changed files with 559 additions and 25 deletions
@@ -0,0 +1,134 @@
package alerting
import (
"encoding/json"
"net/http"
"testing"
"time"
prommodel "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util"
)
const (
notificationSettingsHeader = "X-Grafana-Alerting-Notification-Settings"
)
func TestIntegrationConvertPrometheusNotificationSettings(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
EnableRecordingRules: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "admin",
Login: "admin",
})
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
ds := adminClient.CreateDatasource(t, "prometheus")
namespace := "test-notification-settings"
namespaceUID := util.GenerateShortUID()
adminClient.CreateFolder(t, namespaceUID, namespace)
alertRuleGroup := apimodels.PrometheusRuleGroup{
Name: "test-group-notification-settings",
Interval: prommodel.Duration(60 * time.Second),
Rules: []apimodels.PrometheusRule{
{
Alert: "TestAlert",
Expr: "vector(1) > 0",
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
Labels: map[string]string{
"severity": "critical",
},
Annotations: map[string]string{
"summary": "Test alert with notification settings",
},
},
},
}
t.Run("rules should use notification settings from header", func(t *testing.T) {
receiver := "test-receiver"
receiverSettings, err := simplejson.NewJson([]byte(`{
"url":"https://localhost/webhook"
}`))
require.NoError(t, err)
adminClient.EnsureReceiver(t,
apimodels.EmbeddedContactPoint{
Name: receiver,
Type: "webhook",
Settings: receiverSettings,
},
)
groupBy := []string{"alertname", "instance", "job"}
settings := apimodels.AlertRuleNotificationSettings{
Receiver: receiver,
GroupBy: groupBy,
}
settingsJSON, err := json.Marshal(settings)
require.NoError(t, err)
headers := map[string]string{
notificationSettingsHeader: string(settingsJSON),
}
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, alertRuleGroup, headers)
group, _, _ := adminClient.GetRulesGroupWithStatus(t, namespaceUID, alertRuleGroup.Name)
require.Len(t, group.Rules, 1)
rule := group.Rules[0]
require.NotNil(t, rule.GrafanaManagedAlert.NotificationSettings)
require.Equal(t, receiver, rule.GrafanaManagedAlert.NotificationSettings.Receiver)
require.Equal(t, groupBy, rule.GrafanaManagedAlert.NotificationSettings.GroupBy)
})
t.Run("invalid JSON in notification settings header should return error", func(t *testing.T) {
headers := map[string]string{
notificationSettingsHeader: "{invalid json",
}
_, status, body := adminClient.RawConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, alertRuleGroup, headers)
requireStatusCode(t, http.StatusBadRequest, status, body)
require.Contains(t, body, "Invalid value for header X-Grafana-Alerting-Notification-Settings")
})
t.Run("empty receiver in notification settings should return error", func(t *testing.T) {
settings := apimodels.AlertRuleNotificationSettings{
Receiver: "",
GroupBy: []string{"alertname"},
}
settingsJSON, err := json.Marshal(settings)
require.NoError(t, err)
headers := map[string]string{
notificationSettingsHeader: string(settingsJSON),
}
_, status, body := adminClient.RawConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, alertRuleGroup, headers)
requireStatusCode(t, http.StatusBadRequest, status, body)
require.Contains(t, body, "Invalid value for header X-Grafana-Alerting-Notification-Settings")
})
}