Public: anyone can read this; the crew can change it.
/healthz says which version is serving, in an X-Forge-Version header #2293
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2281-healthz-says-which-version-is-serving-in → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +13
−1
src/http/server.ts +5 −1
| @@ -62,6 +62,7 @@ import { | ||
| 62 | 62 | } from "../core/users.ts"; |
| 63 | 63 | import { edgeIdentity } from "./edge.ts"; |
| 64 | 64 | import { liveResponse, readyResponse } from "./health.ts"; |
| 65 | import { running } from "../kernel/version.ts"; | |
| 65 | 66 | import { runGit } from "../git/env.ts"; |
| 66 | 67 | import { noteRefusedInternal } from "../kernel/internal_refused.ts"; |
| 67 | 68 | import { pushCap, storageChanged, storageRefusal, storageRefusedText, storageSentence } from "../core/storage.ts"; |
| @@ -2226,7 +2227,10 @@ async function answerGet( | ||
| 2226 | 2227 | const url = publicUrl(request, app.config.url); |
| 2227 | 2228 | if (url.pathname === "/healthz") { |
| 2228 | 2229 | noteUsage(request, { pattern: "/healthz", role: "anon" }); |
| 2229 | return liveResponse(() => app.db.query("select 1").get(), app.log); | |
| 2230 | const live = liveResponse(() => app.db.query("select 1").get(), app.log); | |
| 2231 | // A smoke test learns which release answered, old pod or new. | |
| 2232 | live.headers.set("X-Forge-Version", running().version); | |
| 2233 | return live; | |
| 2230 | 2234 | } |
| 2231 | 2235 | if (url.pathname === "/readyz") { |
| 2232 | 2236 | noteUsage(request, { pattern: "/readyz", role: "anon" }); |
test/health.test.ts +8
| @@ -13,6 +13,7 @@ import { | ||
| 13 | 13 | startDraining, |
| 14 | 14 | } from "../src/http/health.ts"; |
| 15 | 15 | import { serve, type Servers } from "../src/http/server.ts"; |
| 16 | import { running } from "../src/kernel/version.ts"; | |
| 16 | 17 | import type { Log, LogFields } from "../src/kernel/log.ts"; |
| 17 | 18 | |
| 18 | 19 | /** A log that keeps its lines, so a test can read what was said. */ |
| @@ -102,6 +103,13 @@ describe("the served paths", () => { | ||
| 102 | 103 | await rm(dataDir, { recursive: true, force: true }); |
| 103 | 104 | }); |
| 104 | 105 | |
| 106 | test("/healthz names the running version and keeps the security headers", async () => { | |
| 107 | const res = await fetch(`${servers.url}/healthz`); | |
| 108 | expect(res.headers.get("x-forge-version")).toBe(running().version); | |
| 109 | expect(res.headers.get("x-content-type-options")).toBe("nosniff"); | |
| 110 | expect(res.headers.get("content-security-policy")).toBeTruthy(); | |
| 111 | }); | |
| 112 | ||
| 105 | 113 | test("a slow database query (fake) does not turn /healthz into 503", async () => { |
| 106 | 114 | const db = app.db as unknown as { query: (sql: string) => unknown }; |
| 107 | 115 | const query = db.query; |
new above
offline
Fine as is.
running()re-reads env per probe, which is negligible. The value passes the VERSION allowlist regex, so header injection is not possible.