Skip to content

Commit e4ce097

Browse files
loks0nclaude
andcommitted
feat(pyroscope): arbitrary ingest headers via --pyroscope-header
Add a repeatable --pyroscope-header "Name: value" flag (env PYROSCOPE_HEADER, newline-separated for several) for gateway auth schemes the built-in --pyroscope-auth-token / --pyroscope-tenant-id don't cover (Basic, X-API-Key, proxy headers). Applied after the built-in auth headers so it can override them. Verified e2e: pfp pushes successfully to Pyroscope with custom headers set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 22fb860 commit e4ce097

5 files changed

Lines changed: 35 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
run continuously and push a gzipped pprof profile to a Grafana Pyroscope
1313
server every `--push-interval-secs` (default 10) instead of writing a file.
1414
Configurable via `--pyroscope-app`, repeatable `--pyroscope-label`, and auth
15-
via `--pyroscope-auth-token` / `--pyroscope-tenant-id`. Push failures are
15+
via `--pyroscope-auth-token` / `--pyroscope-tenant-id`, plus arbitrary
16+
ingest headers via repeatable `--pyroscope-header "Name: value"`. Push failures are
1617
logged and never interrupt sampling. Pulls in a small synchronous HTTP
1718
client behind the default-on `pyroscope` feature.
1819
- Distroless multi-arch (amd64/arm64) container image, published to

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ spec:
111111
112112
Grafana Cloud and multi-tenant servers are supported via
113113
`--pyroscope-auth-token` (or `PYROSCOPE_AUTH_TOKEN`) and `--pyroscope-tenant-id`.
114+
For any other gateway auth scheme, set arbitrary headers with repeatable
115+
`--pyroscope-header "Name: value"` (e.g. `--pyroscope-header "X-API-Key: …"`),
116+
or via the `PYROSCOPE_HEADER` env var (one header, or several separated by
117+
newlines) to inject a secret without putting it on the command line.
114118

115119
### CLI flags
116120

@@ -132,6 +136,7 @@ Grafana Cloud and multi-tenant servers are supported via
132136
| `--push-interval-secs <N>` | Push cadence in sidecar mode (default 10) |
133137
| `--pyroscope-auth-token <T>` | Bearer token (Grafana Cloud); env `PYROSCOPE_AUTH_TOKEN` |
134138
| `--pyroscope-tenant-id <ID>` | Tenant id, sent as `X-Scope-OrgID` |
139+
| `--pyroscope-header <N: V>` | Extra ingest header, e.g. `X-API-Key: …` (repeatable); env `PYROSCOPE_HEADER` |
135140
| `--request-info` | Capture `$_SERVER` URI/method per sample |
136141
| `--php-version <V>` | Force version (e.g. `8.4`) on stripped binaries |
137142
| `--executor-globals <ADDR>` | Override EG address on stripped binaries |

src/cli.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ pub struct Args {
9090
#[arg(long, env = "PYROSCOPE_TENANT_ID")]
9191
pub pyroscope_tenant_id: Option<String>,
9292

93+
/// Extra HTTP header on each ingest request, `Name: value`. Applied after
94+
/// the built-in auth headers, so it can override them. Repeat the flag for
95+
/// several headers, or pass newline-separated headers via the env var.
96+
#[arg(
97+
long = "pyroscope-header",
98+
value_name = "NAME: VALUE",
99+
env = "PYROSCOPE_HEADER",
100+
value_delimiter = '\n'
101+
)]
102+
pub pyroscope_header: Vec<String>,
103+
93104
/// Capture request info ($_SERVER URI/method/etc.).
94105
#[arg(long)]
95106
pub request_info: bool,

src/output/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ fn pyroscope_config(args: &Args) -> pyroscope_sink::PyroscopeConfig {
6161
name,
6262
auth_token: args.pyroscope_auth_token.clone(),
6363
tenant_id: args.pyroscope_tenant_id.clone(),
64+
headers: args
65+
.pyroscope_header
66+
.iter()
67+
.filter_map(|h| match h.split_once(':') {
68+
Some((k, v)) => Some((k.trim().to_string(), v.trim().to_string())),
69+
None => {
70+
tracing::warn!("ignoring malformed --pyroscope-header (no ':'): {h}");
71+
None
72+
}
73+
})
74+
.collect(),
6475
push_interval: std::time::Duration::from_secs(args.push_interval_secs.max(1)),
6576
}
6677
}

src/output/pyroscope_sink.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct PyroscopeConfig {
1313
pub name: String,
1414
pub auth_token: Option<String>,
1515
pub tenant_id: Option<String>,
16+
/// Extra request headers, applied after the built-in auth headers.
17+
pub headers: Vec<(String, String)>,
1618
pub push_interval: Duration,
1719
}
1820

@@ -79,6 +81,10 @@ impl PyroscopeSink {
7981
if let Some(tenant) = &self.cfg.tenant_id {
8082
req = req.set("X-Scope-OrgID", tenant);
8183
}
84+
// Applied last so an explicit --pyroscope-header can override the above.
85+
for (name, value) in &self.cfg.headers {
86+
req = req.set(name, value);
87+
}
8288

8389
req.send_bytes(&body)
8490
.context("POST /ingest")

0 commit comments

Comments
 (0)