@telorun/k8s-runner 0.13.0 → 0.15.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 +166 -107
- 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 +47 -37
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +64 -25
- 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 +4 -0
- package/dist/k8s/backend.d.ts.map +1 -1
- package/dist/k8s/backend.js +77 -51
- package/dist/k8s/backend.js.map +1 -1
- package/dist/k8s/client.d.ts +7 -2
- package/dist/k8s/client.d.ts.map +1 -1
- package/dist/k8s/client.js +3 -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 +5 -0
- package/dist/k8s/pod-status.d.ts.map +1 -1
- package/dist/k8s/pod-status.js +9 -2
- package/dist/k8s/pod-status.js.map +1 -1
- package/dist/k8s/routing/create-or-replace.d.ts +5 -0
- package/dist/k8s/routing/create-or-replace.d.ts.map +1 -0
- package/dist/k8s/routing/create-or-replace.js +15 -0
- package/dist/k8s/routing/create-or-replace.js.map +1 -0
- package/dist/k8s/routing/gateway-router.d.ts +5 -0
- package/dist/k8s/routing/gateway-router.d.ts.map +1 -0
- package/dist/k8s/routing/gateway-router.js +178 -0
- package/dist/k8s/routing/gateway-router.js.map +1 -0
- package/dist/k8s/routing/index.d.ts +24 -0
- package/dist/k8s/routing/index.d.ts.map +1 -0
- package/dist/k8s/routing/index.js +27 -0
- package/dist/k8s/routing/index.js.map +1 -0
- package/dist/k8s/routing/ingress-router.d.ts +13 -0
- package/dist/k8s/routing/ingress-router.d.ts.map +1 -0
- package/dist/k8s/routing/ingress-router.js +125 -0
- package/dist/k8s/routing/ingress-router.js.map +1 -0
- package/dist/k8s/routing/route-health.d.ts +34 -0
- package/dist/k8s/routing/route-health.d.ts.map +1 -0
- package/dist/k8s/routing/route-health.js +77 -0
- package/dist/k8s/routing/route-health.js.map +1 -0
- package/dist/k8s/routing/routing-mode.d.ts +45 -0
- package/dist/k8s/routing/routing-mode.d.ts.map +1 -0
- package/dist/k8s/routing/routing-mode.js +141 -0
- package/dist/k8s/routing/routing-mode.js.map +1 -0
- package/dist/k8s/routing/session-endpoints.d.ts +25 -0
- package/dist/k8s/routing/session-endpoints.d.ts.map +1 -0
- package/dist/k8s/routing/session-endpoints.js +36 -0
- package/dist/k8s/routing/session-endpoints.js.map +1 -0
- package/dist/k8s/routing/session-router.d.ts +57 -0
- package/dist/k8s/routing/session-router.d.ts.map +1 -0
- package/dist/k8s/routing/session-router.js +2 -0
- package/dist/k8s/routing/session-router.js.map +1 -0
- package/dist/k8s/routing/session-service.d.ts +14 -0
- package/dist/k8s/routing/session-service.d.ts.map +1 -0
- package/dist/k8s/routing/session-service.js +42 -0
- package/dist/k8s/routing/session-service.js.map +1 -0
- package/dist/k8s/watch-session.d.ts +7 -5
- package/dist/k8s/watch-session.d.ts.map +1 -1
- package/dist/k8s/watch-session.js +90 -83
- package/dist/k8s/watch-session.js.map +1 -1
- package/dist/k8s/workspace-configmap.d.ts.map +1 -1
- package/dist/k8s/workspace-configmap.js +3 -12
- package/dist/k8s/workspace-configmap.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +37 -4
- 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 +130 -68
- package/src/k8s/api-error.test.ts +89 -0
- package/src/k8s/api-error.ts +128 -0
- package/src/k8s/backend.ts +103 -58
- package/src/k8s/client.ts +10 -3
- package/src/k8s/pod-spec.test.ts +57 -2
- package/src/k8s/pod-spec.ts +25 -36
- package/src/k8s/pod-status.ts +10 -2
- package/src/k8s/routing/create-or-replace.ts +16 -0
- package/src/k8s/routing/gateway-router.test.ts +236 -0
- package/src/k8s/routing/gateway-router.ts +219 -0
- package/src/k8s/routing/index.ts +44 -0
- package/src/k8s/routing/ingress-router.test.ts +236 -0
- package/src/k8s/routing/ingress-router.ts +157 -0
- package/src/k8s/routing/route-health.test.ts +215 -0
- package/src/k8s/routing/route-health.ts +95 -0
- package/src/k8s/routing/routing-mode.test.ts +218 -0
- package/src/k8s/routing/routing-mode.ts +191 -0
- package/src/k8s/routing/session-endpoints.ts +45 -0
- package/src/k8s/routing/session-router.ts +59 -0
- package/src/k8s/routing/session-service.ts +55 -0
- package/src/k8s/watch-pod-spec.test.ts +0 -1
- package/src/k8s/watch-session.ts +90 -77
- package/src/k8s/workspace-configmap.ts +3 -15
- package/src/server.ts +41 -4
- 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/dist/k8s/ingress.d.ts +0 -15
- package/dist/k8s/ingress.d.ts.map +0 -1
- package/dist/k8s/ingress.js +0 -102
- package/dist/k8s/ingress.js.map +0 -1
- package/src/k8s/image-build.test.ts +0 -238
- package/src/k8s/image-build.ts +0 -540
- package/src/k8s/ingress.test.ts +0 -146
- package/src/k8s/ingress.ts +0 -127
package/README.md
CHANGED
|
@@ -27,44 +27,45 @@ serving an anonymous tier, the ceiling *is* the policy.
|
|
|
27
27
|
|
|
28
28
|
Per session the runner resolves the image, creates a Pod (`telo run`), watches it
|
|
29
29
|
for status, attaches a PTY over the Pod `attach` subresource for the interactive
|
|
30
|
-
`/io` channel, and — when
|
|
31
|
-
per-session Service
|
|
32
|
-
an ownerReference to
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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.
|
|
30
|
+
`/io` channel, and — when a routing base domain is configured — creates a
|
|
31
|
+
per-session Service plus routing objects (an Ingress, or one HTTPRoute per port)
|
|
32
|
+
at `<port>-<sessionId>.<base-domain>`, garbage-collected via an ownerReference to
|
|
33
|
+
the Pod.
|
|
34
|
+
|
|
35
|
+
**Every session runs the plain kernel image.** A body-fetch initContainer untars
|
|
36
|
+
the staged bundle into a writable `/app` emptyDir and the session container runs
|
|
37
|
+
`telo run /app/<entry>`, resolving its own module closure into a `/telo-cache`
|
|
38
|
+
emptyDir on the way up. `readOnlyRootFilesystem` stays on — every write lands on
|
|
39
|
+
a mounted emptyDir. The runner needs **no image registry of its own**: it builds
|
|
40
|
+
nothing, pushes nothing, and creates no Jobs.
|
|
41
|
+
|
|
42
|
+
The runner used to prebuild a self-contained per-app image with an on-cluster
|
|
43
|
+
Kaniko Job, so that a slow package registry could not stall the start path. That
|
|
44
|
+
is gone, and the cost is where it now shows: **a run session downloads its
|
|
45
|
+
closure on every start**, since the cache lives and dies with the pod. A watch
|
|
46
|
+
session pays it once and keeps the cache for the pod's life, which is the shape
|
|
47
|
+
to reach for when start latency matters.
|
|
54
48
|
|
|
55
49
|
**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`.
|
|
50
|
+
(`provision` → `boot`) while the session is still `starting`. The session is
|
|
51
|
+
created with a fast `201` carrying the `streamUrl` **before** the pod is created,
|
|
52
|
+
so the client connects immediately and sees provisioning live; the backend then
|
|
53
|
+
runs in the background and a start failure surfaces as a terminal `failed` status
|
|
54
|
+
on the stream. The session flips to `running` when the Pod reaches `Running`.
|
|
62
55
|
|
|
63
56
|
Sandbox hardening is always on (non-root, read-only rootfs, drop-all caps, no
|
|
64
57
|
service-account token, seccomp `RuntimeDefault`); a sandbox RuntimeClass
|
|
65
|
-
(gVisor/Kata) is layered on when configured.
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
(gVisor/Kata) is layered on when configured. A controller whose install needs to
|
|
59
|
+
run scripts or build native code runs them **inside the session pod** under that
|
|
60
|
+
hardening — there is no trusted build step to run them in.
|
|
61
|
+
|
|
62
|
+
**A Kubernetes API rejection reaches the client as its status and `reason`, never
|
|
63
|
+
as the exception.** The API's error carries the full HTTP dump — the raw `Status`
|
|
64
|
+
body naming the runner's ServiceAccount, the audit id, every response header —
|
|
65
|
+
and a start failure's message travels verbatim to the client as the session's
|
|
66
|
+
terminal status. So the client is told the operation, the HTTP code and the
|
|
67
|
+
one-word reason (`Forbidden`, `NotFound`), plus who can fix a `403`; the raw
|
|
68
|
+
exception rides along as the error's `cause` and lands in the runner's log.
|
|
68
69
|
|
|
69
70
|
## Watch sessions
|
|
70
71
|
|
|
@@ -74,7 +75,7 @@ different — a **workspace that runs continuously**. One pod holds a shared
|
|
|
74
75
|
`/workspace` volume, a `workspace` container serving the editor's file routes, one
|
|
75
76
|
`app-<name>` container per application running `telo run --watch`, and optionally
|
|
76
77
|
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
|
|
78
|
+
kernel reload instead of a pod: no schedule, no pull, no module re-resolve.
|
|
78
79
|
|
|
79
80
|
Off unless `RUNNER_WATCH_SESSIONS` is set. Run sessions are entirely unaffected.
|
|
80
81
|
|
|
@@ -105,7 +106,7 @@ agent's port the **manifest wins**: the session starts without the agent and say
|
|
|
105
106
|
so on its stream, rather than refusing to run the user's app over a container
|
|
106
107
|
they never asked for and cannot decline. Nothing else is arranged: the pod's containers
|
|
107
108
|
share one network namespace, so the port simply joins the session's own Service
|
|
108
|
-
and
|
|
109
|
+
and routing objects and answers at `<agentPort>-<sessionId>.<base-domain>`. The `running`
|
|
109
110
|
status carries it as an `agent` endpoint beside `endpoints` — separate, because
|
|
110
111
|
`endpoints` are the ports the user's applications declared, and an
|
|
111
112
|
operator-launched container is not one of them. It is reachable without auth for
|
|
@@ -128,10 +129,10 @@ runner reconciles into a content-addressed ConfigMap — there is no third image
|
|
|
128
129
|
build, and a runner upgrade that changes the manifest leaves running sessions
|
|
129
130
|
mounting the one they booted with.
|
|
130
131
|
|
|
131
|
-
**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
**A watch session resolves its closure once.** It lands in the workspace volume,
|
|
133
|
+
which lives as long as the pod, so the download happens once per session and
|
|
134
|
+
every later reload resolves from local disk — where a run session, whose cache
|
|
135
|
+
dies with its pod, pays for it on every start.
|
|
135
136
|
|
|
136
137
|
**One cache for the whole session.** The runner seeds `telo-workspace.yaml` at
|
|
137
138
|
the workspace root when the session starts, and the kernel anchors its `.telo`
|
|
@@ -144,13 +145,11 @@ manifest lives outside the workspace and the walk-up would never reach the
|
|
|
144
145
|
marker. A workspace that brings its own marker keeps it — overwriting one with a
|
|
145
146
|
real `modules:` list would change what `telo release` discovers.
|
|
146
147
|
|
|
147
|
-
**Egress
|
|
148
|
-
the
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
here — the same stated dependency the build namespace already carries, one
|
|
153
|
-
namespace over.
|
|
148
|
+
**Egress lives in the session namespace.** Every session resolves its own module
|
|
149
|
+
closure, so the session namespace's NetworkPolicy has to reach the module
|
|
150
|
+
registries as well as the model provider. Core NetworkPolicy is CIDR-only and
|
|
151
|
+
registries sit behind rotating-IP CDNs, so a locked-down operator needs a CNI
|
|
152
|
+
with FQDN policy or an egress proxy here.
|
|
154
153
|
|
|
155
154
|
### Two nouns on one stream
|
|
156
155
|
|
|
@@ -214,10 +213,10 @@ transport whose kind does not emit one would silently get no routing.
|
|
|
214
213
|
one-shot app completed is not a change. It touches the named app's entry manifest
|
|
215
214
|
through the same path everything else uses, so it needs no signalling into the
|
|
216
215
|
container, no shared PID namespace and no `exec` — **RBAC gains only `configmaps:
|
|
217
|
-
get, create` and `update` on services
|
|
218
|
-
changes an app's declared port set can re-patch its routing. Without that,
|
|
219
|
-
`ports:` entry leaves the app bound to a port with no
|
|
220
|
-
event.
|
|
216
|
+
get, create` and `update` on services and routing objects**, the latter so a reload
|
|
217
|
+
that changes an app's declared port set can re-patch its routing. Without that,
|
|
218
|
+
adding a `ports:` entry leaves the app bound to a port with no route, no error and
|
|
219
|
+
no event.
|
|
221
220
|
|
|
222
221
|
Changing the app set costs a pod recreate because a pod's container list is fixed
|
|
223
222
|
at creation. That is the only editing action in the design that costs a pod, and
|
|
@@ -288,17 +287,24 @@ outcome than a URL that silently reaches the wrong app.
|
|
|
288
287
|
| `PORT` | `8062` | HTTP listen port |
|
|
289
288
|
| `RUNNER_DISPLAY_NAME` | `Telo Runner` | Display name advertised on `/v1/capabilities` (the editor's runner label) |
|
|
290
289
|
| `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
|
|
290
|
+
| `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
291
|
| `RUNNER_APP_MAX_CPU` | `500m` | CPU ceiling for predefined-app pods (separate from the anonymous-session ceiling) |
|
|
293
292
|
| `RUNNER_APP_MAX_MEMORY` | `512Mi` | Memory ceiling for predefined-app pods |
|
|
294
293
|
| `RUNNER_APP_MAX_TTL_SECONDS` | `21600` | Wall-clock TTL for predefined-app pods (agent sessions are long-lived) |
|
|
295
294
|
| `RUNNER_APP_MAX_EPHEMERAL_STORAGE` | `1Gi` | Ephemeral-storage ceiling for predefined-app pods |
|
|
296
295
|
| `RUNNER_SESSION_NAMESPACE` | `telo-sessions` | Namespace for session objects |
|
|
297
296
|
| `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` |
|
|
297
|
+
| `RUNNER_INIT_IMAGE` | `busybox:stable` | Bundle-fetch initContainer image (wget + tar) |
|
|
298
|
+
| `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
299
|
| `RUNNER_RUNTIME_CLASS` | _(unset → runc)_ | Sandbox RuntimeClass (gvisor/kata) |
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
300
|
+
| `SESSION_ROUTING_MODE` | `auto` | `auto` \| `ingress` \| `gateway` \| `none`. See [Routing layer](#routing-layer) |
|
|
301
|
+
| `SESSION_ROUTING_BASE_DOMAIN` | _(unset → logs-only)_ | Wildcard base for per-session hosts |
|
|
302
|
+
| `SESSION_ROUTE_READY_TIMEOUT_SECONDS` | `60` | How long a published route may go unclaimed before the session reports it `unprogrammed` |
|
|
303
|
+
| `SESSION_INGRESS_CLASS` | _(unset → cluster default)_ | IngressClass for session Ingresses (ingress mode) |
|
|
304
|
+
| `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. **Ingress mode only** — refused at boot in gateway mode, where the cert belongs to the Gateway listener |
|
|
305
|
+
| `SESSION_GATEWAY_NAME` | _(unset)_ | Gateway each session's HTTPRoute attaches to. Required in gateway mode |
|
|
306
|
+
| `SESSION_GATEWAY_NAMESPACE` | _(= session namespace)_ | Namespace of that Gateway |
|
|
307
|
+
| `SESSION_GATEWAY_SECTION_NAME` | _(unset → any listener)_ | Listener name, when only one should carry session traffic |
|
|
302
308
|
| `RUNNER_MAX_CPU` | `50m` | CPU ceiling |
|
|
303
309
|
| `RUNNER_MAX_MEMORY` | `100Mi` | Memory ceiling |
|
|
304
310
|
| `RUNNER_MAX_TTL_SECONDS` | `3600` | Wall-clock TTL (Pod `activeDeadlineSeconds`) |
|
|
@@ -327,12 +333,9 @@ against the same list, so a client that skips the editor can't widen the set.
|
|
|
327
333
|
unreachable. Disable the catalog to lock `image` to `RUNNER_IMAGE`.
|
|
328
334
|
|
|
329
335
|
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`.
|
|
336
|
+
`latest-slim` only refreshes when the session's `pullPolicy` is `always`, which
|
|
337
|
+
is the Pod's own `imagePullPolicy`: the kubelet re-pulls the tag on every
|
|
338
|
+
session. `missing` and `never` reuse whatever that node already cached.
|
|
336
339
|
|
|
337
340
|
| Env | Default | Purpose |
|
|
338
341
|
| --- | --- | --- |
|
|
@@ -346,88 +349,144 @@ degrades to reusing the cached build for it, same as `missing` / `never`.
|
|
|
346
349
|
| `RUNNER_BASE_IMAGE_LIMIT` | `20` | Cap on advertised tags (newest first) |
|
|
347
350
|
| `RUNNER_BASE_IMAGE_REFRESH_SECONDS` | `3600` | Catalog re-fetch cadence |
|
|
348
351
|
|
|
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
352
|
## Deploy (Helm)
|
|
363
353
|
|
|
364
|
-
The
|
|
365
|
-
|
|
354
|
+
The chart is published as an OCI artifact at
|
|
355
|
+
`oci://ghcr.io/telorun/charts/k8s-runner`:
|
|
366
356
|
|
|
367
357
|
```bash
|
|
368
|
-
helm install telo-runner
|
|
369
|
-
--set
|
|
370
|
-
--set-file registry.dockerconfigjson=./dockerconfig.json \ # private-registry auth
|
|
358
|
+
helm install telo-runner oci://ghcr.io/telorun/charts/k8s-runner \
|
|
359
|
+
--set watch.enabled=true \
|
|
371
360
|
--set session.runtimeClass=gvisor
|
|
372
361
|
```
|
|
373
362
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
363
|
+
Helm resolves the newest release when no `--version` is given; pass one to pin.
|
|
364
|
+
From a checkout, `./chart` works the same way and is what you want when changing
|
|
365
|
+
the chart itself.
|
|
366
|
+
|
|
367
|
+
**The chart's version IS the runner's.** `version` and `appVersion` both carry
|
|
368
|
+
`@telorun/k8s-runner`'s package version, stamped in the release PR and checked on
|
|
369
|
+
every PR — so chart `0.13.0` installs runner image `0.13.0`, with no lookup
|
|
370
|
+
table. That is also why `image.tag` defaults to empty and resolves to the chart's
|
|
371
|
+
own `appVersion` rather than to `latest`: `--version 0.13.0` has to mean one
|
|
372
|
+
thing, where a floating tag meant whatever it had moved to overnight.
|
|
383
373
|
|
|
384
374
|
The chart provisions the static scaffolding: the runner Deployment (single
|
|
385
375
|
replica — the registry is in-memory and the runner reaps orphaned pods on boot),
|
|
386
|
-
Service, scoped RBAC, the `telo-runner`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
376
|
+
Service, scoped RBAC, the `telo-runner` and restricted-PSS `telo-sessions`
|
|
377
|
+
namespaces, a `ResourceQuota`, and the session pod-to-pod NetworkPolicy. The
|
|
378
|
+
runner creates per-session objects at runtime. Nothing else is required — no
|
|
379
|
+
image registry, no build namespace, no push credentials.
|
|
380
|
+
|
|
381
|
+
For a kernel image or an operator catalog image held in a **private registry**,
|
|
382
|
+
create the dockerconfigjson Secret in `telo-sessions` yourself and point
|
|
383
|
+
`session.imagePullSecret` at it; the kubelet pulls with it.
|
|
384
|
+
|
|
385
|
+
### Routing layer
|
|
386
|
+
|
|
387
|
+
Session hosts are `<port>-<sessionId>.<base-domain>` — a single label, so one
|
|
388
|
+
wildcard DNS record and one wildcard cert cover every session. **Both current
|
|
389
|
+
routing APIs are supported**, because clusters ship Ingress, Gateway API, or
|
|
390
|
+
both, and hardcoding either leaves the runner unroutable on the rest.
|
|
391
|
+
|
|
392
|
+
`sessionRouting.mode` picks the layer:
|
|
393
|
+
|
|
394
|
+
| mode | behaviour |
|
|
395
|
+
| --- | --- |
|
|
396
|
+
| `auto` (default) | Resolve from what is **configured**, then from what the cluster serves |
|
|
397
|
+
| `ingress` | `networking.k8s.io/v1` Ingress |
|
|
398
|
+
| `gateway` | `gateway.networking.k8s.io` HTTPRoute (`v1`, falling back to `v1beta1`) |
|
|
399
|
+
| `none` | Publish nothing — logs-only even with a base domain set |
|
|
400
|
+
|
|
401
|
+
`auto` reads configuration before installation, and that order is the point: a
|
|
402
|
+
named Gateway wins, then a configured IngressClass, then a **default-marked**
|
|
403
|
+
IngressClass (`ingressclass.kubernetes.io/is-default-class` — the cluster has
|
|
404
|
+
nominated where an unqualified Ingress goes, and unqualified is what this runner
|
|
405
|
+
creates), and only then does it look at what merely exists. **Gateway API CRDs are frequently present without being the intended
|
|
406
|
+
path**, so "the API exists" is not evidence of intent. Where two usable layers
|
|
407
|
+
exist and nothing was configured, the runner **refuses to start** rather than
|
|
408
|
+
guessing — a wrong guess publishes routing objects nothing reconciles, which
|
|
409
|
+
produces a healthy pod and a 404 on every URL, with nothing reported anywhere.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Gateway API
|
|
413
|
+
helm install telo-runner ./chart \
|
|
414
|
+
--set sessionRouting.mode=gateway \
|
|
415
|
+
--set sessionRouting.baseDomain=telo.run \
|
|
416
|
+
--set sessionRouting.gateway.name=public \
|
|
417
|
+
--set sessionRouting.gateway.namespace=gateway-system \
|
|
418
|
+
--set sessionRouting.dataPlaneNamespace=gateway-system
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The Gateway needs a listener whose hostname covers `*.<base-domain>` and whose
|
|
422
|
+
`allowedRoutes.namespaces` admits `telo-sessions`. A route it does not admit is
|
|
423
|
+
reported per session with the controller's own reason (`NotAllowedByListeners`).
|
|
390
424
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
425
|
+
**Upgrading from `sessionIngress.*`** is a breaking values change and the chart
|
|
426
|
+
refuses the old keys rather than ignoring them — Helm drops unknown values
|
|
427
|
+
silently, so an un-migrated file would otherwise boot a runner that logs
|
|
428
|
+
`session routing disabled (logs-only)` and drops every session URL.
|
|
429
|
+
|
|
430
|
+
> **One HTTPRoute per port.** Gateway API scopes `hostnames` to the whole route
|
|
431
|
+
> while a rule matches on path, never host — so a single route carrying every
|
|
432
|
+
> session hostname would send them all to whichever rule matched `/` first. An
|
|
433
|
+
> Ingress rule owns its host, which is why that layer needs only one object.
|
|
434
|
+
|
|
435
|
+
**`sessionRouting.dataPlaneNamespace` is not optional when routing is on.** The
|
|
436
|
+
session NetworkPolicy admits traffic from that namespace only; without it the
|
|
437
|
+
route programs correctly and the workload is still unreachable.
|
|
438
|
+
|
|
439
|
+
### Route verification
|
|
440
|
+
|
|
441
|
+
The runner already dials each declared port from its own network — that proves
|
|
442
|
+
the app is **listening** and says nothing about whether traffic can **reach** it.
|
|
443
|
+
So it separately verifies that what it published was actually programmed, and
|
|
444
|
+
reports each host as `pending` → `programmed` / `unprogrammed` on the session
|
|
445
|
+
event stream (`type: "route"`). Reported, never fatal: a slow controller and an
|
|
446
|
+
absent one look alike for the first few seconds.
|
|
447
|
+
|
|
448
|
+
Gateway API answers this precisely, from the `Accepted` / `ResolvedRefs`
|
|
449
|
+
conditions a controller writes. Ingress has no rejection signal — a controller
|
|
450
|
+
that refuses a rule simply never writes status — so there an unclaimed route is
|
|
451
|
+
caught by the timeout rather than by a reason the cluster gave.
|
|
396
452
|
|
|
397
453
|
### Origin TLS (Cloudflare et al.)
|
|
398
454
|
|
|
455
|
+
**Ingress mode only.** Under Gateway API the origin certificate belongs to the
|
|
456
|
+
Gateway listener's own `certificateRefs`; setting `SESSION_INGRESS_TLS_SECRET`
|
|
457
|
+
alongside `mode: gateway` is refused at boot rather than silently ignored.
|
|
458
|
+
|
|
399
459
|
To have the per-session Ingress present an origin cert (so an upstream like
|
|
400
460
|
Cloudflare in **Full (Strict)** mode validates the origin), give the chart a
|
|
401
461
|
`kubernetes.io/tls` Secret in `telo-sessions`. The cert must cover the wildcard
|
|
402
|
-
`*.<
|
|
462
|
+
`*.<sessionRouting.baseDomain>` — session hosts are a single label
|
|
403
463
|
(`<port>-<sessionId>.<base-domain>`). Two ways:
|
|
404
464
|
|
|
405
465
|
```bash
|
|
406
466
|
# A — reference a Secret you manage in telo-sessions (cert-manager, your own sync)
|
|
407
|
-
helm install telo-runner ./chart --set
|
|
467
|
+
helm install telo-runner ./chart --set sessionRouting.ingress.tls.secretName=telo-origin-tls
|
|
408
468
|
|
|
409
469
|
# B — let the chart create the Secret from your cert + key
|
|
410
470
|
helm install telo-runner ./chart \
|
|
411
|
-
--set-file
|
|
412
|
-
--set-file
|
|
471
|
+
--set-file sessionRouting.ingress.tls.cert=origin.pem \
|
|
472
|
+
--set-file sessionRouting.ingress.tls.key=origin.key
|
|
413
473
|
```
|
|
414
474
|
|
|
415
475
|
Either wires `SESSION_INGRESS_TLS_SECRET`, and the runner stamps a `spec.tls`
|
|
416
476
|
block on every session Ingress. Leave all three empty to skip TLS at the origin
|
|
417
477
|
(terminated entirely upstream).
|
|
418
478
|
|
|
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.
|
|
479
|
+
> **Egress note.** Core NetworkPolicy is CIDR-only and cannot express the
|
|
480
|
+
> package-registry FQDN allowlist the session namespace needs — use a CNI with
|
|
481
|
+
> FQDN policy (Cilium) or an egress proxy to tighten it.
|
|
424
482
|
|
|
425
483
|
## Development
|
|
426
484
|
|
|
427
485
|
```bash
|
|
428
486
|
pnpm --filter @telorun/k8s-runner build # tsc
|
|
429
|
-
pnpm --filter @telorun/k8s-runner test # vitest (limits clamp, tar,
|
|
487
|
+
pnpm --filter @telorun/k8s-runner test # vitest (pod specs, limits clamp, tar, API errors)
|
|
430
488
|
```
|
|
431
489
|
|
|
432
490
|
The Kubernetes backend can't be exercised without a cluster; unit tests cover the
|
|
433
|
-
backend-independent logic (limit clamping, the tar writer,
|
|
491
|
+
backend-independent logic (the pod specs, limit clamping, the tar writer, and
|
|
492
|
+
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"}
|