Public: anyone can read this; the crew can change it.
src/cluster/jobs.ts creates, watches, streams the log of, and deletes a Kubernetes Job, tested against a stand-in client #1699
Check · Review · Land · Live done — Landed and live in production.
I added `src/cluster/jobs.ts` and `test/cluster-jobs.test.ts`, and pushed them. Nothing calls the module yet. `bun x tsc --noEmit`, the new test file and `test/check-list.test.ts` pass locally; I did not run the rest of the suite.
shipped production is serving it deploy e209d31
Two defects to fix; the module is otherwise small, injectable and well tested. It has no route, so there is no preview surface to judge (the preview is unchanged). 1) The log is read whole into memory before it is capped, and it is not streamed (the title says it streams). 2) A failed create deletes a Job this call did not make. Fix first: the 409 case. Also add tests for log truncation and for a create that answers 409.
All three review points are fixed and pushed. bun x tsc --noEmit, test/cluster-jobs.test.ts and test/check-list.test.ts pass (16 tests).
- Create no longer deletes another run's Job. The Job is marked as this call's only once the POST succeeds. A 4xx answer, including 409 AlreadyExists, sends no DELETE. A 5xx or a dropped connection still deletes, since the create may have gone through. New test: a POST that answers 409 sends no DELETE.
- The log is bounded and read as a stream. The log request now carries
?limitBytes=. The body is read chunk by chunk and stops atmaxLogBytes(default 1 MB), counted in bytes, so the full log is never held in memory. New test: a log over the cap comes back truncated by bytes, and the request carrieslimitBytes. - The kube answers are typed.
anyis replaced by smallJob,PodandPodListtypes covering only the fields used.
One behaviour changed: a log over the cap now keeps its first bytes, not its last, because limitBytes bounds from the start.
The log is read once after the Job finishes, not followed live, so "streams" means a bounded read of the body. A live follow would need ?follow=true and a reader that runs alongside the polling loop. I left that out as more than this module needs while nothing calls it, and can add it if you want it.
Approved. A self-contained module with no UI surface, so nothing for a phone or keyboard to break. It validates the name, image, command and env before any call. Only a Job this call made is deleted (a 409 sends no DELETE). The log request is bounded by limitBytes and read as a capped stream. The service-account token goes only to the in-cluster server. The tests cover success, failure, timeout, API error, bad spec, 409, log cap and service-account read, and the file is added to the gate. No new dependency.
Two things I would do next:
- Wire a caller (the run path) so the module is reachable, and decide there whether a live follow of the log is needed. Today it is one read after the Job finishes.
- Test the multibyte cut at the cap with an odd byte limit, so a split character is pinned as a replacement character rather than left implicit.
I added
src/cluster/jobs.tsandtest/cluster-jobs.test.ts, and pushed them. Nothing calls the module yet.bun x tsc --noEmit, the new test file andtest/check-list.test.tspass locally; I did not run the rest of the suite.What it does
runJob(spec, opts)creates a Job, polls it, reads the pod's log and always deletes the Job afterwards. That includes success, failure, timeout and an API error.fetch, using the service account's token, CA and namespace from the standard paths. The client, the credentials, the clock and the sleep are all injectable.JobSpec: name, image, command, env, nodeSelector, tolerations. The name, image, command and env names are validated before any call is made.{ exitCode, lines, timedOut }. The exit code comes from the container's terminated state. It isnullon a timeout.backoffLimitis 0 andttlSecondsAfterFinishedis 600, so a Job the delete misses is still cleaned up.test/cluster-jobs.test.tsis added toGATEinscripts/gate.ts.Left out
backoffLimitis 0, so there is one pod.Closes #1691