rightsize 0.2.0 → 0.3.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 +12 -4
- package/dist/backend-docker/backend.d.ts +27 -4
- package/dist/backend-docker/backend.d.ts.map +1 -1
- package/dist/backend-docker/backend.js +56 -6
- package/dist/backend-docker/backend.js.map +1 -1
- package/dist/backend-docker/cli.d.ts +25 -0
- package/dist/backend-docker/cli.d.ts.map +1 -0
- package/dist/backend-docker/cli.js +64 -0
- package/dist/backend-docker/cli.js.map +1 -0
- package/dist/backend-msb/backend.d.ts +61 -7
- package/dist/backend-msb/backend.d.ts.map +1 -1
- package/dist/backend-msb/backend.js +121 -11
- package/dist/backend-msb/backend.js.map +1 -1
- package/dist/backend-msb/commands.d.ts +10 -0
- package/dist/backend-msb/commands.d.ts.map +1 -1
- package/dist/backend-msb/commands.js +29 -1
- package/dist/backend-msb/commands.js.map +1 -1
- package/dist/backend-msb/snapshot-not-found.d.ts +25 -0
- package/dist/backend-msb/snapshot-not-found.d.ts.map +1 -0
- package/dist/backend-msb/snapshot-not-found.js +27 -0
- package/dist/backend-msb/snapshot-not-found.js.map +1 -0
- package/dist/core/backend.d.ts +57 -6
- package/dist/core/backend.d.ts.map +1 -1
- package/dist/core/checkpoint/api.d.ts +80 -0
- package/dist/core/checkpoint/api.d.ts.map +1 -0
- package/dist/core/checkpoint/api.js +138 -0
- package/dist/core/checkpoint/api.js.map +1 -0
- package/dist/core/checkpoint/name.d.ts +12 -0
- package/dist/core/checkpoint/name.d.ts.map +1 -0
- package/dist/core/checkpoint/name.js +17 -0
- package/dist/core/checkpoint/name.js.map +1 -0
- package/dist/core/checkpoint/ref.d.ts +12 -0
- package/dist/core/checkpoint/ref.d.ts.map +1 -0
- package/dist/core/checkpoint/ref.js +16 -0
- package/dist/core/checkpoint/ref.js.map +1 -0
- package/dist/core/checkpoint/registry.d.ts +107 -0
- package/dist/core/checkpoint/registry.d.ts.map +1 -0
- package/dist/core/checkpoint/registry.js +180 -0
- package/dist/core/checkpoint/registry.js.map +1 -0
- package/dist/core/errors.d.ts +60 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +80 -7
- package/dist/core/errors.js.map +1 -1
- package/dist/core/generic-container.d.ts +87 -18
- package/dist/core/generic-container.d.ts.map +1 -1
- package/dist/core/generic-container.js +171 -22
- package/dist/core/generic-container.js.map +1 -1
- package/dist/core/model.d.ts +25 -8
- package/dist/core/model.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/keycloak.js +1 -1
- package/dist/modules/mysql.d.ts.map +1 -1
- package/dist/modules/mysql.js +4 -3
- package/dist/modules/mysql.js.map +1 -1
- package/package.json +1 -1
package/dist/core/errors.js
CHANGED
|
@@ -102,21 +102,94 @@ export class IsolationRequiredError extends Error {
|
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Thrown by `checkpoint()` when the active backend's
|
|
105
|
-
* `capabilities.checkpoint` is `false` —
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* backend
|
|
105
|
+
* `capabilities.checkpoint` is `false` — both real backends (docker, image
|
|
106
|
+
* commit; microsandbox, disk snapshot) support it today, so this only fires
|
|
107
|
+
* against a backend that genuinely lacks the capability (a test double).
|
|
108
|
+
* Thrown before any backend call: the generic layer gates on the capability
|
|
109
|
+
* itself rather than letting the backend's own `createCheckpoint` reject.
|
|
109
110
|
*/
|
|
110
111
|
export class CheckpointUnsupportedError extends Error {
|
|
111
112
|
backend;
|
|
112
113
|
constructor(
|
|
113
114
|
/** The active backend's name (e.g. `"microsandbox"`). */
|
|
114
115
|
backend) {
|
|
115
|
-
super(`checkpoint() is not supported by the '${backend}' backend —
|
|
116
|
-
"
|
|
117
|
-
"roadmap. Set RIGHTSIZE_BACKEND=docker to use checkpoint/restore.");
|
|
116
|
+
super(`checkpoint() is not supported by the '${backend}' backend — checkpointing needs a backend whose ` +
|
|
117
|
+
"capabilities.checkpoint is true.");
|
|
118
118
|
this.backend = backend;
|
|
119
119
|
this.name = "CheckpointUnsupportedError";
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Thrown by `GenericContainer.fromCheckpoint(cp).start()` when the active
|
|
124
|
+
* backend's name doesn't match the backend that created `cp` — a checkpoint
|
|
125
|
+
* ref is only meaningful to the backend that minted it (a docker image tag
|
|
126
|
+
* means nothing to `msb`, and a msb snapshot name means nothing to docker).
|
|
127
|
+
* Thrown before any backend call.
|
|
128
|
+
*/
|
|
129
|
+
export class CheckpointBackendMismatchError extends Error {
|
|
130
|
+
createdOnBackend;
|
|
131
|
+
activeBackend;
|
|
132
|
+
constructor(
|
|
133
|
+
/** The backend that created the checkpoint. */
|
|
134
|
+
createdOnBackend,
|
|
135
|
+
/** The backend `start()` actually resolved. */
|
|
136
|
+
activeBackend) {
|
|
137
|
+
super(`this checkpoint was created on the '${createdOnBackend}' backend but the active backend is ` +
|
|
138
|
+
`'${activeBackend}' — set RIGHTSIZE_BACKEND=${createdOnBackend} to restore it there, or create a fresh ` +
|
|
139
|
+
`checkpoint under the '${activeBackend}' backend instead.`);
|
|
140
|
+
this.createdOnBackend = createdOnBackend;
|
|
141
|
+
this.activeBackend = activeBackend;
|
|
142
|
+
this.name = "CheckpointBackendMismatchError";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Thrown at `start()` when a container built via `GenericContainer.fromCheckpoint()`
|
|
147
|
+
* is also marked `withReuse()` — reuse's identity hash never covers
|
|
148
|
+
* `checkpointRef`, so an adopted sandbox from an earlier process could never
|
|
149
|
+
* be verified against the checkpoint this container was meant to restore.
|
|
150
|
+
* Thrown only once reuse is actually double opt-in active, the same
|
|
151
|
+
* placement as `ReuseWithNetworkError`.
|
|
152
|
+
*/
|
|
153
|
+
export class ReuseFromCheckpointError extends Error {
|
|
154
|
+
constructor() {
|
|
155
|
+
super("withReuse() cannot be combined with fromCheckpoint() — reuse's identity hash does not cover " +
|
|
156
|
+
"checkpointRef, so an adopted sandbox could never be verified against the checkpoint it was meant to " +
|
|
157
|
+
"restore. Drop either withReuse() or fromCheckpoint().");
|
|
158
|
+
this.name = "ReuseFromCheckpointError";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Thrown by `copyFileToContainer`/`copyContentToContainer`/
|
|
163
|
+
* `copyFileFromContainer` when `containerPath` is not absolute — both
|
|
164
|
+
* backends require an absolute `NAME:/path` shape, so a relative path can
|
|
165
|
+
* never reach either CLI. Thrown before any backend call.
|
|
166
|
+
*/
|
|
167
|
+
export class RelativeContainerPathError extends Error {
|
|
168
|
+
containerPath;
|
|
169
|
+
constructor(
|
|
170
|
+
/** The rejected path, exactly as passed in. */
|
|
171
|
+
containerPath) {
|
|
172
|
+
super(`containerPath must be an absolute path (got '${containerPath}') — both backends require an absolute guest path.`);
|
|
173
|
+
this.containerPath = containerPath;
|
|
174
|
+
this.name = "RelativeContainerPathError";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Thrown by `checkpoint(name)` when `name` doesn't match
|
|
179
|
+
* `^[a-z0-9][a-z0-9-]{0,40}$` — the same pattern pinned across every
|
|
180
|
+
* rightsize language implementation. Thrown before any backend or
|
|
181
|
+
* filesystem call, so a bad name never mints a ref or touches the registry.
|
|
182
|
+
*/
|
|
183
|
+
export class InvalidCheckpointNameError extends Error {
|
|
184
|
+
checkpointName;
|
|
185
|
+
constructor(
|
|
186
|
+
/** The rejected name, exactly as passed in. */
|
|
187
|
+
checkpointName) {
|
|
188
|
+
super(`checkpoint name '${checkpointName}' is invalid — a checkpoint name must match ^[a-z0-9][a-z0-9-]{0,40}$ ` +
|
|
189
|
+
"(start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens after " +
|
|
190
|
+
"that, and be at most 41 characters long).");
|
|
191
|
+
this.checkpointName = checkpointName;
|
|
192
|
+
this.name = "InvalidCheckpointNameError";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
122
195
|
//# sourceMappingURL=errors.js.map
|
package/dist/core/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAGvC;IAEA;IAEA;IANX;IACE,uDAAuD;IAC9C,OAAe;IACxB,yDAAyD;IAChD,OAAe;IACxB,qFAAqF;IAC5E,MAAe;QAExB,KAAK,CAAC,YAAY,OAAO,8BAA8B,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QANjG,YAAO,GAAP,OAAO,CAAQ;QAEf,YAAO,GAAP,OAAO,CAAQ;QAEf,WAAM,GAAN,MAAM,CAAS;QAGxB,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAInC;IAHX,YACE,OAAe;IACf,4FAA4F;IACnF,KAAe;QAExB,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAFnD,UAAK,GAAL,KAAK,CAAU;QAGxB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,gFAAgF;AAChF,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,kIAAkI;AAClI,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C;QACE,KAAK,CACH,mGAAmG;YACjG,oGAAoG;YACpG,iDAAiD,CACpD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAGpC;IAFX;IACE,mDAAmD;IAC1C,OAAe;QAExB,KAAK,CACH,2FAA2F,OAAO,KAAK;YACrG,oGAAoG;YACpG,iFAAiF,CACpF,CAAC;QANO,YAAO,GAAP,OAAO,CAAQ;QAOxB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,25 +35,32 @@ export declare class GenericContainer implements AsyncDisposable, NetworkMember
|
|
|
35
35
|
private backendOverride;
|
|
36
36
|
private reuseRequested;
|
|
37
37
|
private requireIsolationRequested;
|
|
38
|
+
private checkpointRef;
|
|
39
|
+
private checkpointSourceBackend;
|
|
38
40
|
private handle;
|
|
39
41
|
private backend;
|
|
40
42
|
private mappedPorts;
|
|
41
43
|
private running;
|
|
44
|
+
private installedNetworkLinks;
|
|
42
45
|
/** Builds against `image` (e.g. `"redis:8.6-alpine"`); no I/O happens until `start()`. */
|
|
43
46
|
constructor(image: string);
|
|
44
47
|
/** Convenience: `new GenericContainer(image).start()`. */
|
|
45
48
|
static start(image: string): Promise<GenericContainer>;
|
|
46
49
|
/**
|
|
47
50
|
* Builds a normal, ephemeral `GenericContainer` from a `Checkpoint`
|
|
48
|
-
* (`checkpoint()`'s return value): image is `cp.
|
|
51
|
+
* (`checkpoint()`'s return value): image is `cp.ref`, and
|
|
49
52
|
* env/command/exposed ports/memory limit default to `cp.spec` — chain
|
|
50
53
|
* further builder calls (a different `waitingFor`, `withBackend`, …)
|
|
51
54
|
* before `start()`, the same as any other container. Never carries over
|
|
52
|
-
* `cp.spec`'s network/aliases/mounts — the
|
|
53
|
-
*
|
|
55
|
+
* `cp.spec`'s network/aliases/mounts — the captured state already has the
|
|
56
|
+
* filesystem baked in, and (as with `withReuse()`'s own network
|
|
54
57
|
* restriction) network topology is never part of what a checkpoint
|
|
55
|
-
* captures.
|
|
56
|
-
*
|
|
58
|
+
* captures. `start()` throws `CheckpointBackendMismatchError` before any
|
|
59
|
+
* backend call if the active backend isn't the one that created `cp`, and
|
|
60
|
+
* `ReuseFromCheckpointError` if this container is also marked
|
|
61
|
+
* `withReuse()` — reuse's identity hash never covers a checkpoint ref.
|
|
62
|
+
* Once started, a restored container is ordinary in every other respect:
|
|
63
|
+
* fresh host ports, normal reaping-ledger tracking, normal stop.
|
|
57
64
|
*/
|
|
58
65
|
static fromCheckpoint(cp: Checkpoint): GenericContainer;
|
|
59
66
|
/** Sets an environment variable visible to the workload. Last-write-wins if called again with the same key; insertion order is otherwise preserved. */
|
|
@@ -222,19 +229,81 @@ export declare class GenericContainer implements AsyncDisposable, NetworkMember
|
|
|
222
229
|
mappedPort(guestPort: number): number;
|
|
223
230
|
private requireHandle;
|
|
224
231
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* commit
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
* Captures this running container's state and returns a `Checkpoint` — a
|
|
233
|
+
* FILESYSTEM capture, not a memory snapshot: `fromCheckpoint()` boots a
|
|
234
|
+
* container from the captured state with processes restarting from
|
|
235
|
+
* scratch, not resuming. Requires the active backend's
|
|
236
|
+
* `capabilities.checkpoint` (`true` on both real backends today: docker
|
|
237
|
+
* via image commit, microsandbox via disk snapshot) — checked BEFORE any
|
|
238
|
+
* backend call, so an unsupported backend never attempts one. Throws a
|
|
239
|
+
* state error (same shape as `exec`/`logs`) if this container isn't
|
|
240
|
+
* currently running.
|
|
241
|
+
*
|
|
242
|
+
* Passing `name` makes this checkpoint NAMED and durable: the ref becomes
|
|
243
|
+
* deterministic (`rz-ckpt-<name>` / `rightsize/checkpoint:<name>` instead
|
|
244
|
+
* of a random 12-hex suffix), and — only once the backend call below has
|
|
245
|
+
* actually succeeded — a registry entry is written under
|
|
246
|
+
* `<cacheDir>/checkpoints/<name>.json` that `Checkpoints.find`/`list`/
|
|
247
|
+
* `remove` can rediscover later, in this process or a different one. `name`
|
|
248
|
+
* must match `^[a-z0-9][a-z0-9-]{0,40}$`; an invalid name throws
|
|
249
|
+
* `InvalidCheckpointNameError` before any backend call. Checkpointing under
|
|
250
|
+
* a name that already has a registry entry REPLACES it: the ref is the
|
|
251
|
+
* same deterministic value either way, so this best-effort clears the old
|
|
252
|
+
* artifact under that ref before creating the new one, then overwrites the
|
|
253
|
+
* registry entry — the latest checkpoint under a name always wins.
|
|
254
|
+
* Omitting `name` keeps the original behavior byte-for-byte: a random ref,
|
|
255
|
+
* no registry entry, ephemeral.
|
|
256
|
+
*
|
|
257
|
+
* On a backend whose `capabilities.checkpointRestartsWorkload` is `true`
|
|
258
|
+
* (microsandbox: the stop/snapshot/reboot cycle boots a fresh microVM),
|
|
259
|
+
* this re-runs the container's own wait strategy before returning — a bare
|
|
260
|
+
* return right after the backend call would hand back a false-ready
|
|
261
|
+
* container. docker's commit-to-image never disturbs the running
|
|
262
|
+
* container, so no re-wait happens there.
|
|
263
|
+
*
|
|
264
|
+
* That same reboot also kills any network links this container had
|
|
265
|
+
* installed toward already-running siblings (msb emulates them with
|
|
266
|
+
* exec-tunnels, which the workload restart tears down along with
|
|
267
|
+
* everything else on the sandbox), so when links were installed in the
|
|
268
|
+
* first place, this re-runs `installNetworkLinks` with those same links
|
|
269
|
+
* BEFORE the wait-strategy re-run — the wait must gate a container that is
|
|
270
|
+
* fully re-linked, not just booted.
|
|
271
|
+
*
|
|
272
|
+
* Checkpoints are never auto-reaped (a committed image or a disk snapshot
|
|
273
|
+
* is not a container) — see the checkpoints guide for the manual cleanup
|
|
274
|
+
* one-liners and the `Checkpoints.remove` cleanup affordance.
|
|
275
|
+
*/
|
|
276
|
+
checkpoint(name?: string): Promise<Checkpoint>;
|
|
277
|
+
/**
|
|
278
|
+
* Copies a host file or directory into this running container at
|
|
279
|
+
* `containerPath`, creating the destination's parent directory first
|
|
280
|
+
* (`exec: mkdir -p <parent>`) — callers never pre-create directories on
|
|
281
|
+
* either side. `containerPath` must be absolute (both backends' own copy
|
|
282
|
+
* tools require `NAME:/abs/path`); `hostPath` may be a file or a
|
|
283
|
+
* directory, `cp -r`-style: copying a directory to an absent destination
|
|
284
|
+
* produces that destination as a copy of the source's CONTENTS, not the
|
|
285
|
+
* source nested one level down. Requires a running container — throws the
|
|
286
|
+
* same state error `exec()`/`logs()` do, before any backend call. Works on
|
|
287
|
+
* a reuse container too, but the copy mutates shared state that is NOT
|
|
288
|
+
* part of the reuse identity hash — see the reuse guide's caveat.
|
|
289
|
+
*/
|
|
290
|
+
copyFileToContainer(hostPath: string, containerPath: string): Promise<void>;
|
|
291
|
+
/**
|
|
292
|
+
* Convenience over `copyFileToContainer()`: writes `content` to a private
|
|
293
|
+
* (mode `0600`) temp file and copies THAT file in, removing the temp file
|
|
294
|
+
* afterward regardless of outcome. No streaming protocol — content is
|
|
295
|
+
* always fully materialized on host disk before the copy.
|
|
296
|
+
*/
|
|
297
|
+
copyContentToContainer(content: string | Uint8Array, containerPath: string): Promise<void>;
|
|
298
|
+
/**
|
|
299
|
+
* Copies a file or directory OUT of this running container at
|
|
300
|
+
* `containerPath` to `hostPath`, creating the destination's host parent
|
|
301
|
+
* directory first via the standard library — the mirror image of
|
|
302
|
+
* `copyFileToContainer`'s guest-side `mkdir -p`. `containerPath` must be
|
|
303
|
+
* absolute; `cp -r`-style destination naming applies here too. Requires a
|
|
304
|
+
* running container, like `copyFileToContainer`.
|
|
305
|
+
*/
|
|
306
|
+
copyFileFromContainer(containerPath: string, hostPath: string): Promise<void>;
|
|
238
307
|
/** Runs a one-shot command inside the running container and waits for it to exit. Throws if the container is not running. */
|
|
239
308
|
exec(...cmd: string[]): Promise<ExecResult>;
|
|
240
309
|
/** Fetches the workload's logs so far (a bounded tail), as a single string. Throws if the container is not running. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generic-container.d.ts","sourceRoot":"","sources":["../../src/core/generic-container.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generic-container.d.ts","sourceRoot":"","sources":["../../src/core/generic-container.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
2
5
|
import { FreePorts } from "./free-ports.js";
|
|
3
6
|
import { RunId } from "./run-id.js";
|
|
4
7
|
import { Network } from "./network.js";
|
|
5
|
-
import { PortBindConflictError, IsolationRequiredError, CheckpointUnsupportedError } from "./errors.js";
|
|
8
|
+
import { PortBindConflictError, IsolationRequiredError, CheckpointUnsupportedError, CheckpointBackendMismatchError, ReuseFromCheckpointError, RelativeContainerPathError, BackendError, } from "./errors.js";
|
|
9
|
+
import { requireValidCheckpointName } from "./checkpoint/name.js";
|
|
10
|
+
import { checkpointRef } from "./checkpoint/ref.js";
|
|
11
|
+
import { toCheckpointRegistrySpec, writeCheckpointRegistryAtomic } from "./checkpoint/registry.js";
|
|
6
12
|
import { Wait } from "./wait.js";
|
|
7
13
|
import { registerSyncCleanup, unregisterSyncCleanup } from "./cleanup.js";
|
|
8
14
|
import { Backends } from "./backends.js";
|
|
@@ -12,6 +18,12 @@ import { ReuseWithNetworkError } from "./errors.js";
|
|
|
12
18
|
import { reuseEnabled } from "./reuse/env.js";
|
|
13
19
|
import { reuseHash, reuseName } from "./reuse/hash.js";
|
|
14
20
|
import { readRegistry, writeRegistryAtomic, removeRegistry } from "./reuse/registry.js";
|
|
21
|
+
/** Fails fast — before any backend call — on a relative `containerPath`: both backends require an absolute `NAME:/path` shape. */
|
|
22
|
+
function requireAbsoluteContainerPath(containerPath) {
|
|
23
|
+
if (!path.posix.isAbsolute(containerPath)) {
|
|
24
|
+
throw new RelativeContainerPathError(containerPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
const MAX_START_ATTEMPTS = 5;
|
|
16
28
|
let sequence = 0;
|
|
17
29
|
function nextSequence() {
|
|
@@ -94,10 +106,13 @@ export class GenericContainer {
|
|
|
94
106
|
backendOverride;
|
|
95
107
|
reuseRequested = false;
|
|
96
108
|
requireIsolationRequested = false;
|
|
109
|
+
checkpointRef;
|
|
110
|
+
checkpointSourceBackend;
|
|
97
111
|
handle;
|
|
98
112
|
backend;
|
|
99
113
|
mappedPorts = new Map();
|
|
100
114
|
running = false;
|
|
115
|
+
installedNetworkLinks = [];
|
|
101
116
|
/** Builds against `image` (e.g. `"redis:8.6-alpine"`); no I/O happens until `start()`. */
|
|
102
117
|
constructor(image) {
|
|
103
118
|
this.image = image;
|
|
@@ -108,18 +123,24 @@ export class GenericContainer {
|
|
|
108
123
|
}
|
|
109
124
|
/**
|
|
110
125
|
* Builds a normal, ephemeral `GenericContainer` from a `Checkpoint`
|
|
111
|
-
* (`checkpoint()`'s return value): image is `cp.
|
|
126
|
+
* (`checkpoint()`'s return value): image is `cp.ref`, and
|
|
112
127
|
* env/command/exposed ports/memory limit default to `cp.spec` — chain
|
|
113
128
|
* further builder calls (a different `waitingFor`, `withBackend`, …)
|
|
114
129
|
* before `start()`, the same as any other container. Never carries over
|
|
115
|
-
* `cp.spec`'s network/aliases/mounts — the
|
|
116
|
-
*
|
|
130
|
+
* `cp.spec`'s network/aliases/mounts — the captured state already has the
|
|
131
|
+
* filesystem baked in, and (as with `withReuse()`'s own network
|
|
117
132
|
* restriction) network topology is never part of what a checkpoint
|
|
118
|
-
* captures.
|
|
119
|
-
*
|
|
133
|
+
* captures. `start()` throws `CheckpointBackendMismatchError` before any
|
|
134
|
+
* backend call if the active backend isn't the one that created `cp`, and
|
|
135
|
+
* `ReuseFromCheckpointError` if this container is also marked
|
|
136
|
+
* `withReuse()` — reuse's identity hash never covers a checkpoint ref.
|
|
137
|
+
* Once started, a restored container is ordinary in every other respect:
|
|
138
|
+
* fresh host ports, normal reaping-ledger tracking, normal stop.
|
|
120
139
|
*/
|
|
121
140
|
static fromCheckpoint(cp) {
|
|
122
|
-
const container = new GenericContainer(cp.
|
|
141
|
+
const container = new GenericContainer(cp.ref);
|
|
142
|
+
container.checkpointRef = cp.ref;
|
|
143
|
+
container.checkpointSourceBackend = cp.backend;
|
|
123
144
|
for (const [key, value] of cp.spec.env) {
|
|
124
145
|
container.withEnv(key, value);
|
|
125
146
|
}
|
|
@@ -265,6 +286,7 @@ export class GenericContainer {
|
|
|
265
286
|
// requested-but-env-disabled reuse container falls through to here
|
|
266
287
|
// deliberately (Testcontainers semantics), so it must stay false.
|
|
267
288
|
keepAlive: false,
|
|
289
|
+
checkpointRef: this.checkpointRef,
|
|
268
290
|
};
|
|
269
291
|
return this.customizeSpec(spec, (guest) => {
|
|
270
292
|
const p = ports.get(guest);
|
|
@@ -293,6 +315,10 @@ export class GenericContainer {
|
|
|
293
315
|
runId: RunId.value,
|
|
294
316
|
memoryLimitMb: this.memoryLimitMb,
|
|
295
317
|
keepAlive: true,
|
|
318
|
+
// Reuse + fromCheckpoint is rejected in start() before this is ever
|
|
319
|
+
// built (see ReuseFromCheckpointError) — always undefined in
|
|
320
|
+
// practice, present only to satisfy ContainerSpec's shape.
|
|
321
|
+
checkpointRef: this.checkpointRef,
|
|
296
322
|
};
|
|
297
323
|
return this.customizeSpec(spec, (guest) => {
|
|
298
324
|
const p = ports.get(guest);
|
|
@@ -341,6 +367,12 @@ export class GenericContainer {
|
|
|
341
367
|
*/
|
|
342
368
|
async start() {
|
|
343
369
|
const backend = this.resolveBackend();
|
|
370
|
+
// Before any backend work at all: a checkpoint ref is only meaningful
|
|
371
|
+
// to the backend that minted it (a docker image tag means nothing to
|
|
372
|
+
// msb, a msb snapshot name means nothing to docker).
|
|
373
|
+
if (this.checkpointSourceBackend !== undefined && this.checkpointSourceBackend !== backend.name) {
|
|
374
|
+
throw new CheckpointBackendMismatchError(this.checkpointSourceBackend, backend.name);
|
|
375
|
+
}
|
|
344
376
|
// Only the env-resolved path (never an explicit withBackend() override)
|
|
345
377
|
// drives the reaper: writes this process's run record, sweeps dead runs
|
|
346
378
|
// on first use, and spawns the watchdog. Skipping it for overrides is
|
|
@@ -367,6 +399,9 @@ export class GenericContainer {
|
|
|
367
399
|
if (this.network !== undefined) {
|
|
368
400
|
throw new ReuseWithNetworkError();
|
|
369
401
|
}
|
|
402
|
+
if (this.checkpointRef !== undefined) {
|
|
403
|
+
throw new ReuseFromCheckpointError();
|
|
404
|
+
}
|
|
370
405
|
return this.startReuse(backend);
|
|
371
406
|
}
|
|
372
407
|
process.stderr.write(`[rightsize] reuse was requested via withReuse() for '${this.image}' but RIGHTSIZE_REUSE is not ` +
|
|
@@ -441,6 +476,7 @@ export class GenericContainer {
|
|
|
441
476
|
try {
|
|
442
477
|
const links = this.network?.linksForNewMember() ?? [];
|
|
443
478
|
await backend.installNetworkLinks(handle, links);
|
|
479
|
+
this.installedNetworkLinks = links;
|
|
444
480
|
// Register AFTER links are computed/installed — a container must
|
|
445
481
|
// never see itself in its own linksForNewMember() call.
|
|
446
482
|
if (this.network !== undefined) {
|
|
@@ -745,26 +781,139 @@ export class GenericContainer {
|
|
|
745
781
|
return { handle: this.handle, backend: this.backend };
|
|
746
782
|
}
|
|
747
783
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
* commit
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
784
|
+
* Captures this running container's state and returns a `Checkpoint` — a
|
|
785
|
+
* FILESYSTEM capture, not a memory snapshot: `fromCheckpoint()` boots a
|
|
786
|
+
* container from the captured state with processes restarting from
|
|
787
|
+
* scratch, not resuming. Requires the active backend's
|
|
788
|
+
* `capabilities.checkpoint` (`true` on both real backends today: docker
|
|
789
|
+
* via image commit, microsandbox via disk snapshot) — checked BEFORE any
|
|
790
|
+
* backend call, so an unsupported backend never attempts one. Throws a
|
|
791
|
+
* state error (same shape as `exec`/`logs`) if this container isn't
|
|
792
|
+
* currently running.
|
|
793
|
+
*
|
|
794
|
+
* Passing `name` makes this checkpoint NAMED and durable: the ref becomes
|
|
795
|
+
* deterministic (`rz-ckpt-<name>` / `rightsize/checkpoint:<name>` instead
|
|
796
|
+
* of a random 12-hex suffix), and — only once the backend call below has
|
|
797
|
+
* actually succeeded — a registry entry is written under
|
|
798
|
+
* `<cacheDir>/checkpoints/<name>.json` that `Checkpoints.find`/`list`/
|
|
799
|
+
* `remove` can rediscover later, in this process or a different one. `name`
|
|
800
|
+
* must match `^[a-z0-9][a-z0-9-]{0,40}$`; an invalid name throws
|
|
801
|
+
* `InvalidCheckpointNameError` before any backend call. Checkpointing under
|
|
802
|
+
* a name that already has a registry entry REPLACES it: the ref is the
|
|
803
|
+
* same deterministic value either way, so this best-effort clears the old
|
|
804
|
+
* artifact under that ref before creating the new one, then overwrites the
|
|
805
|
+
* registry entry — the latest checkpoint under a name always wins.
|
|
806
|
+
* Omitting `name` keeps the original behavior byte-for-byte: a random ref,
|
|
807
|
+
* no registry entry, ephemeral.
|
|
808
|
+
*
|
|
809
|
+
* On a backend whose `capabilities.checkpointRestartsWorkload` is `true`
|
|
810
|
+
* (microsandbox: the stop/snapshot/reboot cycle boots a fresh microVM),
|
|
811
|
+
* this re-runs the container's own wait strategy before returning — a bare
|
|
812
|
+
* return right after the backend call would hand back a false-ready
|
|
813
|
+
* container. docker's commit-to-image never disturbs the running
|
|
814
|
+
* container, so no re-wait happens there.
|
|
815
|
+
*
|
|
816
|
+
* That same reboot also kills any network links this container had
|
|
817
|
+
* installed toward already-running siblings (msb emulates them with
|
|
818
|
+
* exec-tunnels, which the workload restart tears down along with
|
|
819
|
+
* everything else on the sandbox), so when links were installed in the
|
|
820
|
+
* first place, this re-runs `installNetworkLinks` with those same links
|
|
821
|
+
* BEFORE the wait-strategy re-run — the wait must gate a container that is
|
|
822
|
+
* fully re-linked, not just booted.
|
|
823
|
+
*
|
|
824
|
+
* Checkpoints are never auto-reaped (a committed image or a disk snapshot
|
|
825
|
+
* is not a container) — see the checkpoints guide for the manual cleanup
|
|
826
|
+
* one-liners and the `Checkpoints.remove` cleanup affordance.
|
|
759
827
|
*/
|
|
760
|
-
async checkpoint() {
|
|
828
|
+
async checkpoint(name) {
|
|
829
|
+
if (name !== undefined) {
|
|
830
|
+
requireValidCheckpointName(name);
|
|
831
|
+
}
|
|
761
832
|
const { handle, backend } = this.requireHandle();
|
|
762
833
|
if (!backend.capabilities.checkpoint) {
|
|
763
834
|
throw new CheckpointUnsupportedError(backend.name);
|
|
764
835
|
}
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
836
|
+
const ref = checkpointRef(backend.name, name);
|
|
837
|
+
if (name !== undefined) {
|
|
838
|
+
// Replace semantics: the ref is deterministic from `name`, so a prior
|
|
839
|
+
// checkpoint under this same name — if any — sits under this exact
|
|
840
|
+
// ref. Best-effort clear it before creating the new one.
|
|
841
|
+
await swallow(() => backend.removeCheckpoint(ref));
|
|
842
|
+
}
|
|
843
|
+
await backend.createCheckpoint(handle, ref);
|
|
844
|
+
if (backend.capabilities.checkpointRestartsWorkload) {
|
|
845
|
+
if (this.installedNetworkLinks.length > 0) {
|
|
846
|
+
await backend.installNetworkLinks(handle, this.installedNetworkLinks);
|
|
847
|
+
}
|
|
848
|
+
await this.waitStrategy.waitUntilReady(this.asWaitTarget());
|
|
849
|
+
}
|
|
850
|
+
if (name !== undefined) {
|
|
851
|
+
// Only after the backend checkpoint above has actually succeeded — a
|
|
852
|
+
// failed createCheckpoint() already threw, so a registry entry is
|
|
853
|
+
// never written for a checkpoint that doesn't exist.
|
|
854
|
+
const entry = {
|
|
855
|
+
name,
|
|
856
|
+
ref,
|
|
857
|
+
backend: backend.name,
|
|
858
|
+
createdIso: new Date().toISOString(),
|
|
859
|
+
spec: toCheckpointRegistrySpec(handle.spec),
|
|
860
|
+
};
|
|
861
|
+
await writeCheckpointRegistryAtomic(cacheDir(), name, entry);
|
|
862
|
+
}
|
|
863
|
+
return { ref, backend: backend.name, spec: handle.spec };
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Copies a host file or directory into this running container at
|
|
867
|
+
* `containerPath`, creating the destination's parent directory first
|
|
868
|
+
* (`exec: mkdir -p <parent>`) — callers never pre-create directories on
|
|
869
|
+
* either side. `containerPath` must be absolute (both backends' own copy
|
|
870
|
+
* tools require `NAME:/abs/path`); `hostPath` may be a file or a
|
|
871
|
+
* directory, `cp -r`-style: copying a directory to an absent destination
|
|
872
|
+
* produces that destination as a copy of the source's CONTENTS, not the
|
|
873
|
+
* source nested one level down. Requires a running container — throws the
|
|
874
|
+
* same state error `exec()`/`logs()` do, before any backend call. Works on
|
|
875
|
+
* a reuse container too, but the copy mutates shared state that is NOT
|
|
876
|
+
* part of the reuse identity hash — see the reuse guide's caveat.
|
|
877
|
+
*/
|
|
878
|
+
async copyFileToContainer(hostPath, containerPath) {
|
|
879
|
+
const { handle, backend } = this.requireHandle();
|
|
880
|
+
requireAbsoluteContainerPath(containerPath);
|
|
881
|
+
const parent = path.posix.dirname(containerPath);
|
|
882
|
+
const mkdir = await backend.exec(handle, ["mkdir", "-p", parent]);
|
|
883
|
+
if (mkdir.exitCode !== 0) {
|
|
884
|
+
throw new BackendError(`could not create parent directory '${parent}' in '${handle.spec.name}' before copying in: ${mkdir.stderr.trim()}`);
|
|
885
|
+
}
|
|
886
|
+
await backend.copyToContainer(handle, hostPath, containerPath);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Convenience over `copyFileToContainer()`: writes `content` to a private
|
|
890
|
+
* (mode `0600`) temp file and copies THAT file in, removing the temp file
|
|
891
|
+
* afterward regardless of outcome. No streaming protocol — content is
|
|
892
|
+
* always fully materialized on host disk before the copy.
|
|
893
|
+
*/
|
|
894
|
+
async copyContentToContainer(content, containerPath) {
|
|
895
|
+
const tempPath = path.join(os.tmpdir(), `rightsize-copy-content-${randomBytes(6).toString("hex")}`);
|
|
896
|
+
await fs.writeFile(tempPath, content, { mode: 0o600 });
|
|
897
|
+
try {
|
|
898
|
+
await this.copyFileToContainer(tempPath, containerPath);
|
|
899
|
+
}
|
|
900
|
+
finally {
|
|
901
|
+
await fs.rm(tempPath, { force: true });
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Copies a file or directory OUT of this running container at
|
|
906
|
+
* `containerPath` to `hostPath`, creating the destination's host parent
|
|
907
|
+
* directory first via the standard library — the mirror image of
|
|
908
|
+
* `copyFileToContainer`'s guest-side `mkdir -p`. `containerPath` must be
|
|
909
|
+
* absolute; `cp -r`-style destination naming applies here too. Requires a
|
|
910
|
+
* running container, like `copyFileToContainer`.
|
|
911
|
+
*/
|
|
912
|
+
async copyFileFromContainer(containerPath, hostPath) {
|
|
913
|
+
const { handle, backend } = this.requireHandle();
|
|
914
|
+
requireAbsoluteContainerPath(containerPath);
|
|
915
|
+
await fs.mkdir(path.dirname(hostPath), { recursive: true });
|
|
916
|
+
await backend.copyFromContainer(handle, containerPath, hostPath);
|
|
768
917
|
}
|
|
769
918
|
/** Runs a one-shot command inside the running container and waits for it to exit. Throws if the container is not running. */
|
|
770
919
|
async exec(...cmd) {
|