@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.
Files changed (118) hide show
  1. package/README.md +166 -107
  2. package/dist/bundle-store.d.ts +6 -13
  3. package/dist/bundle-store.d.ts.map +1 -1
  4. package/dist/bundle-store.js +6 -18
  5. package/dist/bundle-store.js.map +1 -1
  6. package/dist/capabilities.d.ts +3 -3
  7. package/dist/capabilities.js +4 -4
  8. package/dist/capabilities.js.map +1 -1
  9. package/dist/config.d.ts +47 -37
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +64 -25
  12. package/dist/config.js.map +1 -1
  13. package/dist/k8s/api-error.d.ts +34 -0
  14. package/dist/k8s/api-error.d.ts.map +1 -0
  15. package/dist/k8s/api-error.js +118 -0
  16. package/dist/k8s/api-error.js.map +1 -0
  17. package/dist/k8s/backend.d.ts +4 -0
  18. package/dist/k8s/backend.d.ts.map +1 -1
  19. package/dist/k8s/backend.js +77 -51
  20. package/dist/k8s/backend.js.map +1 -1
  21. package/dist/k8s/client.d.ts +7 -2
  22. package/dist/k8s/client.d.ts.map +1 -1
  23. package/dist/k8s/client.js +3 -2
  24. package/dist/k8s/client.js.map +1 -1
  25. package/dist/k8s/pod-spec.d.ts +8 -9
  26. package/dist/k8s/pod-spec.d.ts.map +1 -1
  27. package/dist/k8s/pod-spec.js +19 -29
  28. package/dist/k8s/pod-spec.js.map +1 -1
  29. package/dist/k8s/pod-status.d.ts +5 -0
  30. package/dist/k8s/pod-status.d.ts.map +1 -1
  31. package/dist/k8s/pod-status.js +9 -2
  32. package/dist/k8s/pod-status.js.map +1 -1
  33. package/dist/k8s/routing/create-or-replace.d.ts +5 -0
  34. package/dist/k8s/routing/create-or-replace.d.ts.map +1 -0
  35. package/dist/k8s/routing/create-or-replace.js +15 -0
  36. package/dist/k8s/routing/create-or-replace.js.map +1 -0
  37. package/dist/k8s/routing/gateway-router.d.ts +5 -0
  38. package/dist/k8s/routing/gateway-router.d.ts.map +1 -0
  39. package/dist/k8s/routing/gateway-router.js +178 -0
  40. package/dist/k8s/routing/gateway-router.js.map +1 -0
  41. package/dist/k8s/routing/index.d.ts +24 -0
  42. package/dist/k8s/routing/index.d.ts.map +1 -0
  43. package/dist/k8s/routing/index.js +27 -0
  44. package/dist/k8s/routing/index.js.map +1 -0
  45. package/dist/k8s/routing/ingress-router.d.ts +13 -0
  46. package/dist/k8s/routing/ingress-router.d.ts.map +1 -0
  47. package/dist/k8s/routing/ingress-router.js +125 -0
  48. package/dist/k8s/routing/ingress-router.js.map +1 -0
  49. package/dist/k8s/routing/route-health.d.ts +34 -0
  50. package/dist/k8s/routing/route-health.d.ts.map +1 -0
  51. package/dist/k8s/routing/route-health.js +77 -0
  52. package/dist/k8s/routing/route-health.js.map +1 -0
  53. package/dist/k8s/routing/routing-mode.d.ts +45 -0
  54. package/dist/k8s/routing/routing-mode.d.ts.map +1 -0
  55. package/dist/k8s/routing/routing-mode.js +141 -0
  56. package/dist/k8s/routing/routing-mode.js.map +1 -0
  57. package/dist/k8s/routing/session-endpoints.d.ts +25 -0
  58. package/dist/k8s/routing/session-endpoints.d.ts.map +1 -0
  59. package/dist/k8s/routing/session-endpoints.js +36 -0
  60. package/dist/k8s/routing/session-endpoints.js.map +1 -0
  61. package/dist/k8s/routing/session-router.d.ts +57 -0
  62. package/dist/k8s/routing/session-router.d.ts.map +1 -0
  63. package/dist/k8s/routing/session-router.js +2 -0
  64. package/dist/k8s/routing/session-router.js.map +1 -0
  65. package/dist/k8s/routing/session-service.d.ts +14 -0
  66. package/dist/k8s/routing/session-service.d.ts.map +1 -0
  67. package/dist/k8s/routing/session-service.js +42 -0
  68. package/dist/k8s/routing/session-service.js.map +1 -0
  69. package/dist/k8s/watch-session.d.ts +7 -5
  70. package/dist/k8s/watch-session.d.ts.map +1 -1
  71. package/dist/k8s/watch-session.js +90 -83
  72. package/dist/k8s/watch-session.js.map +1 -1
  73. package/dist/k8s/workspace-configmap.d.ts.map +1 -1
  74. package/dist/k8s/workspace-configmap.js +3 -12
  75. package/dist/k8s/workspace-configmap.js.map +1 -1
  76. package/dist/server.d.ts.map +1 -1
  77. package/dist/server.js +37 -4
  78. package/dist/server.js.map +1 -1
  79. package/package.json +2 -2
  80. package/src/bundle-store.ts +6 -19
  81. package/src/capabilities.ts +4 -4
  82. package/src/config.ts +130 -68
  83. package/src/k8s/api-error.test.ts +89 -0
  84. package/src/k8s/api-error.ts +128 -0
  85. package/src/k8s/backend.ts +103 -58
  86. package/src/k8s/client.ts +10 -3
  87. package/src/k8s/pod-spec.test.ts +57 -2
  88. package/src/k8s/pod-spec.ts +25 -36
  89. package/src/k8s/pod-status.ts +10 -2
  90. package/src/k8s/routing/create-or-replace.ts +16 -0
  91. package/src/k8s/routing/gateway-router.test.ts +236 -0
  92. package/src/k8s/routing/gateway-router.ts +219 -0
  93. package/src/k8s/routing/index.ts +44 -0
  94. package/src/k8s/routing/ingress-router.test.ts +236 -0
  95. package/src/k8s/routing/ingress-router.ts +157 -0
  96. package/src/k8s/routing/route-health.test.ts +215 -0
  97. package/src/k8s/routing/route-health.ts +95 -0
  98. package/src/k8s/routing/routing-mode.test.ts +218 -0
  99. package/src/k8s/routing/routing-mode.ts +191 -0
  100. package/src/k8s/routing/session-endpoints.ts +45 -0
  101. package/src/k8s/routing/session-router.ts +59 -0
  102. package/src/k8s/routing/session-service.ts +55 -0
  103. package/src/k8s/watch-pod-spec.test.ts +0 -1
  104. package/src/k8s/watch-session.ts +90 -77
  105. package/src/k8s/workspace-configmap.ts +3 -15
  106. package/src/server.ts +41 -4
  107. package/dist/k8s/image-build.d.ts +0 -103
  108. package/dist/k8s/image-build.d.ts.map +0 -1
  109. package/dist/k8s/image-build.js +0 -427
  110. package/dist/k8s/image-build.js.map +0 -1
  111. package/dist/k8s/ingress.d.ts +0 -15
  112. package/dist/k8s/ingress.d.ts.map +0 -1
  113. package/dist/k8s/ingress.js +0 -102
  114. package/dist/k8s/ingress.js.map +0 -1
  115. package/src/k8s/image-build.test.ts +0 -238
  116. package/src/k8s/image-build.ts +0 -540
  117. package/src/k8s/ingress.test.ts +0 -146
  118. package/src/k8s/ingress.ts +0 -127
