diff --git a/conf/defaults.ini b/conf/defaults.ini index 97ad0aead32..a216bb0792a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -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 diff --git a/conf/sample.ini b/conf/sample.ini index d70f031d6ab..21ef0ad88c6 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -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 diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index adc5f4641e8..f262e6aaf2f 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -256,4 +256,5 @@ export interface FeatureToggles { rendererDisableAppPluginsPreload?: boolean; assetSriChecks?: boolean; alertRuleRestore?: boolean; + grafanaManagedRecordingRulesDatasources?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index cd247b94ffb..f3834bb5757 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 06439b40a21..ff29806e399 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -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 diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index fde91f9bc4e..957b81e842c 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 98851496d97..0b40ecb9551 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -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", diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index f09a20609d2..9f4a62656f6 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -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 diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 03bea25e0aa..bf359c34855 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -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")