Alerting: Allow selection of recording rule write target on per-rule basis. (#101778)

* Alerting: Allow selection of recording rule write target on per-rule basis.

Introduces a new feature flag (`grafanaManagedRecordingRulesDatasources`),
disabled by default, to enable the ability to write recording rules data using
data source settings, and selecting the data source to use on a per-rule basis.

To cope with the scenario of users upgrading, a configuration file option
allows setting the default data source to use, if none is specified in the rule,
emulating the behaviour of recording rules without the flag enabled.

* Lint

* Update conf/sample.ini

Co-authored-by: Alexander Akhmetov <me@alx.cx>

---------

Co-authored-by: Alexander Akhmetov <me@alx.cx>
pull/101794/head
Steve Simpson 2 months ago committed by GitHub
parent 5917ed8227
commit 14ebec527c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      conf/defaults.ini
  2. 7
      conf/sample.ini
  3. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  4. 9
      pkg/services/featuremgmt/registry.go
  5. 1
      pkg/services/featuremgmt/toggles_gen.csv
  6. 4
      pkg/services/featuremgmt/toggles_gen.go
  7. 14
      pkg/services/featuremgmt/toggles_gen.json
  8. 21
      pkg/services/ngalert/ngalert.go
  9. 26
      pkg/setting/setting_unified_alerting.go

@ -1523,6 +1523,13 @@ basic_auth_password =
# Request timeout for recording rule writes.
timeout = 10s
# Default data source UID to write to if not specified in the rule definition.
# Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled.
default_datasource_uid =
# Suffix to apply to the data source URL for remote write requests.
remote_write_path_suffix = /push
# Optional custom headers to include in recording rule write requests.
[recording_rules.custom_headers]
# exampleHeader = exampleValue

@ -1505,6 +1505,13 @@ basic_auth_password =
# Request timeout for recording rule writes.
timeout = 30s
# Default data source UID to write to if not specified in the rule definition.
# Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled.
default_datasource_uid =
# Suffix to apply to the data source URL for remote write requests.
remote_write_path_suffix = /push
# Optional custom headers to include in recording rule write requests.
[recording_rules.custom_headers]
# exampleHeader = exampleValue

@ -256,4 +256,5 @@ export interface FeatureToggles {
rendererDisableAppPluginsPreload?: boolean;
assetSriChecks?: boolean;
alertRuleRestore?: boolean;
grafanaManagedRecordingRulesDatasources?: boolean;
}

@ -1790,6 +1790,15 @@ var (
Owner: grafanaAlertingSquad,
Expression: "true", // enabled by default
},
{
Name: "grafanaManagedRecordingRulesDatasources",
Description: "Enables writing to data sources for Grafana-managed recording rules.",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
AllowSelfServe: false,
HideFromAdminPage: true,
HideFromDocs: true,
},
}
)

@ -237,3 +237,4 @@ newShareReportDrawer,experimental,@grafana/sharing-squad,false,false,false
rendererDisableAppPluginsPreload,experimental,@grafana/sharing-squad,false,false,true
assetSriChecks,experimental,@grafana/frontend-ops,false,false,true
alertRuleRestore,preview,@grafana/alerting-squad,false,false,false
grafanaManagedRecordingRulesDatasources,experimental,@grafana/alerting-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
237 rendererDisableAppPluginsPreload experimental @grafana/sharing-squad false false true
238 assetSriChecks experimental @grafana/frontend-ops false false true
239 alertRuleRestore preview @grafana/alerting-squad false false false
240 grafanaManagedRecordingRulesDatasources experimental @grafana/alerting-squad false false false

@ -958,4 +958,8 @@ const (
// FlagAlertRuleRestore
// Enables the alert rule restore feature
FlagAlertRuleRestore = "alertRuleRestore"
// FlagGrafanaManagedRecordingRulesDatasources
// Enables writing to data sources for Grafana-managed recording rules.
FlagGrafanaManagedRecordingRulesDatasources = "grafanaManagedRecordingRulesDatasources"
)