package/dist/config.d.ts CHANGED
@@ -35,59 +35,69 @@ export interface LimitCeilings {
35
35
  ephemeralStorage: string;
36
36
  }
37
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.
38
+ * Which routing layer the runner publishes per-session routes on.
39
+ *
40
+ * - `auto` — resolve from what is CONFIGURED, then from what the cluster
41
+ * serves; refuse at boot when the answer is genuinely ambiguous.
42
+ * - `ingress` — `networking.k8s.io/v1` Ingress.
43
+ * - `gateway` — `gateway.networking.k8s.io/v1` HTTPRoute.
44
+ * - `none` — publish nothing; the session is logs-only even with a base
45
+ * domain set.
42
46
  */
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). */
47
+ export type SessionRoutingMode = "auto" | "ingress" | "gateway" | "none";
48
+ /** The Gateway a session's HTTPRoute attaches to. Never detected: picking one of
49
+ * several Gateways is the same guess as picking one of two routing APIs, and a
50
+ * route attached to the wrong Gateway fails exactly as silently as no route. */
51
+ export interface GatewayParentConfig {
52
+ name: string;
53
+ /** Defaults to the session namespace — mirroring Gateway API's own
54
+ * `parentRefs[].namespace` default, which is the route's own namespace. */
48
55
  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;
56
+ /** Optional listener name, when the Gateway has several and only one should
57
+ * carry session traffic. */
58
+ sectionName?: string;
59
+ }
60
+ export interface SessionRoutingConfig {
61
+ mode: SessionRoutingMode;
62
+ /** Wildcard base domain for per-session hosts; unset logs-only whatever the
63
+ * mode, since without it no host can be constructed at all. */
64
+ baseDomain?: string;
65
+ /** IngressClass for created session Ingresses (ingress mode). */
66
+ ingressClassName?: string;
67
+ /** `kubernetes.io/tls` Secret (in the session namespace) the per-session
68
+ * Ingress presents so an upstream (e.g. Cloudflare Full (Strict)) can validate
69
+ * the origin. Must cover `*.<baseDomain>`. INGRESS MODE ONLY — under Gateway
70
+ * API the certificate belongs to the Gateway's listener, so setting this in
71
+ * gateway mode is refused at boot rather than silently ignored. */
72
+ tlsSecretName?: string;
73
+ gateway?: GatewayParentConfig;
74
+ /** How long a published route may go unclaimed by any controller before it is
75
+ * reported `unprogrammed`. */
76
+ routeReadyTimeoutMs: number;
63
77
  }
