Alerting: Config option to set default datasource in Prometheus rule import (#115665)

What is this feature?

Add a config option to set data source to imported rules when X-Grafana-Alerting-Datasource-UID is not present.

Why do we need this feature?

Currently mimirtool requires passing --extra-headers 'X-Grafana-Alerting-Datasource-UID: {uid}' when used with Grafana. This config option allows to specify a default, which is used when the header is missing, making it easier to use and more similar to the case when it's used with Mimir.
This commit is contained in:
Alexander Akhmetov
2025-12-23 14:24:53 +00:00
committed by GitHub
parent 47436a3eeb
commit 45f665d203
7 changed files with 83 additions and 1 deletions
+5
View File
@@ -1653,6 +1653,11 @@ loki_basic_auth_password =
# Accepts duration formats like: 30s, 1m, 1h.
rule_query_offset = 1m
# Default data source UID to use for query execution when importing Prometheus rules.
# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided.
# If not set, the header becomes required.
default_datasource_uid =
[recording_rules]
# Enable recording rules.
enabled = true
+5
View File
@@ -1615,6 +1615,11 @@ max_annotations_to_keep =
# Accepts duration formats like: 30s, 1m, 1h.
rule_query_offset = 1m
# Default data source UID to use for query execution when importing Prometheus rules.
# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided.
# If not set, the header becomes required.
default_datasource_uid =
#################################### Recording Rules #####################
[recording_rules]
# Enable recording rules.
@@ -242,6 +242,8 @@ Set to `true` to import recording rules in paused state.
The UID of the data source to use for alert rule queries.
If not specified in the header, Grafana uses the configured default from `unified_alerting.prometheus_conversion.default_datasource_uid`. If neither the header nor the configuration option is provided, the request fails.
#### `X-Grafana-Alerting-Target-Datasource-UID`
The UID of the target data source for recording rules. If not specified, the value from `X-Grafana-Alerting-Datasource-UID` is used.
@@ -2052,6 +2052,10 @@ This section applies only to rules imported as Grafana-managed rules. For more i
Set the query offset to imported Grafana-managed rules when `query_offset` is not defined in the original rule group configuration. The default value is `1m`.
#### `default_datasource_uid`
Set the default data source UID to use for query execution when importing Prometheus rules. Grafana uses this default when the `X-Grafana-Alerting-Datasource-UID` header isn't provided during import. If this option isn't set, the header becomes required. The default value is empty.
<hr>
### `[annotations]`
@@ -375,6 +375,9 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context
}
datasourceUID := strings.TrimSpace(c.Req.Header.Get(datasourceUIDHeader))
if datasourceUID == "" {
datasourceUID = srv.cfg.PrometheusConversion.DefaultDatasourceUID
}
if datasourceUID == "" {
return response.Err(errDatasourceUIDHeaderMissing)
}
@@ -75,6 +75,46 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
require.Contains(t, string(response.Body()), "Missing datasource UID header")
})
t.Run("without datasource UID header but with config default should succeed", func(t *testing.T) {
srv, _, ruleStore := createConvertPrometheusSrv(t)
// Set the config default
srv.cfg.PrometheusConversion.DefaultDatasourceUID = existingDSUID
rc := createRequestCtx()
rc.Req.Header.Set(datasourceUIDHeader, "")
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
require.Equal(t, http.StatusAccepted, response.Status())
// Verify that the config default datasource was used
assertRulesUseDatasource(t, ruleStore, existingDSUID, 2)
})
t.Run("header should take precedence over config default", func(t *testing.T) {
srv, dsCache, ruleStore := createConvertPrometheusSrv(t)
// Add another datasource
anotherDS := &datasources.DataSource{
UID: "another-ds",
Type: datasources.DS_PROMETHEUS,
}
dsCache.DataSources = append(dsCache.DataSources, anotherDS)
// Set the config default to one DS
srv.cfg.PrometheusConversion.DefaultDatasourceUID = "another-ds"
// But use the header to specify a different one
rc := createRequestCtx()
rc.Req.Header.Set(datasourceUIDHeader, existingDSUID)
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
require.Equal(t, http.StatusAccepted, response.Status())
// Verify that the header datasource was used, not the config default
assertRulesUseDatasource(t, ruleStore, existingDSUID, 2)
})
t.Run("with invalid datasource should return error", func(t *testing.T) {
srv, _, _ := createConvertPrometheusSrv(t)
rc := createRequestCtx()
@@ -1761,6 +1801,26 @@ func createRequestCtx() *contextmodel.ReqContext {
}
}
// assertRulesUseDatasource retrieves all alert rules from the store and verifies they use the expected datasource
func assertRulesUseDatasource(t *testing.T, ruleStore *fakes.RuleStore, expectedDatasourceUID string, expectedRuleCount int) {
t.Helper()
rules, err := ruleStore.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{
OrgID: 1,
})
require.NoError(t, err)
require.Len(t, rules, expectedRuleCount)
for _, rule := range rules {
if rule.Record == nil {
require.NotEmpty(t, rule.Data)
require.Equal(t, expectedDatasourceUID, rule.Data[0].DatasourceUID, rule.Title, expectedDatasourceUID)
} else {
require.Equal(t, expectedDatasourceUID, rule.Record.TargetDatasourceUID)
}
}
}
// Test parseBooleanHeader function which handles boolean header values
func TestParseBooleanHeader(t *testing.T) {
headerName := "X-Test-Header"
+4 -1
View File
@@ -190,6 +190,8 @@ type UnifiedAlertingReservedLabelSettings struct {
type UnifiedAlertingPrometheusConversionSettings struct {
// RuleQueryOffset defines a time offset to apply to rule queries during conversion from Prometheus to Grafana format
RuleQueryOffset time.Duration
// DefaultDatasourceUID is the default datasource UID to use when converting Prometheus rules if not specified via header
DefaultDatasourceUID string
}
type UnifiedAlertingLokiSettings struct {
@@ -536,7 +538,8 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
prometheusConversion := iniFile.Section("unified_alerting.prometheus_conversion")
uaCfg.PrometheusConversion = UnifiedAlertingPrometheusConversionSettings{
RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute),
RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute),
DefaultDatasourceUID: prometheusConversion.Key("default_datasource_uid").MustString(""),
}
rr := iniFile.Section("recording_rules")