Public: anyone can read this; the crew can change it.
scripts/drills/run.ts posts its result to POST /api/v1/drills, and docs/DOMAINS.md says how to run it #2582
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2351-scripts-drills-run-ts-posts-its-result-t → main 3 commits fork
3 files
changed +37
−8
docs/DOMAINS.md +2 −1
| @@ -41,7 +41,8 @@ says while unhealthy; when the forge is healthy again it posts what it saw and | ||
| 41 | 41 | the seconds it took to `POST /api/v1/drills` and prints the verdict. If the |
| 42 | 42 | forge is still down after an hour it posts nothing, says so, and exits 0. A |
| 43 | 43 | refused post exits non-zero with the forge's message. `FORGE_TOKEN` must be a |
| 44 | site admin's token. | |
| 44 | site admin's token; without it the script still watches, prints the result as | |
| 45 | JSON and says that nothing was posted. | |
| 45 | 46 | |
| 46 | 47 | |
| 47 | 48 | FORGE_TOKEN=… bun scripts/drills/run.ts runner --forge https://forge.open-platform.sh |
scripts/drills/run.ts +11 −6
| @@ -4,8 +4,9 @@ | ||
| 4 | 4 | * polls `/api/v1/health` and `/` once a second keeping each distinct line the |
| 5 | 5 | * forge says while unhealthy, and when the forge is healthy again posts what |
| 6 | 6 | * it saw and how long it took to `POST /api/v1/drills` (`FORGE_TOKEN` from |
| 7 | * the environment). If the forge never comes back within the ceiling it posts | |
| 8 | * nothing. Zero dependencies; it never shells out. | |
| 7 | * the environment). Without a token it prints the result and says nothing was | |
| 8 | * posted. If the forge never comes back within the ceiling it posts nothing. | |
| 9 | * Zero dependencies; it never shells out. | |
| 9 | 10 | */ |
| 10 | 11 | |
| 11 | 12 | import { DOMAINS } from "../../src/kernel/domains.ts"; |
| @@ -38,7 +39,7 @@ export function forgePoll(forge: string, token: string, fetchIt: typeof fetch = | ||
| 38 | 39 | const get = async (path: string): Promise<{ ok: boolean; lines: string[]; json: unknown }> => { |
| 39 | 40 | try { |
| 40 | 41 | const res = await fetchIt(`${forge}${path}`, { |
| 41 | headers: { authorization: `Bearer ${token}` }, | |
| 42 | headers: token === "" ? {} : { authorization: `Bearer ${token}` }, | |
| 42 | 43 | redirect: "manual", |
| 43 | 44 | signal: AbortSignal.timeout(5_000), |
| 44 | 45 | }); |
| @@ -101,6 +102,11 @@ export async function runDrill(run: DrillRun): Promise<number> { | ||
| 101 | 102 | const { health = [], board = [] } = watched.said; |
| 102 | 103 | const backSeconds = Math.max(0, Math.round(watched.healthyAt / 1000)); |
| 103 | 104 | const body = { domain: domain.key, seen: { health: cap(health), board: cap(board) }, backSeconds }; |
| 105 | if (run.token === "") { | |
| 106 | run.say("FORGE_TOKEN is not set; nothing was posted. The result:"); | |
| 107 | run.say(JSON.stringify({ domain: domain.key, backSeconds, posted: false, body })); | |
| 108 | return 0; | |
| 109 | } | |
| 104 | 110 | let res: Response; |
| 105 | 111 | try { |
| 106 | 112 | res = await fetch(`${run.forge}/api/v1/drills`, { |
| @@ -134,7 +140,7 @@ export interface DrillArgs { | ||
| 134 | 140 | token: string; |
| 135 | 141 | } |
| 136 | 142 | |
| 137 | const USAGE = "usage: FORGE_TOKEN=… bun scripts/drills/run.ts <domain> --forge <url> [--insecure]"; | |
| 143 | const USAGE = "usage: [FORGE_TOKEN=…] bun scripts/drills/run.ts <domain> --forge <url> [--insecure]"; | |
| 138 | 144 | const LOOPBACK = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]); |
| 139 | 145 | |
| 140 | 146 | /** The options, or the one plain line to print before exiting 2. */ |
| @@ -154,8 +160,7 @@ export function checkArgs( | ||
| 154 | 160 | return { ok: false, error: `--forge must be an http(s) URL; ${USAGE}` }; |
| 155 | 161 | } |
| 156 | 162 | const token = env.FORGE_TOKEN ?? ""; |
| 157 | if (token === "") return { ok: false, error: `FORGE_TOKEN is empty; ${USAGE}` }; | |
| 158 | if (url.protocol === "http:" && !LOOPBACK.has(url.hostname) && !insecure) { | |
| 163 | if (token !== "" && url.protocol === "http:" && !LOOPBACK.has(url.hostname) && !insecure) { | |
| 159 | 164 | return { ok: false, error: `refusing to send the admin token over plain http to ${url.hostname}; use https, or pass --insecure` }; |
| 160 | 165 | } |
| 161 | 166 | return { ok: true, args: { domain, forge: forge.replace(/\/+$/, ""), token } }; |
test/drill-run.test.ts +24 −1
| @@ -76,10 +76,33 @@ test("goes down and comes back: posts what it saw and prints the verdict", async | ||
| 76 | 76 | expect(body.seen.health).toContain("Disk is nearly full: 97%"); |
| 77 | 77 | expect(body.seen.board).toContain("The runner is stuck"); |
| 78 | 78 | expect(body.backSeconds).toBe(3); |
| 79 | expect(Object.keys(body).sort()).toEqual(["backSeconds", "domain", "seen"]); | |
| 79 | 80 | expect(h.lines.filter((l) => l.includes("The runner is stuck"))).toHaveLength(1); |
| 80 | 81 | expect(h.lines).toContain("GREEN: every phrase seen"); |
| 81 | 82 | }); |
| 82 | 83 | |
| 84 | test("without a token: prints the result, says nothing was posted, posts nothing", async () => { | |
| 85 | const state: Stub = { down: false, posts: [], refuse: false }; | |
| 86 | const lines: string[] = []; | |
| 87 | let t = 0; | |
| 88 | let sleeps = 0; | |
| 89 | const code = await runDrill({ | |
| 90 | domain: "runner", | |
| 91 | forge: stub(state), | |
| 92 | token: "", | |
| 93 | say: (l) => lines.push(l), | |
| 94 | enter: async () => { state.down = true; }, | |
| 95 | now: () => t, | |
| 96 | sleep: async (ms) => { t += ms; if (++sleeps === 3) state.down = false; }, | |
| 97 | intervalMs: 1000, | |
| 98 | ceilingMs: 60_000, | |
| 99 | }); | |
| 100 | expect(code).toBe(0); | |
| 101 | expect(state.posts).toHaveLength(0); | |
| 102 | expect(lines.some((l) => l.includes("nothing was posted"))).toBe(true); | |
| 103 | expect(JSON.parse(lines.at(-1)!)).toMatchObject({ domain: "runner", backSeconds: 3, posted: false }); | |
| 104 | }); | |
| 105 | ||
| 83 | 106 | test("stays down past the ceiling: posts nothing, exits 0, says so", async () => { |
| 84 | 107 | const state: Stub = { down: false, posts: [], refuse: false }; |
| 85 | 108 | const h = harness(state, { ceilingMs: 5_000 }); |
| @@ -126,7 +149,7 @@ test("checkArgs: each missing piece and the cleartext refusal is an error; the g | ||
| 126 | 149 | }; |
| 127 | 150 | expect(bad(["--forge", "https://f.example"], env)).toContain("domain"); |
| 128 | 151 | expect(bad(["runner"], env)).toContain("--forge"); |
| 129 | expect(bad(["runner", "--forge", "https://f.example"], { FORGE_TOKEN: "" })).toContain("FORGE_TOKEN"); | |
| 152 | expect(checkArgs(["runner", "--forge", "https://f.example"], {}).ok).toBe(true); | |
| 130 | 153 | expect(bad(["runner", "--forge", "http://forge.example"], env)).toContain("plain http"); |
| 131 | 154 | const ok = (argv: string[]) => checkArgs(argv, env); |
| 132 | 155 | expect(ok(["runner", "--forge", "http://forge.example", "--insecure"]).ok).toBe(true); |
new above
offline