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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user