@telorun/k8s-runner 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -92
- package/dist/bundle-store.d.ts +6 -13
- package/dist/bundle-store.d.ts.map +1 -1
- package/dist/bundle-store.js +6 -18
- package/dist/bundle-store.js.map +1 -1
- package/dist/capabilities.d.ts +3 -3
- package/dist/capabilities.js +4 -4
- package/dist/capabilities.js.map +1 -1
- package/dist/config.d.ts +7 -32
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -21
- package/dist/config.js.map +1 -1
- package/dist/k8s/api-error.d.ts +34 -0
- package/dist/k8s/api-error.d.ts.map +1 -0
- package/dist/k8s/api-error.js +118 -0
- package/dist/k8s/api-error.js.map +1 -0
- package/dist/k8s/backend.d.ts.map +1 -1
- package/dist/k8s/backend.js +23 -35
- package/dist/k8s/backend.js.map +1 -1
- package/dist/k8s/client.d.ts +1 -2
- package/dist/k8s/client.d.ts.map +1 -1
- package/dist/k8s/client.js +1 -2
- package/dist/k8s/client.js.map +1 -1
- package/dist/k8s/pod-spec.d.ts +8 -9
- package/dist/k8s/pod-spec.d.ts.map +1 -1
- package/dist/k8s/pod-spec.js +19 -29
- package/dist/k8s/pod-spec.js.map +1 -1
- package/dist/k8s/pod-status.d.ts.map +1 -1
- package/dist/k8s/pod-status.js +2 -2
- package/dist/k8s/pod-status.js.map +1 -1
- package/dist/k8s/watch-session.d.ts +4 -5
- package/dist/k8s/watch-session.d.ts.map +1 -1
- package/dist/k8s/watch-session.js +8 -8
- package/dist/k8s/watch-session.js.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/src/bundle-store.ts +6 -19
- package/src/capabilities.ts +4 -4
- package/src/config.ts +20 -61
- package/src/k8s/api-error.test.ts +89 -0
- package/src/k8s/api-error.ts +128 -0
- package/src/k8s/backend.ts +34 -39
- package/src/k8s/client.ts +1 -10
- package/src/k8s/pod-spec.test.ts +57 -2
- package/src/k8s/pod-spec.ts +25 -36
- package/src/k8s/pod-status.ts +2 -2
- package/src/k8s/watch-pod-spec.test.ts +0 -1
- package/src/k8s/watch-session.ts +8 -13
- package/src/server.ts +3 -3
- package/dist/k8s/image-build.d.ts +0 -103
- package/dist/k8s/image-build.d.ts.map +0 -1
- package/dist/k8s/image-build.js +0 -427
- package/dist/k8s/image-build.js.map +0 -1
- package/src/k8s/image-build.test.ts +0 -238
- package/src/k8s/image-build.ts +0 -540
package/README.md
CHANGED
|
@@ -31,40 +31,40 @@ for status, attaches a PTY over the Pod `attach` subresource for the interactive
|
|
|
31
31
|
per-session Service + Ingress (`<sessionId>.<base-domain>`) garbage-collected via
|
|
32
32
|
an ownerReference to the Pod.
|
|
33
33
|
|
|
34
|
-
**Every session runs
|
|
35
|
-
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
staged bundle into a writable `/app` emptyDir, and the session runs `telo run
|
|
48
|
-
/app/<entry> --no-cache-write` — reading the baked deps from the read-only
|
|
49
|
-
`/telo-cache` and validating in-memory, so `readOnlyRootFilesystem` stays on with
|
|
50
|
-
nothing written to the cache. Builds are existence-checked before building and
|
|
51
|
-
single-flighted; a build failure surfaces as an actionable error carrying the
|
|
52
|
-
build pod's log tail. `RUNNER_IMAGE_REPOSITORY` is therefore **required** — the
|
|
53
|
-
runner refuses to start without a registry to build into.
|
|
34
|
+
**Every session runs the plain kernel image.** A body-fetch initContainer untars
|
|
35
|
+
the staged bundle into a writable `/app` emptyDir and the session container runs
|
|
36
|
+
`telo run /app/<entry>`, resolving its own module closure into a `/telo-cache`
|
|
37
|
+
emptyDir on the way up. `readOnlyRootFilesystem` stays on — every write lands on
|
|
38
|
+
a mounted emptyDir. The runner needs **no image registry of its own**: it builds
|
|
39
|
+
nothing, pushes nothing, and creates no Jobs.
|
|
40
|
+
|
|
41
|
+
The runner used to prebuild a self-contained per-app image with an on-cluster
|
|
42
|
+
Kaniko Job, so that a slow package registry could not stall the start path. That
|
|
43
|
+
is gone, and the cost is where it now shows: **a run session downloads its
|
|
44
|
+
closure on every start**, since the cache lives and dies with the pod. A watch
|
|
45
|
+
session pays it once and keeps the cache for the pod's life, which is the shape
|
|
46
|
+
to reach for when start latency matters.
|
|
54
47
|
|
|
55
48
|
**Coming-up progress** is reported over the `/v1` SSE stream as `progress` events
|
|
56
|
-
(`
|
|
57
|
-
created with a fast `201` carrying the `streamUrl` **before** the
|
|
58
|
-
the client connects immediately and sees
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`Running`.
|
|
49
|
+
(`provision` → `boot`) while the session is still `starting`. The session is
|
|
50
|
+
created with a fast `201` carrying the `streamUrl` **before** the pod is created,
|
|
51
|
+
so the client connects immediately and sees provisioning live; the backend then
|
|
52
|
+
runs in the background and a start failure surfaces as a terminal `failed` status
|
|
53
|
+
on the stream. The session flips to `running` when the Pod reaches `Running`.
|
|
62
54
|
|
|
63
55
|
Sandbox hardening is always on (non-root, read-only rootfs, drop-all caps, no
|
|
64
56
|
service-account token, seccomp `RuntimeDefault`); a sandbox RuntimeClass
|
|
65
|
-
(gVisor/Kata) is layered on when configured.
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
(gVisor/Kata) is layered on when configured. A controller whose install needs to
|
|
58
|
+
run scripts or build native code runs them **inside the session pod** under that
|
|
59
|
+
hardening — there is no trusted build step to run them in.
|
|
60
|
+
|
|
61
|
+
**A Kubernetes API rejection reaches the client as its status and `reason`, never
|
|
62
|
+
as the exception.** The API's error carries the full HTTP dump — the raw `Status`
|
|
63
|
+
body naming the runner's ServiceAccount, the audit id, every response header —
|
|
64
|
+
and a start failure's message travels verbatim to the client as the session's
|
|
65
|
+
terminal status. So the client is told the operation, the HTTP code and the
|
|
66
|
+
one-word reason (`Forbidden`, `NotFound`), plus who can fix a `403`; the raw
|
|
67
|
+
exception rides along as the error's `cause` and lands in the runner's log.
|
|
68
68
|
|
|
69
69
|
## Watch sessions
|
|
70
70
|
|
|
@@ -74,7 +74,7 @@ different — a **workspace that runs continuously**. One pod holds a shared
|
|
|
74
74
|
`/workspace` volume, a `workspace` container serving the editor's file routes, one
|
|
75
75
|
`app-<name>` container per application running `telo run --watch`, and optionally
|
|
76
76
|
a co-resident `agent` drawn from the `RUNNER_APPS` catalog. An edit then costs a
|
|
77
|
-
kernel reload instead of a pod: no schedule, no pull, no
|
|
77
|
+
kernel reload instead of a pod: no schedule, no pull, no module re-resolve.
|
|
78
78
|
|
|
79
79
|
Off unless `RUNNER_WATCH_SESSIONS` is set. Run sessions are entirely unaffected.
|
|
80
80
|
|
|
@@ -128,10 +128,10 @@ runner reconciles into a content-addressed ConfigMap — there is no third image
|
|
|
128
128
|
build, and a runner upgrade that changes the manifest leaves running sessions
|
|
129
129
|
mounting the one they booted with.
|
|
130
130
|
|
|
131
|
-
**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
**A watch session resolves its closure once.** It lands in the workspace volume,
|
|
132
|
+
which lives as long as the pod, so the download happens once per session and
|
|
133
|
+
every later reload resolves from local disk — where a run session, whose cache
|
|
134
|
+
dies with its pod, pays for it on every start.
|
|
135
135
|
|
|
136
136
|
**One cache for the whole session.** The runner seeds `telo-workspace.yaml` at
|
|
137
137
|
the workspace root when the session starts, and the kernel anchors its `.telo`
|
|
@@ -144,13 +144,11 @@ manifest lives outside the workspace and the walk-up would never reach the
|
|
|
144
144
|
marker. A workspace that brings its own marker keeps it — overwriting one with a
|
|
145
145
|
real `modules:` list would change what `telo release` discovers.
|
|
146
146
|
|
|
147
|
-
**Egress
|
|
148
|
-
the
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
here — the same stated dependency the build namespace already carries, one
|
|
153
|
-
namespace over.
|
|
147
|
+
**Egress lives in the session namespace.** Every session resolves its own module
|
|
148
|
+
closure, so the session namespace's NetworkPolicy has to reach the module
|
|
149
|
+
registries as well as the model provider. Core NetworkPolicy is CIDR-only and
|
|
150
|
+
registries sit behind rotating-IP CDNs, so a locked-down operator needs a CNI
|
|
151
|
+
with FQDN policy or an egress proxy here.
|
|
154
152
|
|
|
155
153
|
### Two nouns on one stream
|
|
156
154
|
|
|
@@ -288,14 +286,15 @@ outcome than a URL that silently reaches the wrong app.
|
|
|
288
286
|
| `PORT` | `8062` | HTTP listen port |
|
|
289
287
|
| `RUNNER_DISPLAY_NAME` | `Telo Runner` | Display name advertised on `/v1/capabilities` (the editor's runner label) |
|
|
290
288
|
| `RUNNER_DESCRIPTION` | `Runs the Telo application in a cloud environment` | Description advertised on `/v1/capabilities` |
|
|
291
|
-
| `RUNNER_APPS` | _(unset → no apps)_ | JSON map of operator-predefined apps launchable by name (chart: inline `apps.catalog`, or `apps.catalogSecret` referencing a Secret holding the JSON — use the Secret whenever entries embed secrets in `env`); see the docker-runner README for the entry shape, including the `port` an entry must declare to be usable as a session's co-resident `agent`. App sessions run the catalog image directly as a pod
|
|
289
|
+
| `RUNNER_APPS` | _(unset → no apps)_ | JSON map of operator-predefined apps launchable by name (chart: inline `apps.catalog`, or `apps.catalogSecret` referencing a Secret holding the JSON — use the Secret whenever entries embed secrets in `env`); see the docker-runner README for the entry shape, including the `port` an entry must declare to be usable as a session's co-resident `agent`. App sessions run the catalog image directly as a pod |
|
|
292
290
|
| `RUNNER_APP_MAX_CPU` | `500m` | CPU ceiling for predefined-app pods (separate from the anonymous-session ceiling) |
|
|
293
291
|
| `RUNNER_APP_MAX_MEMORY` | `512Mi` | Memory ceiling for predefined-app pods |
|
|
294
292
|
| `RUNNER_APP_MAX_TTL_SECONDS` | `21600` | Wall-clock TTL for predefined-app pods (agent sessions are long-lived) |
|
|
295
293
|
| `RUNNER_APP_MAX_EPHEMERAL_STORAGE` | `1Gi` | Ephemeral-storage ceiling for predefined-app pods |
|
|
296
294
|
| `RUNNER_SESSION_NAMESPACE` | `telo-sessions` | Namespace for session objects |
|
|
297
295
|
| `RUNNER_IMAGE` | _(baked at build: the CLI version for a released runner, `telorun/node:latest-slim` for a dev build)_ | Default base image; always offered in the picker and the fallback when the catalog is unreachable. Leave the chart's `session.image` empty to keep the session kernel in lockstep with the runner |
|
|
298
|
-
| `RUNNER_INIT_IMAGE` | `busybox:stable` |
|
|
296
|
+
| `RUNNER_INIT_IMAGE` | `busybox:stable` | Bundle-fetch initContainer image (wget + tar) |
|
|
297
|
+
| `RUNNER_IMAGE_PULL_SECRET` | _(unset)_ | dockerconfig Secret (in `telo-sessions`) the kubelet pulls session images with — needed only for a kernel or catalog image in a private registry |
|
|
299
298
|
| `RUNNER_RUNTIME_CLASS` | _(unset → runc)_ | Sandbox RuntimeClass (gvisor/kata) |
|
|
300
299
|
| `SESSION_INGRESS_BASE_DOMAIN` | _(unset → logs-only)_ | Wildcard base for per-session ingress |
|
|
301
300
|
| `SESSION_INGRESS_TLS_SECRET` | _(unset → no TLS block)_ | `kubernetes.io/tls` Secret (in `telo-sessions`) the session Ingress presents; must cover `*.<base-domain>`. Set for Cloudflare Full (Strict) / any origin-cert upstream |
|
|
@@ -327,12 +326,9 @@ against the same list, so a client that skips the editor can't widen the set.
|
|
|
327
326
|
unreachable. Disable the catalog to lock `image` to `RUNNER_IMAGE`.
|
|
328
327
|
|
|
329
328
|
Pinned tags (e.g. `0.30.1-slim`) are immutable. A picked **moving** tag like
|
|
330
|
-
`latest-slim` only refreshes when the session's `pullPolicy` is `always
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
string — is reused). Movement detection reads the digest from Docker Hub, so a
|
|
334
|
-
base hosted elsewhere (GHCR, a private registry) can't be tracked — `always`
|
|
335
|
-
degrades to reusing the cached build for it, same as `missing` / `never`.
|
|
329
|
+
`latest-slim` only refreshes when the session's `pullPolicy` is `always`, which
|
|
330
|
+
is the Pod's own `imagePullPolicy`: the kubelet re-pulls the tag on every
|
|
331
|
+
session. `missing` and `never` reuse whatever that node already cached.
|
|
336
332
|
|
|
337
333
|
| Env | Default | Purpose |
|
|
338
334
|
| --- | --- | --- |
|
|
@@ -346,53 +342,38 @@ degrades to reusing the cached build for it, same as `missing` / `never`.
|
|
|
346
342
|
| `RUNNER_BASE_IMAGE_LIMIT` | `20` | Cap on advertised tags (newest first) |
|
|
347
343
|
| `RUNNER_BASE_IMAGE_REFRESH_SECONDS` | `3600` | Catalog re-fetch cadence |
|
|
348
344
|
|
|
349
|
-
### Image build (required)
|
|
350
|
-
|
|
351
|
-
| Env | Default | Purpose |
|
|
352
|
-
| --- | --- | --- |
|
|
353
|
-
| `RUNNER_IMAGE_REPOSITORY` | _(required)_ | Registry repo for per-app images; tag = bundle hash |
|
|
354
|
-
| `RUNNER_BUILD_NAMESPACE` | `telo-builds` | Namespace the trusted Kaniko build Jobs run in |
|
|
355
|
-
| `RUNNER_BUILDER_IMAGE` | `gcr.io/kaniko-project/executor:latest` | Image builder |
|
|
356
|
-
| `RUNNER_BUILD_TIMEOUT_SECONDS` | `600` | Build Job deadline / wait budget |
|
|
357
|
-
| `RUNNER_REGISTRY_INSECURE` | `false` | Push/pull over HTTP / self-signed |
|
|
358
|
-
| `RUNNER_REGISTRY_API_URL` | _(unset → always build)_ | HTTP(S) base for the manifest existence check (authenticated via the push Secret) |
|
|
359
|
-
| `RUNNER_REGISTRY_PUSH_SECRET` | _(unset)_ | dockerconfig Secret (in `telo-builds`) Kaniko pushes with; also authenticates the existence check |
|
|
360
|
-
| `RUNNER_IMAGE_PULL_SECRET` | _(unset)_ | dockerconfig Secret (in `telo-sessions`) the kubelet pulls per-app images with |
|
|
361
|
-
|
|
362
345
|
## Deploy (Helm)
|
|
363
346
|
|
|
364
|
-
The
|
|
365
|
-
|
|
347
|
+
The chart is published as an OCI artifact at
|
|
348
|
+
`oci://ghcr.io/telorun/charts/k8s-runner`:
|
|
366
349
|
|
|
367
350
|
```bash
|
|
368
|
-
helm install telo-runner
|
|
369
|
-
--set
|
|
370
|
-
--set-file registry.dockerconfigjson=./dockerconfig.json \ # private-registry auth
|
|
351
|
+
helm install telo-runner oci://ghcr.io/telorun/charts/k8s-runner \
|
|
352
|
+
--set watch.enabled=true \
|
|
371
353
|
--set session.runtimeClass=gvisor
|
|
372
354
|
```
|
|
373
355
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
356
|
+
Helm resolves the newest release when no `--version` is given; pass one to pin.
|
|
357
|
+
From a checkout, `./chart` works the same way and is what you want when changing
|
|
358
|
+
the chart itself.
|
|
359
|
+
|
|
360
|
+
**The chart's version IS the runner's.** `version` and `appVersion` both carry
|
|
361
|
+
`@telorun/k8s-runner`'s package version, stamped in the release PR and checked on
|
|
362
|
+
every PR — so chart `0.13.0` installs runner image `0.13.0`, with no lookup
|
|
363
|
+
table. That is also why `image.tag` defaults to empty and resolves to the chart's
|
|
364
|
+
own `appVersion` rather than to `latest`: `--version 0.13.0` has to mean one
|
|
365
|
+
thing, where a floating tag meant whatever it had moved to overnight.
|
|
383
366
|
|
|
384
367
|
The chart provisions the static scaffolding: the runner Deployment (single
|
|
385
368
|
replica — the registry is in-memory and the runner reaps orphaned pods on boot),
|
|
386
|
-
Service, scoped RBAC, the `telo-runner`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
369
|
+
Service, scoped RBAC, the `telo-runner` and restricted-PSS `telo-sessions`
|
|
370
|
+
namespaces, a `ResourceQuota`, and the session pod-to-pod NetworkPolicy. The
|
|
371
|
+
runner creates per-session objects at runtime. Nothing else is required — no
|
|
372
|
+
image registry, no build namespace, no push credentials.
|
|
390
373
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
cloud registry is simpler. Installing with neither a `build.repository` nor
|
|
395
|
-
`registry.enabled` is rejected at template time.
|
|
374
|
+
For a kernel image or an operator catalog image held in a **private registry**,
|
|
375
|
+
create the dockerconfigjson Secret in `telo-sessions` yourself and point
|
|
376
|
+
`session.imagePullSecret` at it; the kubelet pulls with it.
|
|
396
377
|
|
|
397
378
|
### Origin TLS (Cloudflare et al.)
|
|
398
379
|
|
|
@@ -416,18 +397,17 @@ Either wires `SESSION_INGRESS_TLS_SECRET`, and the runner stamps a `spec.tls`
|
|
|
416
397
|
block on every session Ingress. Leave all three empty to skip TLS at the origin
|
|
417
398
|
(terminated entirely upstream).
|
|
418
399
|
|
|
419
|
-
> **Egress
|
|
420
|
-
>
|
|
421
|
-
>
|
|
422
|
-
> cannot express the package-registry FQDN allowlist the build namespace needs —
|
|
423
|
-
> use a CNI with FQDN policy (Cilium) or an egress proxy to tighten it.
|
|
400
|
+
> **Egress note.** Core NetworkPolicy is CIDR-only and cannot express the
|
|
401
|
+
> package-registry FQDN allowlist the session namespace needs — use a CNI with
|
|
402
|
+
> FQDN policy (Cilium) or an egress proxy to tighten it.
|
|
424
403
|
|
|
425
404
|
## Development
|
|
426
405
|
|
|
427
406
|
```bash
|
|
428
407
|
pnpm --filter @telorun/k8s-runner build # tsc
|
|
429
|
-
pnpm --filter @telorun/k8s-runner test # vitest (limits clamp, tar,
|
|
408
|
+
pnpm --filter @telorun/k8s-runner test # vitest (pod specs, limits clamp, tar, API errors)
|
|
430
409
|
```
|
|
431
410
|
|
|
432
411
|
The Kubernetes backend can't be exercised without a cluster; unit tests cover the
|
|
433
|
-
backend-independent logic (limit clamping, the tar writer,
|
|
412
|
+
backend-independent logic (the pod specs, limit clamping, the tar writer, and
|
|
413
|
+
what an API rejection is allowed to tell a client).
|
package/dist/bundle-store.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
2
|
import type { RunBundle } from "@telorun/runner-core";
|
|
3
3
|
/**
|
|
4
|
-
* Holds
|
|
5
|
-
* cluster-internal URL the
|
|
6
|
-
* per-
|
|
4
|
+
* Holds session bundles in memory and serves them over a tokenized,
|
|
5
|
+
* cluster-internal URL the session pod's initContainer fetches once. The
|
|
6
|
+
* per-session unguessable token prevents cross-session disclosure; the entry is
|
|
7
7
|
* dropped after first fetch (or explicitly on cleanup).
|
|
8
8
|
*/
|
|
9
9
|
export declare class BundleStore {
|
|
@@ -11,16 +11,9 @@ export declare class BundleStore {
|
|
|
11
11
|
private readonly bundles;
|
|
12
12
|
constructor(selfUrl: string);
|
|
13
13
|
/**
|
|
14
|
-
* Stages
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
stageBuildContext(id: string, bundle: RunBundle, dockerfile: string): Promise<string>;
|
|
19
|
-
/**
|
|
20
|
-
* Stages the raw session bundle (no Dockerfile) and returns the tokenized,
|
|
21
|
-
* single-use URL the session Pod's body-delivery initContainer fetches into
|
|
22
|
-
* `/app`. Keyed by `sessionId`. The image is keyed only on the dependency
|
|
23
|
-
* closure, so the per-session body is delivered here at boot rather than baked.
|
|
14
|
+
* Stages the session bundle and returns the tokenized, single-use URL the
|
|
15
|
+
* session Pod's body-delivery initContainer fetches into `/app`. Keyed by
|
|
16
|
+
* `sessionId`.
|
|
24
17
|
*/
|
|
25
18
|
stageSessionBundle(sessionId: string, bundle: RunBundle): Promise<string>;
|
|
26
19
|
private stage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle-store.d.ts","sourceRoot":"","sources":["../src/bundle-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AActD;;;;;GAKG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE9B,OAAO,EAAE,MAAM;IAE5C;;;;OAIG;IACG,
|
|
1
|
+
{"version":3,"file":"bundle-store.d.ts","sourceRoot":"","sources":["../src/bundle-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AActD;;;;;GAKG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE9B,OAAO,EAAE,MAAM;IAE5C;;;;OAIG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;YAIjE,KAAK;IAUnB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAM7B,OAAO,CAAC,IAAI;IASZ,yEAAyE;IACzE,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;CAiB1C"}
|
package/dist/bundle-store.js
CHANGED
|
@@ -4,9 +4,9 @@ import { makeBundleTarGz } from "./tar.js";
|
|
|
4
4
|
* stuck/never-started Pod can't grow the store unbounded. */
|
|
5
5
|
const STAGING_TTL_MS = 5 * 60 * 1000;
|
|
6
6
|
/**
|
|
7
|
-
* Holds
|
|
8
|
-
* cluster-internal URL the
|
|
9
|
-
* per-
|
|
7
|
+
* Holds session bundles in memory and serves them over a tokenized,
|
|
8
|
+
* cluster-internal URL the session pod's initContainer fetches once. The
|
|
9
|
+
* per-session unguessable token prevents cross-session disclosure; the entry is
|
|
10
10
|
* dropped after first fetch (or explicitly on cleanup).
|
|
11
11
|
*/
|
|
12
12
|
export class BundleStore {
|
|
@@ -16,21 +16,9 @@ export class BundleStore {
|
|
|
16
16
|
this.selfUrl = selfUrl;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Stages
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
async stageBuildContext(id, bundle, dockerfile) {
|
|
24
|
-
return this.stage(id, {
|
|
25
|
-
entryRelativePath: bundle.entryRelativePath,
|
|
26
|
-
files: [...bundle.files, { relativePath: "Dockerfile", contents: dockerfile }],
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Stages the raw session bundle (no Dockerfile) and returns the tokenized,
|
|
31
|
-
* single-use URL the session Pod's body-delivery initContainer fetches into
|
|
32
|
-
* `/app`. Keyed by `sessionId`. The image is keyed only on the dependency
|
|
33
|
-
* closure, so the per-session body is delivered here at boot rather than baked.
|
|
19
|
+
* Stages the session bundle and returns the tokenized, single-use URL the
|
|
20
|
+
* session Pod's body-delivery initContainer fetches into `/app`. Keyed by
|
|
21
|
+
* `sessionId`.
|
|
34
22
|
*/
|
|
35
23
|
async stageSessionBundle(sessionId, bundle) {
|
|
36
24
|
return this.stage(sessionId, bundle);
|
package/dist/bundle-store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle-store.js","sourceRoot":"","sources":["../src/bundle-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK3D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAQ3C;8DAC8D;AAC9D,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAErC;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAGO;IAFZ,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAE3D,YAA6B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;IAEhD;;;;OAIG;IACH,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"bundle-store.js","sourceRoot":"","sources":["../src/bundle-store.ts"],"names":[],"mappings":"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"}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -19,9 +19,9 @@ export interface KubernetesRunnerCapabilitiesOptions {
|
|
|
19
19
|
* studio renders an editable dropdown constrained to that allowlist — which the
|
|
20
20
|
* session route re-validates server-side. Without a catalog (disabled /
|
|
21
21
|
* first-fetch failure) the list collapses to the single `defaultImage` (locked
|
|
22
|
-
* via `enforced`). `pullPolicy` is client-editable
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* via `enforced`). `pullPolicy` is client-editable and is the pod's own
|
|
23
|
+
* `imagePullPolicy`: `always` re-pulls the kernel image, which is how a picked
|
|
24
|
+
* moving tag like `latest-slim` stays current.
|
|
25
25
|
*
|
|
26
26
|
* `terms`, when set (operator-provided via RUNNER_TERMS_*), are enforced: a
|
|
27
27
|
* session won't start until the client acknowledges the current version. */
|
package/dist/capabilities.js
CHANGED
|
@@ -5,9 +5,9 @@ import { sessionConfigSchema, } from "@telorun/runner-core";
|
|
|
5
5
|
* studio renders an editable dropdown constrained to that allowlist — which the
|
|
6
6
|
* session route re-validates server-side. Without a catalog (disabled /
|
|
7
7
|
* first-fetch failure) the list collapses to the single `defaultImage` (locked
|
|
8
|
-
* via `enforced`). `pullPolicy` is client-editable
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* via `enforced`). `pullPolicy` is client-editable and is the pod's own
|
|
9
|
+
* `imagePullPolicy`: `always` re-pulls the kernel image, which is how a picked
|
|
10
|
+
* moving tag like `latest-slim` stays current.
|
|
11
11
|
*
|
|
12
12
|
* `terms`, when set (operator-provided via RUNNER_TERMS_*), are enforced: a
|
|
13
13
|
* session won't start until the client acknowledges the current version. */
|
|
@@ -30,7 +30,7 @@ export function kubernetesRunnerCapabilities(opts) {
|
|
|
30
30
|
imageDefault: defaultImage,
|
|
31
31
|
enforced: true,
|
|
32
32
|
imageEnum,
|
|
33
|
-
pullPolicyDescription: "
|
|
33
|
+
pullPolicyDescription: "Kernel-image freshness, applied as the Pod's imagePullPolicy. `always` re-pulls on every session, so a moving tag like `latest-slim` picks up a new release. `missing` pulls only when the node has no copy. `never` never pulls at all — on a node that has not already cached the image the session fails to start (ErrImageNeverPull), so it suits a cluster that pre-loads its images and nothing else.",
|
|
34
34
|
}),
|
|
35
35
|
},
|
|
36
36
|
terms,
|
package/dist/capabilities.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAiB9B;;;;;;;;;;;6EAW6E;AAC7E,MAAM,UAAU,4BAA4B,CAC1C,IAAyC;IAEzC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC1E,OAAO;QACL,WAAW;QACX,WAAW;QACX,QAAQ,EAAE;YACR,qEAAqE;YACrE,0EAA0E;YAC1E,kCAAkC;YAClC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;YACtB,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E;QACD,MAAM,EAAE;YACN,MAAM,EAAE,mBAAmB,CAAC;gBAC1B,YAAY,EAAE,YAAY;gBAC1B,QAAQ,EAAE,IAAI;gBACd,SAAS;gBACT,qBAAqB,EACnB,
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAiB9B;;;;;;;;;;;6EAW6E;AAC7E,MAAM,UAAU,4BAA4B,CAC1C,IAAyC;IAEzC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC1E,OAAO;QACL,WAAW;QACX,WAAW;QACX,QAAQ,EAAE;YACR,qEAAqE;YACrE,0EAA0E;YAC1E,kCAAkC;YAClC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;YACtB,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E;QACD,MAAM,EAAE;YACN,MAAM,EAAE,mBAAmB,CAAC;gBAC1B,YAAY,EAAE,YAAY;gBAC1B,QAAQ,EAAE,IAAI;gBACd,SAAS;gBACT,qBAAqB,EACnB,6YAA6Y;aAChZ,CAAC;SACH;QACD,KAAK;KACN,CAAC;AACJ,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -34,33 +34,6 @@ export interface LimitCeilings {
|
|
|
34
34
|
/** Per-Pod ephemeral-storage limit, e.g. "512Mi". */
|
|
35
35
|
ephemeralStorage: string;
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
* On-cluster image-build settings. The runner ALWAYS prebuilds a self-contained
|
|
39
|
-
* per-app image (controllers + module manifests baked in via `telo install`) and
|
|
40
|
-
* the session pod runs it directly — there is no in-pod install fallback, so a
|
|
41
|
-
* registry repository (`RUNNER_IMAGE_REPOSITORY`) is required.
|
|
42
|
-
*/
|
|
43
|
-
export interface ImageBuildConfig {
|
|
44
|
-
/** Registry repository the per-app image is pushed to / pulled from; the tag
|
|
45
|
-
* is the bundle content hash (e.g. `registry.telo-runner.svc:5000/telo-sessions`). */
|
|
46
|
-
repository: string;
|
|
47
|
-
/** Namespace the trusted Kaniko build Jobs run in (registry egress lives here). */
|
|
48
|
-
namespace: string;
|
|
49
|
-
/** Builder image — Kaniko (or a compatible `--context`/`--destination` executor). */
|
|
50
|
-
builderImage: string;
|
|
51
|
-
/** Build Job `activeDeadlineSeconds` and the runner's wait budget. */
|
|
52
|
-
timeoutSeconds: number;
|
|
53
|
-
/** Push/pull to an insecure (HTTP / self-signed) registry — the in-cluster default. */
|
|
54
|
-
insecureRegistry: boolean;
|
|
55
|
-
/** HTTP(S) base for a best-effort manifest existence check (skip build on hit);
|
|
56
|
-
* undefined → always build. */
|
|
57
|
-
registryApiUrl?: string;
|
|
58
|
-
/** Optional dockerconfig Secret (in the build namespace) Kaniko pushes with. */
|
|
59
|
-
pushSecretName?: string;
|
|
60
|
-
/** Optional dockerconfig Secret (in the session namespace) the kubelet uses to
|
|
61
|
-
* pull per-app images from a private registry. */
|
|
62
|
-
imagePullSecret?: string;
|
|
63
|
-
}
|
|
64
37
|
export interface K8sRunnerConfig extends RunnerCoreConfig {
|
|
65
38
|
/** Identity advertised on `/v1/capabilities` — studio labels the runner
|
|
66
39
|
* with these. */
|
|
@@ -72,8 +45,12 @@ export interface K8sRunnerConfig extends RunnerCoreConfig {
|
|
|
72
45
|
* image as `RUNNER_IMAGE` at build time (the kernel version the runner was
|
|
73
46
|
* built against); the literal fallback below is for a source checkout only. */
|
|
74
47
|
defaultImage: string;
|
|
75
|
-
/** Small image for the bundle
|
|
48
|
+
/** Small image for the bundle-fetch initContainer (needs wget + tar). */
|
|
76
49
|
initImage: string;
|
|
50
|
+
/** Optional dockerconfig Secret (in the session namespace) the kubelet pulls
|
|
51
|
+
* session images with — the kernel image, and any operator catalog image
|
|
52
|
+
* (`RUNNER_APPS`) held in a private registry. */
|
|
53
|
+
imagePullSecret?: string;
|
|
77
54
|
/** Optional sandbox RuntimeClass (gvisor/kata). Unset → cluster default (runc). */
|
|
78
55
|
runtimeClass?: string;
|
|
79
56
|
/** Wildcard base domain for per-session ingress; unset → logs-only. */
|
|
@@ -84,10 +61,8 @@ export interface K8sRunnerConfig extends RunnerCoreConfig {
|
|
|
84
61
|
* Ingress presents so an upstream (e.g. Cloudflare Full (Strict)) can validate
|
|
85
62
|
* the origin. Must cover the wildcard `*.<sessionIngressBaseDomain>`. Unset → no TLS block. */
|
|
86
63
|
sessionIngressTlsSecretName?: string;
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
/** Runner's own in-cluster base URL, used to build the bundle/build-context
|
|
90
|
-
* fetch URL the initContainer curls (e.g. http://k8s-runner.telo-runner:8062). */
|
|
64
|
+
/** Runner's own in-cluster base URL, used to build the bundle fetch URL the
|
|
65
|
+
* initContainer curls (e.g. http://k8s-runner.telo-runner:8062). */
|
|
91
66
|
selfUrl: string;
|
|
92
67
|
/** Label applied to every session object, used for orphan reaping. */
|
|
93
68
|
managedByLabel: string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD;sBACkB;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;IACzB;;oFAEgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;sDAEkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;oGAEgG;IAChG,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC;yEACqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB;;;6BAGyB;IACzB,SAAS,EAAE,aAAa,CAAC;IACzB,+EAA+E;IAC/E,gBAAgB,EAAE,sBAAsB,CAAC;CAC1C;AA0CD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,eAAe,CAmD3E"}
|
package/dist/config.js
CHANGED
|
@@ -34,23 +34,6 @@ export function loadK8sRunnerConfig(env) {
|
|
|
34
34
|
throw new RunnerConfigError("RUNNER_SELF_URL env var is required. Set it to the runner's in-cluster base URL " +
|
|
35
35
|
"(e.g. http://k8s-runner.telo-runner.svc:8062) so session initContainers can fetch the bundle.");
|
|
36
36
|
}
|
|
37
|
-
const repository = env.RUNNER_IMAGE_REPOSITORY?.trim();
|
|
38
|
-
if (!repository) {
|
|
39
|
-
throw new RunnerConfigError("RUNNER_IMAGE_REPOSITORY env var is required. The runner prebuilds a per-app session image for " +
|
|
40
|
-
"every run (there is no in-pod install fallback); set it to the registry repository the built " +
|
|
41
|
-
"images are pushed to / pulled from (e.g. registry.example.com/telo-sessions). The tag is the " +
|
|
42
|
-
"bundle content hash.");
|
|
43
|
-
}
|
|
44
|
-
const build = {
|
|
45
|
-
repository: repository.replace(/\/+$/, ""),
|
|
46
|
-
namespace: env.RUNNER_BUILD_NAMESPACE?.trim() || "telo-builds",
|
|
47
|
-
builderImage: env.RUNNER_BUILDER_IMAGE?.trim() || "gcr.io/kaniko-project/executor:latest",
|
|
48
|
-
timeoutSeconds: parsePositiveInt(env.RUNNER_BUILD_TIMEOUT_SECONDS, 600, "RUNNER_BUILD_TIMEOUT_SECONDS"),
|
|
49
|
-
insecureRegistry: env.RUNNER_REGISTRY_INSECURE?.trim() === "true",
|
|
50
|
-
registryApiUrl: env.RUNNER_REGISTRY_API_URL?.trim() || undefined,
|
|
51
|
-
pushSecretName: env.RUNNER_REGISTRY_PUSH_SECRET?.trim() || undefined,
|
|
52
|
-
imagePullSecret: env.RUNNER_IMAGE_PULL_SECRET?.trim() || undefined,
|
|
53
|
-
};
|
|
54
37
|
return {
|
|
55
38
|
...loadCoreConfig(env, { port: DEFAULT_PORT }),
|
|
56
39
|
displayName: env.RUNNER_DISPLAY_NAME?.trim() || "Telo Runner",
|
|
@@ -58,18 +41,27 @@ export function loadK8sRunnerConfig(env) {
|
|
|
58
41
|
sessionNamespace: env.RUNNER_SESSION_NAMESPACE?.trim() || "telo-sessions",
|
|
59
42
|
defaultImage: env.RUNNER_IMAGE?.trim() || "telorun/node:latest-slim",
|
|
60
43
|
initImage: env.RUNNER_INIT_IMAGE?.trim() || "busybox:stable",
|
|
44
|
+
imagePullSecret: env.RUNNER_IMAGE_PULL_SECRET?.trim() || undefined,
|
|
61
45
|
runtimeClass: env.RUNNER_RUNTIME_CLASS?.trim() || undefined,
|
|
62
46
|
sessionIngressBaseDomain: env.SESSION_INGRESS_BASE_DOMAIN?.trim() || undefined,
|
|
63
47
|
sessionIngressClassName: env.SESSION_INGRESS_CLASS?.trim() || undefined,
|
|
64
48
|
sessionIngressTlsSecretName: env.SESSION_INGRESS_TLS_SECRET?.trim() || undefined,
|
|
65
|
-
build,
|
|
66
49
|
selfUrl: selfUrl.replace(/\/+$/, ""),
|
|
67
50
|
managedByLabel: env.RUNNER_MANAGED_BY?.trim() || "telo-k8s-runner",
|
|
51
|
+
// Sized for a pod that RESOLVES ITS OWN module closure. They used to be
|
|
52
|
+
// 50m / 100Mi / 512Mi, which described a different workload: the closure
|
|
53
|
+
// arrived in image layers, so the pod only ran it. It now downloads,
|
|
54
|
+
// unpacks and resolves it in-pod, into an emptyDir that counts against
|
|
55
|
+
// ephemeral-storage — so the old numbers meant an OOMKill on the memory
|
|
56
|
+
// ceiling and an eviction on the storage one, for the ordinary case. The
|
|
57
|
+
// watch path already ran this workload and already carried the roomier
|
|
58
|
+
// ceiling; these match it. `appLimits` stays separate: it still differs in
|
|
59
|
+
// TTL, and both remain the operator's policy to tighten.
|
|
68
60
|
limits: {
|
|
69
|
-
cpu: env.RUNNER_MAX_CPU?.trim() || "
|
|
70
|
-
memory: env.RUNNER_MAX_MEMORY?.trim() || "
|
|
61
|
+
cpu: env.RUNNER_MAX_CPU?.trim() || "500m",
|
|
62
|
+
memory: env.RUNNER_MAX_MEMORY?.trim() || "512Mi",
|
|
71
63
|
ttlSeconds: parsePositiveInt(env.RUNNER_MAX_TTL_SECONDS, 3600, "RUNNER_MAX_TTL_SECONDS"),
|
|
72
|
-
ephemeralStorage: env.RUNNER_MAX_EPHEMERAL_STORAGE?.trim() || "
|
|
64
|
+
ephemeralStorage: env.RUNNER_MAX_EPHEMERAL_STORAGE?.trim() || "1Gi",
|
|
73
65
|
},
|
|
74
66
|
appLimits: {
|
|
75
67
|
cpu: env.RUNNER_APP_MAX_CPU?.trim() || "500m",
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,iBAAiB,GAGlB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,iBAAiB,GAGlB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAgF7B,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,6EAA6E;AAC7E,SAAS,aAAa,CAAC,GAAuB,EAAE,KAAa;IAC3D,MAAM,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CACzB,GAAG,KAAK,uCAAwC,GAAa,CAAC,OAAO,EAAE,CACxE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,GAAsB;IACxD,OAAO;QACL,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,iCAAiC,EAAE,IAAI,EAAE,mCAAmC,CAAC;QACpG,UAAU,EAAE,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,cAAc;QAChE,MAAM,EAAE;YACN,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE,+BAA+B,CAAC;YAC/F,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE,+BAA+B,CAAC;YAC/F,iBAAiB,EAAE,SAAS,CAC1B,GAAG,CAAC,oCAAoC,EACxC,IAAI,EACJ,sCAAsC,CACvC;YACD,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,yBAAyB,EAAE,2BAA2B,CAAC;YAClF,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,yBAAyB,EAAE,2BAA2B,CAAC;SACnF;QACD,KAAK,EAAE,gBAAgB,CAAC,GAAG,CAAC,uBAAuB,EAAE,EAAE,EAAE,yBAAyB,CAAC;QACnF,iBAAiB,EACf,gBAAgB,CACd,GAAG,CAAC,iCAAiC,EACrC,IAAI,EACJ,mCAAmC,CACpC,GAAG,IAAI;KACX,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAsB;IACxD,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CACzB,kFAAkF;YAChF,+FAA+F,CAClG,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,cAAc,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;QAC9C,WAAW,EAAE,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,IAAI,aAAa;QAC7D,WAAW,EACT,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,kDAAkD;QACtF,gBAAgB,EAAE,GAAG,CAAC,wBAAwB,EAAE,IAAI,EAAE,IAAI,eAAe;QACzE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,0BAA0B;QACpE,SAAS,EAAE,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,gBAAgB;QAC5D,eAAe,EAAE,GAAG,CAAC,wBAAwB,EAAE,IAAI,EAAE,IAAI,SAAS;QAClE,YAAY,EAAE,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,SAAS;QAC3D,wBAAwB,EAAE,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,IAAI,SAAS;QAC9E,uBAAuB,EAAE,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,IAAI,SAAS;QACvE,2BAA2B,EAAE,GAAG,CAAC,0BAA0B,EAAE,IAAI,EAAE,IAAI,SAAS;QAChF,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,cAAc,EAAE,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,iBAAiB;QAClE,wEAAwE;QACxE,yEAAyE;QACzE,qEAAqE;QACrE,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,yDAAyD;QACzD,MAAM,EAAE;YACN,GAAG,EAAE,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,MAAM;YACzC,MAAM,EAAE,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,OAAO;YAChD,UAAU,EAAE,gBAAgB,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,wBAAwB,CAAC;YACxF,gBAAgB,EAAE,GAAG,CAAC,4BAA4B,EAAE,IAAI,EAAE,IAAI,KAAK;SACpE;QACD,SAAS,EAAE;YACT,GAAG,EAAE,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,MAAM;YAC7C,MAAM,EAAE,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,IAAI,OAAO;YACpD,UAAU,EAAE,gBAAgB,CAC1B,GAAG,CAAC,0BAA0B,EAC9B,KAAK,EACL,4BAA4B,CAC7B;YACD,gBAAgB,EAAE,GAAG,CAAC,gCAAgC,EAAE,IAAI,EAAE,IAAI,KAAK;SACxE;QACD,gBAAgB,EAAE,0BAA0B,CAAC,GAAG,CAAC;KAClD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SessionStartError, type StartFailureStage } from "@telorun/runner-core";
|
|
2
|
+
/**
|
|
3
|
+
* A Kubernetes API rejection, phrased for the person who clicked Run.
|
|
4
|
+
*
|
|
5
|
+
* `ApiException.message` is a full HTTP dump — status line, the raw `Status`
|
|
6
|
+
* body and every response header — and a start failure's message travels
|
|
7
|
+
* verbatim to the client as the session's terminal `failed` status. That put
|
|
8
|
+
* the runner's ServiceAccount name, the request's audit id and its flowschema
|
|
9
|
+
* UIDs on an end user's screen, none of which they can act on.
|
|
10
|
+
*
|
|
11
|
+
* So the client sees the operation, the HTTP status and the API's own one-word
|
|
12
|
+
* `reason` — enough for an operator to know what was refused — and never the
|
|
13
|
+
* body or the headers. The raw exception rides along as the error's `cause`,
|
|
14
|
+
* which the runner's log serializer records, so nothing is swallowed: the detail
|
|
15
|
+
* moves to the log, it does not disappear.
|
|
16
|
+
*/
|
|
17
|
+
export declare function apiFailure(err: unknown, stage: StartFailureStage, action: string): SessionStartError;
|
|
18
|
+
/** Attach the original error as `cause` so a log serializer can reach it.
|
|
19
|
+
* Every wrap on a failure path goes through here — a rewrap that drops the
|
|
20
|
+
* cause silently undoes the whole point of summarizing the message. */
|
|
21
|
+
export declare function withCause<E extends Error>(error: E, cause: unknown): E;
|
|
22
|
+
/** The client-safe half of a Kubernetes API error: what was refused, and who
|
|
23
|
+
* can act on it — never the `Status` message, which names cluster identities. */
|
|
24
|
+
export declare function apiReason(err: unknown): string;
|
|
25
|
+
/**
|
|
26
|
+
* The HTTP status an error carries, or `undefined` when it carries none.
|
|
27
|
+
*
|
|
28
|
+
* The number check is the whole point: `code` is also where Node puts a system
|
|
29
|
+
* error's string code and where a `DOMException` puts its legacy numeric one, so
|
|
30
|
+
* an unreachable apiserver and an aborted request both arrive here looking like
|
|
31
|
+
* a status. Only a value in the HTTP range is one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function statusCode(err: unknown): number | undefined;
|
|
34
|
+
//# sourceMappingURL=api-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/k8s/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,MAAM,GACb,iBAAiB,CAKnB;AAED;;wEAEwE;AACxE,wBAAgB,SAAS,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAGtE;AAED;kFACkF;AAClF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAoB9C;AAuDD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM3D"}
|