@ -1964,6 +1964,20 @@
"hideFromDocs": true
}
},
{
"metadata": {
"name": "grafanaManagedRecordingRulesDatasources",
"resourceVersion": "1741291902441",
"creationTimestamp": "2025-03-06T20:11:42Z"
},
"spec": {
"description": "Enables writing to data sources for Grafana-managed recording rules.",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"hideFromAdminPage": true,
"hideFromDocs": true
}
},
{
"metadata": {
"name": "grafanaconThemes",

@ -374,7 +374,7 @@ func (ng *AlertNG) init() error {
// Force-disable the feature if the feature toggle is not on - sets us up for feature toggle removal.
ng.Cfg.UnifiedAlerting.RecordingRules.Enabled = false
}
recordingWriter, err := createRecordingWriter(ng.FeatureToggles, ng.Cfg.UnifiedAlerting.RecordingRules, ng.httpClientProvider, clk, ng.Metrics.GetRemoteWriterMetrics())
recordingWriter, err := createRecordingWriter(ng.FeatureToggles, ng.Cfg.UnifiedAlerting.RecordingRules, ng.httpClientProvider, ng.DataSourceService, clk, ng.Metrics.GetRemoteWriterMetrics())
if err != nil {
return fmt.Errorf("failed to initialize recording writer: %w", err)
}
@ -754,11 +754,26 @@ func createRemoteAlertmanager(cfg remote.AlertmanagerConfig, kvstore kvstore.KVS
return remote.NewAlertmanager(cfg, notifier.NewFileStore(cfg.OrgID, kvstore), decryptFn, autogenFn, m, tracer)
}
func createRecordingWriter(featureToggles featuremgmt.FeatureToggles, settings setting.RecordingRuleSettings, httpClientProvider httpclient.Provider, clock clock.Clock, m *metrics.RemoteWriter) (schedule.RecordingWriter, error) {
func createRecordingWriter(featureToggles featuremgmt.FeatureToggles, settings setting.RecordingRuleSettings, httpClientProvider httpclient.Provider, datasourceService datasources.DataSourceService, clock clock.Clock, m *metrics.RemoteWriter) (schedule.RecordingWriter, error) {
logger := log.New("ngalert.writer")
if settings.Enabled {
return writer.NewPrometheusWriterWithSettings(settings, httpClientProvider, clock, logger, m)
if featureToggles.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources) {
cfg := writer.DatasourceWriterConfig{
Timeout: settings.Timeout,
DefaultDatasourceUID: settings.DefaultDatasourceUID,
RemoteWritePathSuffix: settings.RemoteWritePathSuffix,
}
logger.Info("Setting up remote write using data sources",
"timeout", cfg.Timeout, "default_datasource_uid", cfg.DefaultDatasourceUID,
"remote_write_path_suffix", cfg.RemoteWritePathSuffix)
return writer.NewDatasourceWriter(cfg, datasourceService, httpClientProvider, clock, logger, m), nil
} else {
logger.Info("Setting up remote write using static configuration")
return writer.NewPrometheusWriterWithSettings(settings, httpClientProvider, clock, logger, m)
}
}
return writer.NoopWriter{}, nil

@ -132,12 +132,14 @@ type UnifiedAlertingSettings struct {
}
type RecordingRuleSettings struct {
Enabled bool
URL string
BasicAuthUsername string
BasicAuthPassword string
CustomHeaders map[string]string
Timeout time.Duration
Enabled bool
URL string
BasicAuthUsername string
BasicAuthPassword string
CustomHeaders map[string]string
Timeout time.Duration
DefaultDatasourceUID string
RemoteWritePathSuffix string
}
// RemoteAlertmanagerSettings contains the configuration needed
@ -435,11 +437,13 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
rr := iniFile.Section("recording_rules")
uaCfgRecordingRules := RecordingRuleSettings{
Enabled: rr.Key("enabled").MustBool(false),
URL: rr.Key("url").MustString(""),
BasicAuthUsername: rr.Key("basic_auth_username").MustString(""),
BasicAuthPassword: rr.Key("basic_auth_password").MustString(""),
Timeout: rr.Key("timeout").MustDuration(defaultRecordingRequestTimeout),
Enabled: rr.Key("enabled").MustBool(false),
URL: rr.Key("url").MustString(""),
BasicAuthUsername: rr.Key("basic_auth_username").MustString(""),
BasicAuthPassword: rr.Key("basic_auth_password").MustString(""),
Timeout: rr.Key("timeout").MustDuration(defaultRecordingRequestTimeout),
DefaultDatasourceUID: rr.Key("default_datasource_uid").MustString(""),
RemoteWritePathSuffix: rr.Key("remote_write_path_suffix").MustString("/push"),
}
rrHeaders := iniFile.Section("recording_rules.custom_headers")

Loading…
Cancel
Save