@namzu/sandbox 13.0.0 → 14.0.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/CHANGELOG.md +309 -0
- package/README.md +151 -0
- package/dist/backends/firecracker/protocol.d.ts +22 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +104 -9
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +139 -13
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +219 -0
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/egress-policy.js +314 -0
- package/dist/backends/kubernetes/egress-policy.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +374 -0
- package/dist/backends/kubernetes/index.d.ts.map +1 -0
- package/dist/backends/kubernetes/index.js +671 -0
- package/dist/backends/kubernetes/index.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +125 -0
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
- package/dist/backends/kubernetes/k8s-client.js +246 -0
- package/dist/backends/kubernetes/k8s-client.js.map +1 -0
- package/dist/backends/kubernetes/lease.d.ts +119 -0
- package/dist/backends/kubernetes/lease.d.ts.map +1 -0
- package/dist/backends/kubernetes/lease.js +151 -0
- package/dist/backends/kubernetes/lease.js.map +1 -0
- package/dist/backends/kubernetes/objects.d.ts +282 -0
- package/dist/backends/kubernetes/objects.d.ts.map +1 -0
- package/dist/backends/kubernetes/objects.js +156 -0
- package/dist/backends/kubernetes/objects.js.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.js +185 -0
- package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +123 -0
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
- package/dist/backends/kubernetes/sandbox.js +299 -0
- package/dist/backends/kubernetes/sandbox.js.map +1 -0
- package/dist/backends/kubernetes/transport.d.ts +122 -0
- package/dist/backends/kubernetes/transport.d.ts.map +1 -0
- package/dist/backends/kubernetes/transport.js +197 -0
- package/dist/backends/kubernetes/transport.js.map +1 -0
- package/dist/backends/kubernetes/workspace.d.ts +381 -0
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace.js +1064 -0
- package/dist/backends/kubernetes/workspace.js.map +1 -0
- package/dist/index.d.ts +132 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -34
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +193 -0
- package/dist/testing/sandbox-conformance.d.ts.map +1 -0
- package/dist/testing/sandbox-conformance.js +465 -0
- package/dist/testing/sandbox-conformance.js.map +1 -0
- package/package.json +5 -4
- package/src/backends/firecracker/protocol.ts +27 -0
- package/src/backends/firecracker/transport.ts +199 -28
- package/src/backends/kubernetes/egress-policy.ts +437 -0
- package/src/backends/kubernetes/index.ts +1012 -0
- package/src/backends/kubernetes/k8s-client.ts +352 -0
- package/src/backends/kubernetes/lease.ts +198 -0
- package/src/backends/kubernetes/objects.ts +363 -0
- package/src/backends/kubernetes/privilege-probe.ts +261 -0
- package/src/backends/kubernetes/sandbox.ts +395 -0
- package/src/backends/kubernetes/transport.ts +286 -0
- package/src/backends/kubernetes/workspace.ts +1386 -0
- package/src/index.ts +257 -35
- package/src/testing/sandbox-conformance.ts +667 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistent workspace: a Sandbox that keeps its disk across suspends.
|
|
3
|
+
*
|
|
4
|
+
* A task sandbox (`index.ts`) is claimed, used and deleted inside one run. A
|
|
5
|
+
* workspace is the opposite object: it is created once, addressed by a name
|
|
6
|
+
* the CALLER chooses, suspended when nobody is using it, resumed days later
|
|
7
|
+
* with yesterday's dependency cache and git checkout still on its disk, and
|
|
8
|
+
* deleted only when someone says so.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is a directly created Sandbox and never a claim
|
|
11
|
+
*
|
|
12
|
+
* `Sandbox.spec.volumeClaimTemplates` is CEL-immutable on the served CRD
|
|
13
|
+
* ("volumeClaimTemplates is immutable"), and a `SandboxClaim` that carries
|
|
14
|
+
* `spec.volumeClaimTemplates` is forced to cold-start instead of adopting a
|
|
15
|
+
* warm pool sandbox. So the disk has to be in the spec at creation, and the
|
|
16
|
+
* appealing middle road — claim a warm diskless sandbox and attach a disk to
|
|
17
|
+
* it — is not expressible in this API at all. A workspace is therefore a
|
|
18
|
+
* `Sandbox` POSTed directly, with a deterministic name, and the warm pool has
|
|
19
|
+
* nothing to do with it.
|
|
20
|
+
*
|
|
21
|
+
* ## Block, not a filesystem
|
|
22
|
+
*
|
|
23
|
+
* The disk must be `volumeMode: Block`, consumed through the container's
|
|
24
|
+
* `volumeDevices`, and this module REFUSES a template whose disk is anything
|
|
25
|
+
* else. Under a VM-isolating RuntimeClass a `Filesystem` PVC reaches the guest
|
|
26
|
+
* through a host/guest filesystem passthrough (virtio-fs), whose per-file
|
|
27
|
+
* overhead lands squarely on the two things a workspace does all day: walking
|
|
28
|
+
* a dependency tree and touching thousands of small files. Nothing FAILS; the
|
|
29
|
+
* workspace is merely several times slower, and no functional test can see
|
|
30
|
+
* that. A raw block device the guest formats and mounts itself is an ordinary
|
|
31
|
+
* local filesystem inside the VM. See {@link KubernetesWorkspaceDiskError}.
|
|
32
|
+
*
|
|
33
|
+
* ## No lease
|
|
34
|
+
*
|
|
35
|
+
* Every task sandbox carries `shutdownTime` + `shutdownPolicy: Delete`, so a
|
|
36
|
+
* host that dies mid-run costs the cluster one expiry rather than a leak, and
|
|
37
|
+
* its handle renews that expiry for as long as it lives. A workspace carries
|
|
38
|
+
* NEITHER. An expiry on a workspace is a timer that deletes a caller's files,
|
|
39
|
+
* and a renewal loop makes losing them conditional on a host process staying
|
|
40
|
+
* up — exactly backwards for an object whose whole purpose is to outlive the
|
|
41
|
+
* host. A workspace is explicitly managed: it goes away when
|
|
42
|
+
* `destroy({ deleteDisk: true })` says so, and not before.
|
|
43
|
+
*
|
|
44
|
+
* ## Suspend, resume, and what changes across one
|
|
45
|
+
*
|
|
46
|
+
* `suspend()` merge-PATCHes `spec.operatingMode: Suspended`; the controller
|
|
47
|
+
* deletes only the Pod and reconciles PVCs unconditionally on every pass, so
|
|
48
|
+
* the disk survives with the same UID. It then waits on the POD — not on the
|
|
49
|
+
* Sandbox's `Suspended` condition, which upstream documents as lingering True
|
|
50
|
+
* after a resume, and not merely on a `deletionTimestamp`, which appears
|
|
51
|
+
* while the guest is still running. `resume()` PATCHes it back and waits
|
|
52
|
+
* for a new pod — and a resumed pod keeps the sandbox's NAME while getting a
|
|
53
|
+
* new uid and a new IP. Both matter: the uid is the agent's bind token, and
|
|
54
|
+
* the IP is where the agent answers. So resume re-resolves the address, re-
|
|
55
|
+
* reads the uid (skipping the outgoing pod, which is still listed under the
|
|
56
|
+
* same name while it terminates) and rebuilds the transport. Nothing from
|
|
57
|
+
* before the suspend is reused.
|
|
58
|
+
*
|
|
59
|
+
* Between the two, every call refuses with
|
|
60
|
+
* {@link KubernetesWorkspaceSuspendedError} and issues no dial. A dial would
|
|
61
|
+
* be worse than useless: the address still resolves — the Service outlives
|
|
62
|
+
* the pod — so the call would hang until a connect timeout with nothing in
|
|
63
|
+
* the failure naming the suspend.
|
|
64
|
+
*
|
|
65
|
+
* ## Adoption is checked against the object, not against the caller
|
|
66
|
+
*
|
|
67
|
+
* A create that collides with an existing object of the same name ADOPTS it,
|
|
68
|
+
* because the deterministic name is only worth having if coming back is the
|
|
69
|
+
* normal path. What is adopted is then checked against the configuration: the
|
|
70
|
+
* block disk, the `sandbox.namzu.ai/template` pod label (the label an egress
|
|
71
|
+
* NetworkPolicy selects by) and `runtimeClassName` (the VM boundary). A
|
|
72
|
+
* standing object that disagrees with any of them is refused by name rather
|
|
73
|
+
* than driven — see {@link KubernetesWorkspaceMismatchError}. What is NOT
|
|
74
|
+
* checked, and cannot be from here, is whether somebody else is already using
|
|
75
|
+
* it: two host processes can hold handles to one running workspace, and the
|
|
76
|
+
* `destroy()` of either suspends the pod the other is executing in. A
|
|
77
|
+
* workspace id is a name, not a lock.
|
|
78
|
+
*
|
|
79
|
+
* ## There is no delete-compute-keep-disk verb
|
|
80
|
+
*
|
|
81
|
+
* The API has `operatingMode` and it has DELETE. Nothing in between. So
|
|
82
|
+
* `destroy()` with no options, or `deleteDisk: false`, SUSPENDS and leaves
|
|
83
|
+
* the object standing; only `destroy({ deleteDisk: true })` DELETEs the
|
|
84
|
+
* Sandbox, which cascades to the Pod, the Service and the PVC through
|
|
85
|
+
* ownerReferences. The default is the non-destructive one because `destroy()`
|
|
86
|
+
* is what a `finally` block calls, and a `finally` block must not be able to
|
|
87
|
+
* erase a workspace nobody asked to erase.
|
|
88
|
+
*
|
|
89
|
+
* The same holds for the paths nobody asked for at all. A create or resume
|
|
90
|
+
* that fails suspends the object and rethrows rather than cleaning it up (see
|
|
91
|
+
* {@link createKubernetesWorkspace}), and a pod that stops being able to say
|
|
92
|
+
* what happened to a command — the shared execution controller's unconfirmed
|
|
93
|
+
* cancellation, which retires a task sandbox by DELETING it — is retired here
|
|
94
|
+
* by that same suspend patch (see {@link retireSession}). Exactly one DELETE
|
|
95
|
+
* is reachable from this module, and it is the one `deleteDisk: true` asks
|
|
96
|
+
* for.
|
|
97
|
+
*
|
|
98
|
+
* ## A state is committed when the cluster confirms it, never before
|
|
99
|
+
*
|
|
100
|
+
* Both verbs are idempotent, and both are idempotent by EARLY-RETURNING on a
|
|
101
|
+
* state. That makes the moment a state is written the whole correctness
|
|
102
|
+
* question: a handle that marks itself `deleted` before its DELETE lands
|
|
103
|
+
* answers every later `destroy()` from that mark, so a 500 is thrown once and
|
|
104
|
+
* the Sandbox then stands on the cluster with nothing left that would remove
|
|
105
|
+
* it. The same shape on `suspend()` leaves a pod running — and billing —
|
|
106
|
+
* behind a handle that says it is suspended.
|
|
107
|
+
*
|
|
108
|
+
* So `suspended` is written after the patch lands AND the pod is observed
|
|
109
|
+
* stopped, `deleted` after the DELETE resolves (or reports the object already
|
|
110
|
+
* gone), and a request that fails leaves the state it found. Concurrency is
|
|
111
|
+
* covered the other way round, by a single flight per verb: a second caller
|
|
112
|
+
* arriving mid-transition awaits the one in progress instead of sending a
|
|
113
|
+
* second request into the gap the deferred mark opens.
|
|
114
|
+
*/
|
|
115
|
+
import type { OpenTerminalOptions, Sandbox, SandboxDestroyOptions, SandboxTcpConnectOptions, SandboxTcpConnection, TerminalSession } from '@namzu/sdk';
|
|
116
|
+
import { type KubernetesBackendInternalConfig } from './index.js';
|
|
117
|
+
import { type SandboxPodTemplate, type SandboxVolumeClaimTemplate } from './objects.js';
|
|
118
|
+
/**
|
|
119
|
+
* Thrown when a workspace's `SandboxTemplate` does not describe a block disk
|
|
120
|
+
* this backend is willing to build a workspace on.
|
|
121
|
+
*
|
|
122
|
+
* Named, and thrown before anything is created, because every shape it
|
|
123
|
+
* refuses WORKS: a template with no disk produces a sandbox whose files
|
|
124
|
+
* vanish on the next suspend, and a `Filesystem` disk produces one that keeps
|
|
125
|
+
* its files and is quietly several times slower at the small-file IO a
|
|
126
|
+
* workspace is made of. Neither fails a functional test, so neither can be
|
|
127
|
+
* left to be noticed later.
|
|
128
|
+
*/
|
|
129
|
+
export declare class KubernetesWorkspaceDiskError extends Error {
|
|
130
|
+
/** What was inspected — a `SandboxTemplate`, or an existing Sandbox. */
|
|
131
|
+
readonly source: string;
|
|
132
|
+
readonly name = "KubernetesWorkspaceDiskError";
|
|
133
|
+
constructor(
|
|
134
|
+
/** What was inspected — a `SandboxTemplate`, or an existing Sandbox. */
|
|
135
|
+
source: string, message: string);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Thrown when the Sandbox already standing under a workspace's name was not
|
|
139
|
+
* built the way this caller is configured to build one.
|
|
140
|
+
*
|
|
141
|
+
* Adoption is the NORMAL path — the deterministic name exists so that coming
|
|
142
|
+
* back to a workspace is cheap — and that is exactly why the object handed
|
|
143
|
+
* back is checked against the configuration rather than against the caller's
|
|
144
|
+
* intention. Two controls would otherwise be lost silently, and lost for as
|
|
145
|
+
* long as the workspace lives, which is the longest of anything this backend
|
|
146
|
+
* makes:
|
|
147
|
+
*
|
|
148
|
+
* - the `sandbox.namzu.ai/template` pod label, which is what a translated
|
|
149
|
+
* egress `NetworkPolicy`'s `podSelector` matches. A pod carrying another
|
|
150
|
+
* value — or none — is not selected by the policy this call just verified,
|
|
151
|
+
* so the boundary would report verified while covering nothing.
|
|
152
|
+
* - `runtimeClassName`, which is the VM boundary. The privilege probe cannot
|
|
153
|
+
* stand in for it: `/proc/self/status` reads the same inside a VM guest as
|
|
154
|
+
* it does inside an ordinary shared-kernel container.
|
|
155
|
+
*
|
|
156
|
+
* Nothing is patched to make the standing object match. Its disk may hold a
|
|
157
|
+
* month of the caller's files, and rewriting a live workspace's podTemplate to
|
|
158
|
+
* fit a new configuration is a larger decision than reattaching to it.
|
|
159
|
+
*/
|
|
160
|
+
export declare class KubernetesWorkspaceMismatchError extends Error {
|
|
161
|
+
/** The Sandbox found standing under this workspace's name. */
|
|
162
|
+
readonly sandboxName: string;
|
|
163
|
+
/** Which configured field the standing object disagrees with. */
|
|
164
|
+
readonly field: 'sandboxTemplateName' | 'runtimeClassName';
|
|
165
|
+
/** What the configuration asked for. */
|
|
166
|
+
readonly expected: string;
|
|
167
|
+
/** What the object carries — absent when it carries nothing at all. */
|
|
168
|
+
readonly actual: string | undefined;
|
|
169
|
+
readonly name = "KubernetesWorkspaceMismatchError";
|
|
170
|
+
constructor(
|
|
171
|
+
/** The Sandbox found standing under this workspace's name. */
|
|
172
|
+
sandboxName: string,
|
|
173
|
+
/** Which configured field the standing object disagrees with. */
|
|
174
|
+
field: 'sandboxTemplateName' | 'runtimeClassName',
|
|
175
|
+
/** What the configuration asked for. */
|
|
176
|
+
expected: string,
|
|
177
|
+
/** What the object carries — absent when it carries nothing at all. */
|
|
178
|
+
actual: string | undefined, message: string);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Thrown by every operation on a workspace that is currently suspended.
|
|
182
|
+
*
|
|
183
|
+
* Distinct from {@link KubernetesSandboxDestroyedError} because the state is
|
|
184
|
+
* RECOVERABLE and the advice is one word: call `resume()`. Nothing is dialed
|
|
185
|
+
* before it is thrown.
|
|
186
|
+
*/
|
|
187
|
+
export declare class KubernetesWorkspaceSuspendedError extends Error {
|
|
188
|
+
readonly operation: string;
|
|
189
|
+
readonly workspaceId: string;
|
|
190
|
+
readonly sandboxName: string;
|
|
191
|
+
readonly name = "KubernetesWorkspaceSuspendedError";
|
|
192
|
+
constructor(operation: string, workspaceId: string, sandboxName: string);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Thrown when a suspend's `operatingMode: Suspended` patch was accepted and
|
|
196
|
+
* the pod had still not stopped by the readiness deadline.
|
|
197
|
+
*
|
|
198
|
+
* The workspace is left in the state that is TRUE rather than the one that
|
|
199
|
+
* was asked for: the patch landed, so the pod is on its way out and no call
|
|
200
|
+
* is admitted — but the suspend is not recorded as finished, because it did
|
|
201
|
+
* not finish. A later `suspend()` sends the patch again and waits again
|
|
202
|
+
* instead of returning on a mark this one left behind, and `resume()` still
|
|
203
|
+
* works.
|
|
204
|
+
*
|
|
205
|
+
* Recording it as suspended here is exactly the defect this class exists to
|
|
206
|
+
* make impossible. The guest is still running, still holding the block device
|
|
207
|
+
* open and still writing to it, and "suspended" is a promise that the disk is
|
|
208
|
+
* quiesced — so a handle that made that promise on a wait it lost would let
|
|
209
|
+
* the next caller resume, or delete, a workspace mid-write.
|
|
210
|
+
*/
|
|
211
|
+
export declare class KubernetesWorkspaceSuspendTimeoutError extends Error {
|
|
212
|
+
readonly workspaceId: string;
|
|
213
|
+
readonly sandboxName: string;
|
|
214
|
+
/** The readiness budget the wait was given, in milliseconds. */
|
|
215
|
+
readonly timeoutMs: number;
|
|
216
|
+
readonly name = "KubernetesWorkspaceSuspendTimeoutError";
|
|
217
|
+
constructor(workspaceId: string, sandboxName: string,
|
|
218
|
+
/** The readiness budget the wait was given, in milliseconds. */
|
|
219
|
+
timeoutMs: number);
|
|
220
|
+
}
|
|
221
|
+
/** Authority for one lifecycle transition, owned independently of the run. */
|
|
222
|
+
export interface KubernetesWorkspaceTransitionOptions {
|
|
223
|
+
readonly signal?: AbortSignal;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* `destroy()` on a workspace, with the one field that decides whether the
|
|
227
|
+
* disk survives. See the module comment: the default keeps it.
|
|
228
|
+
*/
|
|
229
|
+
export interface KubernetesWorkspaceDestroyOptions extends SandboxDestroyOptions {
|
|
230
|
+
/**
|
|
231
|
+
* `true` DELETEs the Sandbox, cascading to its Pod, Service and PVC —
|
|
232
|
+
* the caller's files are gone and nothing brings them back. Anything else,
|
|
233
|
+
* including the default, suspends and leaves the object standing.
|
|
234
|
+
*/
|
|
235
|
+
readonly deleteDisk?: boolean;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* A {@link Sandbox} that survives having its compute taken away.
|
|
239
|
+
*
|
|
240
|
+
* Declared here rather than on the SDK's `Sandbox`: the brief frames these as
|
|
241
|
+
* BACKEND capabilities, and keeping them out of `@namzu/sdk` means no new
|
|
242
|
+
* `SandboxStatus` member, no optional `suspend?()`/`resume?()` on the shared
|
|
243
|
+
* contract that every other backend would then have to answer for, and no
|
|
244
|
+
* `deleteDisk` field on the shared `SandboxDestroyOptions`.
|
|
245
|
+
*/
|
|
246
|
+
export interface KubernetesWorkspace extends Sandbox {
|
|
247
|
+
/**
|
|
248
|
+
* True from the moment `suspend()` starts until `resume()` finishes.
|
|
249
|
+
*
|
|
250
|
+
* `status` cannot say this — `SandboxStatus` has four members and none of
|
|
251
|
+
* them is "suspended" — so a suspended workspace reports `destroyed`,
|
|
252
|
+
* which is the only member that means "cannot serve a call". This flag is
|
|
253
|
+
* what tells the recoverable state from the final one without widening the
|
|
254
|
+
* SDK's union.
|
|
255
|
+
*/
|
|
256
|
+
readonly suspended: boolean;
|
|
257
|
+
openTerminal(options: OpenTerminalOptions): Promise<TerminalSession>;
|
|
258
|
+
openTcpConnection(options: SandboxTcpConnectOptions): Promise<SandboxTcpConnection>;
|
|
259
|
+
/**
|
|
260
|
+
* Give the compute back and keep the disk. Idempotent: suspending a
|
|
261
|
+
* workspace whose suspend has been CONFIRMED sends nothing.
|
|
262
|
+
*
|
|
263
|
+
* Resolves once the POD has actually stopped, not merely once the patch
|
|
264
|
+
* was accepted and not on the Sandbox's `Suspended` condition, which
|
|
265
|
+
* upstream leaves standing after a resume — a guest that ignores SIGTERM
|
|
266
|
+
* rides out its `terminationGracePeriodSeconds` first, and it is writing
|
|
267
|
+
* to the disk for all of it.
|
|
268
|
+
*
|
|
269
|
+
* Rejects, leaving the workspace usable and unchanged, if the cluster
|
|
270
|
+
* refuses the patch; rejects with {@link KubernetesWorkspaceSuspendTimeoutError},
|
|
271
|
+
* admitting no call, if the patch landed and the pod outlived the wait.
|
|
272
|
+
* Either way the next `suspend()` sends the patch again rather than
|
|
273
|
+
* returning on this one's word. Concurrent calls share one transition.
|
|
274
|
+
*
|
|
275
|
+
* A call already in flight when this is called is not cancelled: it fails
|
|
276
|
+
* at the transport when the pod goes away, rather than with the named
|
|
277
|
+
* suspended error, which only covers calls admitted from here on.
|
|
278
|
+
*/
|
|
279
|
+
suspend(options?: KubernetesWorkspaceTransitionOptions): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* Take a new pod, on a new address, with a new agent token, and prove it
|
|
282
|
+
* is deprivileged before handing it back. Idempotent: resuming a running
|
|
283
|
+
* workspace sends nothing.
|
|
284
|
+
*/
|
|
285
|
+
resume(options?: KubernetesWorkspaceTransitionOptions): Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* With no options, or `deleteDisk: false`, this SUSPENDS: the disk stays
|
|
288
|
+
* and so does the handle, which reports `status: 'destroyed'` and
|
|
289
|
+
* `suspended: true` and can still be `resume()`d. Only
|
|
290
|
+
* `deleteDisk: true` is final.
|
|
291
|
+
*
|
|
292
|
+
* A `deleteDisk: true` whose DELETE fails REJECTS and stays retryable —
|
|
293
|
+
* the workspace is not recorded as deleted on a request that did not
|
|
294
|
+
* land, because the early return on that record is what a retry would
|
|
295
|
+
* hit. Concurrent calls share one DELETE.
|
|
296
|
+
*/
|
|
297
|
+
destroy(options?: KubernetesWorkspaceDestroyOptions): Promise<void>;
|
|
298
|
+
}
|
|
299
|
+
export interface KubernetesWorkspaceOptions {
|
|
300
|
+
/**
|
|
301
|
+
* The caller's own stable name for this workspace. The Sandbox is named
|
|
302
|
+
* `namzu-ws-<workspaceId>`, deterministically, which is what makes a
|
|
303
|
+
* workspace reachable again from a different host process — see
|
|
304
|
+
* {@link workspaceSandboxName} for why the id is refused rather than
|
|
305
|
+
* sanitised.
|
|
306
|
+
*/
|
|
307
|
+
readonly workspaceId: string;
|
|
308
|
+
/** Reported as `rootDir`; where the guest's entrypoint mounted the disk. */
|
|
309
|
+
readonly workingDirectory: string;
|
|
310
|
+
/**
|
|
311
|
+
* `SandboxTemplate` whose `podTemplate` AND `volumeClaimTemplates` this
|
|
312
|
+
* workspace is built from. Defaults to the backend's own
|
|
313
|
+
* `sandboxTemplateName`, but a deployment normally has two — a task
|
|
314
|
+
* template with no disk, and a workspace template with a block one.
|
|
315
|
+
*/
|
|
316
|
+
readonly sandboxTemplateName?: string;
|
|
317
|
+
readonly signal?: AbortSignal;
|
|
318
|
+
}
|
|
319
|
+
/** Prefix every workspace Sandbox's name carries. */
|
|
320
|
+
export declare const WORKSPACE_NAME_PREFIX = "namzu-ws-";
|
|
321
|
+
/**
|
|
322
|
+
* The Sandbox name for a workspace id.
|
|
323
|
+
*
|
|
324
|
+
* Deterministic on purpose: it is the only way a second host process, or the
|
|
325
|
+
* same one tomorrow, finds the workspace again. Which is also why an id that
|
|
326
|
+
* does not already fit a DNS-1123 label is REFUSED rather than lowercased,
|
|
327
|
+
* stripped or hashed: sanitising maps two ids onto one name, and two callers
|
|
328
|
+
* who believe they have separate workspaces would be sharing one disk.
|
|
329
|
+
* Hashing would fit every id and make the name unreadable in `kubectl get
|
|
330
|
+
* sandbox`, which is most of what the deterministic name is for.
|
|
331
|
+
*/
|
|
332
|
+
export declare function workspaceSandboxName(workspaceId: string): string;
|
|
333
|
+
/**
|
|
334
|
+
* Refuse anything that is not a block disk a container actually consumes as
|
|
335
|
+
* one. Every branch here describes a configuration that would work and then
|
|
336
|
+
* disappoint — see {@link KubernetesWorkspaceDiskError}.
|
|
337
|
+
*/
|
|
338
|
+
export declare function assertBlockModeWorkspaceDisk(source: string, podTemplate: SandboxPodTemplate | undefined, volumeClaimTemplates: readonly SandboxVolumeClaimTemplate[] | undefined): void;
|
|
339
|
+
/**
|
|
340
|
+
* Refuse a standing Sandbox that was built from another `SandboxTemplate`, or
|
|
341
|
+
* that runs without the RuntimeClass this backend is configured for.
|
|
342
|
+
*
|
|
343
|
+
* Read off `spec.podTemplate` — the copy the controller actually runs a pod
|
|
344
|
+
* from — rather than off anything this process decided, because the question
|
|
345
|
+
* is what the POD is, not what the caller meant it to be.
|
|
346
|
+
*
|
|
347
|
+
* The template check is unconditional, `config.egress` set or not: the label
|
|
348
|
+
* is also how an operator reads which template an object came from, and an
|
|
349
|
+
* object whose label says one thing while the caller builds from another is a
|
|
350
|
+
* mix-up worth naming the first time it is seen rather than the first time a
|
|
351
|
+
* policy is switched on. See {@link KubernetesWorkspaceMismatchError}.
|
|
352
|
+
*/
|
|
353
|
+
export declare function assertAdoptedWorkspaceMatchesConfig(sandboxName: string, namespace: string, podTemplate: SandboxPodTemplate | undefined, expected: {
|
|
354
|
+
readonly sandboxTemplateName: string;
|
|
355
|
+
readonly runtimeClassName?: string;
|
|
356
|
+
}): void;
|
|
357
|
+
/**
|
|
358
|
+
* Create the workspace, or take over the one that is already there.
|
|
359
|
+
*
|
|
360
|
+
* A second call with the same `workspaceId` ADOPTS rather than fails: the
|
|
361
|
+
* POST comes back 409 Conflict, and the object it collided with is this
|
|
362
|
+
* caller's own workspace from an earlier process. The deterministic name is
|
|
363
|
+
* only useful if coming back to it is the normal path. An adopted object is
|
|
364
|
+
* checked against the same block-disk rule a fresh one is, so a Sandbox
|
|
365
|
+
* standing under this name that is not a workspace is refused rather than
|
|
366
|
+
* used.
|
|
367
|
+
*
|
|
368
|
+
* Nothing here is ever deleted on failure. A create that gets as far as an
|
|
369
|
+
* existing object and then fails — a readiness timeout, a privilege probe
|
|
370
|
+
* refusal — SUSPENDS it and rethrows, because the object may be a workspace
|
|
371
|
+
* with a disk full of the caller's files and `deleteDisk` is not a decision
|
|
372
|
+
* a failure path gets to make. That holds even when THIS call POSTed the
|
|
373
|
+
* object and its disk is therefore empty: the 409 above means two processes
|
|
374
|
+
* can be coming up on one name at once, and the one that got the 201 deleting
|
|
375
|
+
* its "own" fresh object would take the disk of the one that adopted it. The
|
|
376
|
+
* cost is named rather than paid: a failed create can leave one suspended
|
|
377
|
+
* Sandbox standing, which the caller finds again under the same deterministic
|
|
378
|
+
* name and nothing reaps for them — see the docs page.
|
|
379
|
+
*/
|
|
380
|
+
export declare function createKubernetesWorkspace(config: KubernetesBackendInternalConfig, options: KubernetesWorkspaceOptions): Promise<KubernetesWorkspace>;
|
|
381
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/workspace.ts"],"names":[],"mappings":"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"}
|