From 03ed1287c3e34eb062756460d2ce3bdefd1308f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 16:35:03 +0000 Subject: [PATCH 1/2] fix(runner/config): split header env vars on ';' to match legacy agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ParseHeaders split PROMETHEUS_HEADERS / LOKI_EXTRA_HEADER / ELASTICSEARCH_HEADER / ALERTMANAGER_HEADERS / LOKI_RULES_HEADERS on ','. The legacy Python agent split these on ';', so a multi-header config (or any header value containing a comma, e.g. a multi-value Accept header) was truncated at the first comma — the first header got a corrupted value and the rest were dropped. Split on ';' to restore parity. Docs, chart values, and .env.example updated to document the ';' delimiter. Migration: multi-header configs must now separate pairs with ';' (e.g. "X-Scope-OrgID: t1; Authorization: Bearer x"). Single-header configs — the common case — are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01L2HAXQH1gEtX7h4sUJsi9j (cherry picked from commit 65dfefae4d0c1a6da06cf269273d05d7b2f3bfe7) --- charts/nudgebee-agent/values.yaml | 2 +- runner/.env.example | 2 +- runner/docs/configuration.md | 6 +++--- runner/pkg/config/config.go | 14 +++++++++----- runner/pkg/config/config_test.go | 8 ++++++-- runner/pkg/grafana/proxy.go | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/charts/nudgebee-agent/values.yaml b/charts/nudgebee-agent/values.yaml index 2b163eca..668e6987 100644 --- a/charts/nudgebee-agent/values.yaml +++ b/charts/nudgebee-agent/values.yaml @@ -8,7 +8,7 @@ globalConfig: # Prometheus URL the runner queries (e.g. "http://prometheus-server.prometheus.svc:80"). # Surfaces as PROMETHEUS_URL on the runner pod. Leave empty to let the agent auto-discover. prometheus_url: "" - # Optional headers (comma-separated "Header: value" pairs). Surfaces as PROMETHEUS_HEADERS. + # Optional headers (semicolon-separated "Header: value" pairs, e.g. "X-Scope-OrgID: t1; Authorization: Bearer x"). Surfaces as PROMETHEUS_HEADERS. prometheus_headers: "" # Optional labels appended to every PromQL query (legacy multi-cluster parity). # A YAML map, e.g. {k8s_cluster: aws-prod}. Surfaces as PROMETHEUS_ADDITIONAL_LABELS diff --git a/runner/.env.example b/runner/.env.example index b7637b79..0e497929 100644 --- a/runner/.env.example +++ b/runner/.env.example @@ -33,7 +33,7 @@ NUDGEBEE_ENDPOINT=https://api.nudgebee.com # --- Observability (optional but typical) ----------------------------------- # PROMETHEUS_URL=http://prometheus.monitoring.svc:9090 -# PROMETHEUS_HEADERS=Authorization: Bearer xxx # static header auth (basic/bearer) +# PROMETHEUS_HEADERS=Authorization: Bearer xxx # static header auth (basic/bearer); multiple headers are ";"-separated # Managed Prometheus auth (pick one; precedence AWS → Coralogix → Azure): # AWS SigV4 (Amazon Managed Prometheus): AWS_ACCESS_KEY / AWS_SECRET_ACCESS_KEY / AWS_REGION [/ AWS_SERVICE_NAME=aps] # Coralogix: CORALOGIX_PROMETHEUS_TOKEN diff --git a/runner/docs/configuration.md b/runner/docs/configuration.md index 0ebf338b..8539f761 100644 --- a/runner/docs/configuration.md +++ b/runner/docs/configuration.md @@ -45,7 +45,7 @@ If a K8s subsystem is enabled but the agent fails to build a K8s client (no kube | Variable | Required | Description | |---|---|---| | `PROMETHEUS_URL` | recommended | Enables `prometheus_*` actions and `service_map` | -| `PROMETHEUS_HEADERS` | optional | Comma-separated `Header: value` pairs (e.g. `X-Scope-OrgID: tenant-1`); use for static basic/bearer auth | +| `PROMETHEUS_HEADERS` | optional | Semicolon-separated `Header: value` pairs (e.g. `X-Scope-OrgID: tenant-1`); use for static basic/bearer auth | | `AWS_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION` | optional | Managed Prometheus: sign requests with AWS SigV4. `AWS_SERVICE_NAME` defaults to `aps` | | `CORALOGIX_PROMETHEUS_TOKEN` | optional | Managed Prometheus: sent as `token` header | | `AZURE_USE_MANAGED_ID` / `AZURE_CLIENT_SECRET` (+ `AZURE_CLIENT_ID` / `AZURE_TENANT_ID`) | optional | Managed Prometheus: Azure AD Bearer token (managed identity or client-secret). Precedence: AWS → Coralogix → Azure | @@ -68,9 +68,9 @@ If a K8s subsystem is enabled but the agent fails to build a K8s client (no kube | Variable | Description | |---|---| | `ALERTMANAGER_URL` | Enables `get_silences`, `add_silence`, `delete_silence` | -| `ALERTMANAGER_HEADERS` | Comma-separated headers for AlertManager | +| `ALERTMANAGER_HEADERS` | Semicolon-separated headers for AlertManager | | `LOKI_RULES_URL` | Loki ruler component URL — enables `create_loki_alert_rule`, etc. | -| `LOKI_RULES_HEADERS` | Comma-separated headers for Loki rules API | +| `LOKI_RULES_HEADERS` | Semicolon-separated headers for Loki rules API | ## Authentication diff --git a/runner/pkg/config/config.go b/runner/pkg/config/config.go index 366eb4f5..70adebda 100644 --- a/runner/pkg/config/config.go +++ b/runner/pkg/config/config.go @@ -386,16 +386,20 @@ func ParseTargets(s string) map[string]string { return out } -// ParseHeaders splits a comma-separated "Header: value" string into an -// http.Header. Returns an empty Header for empty input. Same shape used -// by the GRAFANA_EXTRA_HEADER / LOKI_EXTRA_HEADER pattern (a single -// "Header: value" or comma-separated list). +// ParseHeaders splits a semicolon-separated "Header: value" string into an +// http.Header. Returns an empty Header for empty input. Same shape used by +// the GRAFANA_EXTRA_HEADER / LOKI_EXTRA_HEADER / PROMETHEUS_HEADERS pattern +// (a single "Header: value" or ";"-separated list). +// +// The legacy Python agent split these vars on ";", so a header *value* may +// itself contain a comma (e.g. a multi-value Accept header) without being +// truncated. Multi-header configs must use ";" between pairs. func ParseHeaders(s string) http.Header { h := http.Header{} if s == "" { return h } - for _, part := range strings.Split(s, ",") { + for _, part := range strings.Split(s, ";") { part = strings.TrimSpace(part) if part == "" { continue diff --git a/runner/pkg/config/config_test.go b/runner/pkg/config/config_test.go index d07f10be..c535d935 100644 --- a/runner/pkg/config/config_test.go +++ b/runner/pkg/config/config_test.go @@ -176,16 +176,20 @@ func TestParseHeaders(t *testing.T) { {"one", "X-Scope-OrgID: tenant-1", http.Header{"X-Scope-Orgid": []string{"tenant-1"}}}, { "multi", - "X-Scope-OrgID: tenant-1, Authorization: Bearer abc", + "X-Scope-OrgID: tenant-1; Authorization: Bearer abc", http.Header{ "X-Scope-Orgid": []string{"tenant-1"}, "Authorization": []string{"Bearer abc"}, }, }, {"trims_whitespace", " X-A : v ", http.Header{"X-A": []string{"v"}}}, - {"skips_invalid", "no-colon-here, X-Y: ok", http.Header{"X-Y": []string{"ok"}}}, + {"skips_invalid", "no-colon-here; X-Y: ok", http.Header{"X-Y": []string{"ok"}}}, {"value_can_contain_colons", "Authorization: Bearer x:y:z", http.Header{"Authorization": []string{"Bearer x:y:z"}}}, + // A comma inside a value must survive — we split on ";" only, so a + // multi-value header (e.g. Accept) is not truncated at the comma. + {"value_can_contain_comma", "Accept: text/html, application/json", + http.Header{"Accept": []string{"text/html, application/json"}}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { diff --git a/runner/pkg/grafana/proxy.go b/runner/pkg/grafana/proxy.go index bb58355f..73435590 100644 --- a/runner/pkg/grafana/proxy.go +++ b/runner/pkg/grafana/proxy.go @@ -62,7 +62,7 @@ type Proxy struct { PrometheusURL string // PrometheusHeaders is the parsed PROMETHEUS_HEADERS env (raw - // "Header: value, Header: value" string) — applied to every + // "Header: value; Header: value" string) — applied to every // Prometheus proxy request so X-Scope-OrgID / tenant headers reach // the upstream. Same shape ExtraHeaders uses for Grafana. PrometheusHeaders http.Header From 73f533b56a4899b327a3359fc7e51145001905fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Aug 2026 08:30:58 +0000 Subject: [PATCH 2/2] chore: update image tags for main release --- charts/nudgebee-agent/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nudgebee-agent/values.yaml b/charts/nudgebee-agent/values.yaml index 6c388823..be3a8b7b 100644 --- a/charts/nudgebee-agent/values.yaml +++ b/charts/nudgebee-agent/values.yaml @@ -65,7 +65,7 @@ runnerServiceAccount: runner: image: repository: ghcr.io/nudgebee/nudgebee-agent - tag: 2026-08-12T06-01-54_7ec58f7701909a3ce172ad2a9235f8b15255e363 + tag: 2026-08-12T08-10-04_e13ce92bc8c238e883d050b1276c6128b2c8fb44 # Image template the pod_profiler action launches debugger pods from. # The agent substitutes `{}` for the variant (bpf, jvm, python, perf, ruby). # Surfaces as PROFILER_IMAGE; leave empty to fall back to the binary default.