64
78
  export interface K8sRunnerConfig extends RunnerCoreConfig {
65
79
  /** Identity advertised on `/v1/capabilities` — studio labels the runner
66
80
  * with these. */
67
81
  displayName: string;
68
82
  description: string;
69
- /** Namespace where session Pods/Services/Ingresses are created. */
83
+ /** Namespace where session Pods/Services and routing objects are created. */
70
84
  sessionNamespace: string;
71
85
  /** Default image for spawned session Pods (telorun/node). Baked into the runner
72
86
  * image as `RUNNER_IMAGE` at build time (the kernel version the runner was
73
87
  * built against); the literal fallback below is for a source checkout only. */
74
88
  defaultImage: string;
75
- /** Small image for the bundle/build-context fetch initContainer (needs wget + tar). */
89
+ /** Small image for the bundle-fetch initContainer (needs wget + tar). */
76
90
  initImage: string;
91
+ /** Optional dockerconfig Secret (in the session namespace) the kubelet pulls
92
+ * session images with — the kernel image, and any operator catalog image
93
+ * (`RUNNER_APPS`) held in a private registry. */
94
+ imagePullSecret?: string;
77
95
  /** Optional sandbox RuntimeClass (gvisor/kata). Unset → cluster default (runc). */
78
96
  runtimeClass?: string;
79
- /** Wildcard base domain for per-session ingress; unset → logs-only. */
80
- sessionIngressBaseDomain?: string;
81
- /** Optional IngressClass name for created session Ingresses. */
82
- sessionIngressClassName?: string;
83
- /** Optional `kubernetes.io/tls` Secret (in the session namespace) the per-session
84
- * Ingress presents so an upstream (e.g. Cloudflare Full (Strict)) can validate
85
- * the origin. Must cover the wildcard `*.<sessionIngressBaseDomain>`. Unset → no TLS block. */
86
- sessionIngressTlsSecretName?: string;
87
- /** On-cluster image build — the only delivery path (always present). */
88
- build: ImageBuildConfig;
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). */
97
+ /** How per-session routes are published. */
98
+ sessionRouting: SessionRoutingConfig;
99
+ /** Runner's own in-cluster base URL, used to build the bundle fetch URL the
100
+ * initContainer curls (e.g. http://k8s-runner.telo-runner:8062). */
91
101
  selfUrl: string;
