Skip to content

Commit d69156b

Browse files
CharlieTLeclaudefriedrichg
authored
Add per-tenant alert generator URL template for customizable alert source links (#7302)
* Add per-tenant Grafana Explore URL format for alert GeneratorURL Add support for tenants to configure alert GeneratorURL to use Grafana Explore format instead of the default Prometheus /graph format. This is controlled by three new per-tenant settings: ruler_alert_generator_url_format, ruler_grafana_datasource_uid, and ruler_grafana_org_id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Fix gofmt alignment in ruler and validation packages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Replace Grafana-specific config with generic Go template for alert generator URLs Replace the 3 Grafana-specific per-tenant config fields (ruler_alert_generator_url_format, ruler_grafana_datasource_uid, ruler_grafana_org_id) with a single generic field: ruler_alert_generator_url_template. This field accepts a Go text/template string with .ExternalURL and .Expression variables, plus built-in functions like urlquery. Users can construct any URL format (Grafana, Perses, etc.) without Cortex needing to understand specific UI formats. The ruler_external_url per-tenant override and SendAlerts signature (func(expr string) string) are kept unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Add getting-started example for per-tenant alert generator URL templates Add per-tenant Alertmanager datasources (tenant-a, tenant-b) to Grafana provisioning so alerts are visible in Grafana's alerting UI. Add runtime-config.yaml with per-tenant overrides: - tenant-a: Grafana Explore URL template with full pane JSON - tenant-b: Perses explore URL template with PrometheusTimeSeriesQuery Update Perses from v0.49 to v0.53.1 and enable the explorer feature (frontend.explorer.enable: true). Rename project from "default" to "cortex" to match template URLs. Add Step 7 to the getting-started guide with instructions for: - Configuring per-tenant alert generator URL templates - Loading alertmanager configs and demo alert rules - Viewing alerts in Grafana at /alerting/groups?groupBy=alertname - Verifying generator URLs via the API Also configure ruler.alertmanager_url and ruler.external_url, and set an explicit UID on the Grafana Cortex datasource for use in templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Add per-tenant Grafana Explore URL format for alert GeneratorURL Add support for tenants to configure alert GeneratorURL to use Grafana Explore format instead of the default Prometheus /graph format. This is controlled by three new per-tenant settings: ruler_alert_generator_url_format, ruler_grafana_datasource_uid, and ruler_grafana_org_id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Fix gofmt alignment in ruler and validation packages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Replace Grafana-specific config with generic Go template for alert generator URLs Replace the 3 Grafana-specific per-tenant config fields (ruler_alert_generator_url_format, ruler_grafana_datasource_uid, ruler_grafana_org_id) with a single generic field: ruler_alert_generator_url_template. This field accepts a Go text/template string with .ExternalURL and .Expression variables, plus built-in functions like urlquery. Users can construct any URL format (Grafana, Perses, etc.) without Cortex needing to understand specific UI formats. The ruler_external_url per-tenant override and SendAlerts signature (func(expr string) string) are kept unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Add getting-started example for per-tenant alert generator URL templates Add per-tenant Alertmanager datasources (tenant-a, tenant-b) to Grafana provisioning so alerts are visible in Grafana's alerting UI. Add runtime-config.yaml with per-tenant overrides: - tenant-a: Grafana Explore URL template with full pane JSON - tenant-b: Perses explore URL template with PrometheusTimeSeriesQuery Update Perses from v0.49 to v0.53.1 and enable the explorer feature (frontend.explorer.enable: true). Rename project from "default" to "cortex" to match template URLs. Add Step 7 to the getting-started guide with instructions for: - Configuring per-tenant alert generator URL templates - Loading alertmanager configs and demo alert rules - Viewing alerts in Grafana at /alerting/groups?groupBy=alertname - Verifying generator URLs via the API Also configure ruler.alertmanager_url and ruler.external_url, and set an explicit UID on the Grafana Cortex datasource for use in templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * fix linting Signed-off-by: Charlie Le <charlie_le@apple.com> * Fix per-tenant ExternalURL, datasource UID, and add CHANGELOG - Set ExternalURL on rules.ManagerOptions to the per-tenant override so {{ $externalURL }} in alert annotation/label templates reflects the tenant's ruler_external_url, not just the global config. - Fix Grafana datasource: add explicit uid: tenant-a so the template reference "datasource":"tenant-a" resolves correctly. - Fix runtime-config.yaml template to reference "datasource":"tenant-a" instead of "datasource":"cortex". - Add text/template security comment explaining the intentional choice over html/template. - Add CHANGELOG entry for the ruler_alert_generator_url_template feature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Add URL validation to generator URL template output Validate that the output of executeGeneratorURLTemplate produces a safe URL by checking: - Scheme must be http or https (blocks javascript: and data: URIs) - Host must be present - Fragment must not contain HTML characters < or > (blocks script injection) Add test cases covering javascript URI, data URI, fragment injection, missing host, and valid fragment scenarios. Signed-off-by: Friedrich Gonzalez <charlie_le@apple.com> Signed-off-by: Friedrich Gonzalez <1517449+friedrichg@users.noreply.github.com> * Re-resolve per-tenant external URL on every alert send The NotifyFunc closure was capturing externalURLStr once at manager creation time, so runtime config changes to ruler_external_url would not take effect until the ruler was restarted. Move the resolution into a helper that re-reads from overrides on each call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> * Cache parsed generator URL template to avoid re-parsing on every alert The generator URL template is parsed from the runtime config string on every alert send. Cache the parsed template and only re-parse when the template string changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Charlie Le <charlie_le@apple.com> --------- Signed-off-by: Charlie Le <charlie_le@apple.com> Signed-off-by: Friedrich Gonzalez <charlie_le@apple.com> Signed-off-by: Friedrich Gonzalez <1517449+friedrichg@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Friedrich Gonzalez <1517449+friedrichg@users.noreply.github.com>
1 parent be1dfa0 commit d69156b

21 files changed

Lines changed: 709 additions & 39 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## master / unreleased
4+
* [FEATURE] Ruler: Add per-tenant `ruler_alert_generator_url_template` runtime config option to customize alert generator URLs using Go templates. Supports Grafana Explore, Perses, and other UIs. #7302
45
* [FEATURE] Distributor: Add experimental `-distributor.enable-start-timestamp` flag for Prometheus Remote Write 2.0. When enabled, `StartTimestamp (ST)` is ingested. #7371
56
* [FEATURE] Memberlist: Add `-memberlist.cluster-label` and `-memberlist.cluster-label-verification-disabled` to prevent accidental cross-cluster gossip joins and support rolling label rollout. #7385
67
* [FEATURE] Querier: Add timeout classification to classify query timeouts as 4XX (user error) or 5XX (system error) based on phase timing. When enabled, queries that spend most of their time in PromQL evaluation return `422 Unprocessable Entity` instead of `503 Service Unavailable`. #7374

docs/configuration/config-file-reference.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4388,6 +4388,16 @@ query_rejection:
43884388
# external labels for alerting rules
43894389
[ruler_external_labels: <map of string (labelName) to string (labelValue)> | default = []]
43904390

4391+
# Per-tenant external URL for the ruler. If set, it overrides the global
4392+
# -ruler.external.url for this tenant's alert notifications.
4393+
[ruler_external_url: <string> | default = ""]
4394+
4395+
# Go text/template for alert generator URLs. Available variables: .ExternalURL
4396+
# (resolved external URL) and .Expression (PromQL expression). Built-in
4397+
# functions like urlquery are available. If empty, uses default Prometheus
4398+
# /graph format.
4399+
[ruler_alert_generator_url_template: <string> | default = ""]
4400+
43914401
# Enable to allow rules to be evaluated with data from a single zone, if other
43924402
# zones are not available.
43934403
[rules_partial_data: <boolean> | default = false]

docs/getting-started/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ CORTEX_VERSION=v1.20.1
22
GRAFANA_VERSION=10.4.2
33
PROMETHEUS_VERSION=v3.2.1
44
SEAWEEDFS_VERSION=3.67
5-
PERSES_VERSION=v0.49-distroless-debug
5+
PERSES_VERSION=v0.53.1-distroless-debug

docs/getting-started/cortex-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ frontend_worker:
8282
# https://cortexmetrics.io/docs/configuration/configuration-file/#ruler_config
8383
ruler:
8484
enable_api: true
85+
external_url: http://localhost:9009
86+
alertmanager_url: http://localhost:9009/alertmanager
87+
88+
# Per-tenant runtime configuration (hot-reloaded without restart).
89+
# This file configures per-tenant overrides such as custom alert generator
90+
# URL templates for Grafana, Perses, or any metrics explorer.
91+
runtime_config:
92+
file: /config/runtime-config.yaml
8593

8694
# https://cortexmetrics.io/docs/configuration/configuration-file/#ruler_storage_config
8795
ruler_storage:

docs/getting-started/docker-compose.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ services:
1717
- -config.file=/config/cortex-config.yaml
1818
volumes:
1919
- ./cortex-config.yaml:/config/cortex-config.yaml:ro
20+
- ./runtime-config.yaml:/config/runtime-config.yaml:ro
2021
ports:
2122
- "9009:9009"
2223
healthcheck:
@@ -47,6 +48,8 @@ services:
4748
volumes:
4849
- ./perses/config.yaml:/etc/perses/config/config.yaml:ro
4950
- ./perses/datasource.yaml:/etc/perses/resources/datasource.yaml:ro
51+
- ./perses/datasource-tenant-a.yaml:/etc/perses/resources/datasource-tenant-a.yaml:ro
52+
- ./perses/datasource-tenant-b.yaml:/etc/perses/resources/datasource-tenant-b.yaml:ro
5053
- ./perses/project.yaml:/etc/perses/resources/project.yaml:ro
5154
- ./perses/dashboards/cortex-writes.yaml:/etc/perses/resources/cortex-writes.yaml:ro
5255
prometheus:

docs/getting-started/grafana-datasource-docker.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ apiVersion: 1
55
datasources:
66
- name: Cortex
77
type: prometheus
8+
uid: cortex
89
access: proxy
910
orgId: 1
1011
url: http://cortex:9009/api/prom
@@ -22,6 +23,7 @@ datasources:
2223
isDefault: true
2324
- name: Tenant A
2425
type: prometheus
26+
uid: tenant-a
2527
access: proxy
2628
orgId: 1
2729
url: http://cortex:9009/api/prom
@@ -71,3 +73,25 @@ datasources:
7173
secureJsonData:
7274
httpHeaderValue1: cortex
7375
version: 1
76+
- orgId: 1
77+
name: Tenant A Alertmanager
78+
type: alertmanager
79+
access: proxy
80+
url: http://cortex:9009/
81+
jsonData:
82+
httpHeaderName1: X-Scope-OrgID
83+
implementation: cortex
84+
secureJsonData:
85+
httpHeaderValue1: tenant-a
86+
version: 1
87+
- orgId: 1
88+
name: Tenant B Alertmanager
89+
type: alertmanager
90+
access: proxy
91+
url: http://cortex:9009/
92+
jsonData:
93+
httpHeaderName1: X-Scope-OrgID
94+
implementation: cortex
95+
secureJsonData:
96+
httpHeaderValue1: tenant-b
97+
version: 1

docs/getting-started/perses/config.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ security:
88
database:
99
file:
1010
extension: yaml
11-
folder: /perses
11+
folder: /tmp/perses-data
1212

1313
schemas:
1414
datasources_path: /etc/perses/cue/schemas/datasources
1515
interval: 5m
1616
panels_path: /etc/perses/cue/schemas/panels
1717
queries_path: /etc/perses/cue/schemas/queries
1818
variables_path: /etc/perses/cue/schemas/variables
19+
20+
frontend:
21+
explorer:
22+
enable: true
23+
1924
provisioning:
2025
folders:
2126
- /etc/perses/resources

docs/getting-started/perses/dashboards/cortex-writes.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ metadata:
44
createdAt: 2025-03-24T19:15:47.468680767Z
55
updatedAt: 2025-03-24T19:43:53.000136362Z
66
version: 12
7-
project: default
7+
project: cortex
88
spec:
99
display:
1010
name: Cortex / Writes
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
kind: GlobalDatasource
2+
metadata:
3+
name: TenantA
4+
spec:
5+
default: false
6+
plugin:
7+
kind: PrometheusDatasource
8+
spec:
9+
proxy:
10+
kind: HTTPProxy
11+
spec:
12+
url: http://cortex:9009/api/prom
13+
headers:
14+
X-Scope-OrgID: tenant-a
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
kind: GlobalDatasource
2+
metadata:
3+
name: TenantB
4+
spec:
5+
default: false
6+
plugin:
7+
kind: PrometheusDatasource
8+
spec:
9+
proxy:
10+
kind: HTTPProxy
11+
spec:
12+
url: http://cortex:9009/api/prom
13+
headers:
14+
X-Scope-OrgID: tenant-b

0 commit comments

Comments
 (0)