@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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
2
|
+
import type { KubeClient } from "../client.js";
|
|
3
|
+
export type ResolvedRouting = {
|
|
4
|
+
layer: "none";
|
|
5
|
+
reason: string;
|
|
6
|
+
} | {
|
|
7
|
+
layer: "ingress";
|
|
8
|
+
} | {
|
|
9
|
+
layer: "gateway";
|
|
10
|
+
apiVersion: string;
|
|
11
|
+
};
|
|
12
|
+
export interface ClusterRoutingSupport {
|
|
13
|
+
/** Served Gateway API version, preferred first; undefined when the group is
|
|
14
|
+
* not served at all. */
|
|
15
|
+
gatewayApiVersion?: string;
|
|
16
|
+
/** Number of IngressClasses, or **undefined when the list could not be read**.
|
|
17
|
+
* `networking.k8s.io` is always served, so the API being present is no
|
|
18
|
+
* evidence anyone can route with it — an installed controller registers a
|
|
19
|
+
* class, and that is the usable signal. Zero and unknown are kept apart
|
|
20
|
+
* because a refusal phrased from them is a different claim: one is about the
|
|
21
|
+
* cluster, the other about this runner's permissions. */
|
|
22
|
+
ingressClassCount?: number;
|
|
23
|
+
/** Set when at least one IngressClass carries the default-class annotation. */
|
|
24
|
+
hasDefaultIngressClass: boolean;
|
|
25
|
+
/** Why the IngressClass list could not be read, when it could not. */
|
|
26
|
+
ingressClassError?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** What the cluster can actually route with. Both probes fail SOFT — discovery
|
|
29
|
+
* and IngressClass listing are the runner's least important permissions — but a
|
|
30
|
+
* failure is REPORTED rather than folded into a zero, so nothing downstream can
|
|
31
|
+
* turn a permission error into a claim about the cluster. */
|
|
32
|
+
export declare function detectRoutingSupport(kube: KubeClient): Promise<ClusterRoutingSupport>;
|
|
33
|
+
/**
|
|
34
|
+
* Decide the routing layer ONCE, at boot, so a misconfiguration fails the runner
|
|
35
|
+
* instead of 404-ing every session it later accepts.
|
|
36
|
+
*
|
|
37
|
+
* `auto` resolves on what is CONFIGURED before what is INSTALLED, and that order
|
|
38
|
+
* is the whole point: Gateway API CRDs are frequently present without being the
|
|
39
|
+
* intended path, so "both APIs available" is not evidence of intent and must not
|
|
40
|
+
* decide anything. Only a cluster that offers two usable layers and was told
|
|
41
|
+
* nothing is genuinely ambiguous — and there the answer is to refuse, because
|
|
42
|
+
* guessing wrong publishes routes nothing reconciles, which is silent.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveRouting(kube: KubeClient, config: K8sRunnerConfig): Promise<ResolvedRouting>;
|
|
45
|
+
//# sourceMappingURL=routing-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-mode.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/routing-mode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ/C,MAAM,MAAM,eAAe,GACvB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAM7C,MAAM,WAAW,qBAAqB;IACpC;6BACyB;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;8DAK0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,sBAAsB,EAAE,OAAO,CAAC;IAChC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;8DAG8D;AAC9D,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyB3F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CAmB1B"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { RunnerConfigError } from "../../config.js";
|
|
2
|
+
import { apiReason } from "../api-error.js";
|
|
3
|
+
const GATEWAY_GROUP = "gateway.networking.k8s.io";
|
|
4
|
+
/** Preference order among served Gateway API versions. `v1` is GA; plenty of
|
|
5
|
+
* clusters still serve only `v1beta1`, and an HTTPRoute posted at a version the
|
|
6
|
+
* apiserver does not serve is a 404 at publish time rather than a bad route. */
|
|
7
|
+
const GATEWAY_VERSIONS = ["v1", "v1beta1"];
|
|
8
|
+
/** The annotation by which a cluster nominates the class unqualified Ingresses
|
|
9
|
+
* go to — which is exactly what this runner creates when no class is set. */
|
|
10
|
+
const DEFAULT_CLASS_ANNOTATION = "ingressclass.kubernetes.io/is-default-class";
|
|
11
|
+
/** What the cluster can actually route with. Both probes fail SOFT — discovery
|
|
12
|
+
* and IngressClass listing are the runner's least important permissions — but a
|
|
13
|
+
* failure is REPORTED rather than folded into a zero, so nothing downstream can
|
|
14
|
+
* turn a permission error into a claim about the cluster. */
|
|
15
|
+
export async function detectRoutingSupport(kube) {
|
|
16
|
+
let gatewayApiVersion;
|
|
17
|
+
try {
|
|
18
|
+
const groups = await kube.apis.getAPIVersions();
|
|
19
|
+
const group = groups.groups?.find((g) => g.name === GATEWAY_GROUP);
|
|
20
|
+
const served = new Set((group?.versions ?? []).map((v) => v.version));
|
|
21
|
+
gatewayApiVersion =
|
|
22
|
+
GATEWAY_VERSIONS.find((v) => served.has(v)) ?? group?.preferredVersion?.version;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
gatewayApiVersion = undefined;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const classes = await kube.networking.listIngressClass();
|
|
29
|
+
const items = classes.items ?? [];
|
|
30
|
+
return {
|
|
31
|
+
gatewayApiVersion,
|
|
32
|
+
ingressClassCount: items.length,
|
|
33
|
+
hasDefaultIngressClass: items.some((c) => c.metadata?.annotations?.[DEFAULT_CLASS_ANNOTATION] === "true"),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return { gatewayApiVersion, hasDefaultIngressClass: false, ingressClassError: err };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decide the routing layer ONCE, at boot, so a misconfiguration fails the runner
|
|
42
|
+
* instead of 404-ing every session it later accepts.
|
|
43
|
+
*
|
|
44
|
+
* `auto` resolves on what is CONFIGURED before what is INSTALLED, and that order
|
|
45
|
+
* is the whole point: Gateway API CRDs are frequently present without being the
|
|
46
|
+
* intended path, so "both APIs available" is not evidence of intent and must not
|
|
47
|
+
* decide anything. Only a cluster that offers two usable layers and was told
|
|
48
|
+
* nothing is genuinely ambiguous — and there the answer is to refuse, because
|
|
49
|
+
* guessing wrong publishes routes nothing reconciles, which is silent.
|
|
50
|
+
*/
|
|
51
|
+
export async function resolveRouting(kube, config) {
|
|
52
|
+
const routing = config.sessionRouting;
|
|
53
|
+
if (routing.mode === "none") {
|
|
54
|
+
return { layer: "none", reason: "SESSION_ROUTING_MODE=none" };
|
|
55
|
+
}
|
|
56
|
+
// The base domain is what makes a host constructible at all, so it stays the
|
|
57
|
+
// master switch it has always been — no domain, no routing, whatever the mode.
|
|
58
|
+
if (!routing.baseDomain) {
|
|
59
|
+
return {
|
|
60
|
+
layer: "none",
|
|
61
|
+
reason: "SESSION_ROUTING_BASE_DOMAIN is unset, so no session host can be constructed",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const support = await detectRoutingSupport(kube);
|
|
65
|
+
const resolved = decideLayer(config, support);
|
|
66
|
+
assertTlsMatchesLayer(config, resolved);
|
|
67
|
+
return resolved;
|
|
68
|
+
}
|
|
69
|
+
/** The origin certificate is refused HERE as well as at config load, because
|
|
70
|
+
* under `auto` the effective layer is not known until now: a named Gateway wins,
|
|
71
|
+
* so `mode: auto` + a Gateway + `SESSION_INGRESS_TLS_SECRET` passed the
|
|
72
|
+
* config-time check and then routed on Gateway API with the certificate
|
|
73
|
+
* ignored — plaintext origin traffic under a configuration that reads as
|
|
74
|
+
* TLS-configured, which is the silent downgrade the check exists to prevent. */
|
|
75
|
+
function assertTlsMatchesLayer(config, resolved) {
|
|
76
|
+
if (resolved.layer !== "gateway" || !config.sessionRouting.tlsSecretName)
|
|
77
|
+
return;
|
|
78
|
+
throw new RunnerConfigError("SESSION_INGRESS_TLS_SECRET is set but session routing resolved to Gateway API. Under Gateway " +
|
|
79
|
+
"API the origin certificate belongs to the Gateway listener's own `tls.certificateRefs`, not " +
|
|
80
|
+
"to the per-session route, so this Secret would be silently ignored. Move the reference to " +
|
|
81
|
+
"the Gateway and unset this, or set SESSION_ROUTING_MODE=ingress.");
|
|
82
|
+
}
|
|
83
|
+
function decideLayer(config, support) {
|
|
84
|
+
const routing = config.sessionRouting;
|
|
85
|
+
if (routing.mode === "ingress")
|
|
86
|
+
return { layer: "ingress" };
|
|
87
|
+
if (routing.mode === "gateway") {
|
|
88
|
+
if (!support.gatewayApiVersion) {
|
|
89
|
+
throw new RunnerConfigError(`SESSION_ROUTING_MODE=gateway but the cluster does not serve '${GATEWAY_GROUP}'. ` +
|
|
90
|
+
"Install the Gateway API CRDs, or set SESSION_ROUTING_MODE=ingress.");
|
|
91
|
+
}
|
|
92
|
+
return { layer: "gateway", apiVersion: support.gatewayApiVersion };
|
|
93
|
+
}
|
|
94
|
+
// auto — configured target first.
|
|
95
|
+
if (routing.gateway) {
|
|
96
|
+
if (!support.gatewayApiVersion) {
|
|
97
|
+
throw new RunnerConfigError(`SESSION_GATEWAY_NAME is set but the cluster does not serve '${GATEWAY_GROUP}'. ` +
|
|
98
|
+
"Install the Gateway API CRDs, or unset SESSION_GATEWAY_NAME to route via Ingress.");
|
|
99
|
+
}
|
|
100
|
+
return { layer: "gateway", apiVersion: support.gatewayApiVersion };
|
|
101
|
+
}
|
|
102
|
+
if (routing.ingressClassName)
|
|
103
|
+
return { layer: "ingress" };
|
|
104
|
+
// A DEFAULT-marked IngressClass is configuration, not mere availability: the
|
|
105
|
+
// cluster has nominated where an unqualified Ingress goes, and unqualified is
|
|
106
|
+
// exactly what this runner creates. It therefore ranks with a named Gateway
|
|
107
|
+
// and a configured class rather than with "an IngressClass exists" — without
|
|
108
|
+
// which every cluster that ships the Gateway CRDs beside a default ingress
|
|
109
|
+
// controller would refuse to boot after this upgrade, having worked before.
|
|
110
|
+
if (support.hasDefaultIngressClass)
|
|
111
|
+
return { layer: "ingress" };
|
|
112
|
+
const gatewayUsable = Boolean(support.gatewayApiVersion);
|
|
113
|
+
const ingressUsable = (support.ingressClassCount ?? 0) > 0;
|
|
114
|
+
if (gatewayUsable && ingressUsable) {
|
|
115
|
+
throw new RunnerConfigError(`SESSION_ROUTING_MODE=auto cannot choose: this cluster serves '${GATEWAY_GROUP}' AND has ` +
|
|
116
|
+
`${support.ingressClassCount} IngressClass(es) with none marked default, and nothing says ` +
|
|
117
|
+
"which should carry session traffic. Set SESSION_ROUTING_MODE=ingress (with " +
|
|
118
|
+
"SESSION_INGRESS_CLASS) or SESSION_ROUTING_MODE=gateway (with SESSION_GATEWAY_NAME).");
|
|
119
|
+
}
|
|
120
|
+
if (gatewayUsable) {
|
|
121
|
+
throw new RunnerConfigError(`SESSION_ROUTING_MODE=auto resolved to Gateway API, but SESSION_GATEWAY_NAME is unset — a ` +
|
|
122
|
+
"route has to name the Gateway it attaches to, and picking one of several would fail as " +
|
|
123
|
+
"silently as publishing none. Set SESSION_GATEWAY_NAME (and SESSION_GATEWAY_NAMESPACE " +
|
|
124
|
+
`when the Gateway is not in '${config.sessionNamespace}').`);
|
|
125
|
+
}
|
|
126
|
+
if (ingressUsable)
|
|
127
|
+
return { layer: "ingress" };
|
|
128
|
+
// Say which of the two it is. Reporting a permission failure as "no
|
|
129
|
+
// IngressClass exists" is a claim about the cluster drawn from a fact about
|
|
130
|
+
// this runner's access, and it sends an operator to install a controller they
|
|
131
|
+
// already have.
|
|
132
|
+
if (support.ingressClassError !== undefined) {
|
|
133
|
+
throw new RunnerConfigError("SESSION_ROUTING_BASE_DOMAIN is set but the routing layer could not be resolved: listing " +
|
|
134
|
+
`IngressClasses failed (${apiReason(support.ingressClassError)}) and '${GATEWAY_GROUP}' is ` +
|
|
135
|
+
"not served. Grant `list` on `ingressclasses`, or set SESSION_ROUTING_MODE explicitly.");
|
|
136
|
+
}
|
|
137
|
+
throw new RunnerConfigError("SESSION_ROUTING_BASE_DOMAIN is set but this cluster offers no usable routing layer: no " +
|
|
138
|
+
`IngressClass exists and '${GATEWAY_GROUP}' is not served. Install an Ingress controller or ` +
|
|
139
|
+
"the Gateway API, or set SESSION_ROUTING_MODE=none to run logs-only.");
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=routing-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-mode.js","sourceRoot":"","sources":["../../../src/k8s/routing/routing-mode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD;;iFAEiF;AACjF,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAO3C;8EAC8E;AAC9E,MAAM,wBAAwB,GAAG,6CAA6C,CAAC;AAmB/E;;;8DAG8D;AAC9D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAgB;IACzD,IAAI,iBAAqC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACtE,iBAAiB;YACf,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,gBAAgB,EAAE,OAAO,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB,GAAG,SAAS,CAAC;IAChC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAClC,OAAO;YACL,iBAAiB;YACjB,iBAAiB,EAAE,KAAK,CAAC,MAAM;YAC/B,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,wBAAwB,CAAC,KAAK,MAAM,CACtE;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC;IACtF,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAgB,EAChB,MAAuB;IAEvB,MAAM,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;IAEtC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAChE,CAAC;IACD,6EAA6E;IAC7E,+EAA+E;IAC/E,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,6EAA6E;SACtF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;iFAKiF;AACjF,SAAS,qBAAqB,CAAC,MAAuB,EAAE,QAAyB;IAC/E,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa;QAAE,OAAO;IACjF,MAAM,IAAI,iBAAiB,CACzB,+FAA+F;QAC7F,8FAA8F;QAC9F,4FAA4F;QAC5F,kEAAkE,CACrE,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,MAAuB,EAAE,OAA8B;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;IAEtC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAE5D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,IAAI,iBAAiB,CACzB,gEAAgE,aAAa,KAAK;gBAChF,oEAAoE,CACvE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC;IACrE,CAAC;IAED,kCAAkC;IAClC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,IAAI,iBAAiB,CACzB,+DAA+D,aAAa,KAAK;gBAC/E,mFAAmF,CACtF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC;IACrE,CAAC;IACD,IAAI,OAAO,CAAC,gBAAgB;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC1D,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAI,OAAO,CAAC,sBAAsB;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAEhE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAE3D,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,IAAI,iBAAiB,CACzB,iEAAiE,aAAa,YAAY;YACxF,GAAG,OAAO,CAAC,iBAAiB,+DAA+D;YAC3F,6EAA6E;YAC7E,qFAAqF,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,iBAAiB,CACzB,2FAA2F;YACzF,yFAAyF;YACzF,uFAAuF;YACvF,+BAA+B,MAAM,CAAC,gBAAgB,KAAK,CAC9D,CAAC;IACJ,CAAC;IACD,IAAI,aAAa;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAE/C,oEAAoE;IACpE,4EAA4E;IAC5E,8EAA8E;IAC9E,gBAAgB;IAChB,IAAI,OAAO,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC5C,MAAM,IAAI,iBAAiB,CACzB,0FAA0F;YACxF,0BAA0B,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,aAAa,OAAO;YAC5F,uFAAuF,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,iBAAiB,CACzB,yFAAyF;QACvF,4BAA4B,aAAa,oDAAoD;QAC7F,qEAAqE,CACxE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PortMapping, RunnerEndpoint } from "@telorun/runner-core";
|
|
2
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
3
|
+
/** Host fronting a single tcp port: `<port>-<sessionId>.<domain>`. The port rides
|
|
4
|
+
* as a leading label (no dots), so it stays a single label under the base domain
|
|
5
|
+
* — matching the docker runner's proxy scheme and compatible with a single-label
|
|
6
|
+
* wildcard cert (`*.<domain>`).
|
|
7
|
+
*
|
|
8
|
+
* Shared by both routing layers on purpose: the host scheme is what a client is
|
|
9
|
+
* told and what DNS is configured for, so it must not vary with the cluster's
|
|
10
|
+
* choice of Ingress or Gateway API. */
|
|
11
|
+
export declare function hostForPort(config: K8sRunnerConfig, sessionId: string, port: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Endpoints announced on the `running` status. Every tcp port is fronted by its
|
|
14
|
+
* own per-session host (served on 443) and carries an external `url`. udp ports
|
|
15
|
+
* aren't HTTP-routable, so they keep the host-less form. Without a base domain,
|
|
16
|
+
* host is left blank for the client adapter to fill (parity with docker).
|
|
17
|
+
*
|
|
18
|
+
* `routed` is what says a URL will be served, and it is separate from the base
|
|
19
|
+
* domain because the two can disagree: `SESSION_ROUTING_MODE=none` with a domain
|
|
20
|
+
* configured is a valid, documented setup that publishes nothing. Keying on the
|
|
21
|
+
* domain alone handed the editor a URL nothing serves — and the route watch
|
|
22
|
+
* never runs in that mode, so no `route` event would have said so either.
|
|
23
|
+
*/
|
|
24
|
+
export declare function endpointsFor(config: K8sRunnerConfig, sessionId: string, ports: PortMapping[], routed: boolean): RunnerEndpoint[];
|
|
25
|
+
//# sourceMappingURL=session-endpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-endpoints.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/session-endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;;wCAOwC;AACxC,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAAE,EACpB,MAAM,EAAE,OAAO,GACd,cAAc,EAAE,CAWlB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Host fronting a single tcp port: `<port>-<sessionId>.<domain>`. The port rides
|
|
2
|
+
* as a leading label (no dots), so it stays a single label under the base domain
|
|
3
|
+
* — matching the docker runner's proxy scheme and compatible with a single-label
|
|
4
|
+
* wildcard cert (`*.<domain>`).
|
|
5
|
+
*
|
|
6
|
+
* Shared by both routing layers on purpose: the host scheme is what a client is
|
|
7
|
+
* told and what DNS is configured for, so it must not vary with the cluster's
|
|
8
|
+
* choice of Ingress or Gateway API. */
|
|
9
|
+
export function hostForPort(config, sessionId, port) {
|
|
10
|
+
return `${port}-${sessionId}.${config.sessionRouting.baseDomain}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Endpoints announced on the `running` status. Every tcp port is fronted by its
|
|
14
|
+
* own per-session host (served on 443) and carries an external `url`. udp ports
|
|
15
|
+
* aren't HTTP-routable, so they keep the host-less form. Without a base domain,
|
|
16
|
+
* host is left blank for the client adapter to fill (parity with docker).
|
|
17
|
+
*
|
|
18
|
+
* `routed` is what says a URL will be served, and it is separate from the base
|
|
19
|
+
* domain because the two can disagree: `SESSION_ROUTING_MODE=none` with a domain
|
|
20
|
+
* configured is a valid, documented setup that publishes nothing. Keying on the
|
|
21
|
+
* domain alone handed the editor a URL nothing serves — and the route watch
|
|
22
|
+
* never runs in that mode, so no `route` event would have said so either.
|
|
23
|
+
*/
|
|
24
|
+
export function endpointsFor(config, sessionId, ports, routed) {
|
|
25
|
+
if (!routed || !config.sessionRouting.baseDomain || ports.length === 0) {
|
|
26
|
+
return ports.map((p) => ({ host: "", port: p.port, protocol: p.protocol }));
|
|
27
|
+
}
|
|
28
|
+
return ports.map((p) => {
|
|
29
|
+
if (p.protocol !== "tcp") {
|
|
30
|
+
return { host: "", port: p.port, protocol: p.protocol };
|
|
31
|
+
}
|
|
32
|
+
const host = hostForPort(config, sessionId, p.port);
|
|
33
|
+
return { host, port: p.port, protocol: p.protocol, url: `https://${host}` };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=session-endpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-endpoints.js","sourceRoot":"","sources":["../../../src/k8s/routing/session-endpoints.ts"],"names":[],"mappings":"AAIA;;;;;;;wCAOwC;AACxC,MAAM,UAAU,WAAW,CAAC,MAAuB,EAAE,SAAiB,EAAE,IAAY;IAClF,OAAO,GAAG,IAAI,IAAI,SAAS,IAAI,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAuB,EACvB,SAAiB,EACjB,KAAoB,EACpB,MAAe;IAEf,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACrB,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YACzB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1D,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,IAAI,EAAE,EAAE,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { PortMapping } from "@telorun/runner-core";
|
|
2
|
+
/** One published host and the session port behind it. */
|
|
3
|
+
export interface PublishedRoute {
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PublishRouteArgs {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
/** The Service the routing object points at. */
|
|
10
|
+
serviceName: string;
|
|
11
|
+
/** Owner of every object created — see `podOwnerRef`. */
|
|
12
|
+
podName: string;
|
|
13
|
+
podUid: string;
|
|
14
|
+
/** Every port the session must carry. Non-tcp ports are ignored: they are not
|
|
15
|
+
* HTTP-routable, and the Service still exposes them. */
|
|
16
|
+
ports: PortMapping[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What a routing layer has to say about one published host.
|
|
20
|
+
*
|
|
21
|
+
* `pending` is a real answer, not an absence: a controller that has not yet
|
|
22
|
+
* written status is indistinguishable from one that will never claim the route,
|
|
23
|
+
* and only time separates them.
|
|
24
|
+
*/
|
|
25
|
+
export type RouteVerdict = {
|
|
26
|
+
kind: "programmed";
|
|
27
|
+
} | {
|
|
28
|
+
kind: "pending";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "rejected";
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* How this cluster publishes a session's public routes.
|
|
35
|
+
*
|
|
36
|
+
* The seam exists because Ingress and Gateway API are both current: neither is a
|
|
37
|
+
* migration target for the other, clusters ship one, the other, or both, and a
|
|
38
|
+
* runner that hardcodes either is unroutable on half of them. It deliberately
|
|
39
|
+
* stays inside the k8s backend — docker publishes host ports through its own
|
|
40
|
+
* proxy and has no use for it, and `runner-core` owns the backend-neutral `/v1`
|
|
41
|
+
* contract, where "Ingress or HTTPRoute" is not a distinction that exists.
|
|
42
|
+
*/
|
|
43
|
+
export interface SessionRouter {
|
|
44
|
+
/** Names the layer for diagnostics. */
|
|
45
|
+
readonly layer: "ingress" | "gateway";
|
|
46
|
+
/** Create-or-replace this session's routing objects. Returns the hosts it
|
|
47
|
+
* published, which is what the route watch then asks about. */
|
|
48
|
+
publish(args: PublishRouteArgs): Promise<PublishedRoute[]>;
|
|
49
|
+
/** This layer's current verdict on one published host. Asked repeatedly by the
|
|
50
|
+
* route watch until it settles or the deadline passes. */
|
|
51
|
+
verdictFor(sessionId: string, route: PublishedRoute): Promise<RouteVerdict>;
|
|
52
|
+
/** What to tell an operator when a route is still `pending` at the deadline.
|
|
53
|
+
* Layer-specific because the causes are: no controller for the IngressClass,
|
|
54
|
+
* versus a Gateway that never claimed the route. */
|
|
55
|
+
unclaimedReason(): string;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=session-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-router.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/session-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf;6DACyD;IACzD,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAEtC;oEACgE;IAChE,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAE3D;+DAC2D;IAC3D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAE5E;;yDAEqD;IACrD,eAAe,IAAI,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-router.js","sourceRoot":"","sources":["../../../src/k8s/routing/session-router.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { V1OwnerReference, V1Service } from "@kubernetes/client-node";
|
|
2
|
+
import type { PortMapping } from "@telorun/runner-core";
|
|
3
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
4
|
+
/** OwnerReference to the session Pod so every routing object is garbage collected
|
|
5
|
+
* when the Pod dies — essential for sub-minute sessions that would otherwise leak
|
|
6
|
+
* routing objects. Both routing layers use it: an HTTPRoute is namespaced like an
|
|
7
|
+
* Ingress, so the same ownership works unchanged. */
|
|
8
|
+
export declare function podOwnerRef(podName: string, podUid: string): V1OwnerReference;
|
|
9
|
+
/** The name every routing object for one session shares. */
|
|
10
|
+
export declare function sessionObjectName(sessionId: string): string;
|
|
11
|
+
/** The Service both routing layers point at — the routing layer decides how
|
|
12
|
+
* traffic ARRIVES, never what it arrives at, so this is layer-neutral. */
|
|
13
|
+
export declare function buildSessionService(config: K8sRunnerConfig, sessionId: string, podName: string, podUid: string, ports: PortMapping[]): V1Service;
|
|
14
|
+
//# sourceMappingURL=session-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-service.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/session-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;sDAGsD;AACtD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAS7E;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;2EAC2E;AAC3E,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,EAAE,GACnB,SAAS,CAoBX"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** OwnerReference to the session Pod so every routing object is garbage collected
|
|
2
|
+
* when the Pod dies — essential for sub-minute sessions that would otherwise leak
|
|
3
|
+
* routing objects. Both routing layers use it: an HTTPRoute is namespaced like an
|
|
4
|
+
* Ingress, so the same ownership works unchanged. */
|
|
5
|
+
export function podOwnerRef(podName, podUid) {
|
|
6
|
+
return {
|
|
7
|
+
apiVersion: "v1",
|
|
8
|
+
kind: "Pod",
|
|
9
|
+
name: podName,
|
|
10
|
+
uid: podUid,
|
|
11
|
+
controller: true,
|
|
12
|
+
blockOwnerDeletion: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** The name every routing object for one session shares. */
|
|
16
|
+
export function sessionObjectName(sessionId) {
|
|
17
|
+
return `telo-run-${sessionId}`;
|
|
18
|
+
}
|
|
19
|
+
/** The Service both routing layers point at — the routing layer decides how
|
|
20
|
+
* traffic ARRIVES, never what it arrives at, so this is layer-neutral. */
|
|
21
|
+
export function buildSessionService(config, sessionId, podName, podUid, ports) {
|
|
22
|
+
return {
|
|
23
|
+
apiVersion: "v1",
|
|
24
|
+
kind: "Service",
|
|
25
|
+
metadata: {
|
|
26
|
+
name: sessionObjectName(sessionId),
|
|
27
|
+
namespace: config.sessionNamespace,
|
|
28
|
+
labels: { "app.kubernetes.io/managed-by": config.managedByLabel },
|
|
29
|
+
ownerReferences: [podOwnerRef(podName, podUid)],
|
|
30
|
+
},
|
|
31
|
+
spec: {
|
|
32
|
+
selector: { "telo.run/session-id": sessionId },
|
|
33
|
+
ports: ports.map((p) => ({
|
|
34
|
+
name: `p${p.port}`,
|
|
35
|
+
port: p.port,
|
|
36
|
+
targetPort: p.port,
|
|
37
|
+
protocol: p.protocol.toUpperCase(),
|
|
38
|
+
})),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=session-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../../../src/k8s/routing/session-service.ts"],"names":[],"mappings":"AAMA;;;sDAGsD;AACtD,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,MAAc;IACzD,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,OAAO;QACb,GAAG,EAAE,MAAM;QACX,UAAU,EAAE,IAAI;QAChB,kBAAkB,EAAE,IAAI;KACzB,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,YAAY,SAAS,EAAE,CAAC;AACjC,CAAC;AAED;2EAC2E;AAC3E,MAAM,UAAU,mBAAmB,CACjC,MAAuB,EACvB,SAAiB,EACjB,OAAe,EACf,MAAc,EACd,KAAoB;IAEpB,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,IAAI,EAAE,iBAAiB,CAAC,SAAS,CAAC;YAClC,SAAS,EAAE,MAAM,CAAC,gBAAgB;YAClC,MAAM,EAAE,EAAE,8BAA8B,EAAE,MAAM,CAAC,cAAc,EAAE;YACjE,eAAe,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SAChD;QACD,IAAI,EAAE;YACJ,QAAQ,EAAE,EAAE,qBAAqB,EAAE,SAAS,EAAE;YAC9C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvB,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE;gBAClB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,UAAU,EAAE,CAAC,CAAC,IAAI;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE;aACnC,CAAC,CAAC;SACJ;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import type { BackendSession, BackendStartSpec } from "@telorun/runner-core";
|
|
2
2
|
import type { K8sRunnerConfig } from "../config.js";
|
|
3
3
|
import type { KubeClient } from "./client.js";
|
|
4
|
+
import { type SessionRouter } from "./routing/index.js";
|
|
4
5
|
export interface WatchSessionDeps {
|
|
5
6
|
kube: KubeClient;
|
|
6
7
|
config: K8sRunnerConfig;
|
|
8
|
+
/** Resolved at boot; absent → logs-only. */
|
|
9
|
+
router?: SessionRouter;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* A watch session: one pod, one workspace volume, one container per running
|
|
10
13
|
* application, and the session outliving every run inside it.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* later reload resolves from local disk.
|
|
15
|
+
* Its cache is what separates it from a run session: the module closure lands in
|
|
16
|
+
* a directory that lives as long as the POD rather than as long as one run, so
|
|
17
|
+
* the download happens once per session and every later reload resolves from
|
|
18
|
+
* local disk.
|
|
17
19
|
*/
|
|
18
20
|
export declare function startWatchSession(deps: WatchSessionDeps, spec: BackendStartSpec): Promise<BackendSession>;
|
|
19
21
|
//# sourceMappingURL=watch-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-session.d.ts","sourceRoot":"","sources":["../../src/k8s/watch-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,cAAc,EACd,gBAAgB,EAMjB,MAAM,sBAAsB,CAAC;AAW9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"watch-session.d.ts","sourceRoot":"","sources":["../../src/k8s/watch-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,cAAc,EACd,gBAAgB,EAMjB,MAAM,sBAAsB,CAAC;AAW9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAqB5B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,cAAc,CAAC,CAqlBzB"}
|