92
102
  /** Label applied to every session object, used for orphan reaping. */
93
103
  managedByLabel: string;
@@ -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;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;2FACuF;IACvF,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,gBAAgB,EAAE,OAAO,CAAC;IAC1B;oCACgC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;uDACmD;IACnD,eAAe,CAAC,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,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,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,wEAAwE;IACxE,KAAK,EAAE,gBAAgB,CAAC;IACxB;uFACmF;IACnF,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,CAkE3E"}
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;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAIzE;;iFAEiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb;gFAC4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB;iCAC6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB;oEACgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;wEAIoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B;mCAC+B;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD;sBACkB;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,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,4CAA4C;IAC5C,cAAc,EAAE,oBAAoB,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;AAyGD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,eAAe,CAmD3E"}
package/dist/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { loadCoreConfig, parseBool, parsePositiveInt, RunnerConfigError, } from "@telorun/runner-core";
2
2
  export { RunnerConfigError };
3
+ const ROUTING_MODES = ["auto", "ingress", "gateway", "none"];
3
4
  const DEFAULT_PORT = 8062;
4
5
  /** Compile a single optional regex env var into a one-element `RegExp[]`. */
5
6
  function parseTagRegex(raw, field) {
@@ -13,6 +14,53 @@ function parseTagRegex(raw, field) {
13
14
  throw new RunnerConfigError(`${field} is not a valid regular expression: ${err.message}`);
14
15
  }
15
16
  }
