Skip to content

Commit 0bb32d4

Browse files
committed
feat(control-plane): report tenant containers to the central Sentry DSN (#7876)
1 parent 264f26a commit 0bb32d4

7 files changed

Lines changed: 129 additions & 6 deletions

File tree

control-plane/src/container-driver.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export type ContainerDriverConfig = {
4040
* `product` string `TenantProvisioningRequest` carries; an unconfigured product is a real
4141
* misconfiguration, not a silent no-op (see `bindingFor`). */
4242
bindings: Record<Product, ContainerNamespaceLike>;
43+
/** The hosted fleet's central Sentry DSN (#7876), injected into every tenant container at cold boot as
44+
* {@link SENTRY_DSN_ENV_VAR} so its self-host process reports to it. Omitted/blank ⇒ containers start with
45+
* no injected DSN, byte-identical to the pre-#7876 call — the platform simply isn't reporting yet (e.g.
46+
* before #7875 provisions the secret). Sourced from the control-plane's own env in driver-factory.ts. */
47+
centralSentryDsn?: string;
4348
};
4449

4550
export type ContainerDriver = {
@@ -75,21 +80,34 @@ export const PINNED_VERSION_ENV_VAR = "LOOPOVER_PINNED_VERSION";
7580
* actually has custodied -- this driver never sees or needs to know what that is. */
7681
export const TENANT_SECRET_ENV_VAR = "LOOPOVER_TENANT_SECRET_TOKEN";
7782

83+
/** The env var a tenant's container reads its Sentry DSN from at cold boot (#7876, implements #4934). A tenant
84+
* container runs an unmodified self-host image (root Dockerfile for ORB; packages/loopover-miner/Dockerfile
85+
* for AMS), whose process already inits error reporting from this exact var via the opt-in `initSentry`
86+
* pattern (`src/selfhost/sentry.ts`: a complete no-op when unset) — so pointing the hosted fleet at the
87+
* central DSN is purely INJECTING that value here, never a second Sentry-wiring mechanism. Deliberately the
88+
* self-host name `SENTRY_DSN` (not a hosted-only alias): the image's own init reads this and only this, and a
89+
* self-hoster who sets their own `SENTRY_DSN` is completely unaffected — this only supplies a value the
90+
* hosted platform would otherwise leave unset. The value is a control-plane secret (provisioned by #7875),
91+
* never hardcoded or committed. */
92+
export const SENTRY_DSN_ENV_VAR = "SENTRY_DSN";
93+
7894
/** Idempotent: an already-provisioned tenant's container is left running as-is, never restarted -- a repeat
7995
* create must not interrupt a container mid-work. This is also the ONLY point in a tenant's lifecycle where
8096
* `envVars` actually reach the container (confirmed against the real `@cloudflare/containers` SDK: a `start()`
8197
* call against an already-running/starting instance is a no-op or throws, never re-applies `envVars`) -- so
82-
* both of the values below must already be known by the time this runs, not supplied later. A tenant with a
83-
* `pinnedVersion` (#4898) starts with that version in {@link PINNED_VERSION_ENV_VAR}; one with a
98+
* all three of the values below must already be known by the time this runs, not supplied later. A tenant with
99+
* a `pinnedVersion` (#4898) starts with that version in {@link PINNED_VERSION_ENV_VAR}; one with a
84100
* `bootstrapSecret` (#8202, set on `request` by `provisionTenant` from `injectSecrets`' result) starts with it
85-
* in {@link TENANT_SECRET_ENV_VAR}; a tenant with neither gets the exact pre-#4898 `start()` call, so every
86-
* existing tenant's behavior is byte-identical until either rollout applies. */
101+
* in {@link TENANT_SECRET_ENV_VAR}; and when the config carries a central Sentry DSN (#7876) every container
102+
* starts with it in {@link SENTRY_DSN_ENV_VAR}. A tenant with none of them gets the exact pre-#4898 `start()`
103+
* call, so every existing tenant's behavior is byte-identical until a rollout applies. */
87104
export async function createTenantContainer(config: ContainerDriverConfig, request: TenantProvisioningRequest): Promise<void> {
88105
const stub = bindingFor(config, request.product).getByName(instanceNameFor(request));
89106
if (await stub.isProvisioned()) return;
90107
const envVars: Record<string, string> = {};
91108
if (request.tenant.pinnedVersion) envVars[PINNED_VERSION_ENV_VAR] = request.tenant.pinnedVersion;
92109
if (request.bootstrapSecret) envVars[TENANT_SECRET_ENV_VAR] = request.bootstrapSecret;
110+
if (config.centralSentryDsn) envVars[SENTRY_DSN_ENV_VAR] = config.centralSentryDsn;
93111
if (Object.keys(envVars).length > 0) {
94112
await stub.start({ envVars });
95113
} else {

control-plane/src/driver-factory.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,12 @@ export function createTenantProvisioningDriver(
7575
}
7676

7777
if (containerBindings && Object.keys(containerBindings).length > 0) {
78-
driver = withRealContainerDriver(driver, createContainerDriver({ bindings: containerBindings }));
78+
// #7876: point every tenant container at the hosted fleet's central Sentry DSN when the control-plane
79+
// secret is set (per #7875). Blank/unset ⇒ omitted, so containers start byte-identical to pre-#7876.
80+
driver = withRealContainerDriver(
81+
driver,
82+
createContainerDriver({ bindings: containerBindings, ...(nonBlank(env.SENTRY_DSN) ? { centralSentryDsn: nonBlank(env.SENTRY_DSN) } : {}) }),
83+
);
7984
}
8085

8186
const mainAppBaseUrl = nonBlank(env.MAIN_APP_BASE_URL);

control-plane/src/env.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ declare global {
2424
* (a plain var, see wrangler.jsonc); createTenantProvisioningDriver falls back to the fake otherwise.
2525
* Genuinely sensitive: whoever holds it can call every internal admin route in the main app. */
2626
INTERNAL_JOB_TOKEN?: string;
27+
/** The hosted fleet's central Sentry DSN (#7876, implements #4934), provisioned as a secret by #7875.
28+
* createTenantProvisioningDriver injects it into every tenant container as `SENTRY_DSN` so the container's
29+
* own self-host process reports to it (`src/selfhost/sentry.ts`'s opt-in init). Unset ⇒ containers start
30+
* with no injected DSN, byte-identical to today. A DSN is a write-only ingest URL rather than a
31+
* credential, but it's kept a secret (never a committed var) so the hosted ingest endpoint isn't public. */
32+
SENTRY_DSN?: string;
2733
}
2834
}
2935

control-plane/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export {
6666
instanceNameFor,
6767
PINNED_VERSION_ENV_VAR,
6868
TENANT_SECRET_ENV_VAR,
69+
SENTRY_DSN_ENV_VAR,
6970
tenantContainerExists,
7071
type ContainerDriver,
7172
type ContainerDriverConfig,

control-plane/src/worker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ export default {
5656
const driver = createTenantProvisioningDriver(
5757
// #8066: MAIN_APP_BASE_URL/INTERNAL_JOB_TOKEN select the real secret driver (against the main app's
5858
// token broker) once both are configured, same opt-in-per-piece shape as NEON_API_KEY/NEON_PROJECT_ID.
59-
{ NEON_API_KEY: env.NEON_API_KEY, NEON_PROJECT_ID: env.NEON_PROJECT_ID, MAIN_APP_BASE_URL: env.MAIN_APP_BASE_URL, INTERNAL_JOB_TOKEN: env.INTERNAL_JOB_TOKEN },
59+
// #7876: SENTRY_DSN is the hosted fleet's central DSN (a control-plane secret, #7875) — the factory
60+
// injects it into every tenant container so its self-host process reports to it (unset ⇒ no-op).
61+
{ NEON_API_KEY: env.NEON_API_KEY, NEON_PROJECT_ID: env.NEON_PROJECT_ID, MAIN_APP_BASE_URL: env.MAIN_APP_BASE_URL, INTERNAL_JOB_TOKEN: env.INTERNAL_JOB_TOKEN, SENTRY_DSN: env.SENTRY_DSN },
6062
{ orb: env.ORB_TENANT_CONTAINER, ams: env.AMS_TENANT_CONTAINER },
6163
);
6264
const app = createTenantHttpApp({

control-plane/test/container-driver.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
destroyTenantContainer,
1111
PINNED_VERSION_ENV_VAR,
1212
TENANT_SECRET_ENV_VAR,
13+
SENTRY_DSN_ENV_VAR,
1314
tenantContainerExists,
1415
type ContainerDriverConfig,
1516
type ContainerNamespaceLike,
@@ -224,3 +225,46 @@ test("a repeat create of an already-provisioned tenant with a bootstrap secret n
224225

225226
assert.deepEqual(stub.startOptions, []);
226227
});
228+
229+
// #7876: the hosted fleet's central Sentry DSN rides into every tenant container at cold boot as
230+
// SENTRY_DSN_ENV_VAR — the exact self-host var its own process reads via src/selfhost/sentry.ts's opt-in
231+
// init, so pointing the fleet at central telemetry is purely injecting the value, not a second mechanism.
232+
function configWithDsn(stub: ContainerStubLike, centralSentryDsn: string): ContainerDriverConfig {
233+
return { bindings: { orb: { getByName: () => stub } }, centralSentryDsn };
234+
}
235+
236+
test("a container starts with SENTRY_DSN_ENV_VAR when the config carries the central DSN (#7876)", async () => {
237+
const stub = optionCapturingStub();
238+
239+
await createTenantContainer(configWithDsn(stub, "https://k@o0.ingest.sentry.io/1"), { tenant: { name: "acme" }, product: "orb" });
240+
241+
assert.deepEqual(stub.startOptions, [{ envVars: { [SENTRY_DSN_ENV_VAR]: "https://k@o0.ingest.sentry.io/1" } }]);
242+
});
243+
244+
test("the central DSN merges with pinnedVersion + bootstrapSecret into one start() call (#7876)", async () => {
245+
const stub = optionCapturingStub();
246+
247+
await createTenantContainer(configWithDsn(stub, "https://k@o0.ingest.sentry.io/1"), {
248+
tenant: { name: "acme", pinnedVersion: "v1.4.2" },
249+
product: "orb",
250+
bootstrapSecret: "orbsec_xyz",
251+
});
252+
253+
assert.deepEqual(stub.startOptions, [
254+
{
255+
envVars: {
256+
[PINNED_VERSION_ENV_VAR]: "v1.4.2",
257+
[TENANT_SECRET_ENV_VAR]: "orbsec_xyz",
258+
[SENTRY_DSN_ENV_VAR]: "https://k@o0.ingest.sentry.io/1",
259+
},
260+
},
261+
]);
262+
});
263+
264+
test("a config with no central DSN never injects SENTRY_DSN — byte-identical to today for an otherwise-plain tenant (#7876)", async () => {
265+
const stub = optionCapturingStub();
266+
267+
await createTenantContainer(configFor(stub), { tenant: { name: "acme" }, product: "orb" });
268+
269+
assert.deepEqual(stub.startOptions, [undefined]);
270+
});

control-plane/test/driver-factory.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, test } from "node:test";
66
import {
77
createFakeTenantProvisioningDriver,
88
createTenantProvisioningDriver,
9+
SENTRY_DSN_ENV_VAR,
910
withRealContainerDriver,
1011
withRealDatabaseDriver,
1112
withRealSecretDriver,
@@ -17,6 +18,32 @@ import {
1718
type TenantProvisioningRequest,
1819
} from "../dist/index.js";
1920

21+
/** A container namespace whose stub records each `start()` call's options — lets a factory test assert what
22+
* the composed container driver actually injects (the shared fake above deliberately discards options). */
23+
function capturingContainerNamespace(): ContainerNamespaceLike & { startOptions: Parameters<ContainerStubLike["start"]>[0][] } {
24+
const startOptions: Parameters<ContainerStubLike["start"]>[0][] = [];
25+
let flag = false;
26+
const stub: ContainerStubLike = {
27+
async start(options) {
28+
startOptions.push(options);
29+
flag = true;
30+
},
31+
async stop() {
32+
flag = false;
33+
},
34+
async isProvisioned() {
35+
return flag;
36+
},
37+
async markProvisioned() {
38+
flag = true;
39+
},
40+
async markDeprovisioned() {
41+
flag = false;
42+
},
43+
};
44+
return { getByName: () => stub, startOptions };
45+
}
46+
2047
function fakeContainerNamespace(provisioned = false): ContainerNamespaceLike {
2148
let flag = provisioned;
2249
const stub: ContainerStubLike = {
@@ -172,6 +199,26 @@ test("createTenantProvisioningDriver: selects the real container driver when con
172199
assert.equal(await driver.containerExists(REQUEST), true);
173200
});
174201

202+
test("createTenantProvisioningDriver: injects env.SENTRY_DSN into tenant containers when set (#7876)", async () => {
203+
const orb = capturingContainerNamespace();
204+
const driver = createTenantProvisioningDriver({ SENTRY_DSN: "https://k@o0.ingest.sentry.io/1" }, { orb });
205+
206+
await driver.createContainer(REQUEST);
207+
208+
assert.deepEqual(orb.startOptions, [{ envVars: { [SENTRY_DSN_ENV_VAR]: "https://k@o0.ingest.sentry.io/1" } }]);
209+
});
210+
211+
test("createTenantProvisioningDriver: a blank or unset SENTRY_DSN injects nothing — containers start as before (#7876)", async () => {
212+
for (const env of [{}, { SENTRY_DSN: "" }, { SENTRY_DSN: " " }]) {
213+
const orb = capturingContainerNamespace();
214+
const driver = createTenantProvisioningDriver(env, { orb });
215+
216+
await driver.createContainer(REQUEST);
217+
218+
assert.deepEqual(orb.startOptions, [undefined]);
219+
}
220+
});
221+
175222
test("createTenantProvisioningDriver: composes both the real database driver AND the real container driver together", async () => {
176223
globalThis.fetch = (async () => new Response(JSON.stringify({ branches: [] }), { status: 200 })) as unknown as typeof fetch;
177224

0 commit comments

Comments
 (0)