17
+ /** Parse the routing mode, THROWING on an unrecognized one rather than degrading
18
+ * to a default — a typo'd mode that silently fell back to `auto` is the same
19
+ * class of failure this whole mechanism exists to remove. */
20
+ function parseRoutingMode(raw) {
21
+ const v = raw?.trim();
22
+ if (!v)
23
+ return "auto";
24
+ if (ROUTING_MODES.includes(v))
25
+ return v;
26
+ throw new RunnerConfigError(`SESSION_ROUTING_MODE must be one of ${ROUTING_MODES.join(" | ")} (got '${v}').`);
27
+ }
28
+ function loadSessionRoutingConfig(env, sessionNamespace) {
29
+ const mode = parseRoutingMode(env.SESSION_ROUTING_MODE);
30
+ const gatewayName = env.SESSION_GATEWAY_NAME?.trim() || undefined;
31
+ const tlsSecretName = env.SESSION_INGRESS_TLS_SECRET?.trim() || undefined;
32
+ // A certificate configured for a layer that will never present it is a silent
33
+ // downgrade to plaintext origin traffic, so it is refused rather than ignored.
34
+ // This is only the FAST PATH, for the mode that states the layer outright: the
35
+ // authoritative refusal is at routing resolution, because under `auto` a named
36
+ // Gateway decides the layer and nothing here can see that yet.
37
+ if (mode === "gateway" && tlsSecretName) {
38
+ throw new RunnerConfigError("SESSION_INGRESS_TLS_SECRET is set but SESSION_ROUTING_MODE=gateway. Under Gateway API the " +
39
+ "origin certificate belongs to the Gateway listener's own `tls.certificateRefs`, not to the " +
40
+ "per-session route. Move the Secret reference to the Gateway and unset this.");
41
+ }
42
+ if (mode === "gateway" && !gatewayName) {
43
+ throw new RunnerConfigError("SESSION_ROUTING_MODE=gateway requires SESSION_GATEWAY_NAME — the Gateway each session's " +
44
+ "HTTPRoute attaches to. Set it (and SESSION_GATEWAY_NAMESPACE when the Gateway is not in " +
45
+ `'${sessionNamespace}').`);
46
+ }
47
+ return {
48
+ mode,
49
+ baseDomain: env.SESSION_ROUTING_BASE_DOMAIN?.trim() || undefined,
50
+ ingressClassName: env.SESSION_INGRESS_CLASS?.trim() || undefined,
51
+ tlsSecretName,
52
+ ...(gatewayName
53
+ ? {
54
+ gateway: {
55
+ name: gatewayName,
56
+ namespace: env.SESSION_GATEWAY_NAMESPACE?.trim() || sessionNamespace,
57
+ sectionName: env.SESSION_GATEWAY_SECTION_NAME?.trim() || undefined,
58
+ },
59
+ }
60
+ : {}),
61
+ routeReadyTimeoutMs: parsePositiveInt(env.SESSION_ROUTE_READY_TIMEOUT_SECONDS, 60, "SESSION_ROUTE_READY_TIMEOUT_SECONDS") * 1000,
62
+ };
63
+ }
16
64
  function loadBaseImageCatalogConfig(env) {
17
65
  return {
18
66
  enabled: parseBool(env.RUNNER_BASE_IMAGE_CATALOG_ENABLED, true, "RUNNER_BASE_IMAGE_CATALOG_ENABLED"),
@@ -34,42 +82,33 @@ export function loadK8sRunnerConfig(env) {
34
82
  throw new RunnerConfigError("RUNNER_SELF_URL env var is required. Set it to the runner's in-cluster base URL " +
35
83
  "(e.g. http://k8s-runner.telo-runner.svc:8062) so session initContainers can fetch the bundle.");
36
84
  }
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
- };
85
+ const sessionNamespace = env.RUNNER_SESSION_NAMESPACE?.trim() || "telo-sessions";
54
86
  return {
55
87
  ...loadCoreConfig(env, { port: DEFAULT_PORT }),
56
88
  displayName: env.RUNNER_DISPLAY_NAME?.trim() || "Telo Runner",
57
89
  description: env.RUNNER_DESCRIPTION?.trim() || "Runs the Telo application in a cloud environment",
58
- sessionNamespace: env.RUNNER_SESSION_NAMESPACE?.trim() || "telo-sessions",
90
+ sessionNamespace,
59
91
  defaultImage: env.RUNNER_IMAGE?.trim() || "telorun/node:latest-slim",
60
92
  initImage: env.RUNNER_INIT_IMAGE?.trim() || "busybox:stable",
93
+ imagePullSecret: env.RUNNER_IMAGE_PULL_SECRET?.trim() || undefined,
61
94
  runtimeClass: env.RUNNER_RUNTIME_CLASS?.trim() || undefined,
62
- sessionIngressBaseDomain: env.SESSION_INGRESS_BASE_DOMAIN?.trim() || undefined,
63
- sessionIngressClassName: env.SESSION_INGRESS_CLASS?.trim() || undefined,
64
- sessionIngressTlsSecretName: env.SESSION_INGRESS_TLS_SECRET?.trim() || undefined,
65
- build,
95
+ sessionRouting: loadSessionRoutingConfig(env, sessionNamespace),
66
96
  selfUrl: selfUrl.replace(/\/+$/, ""),
67
97
  managedByLabel: env.RUNNER_MANAGED_BY?.trim() || "telo-k8s-runner",
98
+ // Sized for a pod that RESOLVES ITS OWN module closure. They used to be
99
+ // 50m / 100Mi / 512Mi, which described a different workload: the closure
100
+ // arrived in image layers, so the pod only ran it. It now downloads,
101
+ // unpacks and resolves it in-pod, into an emptyDir that counts against
102
+ // ephemeral-storage — so the old numbers meant an OOMKill on the memory
103
+ // ceiling and an eviction on the storage one, for the ordinary case. The
104
+ // watch path already ran this workload and already carried the roomier
105
+ // ceiling; these match it. `appLimits` stays separate: it still differs in
106
+ // TTL, and both remain the operator's policy to tighten.
68
107
  limits: {
69
- cpu: env.RUNNER_MAX_CPU?.trim() || "50m",
70
- memory: env.RUNNER_MAX_MEMORY?.trim() || "100Mi",
108
+ cpu: env.RUNNER_MAX_CPU?.trim() || "500m",
109
+ memory: env.RUNNER_MAX_MEMORY?.trim() || "512Mi",
71
110
  ttlSeconds: parsePositiveInt(env.RUNNER_MAX_TTL_SECONDS, 3600, "RUNNER_MAX_TTL_SECONDS"),
72
- ephemeralStorage: env.RUNNER_MAX_EPHEMERAL_STORAGE?.trim() || "512Mi",
111
+ ephemeralStorage: env.RUNNER_MAX_EPHEMERAL_STORAGE?.trim() || "1Gi",
73
112
  },
74
113
  appLimits: {
75
114
  cpu: env.RUNNER_APP_MAX_CPU?.trim() || "500m",
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -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"}
@@ -0,0 +1,118 @@
1
+ import { SessionStartError } 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 function apiFailure(err, stage, action) {
18
+ return withCause(new SessionStartError("start_failed", stage, `${action}: ${apiReason(err)}`), err);
19
+ }
20
+ /** Attach the original error as `cause` so a log serializer can reach it.
21
+ * Every wrap on a failure path goes through here — a rewrap that drops the
22
+ * cause silently undoes the whole point of summarizing the message. */
23
+ export function withCause(error, cause) {
24
+ error.cause = cause;
25
+ return error;
26
+ }
27
+ /** The client-safe half of a Kubernetes API error: what was refused, and who
28
+ * can act on it — never the `Status` message, which names cluster identities. */
29
+ export function apiReason(err) {
30
+ const code = statusCode(err);
31
+ // No HTTP status means the request never got an answer: a refused connection,
32
+ // a DNS failure, an abort. Those carry a `code` too (`ECONNREFUSED`, an
33
+ // `ABORT_ERR` number), which is why `statusCode` accepts only what an HTTP
34
+ // response could have produced — reporting `HTTP ECONNREFUSED` claimed the
35
+ // apiserver rejected something when it was never reached, which is exactly
36
+ // the case where the difference matters.
37
+ if (code === undefined)
38
+ return "the Kubernetes API could not be reached";
39
+ const reason = statusReason(err);
40
+ const detail = reason ? `${reason}, HTTP ${code}` : `HTTP ${code}`;
41
+ const cause = refusalCause(err);
42
+ if (cause === "quota") {
43
+ return `the session namespace's resource quota is exhausted (${detail})`;
44
+ }
45
+ if (cause === "rbac") {
46
+ return `the runner is not permitted to do this (${detail}). The cluster operator must check the runner's RBAC.`;
47
+ }
48
+ return `the Kubernetes API rejected the request (${detail})`;
49
+ }
50
+ /**
51
+ * Which of the two things a `403 Forbidden` means here.
52
+ *
53
+ * The status code alone cannot say: an RBAC denial and a `ResourceQuota`
54
+ * rejection are both `403` with `reason: Forbidden`, and this chart ships a
55
+ * quota (32 pods) enabled by default — so blaming RBAC on every 403 tells the
56
+ * operator to re-check a Role at exactly the moment the cluster is simply full.
57
+ * The two also differ in when they happen: an RBAC 403 is an install-time
58
+ * mistake, a quota 403 arrives under load.
59
+ *
60
+ * The only thing that separates them is the admission message, which is why it
61
+ * is READ here and never echoed: the classification crosses the boundary, the
62
+ * text does not. An unrecognized 403 degrades to the neutral wording rather than
63
+ * to a guess.
64
+ */
65
+ function refusalCause(err) {
66
+ const code = statusCode(err);
67
+ if (code !== 401 && code !== 403)
68
+ return undefined;
69
+ const message = statusMessage(err);
70
+ if (message && /exceeded quota|forbidden: failed quota/i.test(message))
71
+ return "quota";
72
+ if (!message || /\bis forbidden: User\b|\bcannot \w+ resource\b/i.test(message))
73
+ return "rbac";
74
+ return undefined;
75
+ }
76
+ /** The API `Status` object, however the client-node deserializer left it: a
77
+ * parsed object for a status code it recognizes, the raw JSON text for one it
78
+ * does not (which is the 403 case, reported as "Unknown API Status Code!"). */
79
+ function status(err) {
80
+ const body = err?.body;
81
+ const parsed = typeof body === "string" ? parseJson(body) : body;
82
+ return parsed && typeof parsed === "object" ? parsed : undefined;
83
+ }
84
+ /** `Forbidden`, `NotFound`, `Invalid`, … — the one word safe to pass on. */
85
+ function statusReason(err) {
86
+ const reason = status(err)?.reason;
87
+ return typeof reason === "string" && reason.trim() !== "" ? reason : undefined;
88
+ }
89
+ /** Read for classification only — it names cluster identities and never leaves. */
90
+ function statusMessage(err) {
91
+ const message = status(err)?.message;
92
+ return typeof message === "string" ? message : undefined;
93
+ }
94
+ function parseJson(text) {
95
+ try {
96
+ return JSON.parse(text);
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ }
102
+ /**
103
+ * The HTTP status an error carries, or `undefined` when it carries none.
104
+ *
105
+ * The number check is the whole point: `code` is also where Node puts a system
106
+ * error's string code and where a `DOMException` puts its legacy numeric one, so
107
+ * an unreachable apiserver and an aborted request both arrive here looking like
108
+ * a status. Only a value in the HTTP range is one.
109
+ */
110
+ export function statusCode(err) {
111
+ const e = err;
112
+ for (const candidate of [e?.statusCode, e?.code, e?.response?.statusCode]) {
113
+ if (typeof candidate === "number" && candidate >= 100 && candidate <= 599)
114
+ return candidate;
115
+ }
116
+ return undefined;
117
+ }
118
+ //# sourceMappingURL=api-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-error.js","sourceRoot":"","sources":["../../src/k8s/api-error.ts"],"names":[],"mappings":"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"}
@@ -2,11 +2,15 @@ import type { RunnerBackend } from "@telorun/runner-core";
2
2
  import type { BundleStore } from "../bundle-store.js";
3
3
  import type { K8sRunnerConfig } from "../config.js";
4
4
  import type { KubeClient } from "./client.js";
5
+ import { type SessionRouter } from "./routing/index.js";
5
6
  export { podFailureMessage } from "./pod-status.js";
6
7
  export interface K8sBackendDeps {
7
8
  kube: KubeClient;
8
9
  config: K8sRunnerConfig;
9
10
  bundleStore: BundleStore;
11
+ /** How this cluster publishes session routes, resolved once at boot. Absent →
12
+ * logs-only: no base domain, or `SESSION_ROUTING_MODE=none`. */
13
+ router?: SessionRouter;
10
14
  }
11
15
  export declare function createKubernetesBackend(deps: K8sBackendDeps): RunnerBackend;
12
16
  //# sourceMappingURL=backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/k8s/backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAOV,aAAa,EACd,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAgB9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAepD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CA0Y3E"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/k8s/backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAOV,aAAa,EACd,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAepD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB;qEACiE;IACjE,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CA8a3E"}