rightsize 0.2.0 → 0.4.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 +36 -26
- package/dist/backend-docker/backend.d.ts +41 -4
- package/dist/backend-docker/backend.d.ts.map +1 -1
- package/dist/backend-docker/backend.js +84 -6
- package/dist/backend-docker/backend.js.map +1 -1
- package/dist/backend-docker/cli.d.ts +29 -0
- package/dist/backend-docker/cli.d.ts.map +1 -0
- package/dist/backend-docker/cli.js +72 -0
- package/dist/backend-docker/cli.js.map +1 -0
- package/dist/backend-msb/backend.d.ts +91 -7
- package/dist/backend-msb/backend.d.ts.map +1 -1
- package/dist/backend-msb/backend.js +185 -11
- package/dist/backend-msb/backend.js.map +1 -1
- package/dist/backend-msb/commands.d.ts +16 -0
- package/dist/backend-msb/commands.d.ts.map +1 -1
- package/dist/backend-msb/commands.js +41 -1
- package/dist/backend-msb/commands.js.map +1 -1
- package/dist/backend-msb/snapshot-import.d.ts +33 -0
- package/dist/backend-msb/snapshot-import.d.ts.map +1 -0
- package/dist/backend-msb/snapshot-import.js +48 -0
- package/dist/backend-msb/snapshot-import.js.map +1 -0
- package/dist/backend-msb/snapshot-list.d.ts +43 -0
- package/dist/backend-msb/snapshot-list.d.ts.map +1 -0
- package/dist/backend-msb/snapshot-list.js +53 -0
- package/dist/backend-msb/snapshot-list.js.map +1 -0
- 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 +86 -6
- package/dist/core/backend.d.ts.map +1 -1
- package/dist/core/checkpoint/api.d.ts +129 -0
- package/dist/core/checkpoint/api.d.ts.map +1 -0
- package/dist/core/checkpoint/api.js +261 -0
- package/dist/core/checkpoint/api.js.map +1 -0
- package/dist/core/checkpoint/archive.d.ts +52 -0
- package/dist/core/checkpoint/archive.d.ts.map +1 -0
- package/dist/core/checkpoint/archive.js +134 -0
- package/dist/core/checkpoint/archive.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 +109 -0
- package/dist/core/checkpoint/registry.d.ts.map +1 -0
- package/dist/core/checkpoint/registry.js +181 -0
- package/dist/core/checkpoint/registry.js.map +1 -0
- package/dist/core/checkpoint/tar-cli.d.ts +45 -0
- package/dist/core/checkpoint/tar-cli.d.ts.map +1 -0
- package/dist/core/checkpoint/tar-cli.js +86 -0
- package/dist/core/checkpoint/tar-cli.js.map +1 -0
- package/dist/core/errors.d.ts +97 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +124 -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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { cacheDir } from "../cache-dir.js";
|
|
2
|
+
import { Backends } from "../backends.js";
|
|
3
|
+
import { CheckpointArtifactMissingError, CheckpointBackendMismatchError } from "../errors.js";
|
|
4
|
+
import { requireValidCheckpointName } from "./name.js";
|
|
5
|
+
import { readCheckpointRegistry, removeCheckpointRegistryFile, writeCheckpointRegistryAtomic, listCheckpointNames, fromCheckpointRegistryEntry, toCheckpointRegistrySpec, } from "./registry.js";
|
|
6
|
+
import { writeCheckpointArchive, readCheckpointArchive, CHECKPOINT_ARCHIVE_VERSION } from "./archive.js";
|
|
7
|
+
/** Every field of a `Checkpoint` comes straight from the registry entry except `spec`, which is reconstructed via `fromCheckpointRegistryEntry` — see that function's own doc for what is and isn't meaningful in the result. */
|
|
8
|
+
function toCheckpoint(entry) {
|
|
9
|
+
return { ref: entry.ref, backend: entry.backend, spec: fromCheckpointRegistryEntry(entry) };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Rediscovers a named checkpoint written by an earlier `checkpoint(name)`
|
|
13
|
+
* call — in this process or an entirely different one, since the registry
|
|
14
|
+
* lives on disk under the shared rightsize cache directory. No entry for
|
|
15
|
+
* `name` resolves to `undefined`. A corrupt entry is treated the same as
|
|
16
|
+
* absent, with a best-effort delete of the bad file.
|
|
17
|
+
*
|
|
18
|
+
* When the entry's own recorded backend matches the CURRENTLY active
|
|
19
|
+
* backend, the underlying artifact is probed via the backend's
|
|
20
|
+
* `hasCheckpoint` SPI before this resolves — an artifact that's gone (removed
|
|
21
|
+
* by hand, or by something outside this library) makes the entry stale: it's
|
|
22
|
+
* best-effort deleted and this resolves to `undefined`, the same as if it
|
|
23
|
+
* had never existed. A probe FAILURE (the backend call itself throws) is
|
|
24
|
+
* never swallowed into a `false` — only a confirmed "does not exist"
|
|
25
|
+
* resolves that way, so a probe failure propagates out of this call.
|
|
26
|
+
*
|
|
27
|
+
* When the entry's recorded backend DIFFERS from the active one, this
|
|
28
|
+
* returns the entry unprobed — an msb ref means nothing to a docker probe
|
|
29
|
+
* and vice versa. `GenericContainer.fromCheckpoint(cp).start()`'s own
|
|
30
|
+
* `CheckpointBackendMismatchError` gate stays the sole authority for that
|
|
31
|
+
* mismatch; this function must not force-resolve a backend the host may not
|
|
32
|
+
* even have.
|
|
33
|
+
*
|
|
34
|
+
* `name` is validated against `CHECKPOINT_NAME_PATTERN` before anything else
|
|
35
|
+
* — including before the registry file is even looked up — so a `name`
|
|
36
|
+
* carrying `../` segments can never reach path construction; an invalid name
|
|
37
|
+
* throws `InvalidCheckpointNameError` and touches no file.
|
|
38
|
+
*/
|
|
39
|
+
export async function find(name) {
|
|
40
|
+
requireValidCheckpointName(name);
|
|
41
|
+
const dir = cacheDir();
|
|
42
|
+
const read = await readCheckpointRegistry(dir, name);
|
|
43
|
+
if (read.kind === "missing") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (read.kind === "corrupt") {
|
|
47
|
+
await removeCheckpointRegistryFile(dir, name);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const entry = read.entry;
|
|
51
|
+
const active = Backends.active();
|
|
52
|
+
if (entry.backend !== active.name) {
|
|
53
|
+
return toCheckpoint(entry);
|
|
54
|
+
}
|
|
55
|
+
const exists = await active.hasCheckpoint(entry.ref);
|
|
56
|
+
if (!exists) {
|
|
57
|
+
await removeCheckpointRegistryFile(dir, name);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return toCheckpoint(entry);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Every named checkpoint currently in the registry — registry contents
|
|
64
|
+
* only, never probed against a backend (unlike `find`), so a stale entry
|
|
65
|
+
* whose artifact is gone still appears here until something calls `find` or
|
|
66
|
+
* `remove` on it. A corrupt entry is silently skipped, never removed (only
|
|
67
|
+
* `find`/`remove` clean those up, since `list` never resolves a single name
|
|
68
|
+
* the caller could target for a retry).
|
|
69
|
+
*/
|
|
70
|
+
export async function list() {
|
|
71
|
+
const dir = cacheDir();
|
|
72
|
+
const names = await listCheckpointNames(dir);
|
|
73
|
+
const checkpoints = [];
|
|
74
|
+
for (const name of names) {
|
|
75
|
+
const read = await readCheckpointRegistry(dir, name);
|
|
76
|
+
if (read.kind === "found") {
|
|
77
|
+
checkpoints.push(toCheckpoint(read.entry));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return checkpoints;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Deletes a named checkpoint: best-effort removal of the backend artifact
|
|
84
|
+
* (only when the entry's recorded backend matches the currently active
|
|
85
|
+
* one — this call never touches a backend that isn't active) plus the
|
|
86
|
+
* registry file, regardless of order of failure in either. Idempotent and
|
|
87
|
+
* always best-effort: "not found" anywhere (no registry entry at all) is
|
|
88
|
+
* success, reported as `false`; an existing entry — valid or corrupt —
|
|
89
|
+
* reports `true` once its registry file is gone.
|
|
90
|
+
*
|
|
91
|
+
* When the entry's recorded backend DIFFERS from the currently active one,
|
|
92
|
+
* only the registry record is deleted — the underlying artifact (the docker
|
|
93
|
+
* image, or the microsandbox snapshot) is left on disk PERMANENTLY, since
|
|
94
|
+
* this call never touches a backend that isn't active. Nothing in this
|
|
95
|
+
* library reclaims it automatically — and once the record is gone, a later
|
|
96
|
+
* `remove(name)` finds nothing to act on. Remove a checkpoint under its
|
|
97
|
+
* creating backend in the first place, or clean the leftover artifact
|
|
98
|
+
* directly with that backend's own CLI one-liner (see the
|
|
99
|
+
* [checkpoints guide](/guide/checkpoints#cleanup-checkpoints-are-not-auto-reaped)).
|
|
100
|
+
*
|
|
101
|
+
* `name` is validated against `CHECKPOINT_NAME_PATTERN` before anything else
|
|
102
|
+
* — including before the registry file is even looked up — so a `name`
|
|
103
|
+
* carrying `../` segments can never reach path construction; an invalid name
|
|
104
|
+
* throws `InvalidCheckpointNameError` and touches no file.
|
|
105
|
+
*/
|
|
106
|
+
export async function remove(name) {
|
|
107
|
+
requireValidCheckpointName(name);
|
|
108
|
+
const dir = cacheDir();
|
|
109
|
+
const read = await readCheckpointRegistry(dir, name);
|
|
110
|
+
if (read.kind === "missing") {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (read.kind === "corrupt") {
|
|
114
|
+
await removeCheckpointRegistryFile(dir, name);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const entry = read.entry;
|
|
118
|
+
const active = Backends.active();
|
|
119
|
+
if (entry.backend === active.name) {
|
|
120
|
+
await active.removeCheckpoint(entry.ref).catch(() => { });
|
|
121
|
+
}
|
|
122
|
+
await removeCheckpointRegistryFile(dir, name);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Reverse lookup: the registry entry (if any) whose `ref`/`backend` match a
|
|
127
|
+
* `Checkpoint`'s own — `exportTo`'s only way to learn whether the checkpoint
|
|
128
|
+
* it's given was ever named, and under what name, since a `Checkpoint`
|
|
129
|
+
* object itself never carries one (see `toCheckpoint` above). Scans every
|
|
130
|
+
* registered name the same way `list()` does; a corrupt entry is silently
|
|
131
|
+
* skipped, matching `list()`'s own tolerance.
|
|
132
|
+
*/
|
|
133
|
+
async function findRegistryEntryByRef(dir, ref, backend) {
|
|
134
|
+
const names = await listCheckpointNames(dir);
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
const read = await readCheckpointRegistry(dir, name);
|
|
137
|
+
if (read.kind === "found" && read.entry.ref === ref && read.entry.backend === backend) {
|
|
138
|
+
return read.entry;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Bundles `cp` into a self-describing archive at `destPath`: `checkpoint.json`
|
|
145
|
+
* (the pinned metadata — format version, this checkpoint's name if it was
|
|
146
|
+
* ever registered under one, ref, backend, creation time, and spec) plus an
|
|
147
|
+
* `artifact` member holding the backend's own payload, byte-for-byte what
|
|
148
|
+
* `SandboxBackend.exportCheckpoint` produces. See the
|
|
149
|
+
* [checkpoints guide](/guide/checkpoints#moving-checkpoints-between-machines)
|
|
150
|
+
* for the full CI-cache pattern this exists for.
|
|
151
|
+
*
|
|
152
|
+
* Requires the ACTIVE backend to equal `cp.backend` — the same
|
|
153
|
+
* `CheckpointBackendMismatchError` `fromCheckpoint(cp).start()` throws —
|
|
154
|
+
* before any backend or filesystem work. Then probes the artifact still
|
|
155
|
+
* exists via `hasCheckpoint`: exporting a stale checkpoint throws
|
|
156
|
+
* `CheckpointArtifactMissingError` rather than producing a broken archive.
|
|
157
|
+
* Only once both checks pass does this stage the export in a fresh unique
|
|
158
|
+
* temp directory (removed in a `finally` regardless of outcome) and tar it
|
|
159
|
+
* into `destPath` (parent directories created; a pre-existing file there is
|
|
160
|
+
* overwritten). Works on an ephemeral (unnamed) checkpoint too — the
|
|
161
|
+
* resulting archive just carries `name: null`.
|
|
162
|
+
*/
|
|
163
|
+
export async function exportTo(cp, destPath) {
|
|
164
|
+
const active = Backends.active();
|
|
165
|
+
if (cp.backend !== active.name) {
|
|
166
|
+
throw new CheckpointBackendMismatchError(cp.backend, active.name);
|
|
167
|
+
}
|
|
168
|
+
const exists = await active.hasCheckpoint(cp.ref);
|
|
169
|
+
if (!exists) {
|
|
170
|
+
throw new CheckpointArtifactMissingError(cp.ref, cp.backend);
|
|
171
|
+
}
|
|
172
|
+
const dir = cacheDir();
|
|
173
|
+
const registryEntry = await findRegistryEntryByRef(dir, cp.ref, cp.backend);
|
|
174
|
+
const metadata = {
|
|
175
|
+
rightsizeArchive: CHECKPOINT_ARCHIVE_VERSION,
|
|
176
|
+
name: registryEntry?.name ?? null,
|
|
177
|
+
ref: cp.ref,
|
|
178
|
+
backend: cp.backend,
|
|
179
|
+
createdIso: registryEntry?.createdIso ?? new Date().toISOString(),
|
|
180
|
+
spec: toCheckpointRegistrySpec(cp.spec),
|
|
181
|
+
};
|
|
182
|
+
await writeCheckpointArchive(destPath, metadata, (artifactPath) => active.exportCheckpoint(cp.ref, artifactPath));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The inverse of `exportTo`: extracts `srcPath`, validates its
|
|
186
|
+
* `checkpoint.json` (format version, `name` against
|
|
187
|
+
* `CHECKPOINT_NAME_PATTERN` when non-null, backend against the ACTIVE
|
|
188
|
+
* backend — a `MalformedCheckpointArchiveError` or `CheckpointBackendMismatchError`
|
|
189
|
+
* either way, before any backend call or registry write), then hands the
|
|
190
|
+
* extracted `artifact` to `SandboxBackend.importCheckpoint`, which
|
|
191
|
+
* materializes it and returns the EFFECTIVE ref (docker: the same `ref` the
|
|
192
|
+
* archive recorded; microsandbox: the digest `snapshot import` actually
|
|
193
|
+
* assigned it — never necessarily the archive's own `ref`).
|
|
194
|
+
*
|
|
195
|
+
* A NAMED archive (`name` non-null) gets replace semantics matching
|
|
196
|
+
* `checkpoint(name)`: if a registry entry already exists for that name under
|
|
197
|
+
* a DIFFERENT ref and its recorded backend matches the active one, its old
|
|
198
|
+
* artifact is best-effort removed first (never a foreign-backend
|
|
199
|
+
* `removeCheckpoint` call — the same gate `remove()` applies); the registry
|
|
200
|
+
* entry is then written (or overwritten) with the effective ref. A NAMELESS
|
|
201
|
+
* archive writes no registry entry at all — the returned `Checkpoint` is
|
|
202
|
+
* purely ephemeral, the same as an unnamed `checkpoint()` call's result.
|
|
203
|
+
* Either way, the returned `Checkpoint` restores via the existing
|
|
204
|
+
* `fromCheckpoint()` path with zero changes — refs are opaque throughout
|
|
205
|
+
* this library.
|
|
206
|
+
*/
|
|
207
|
+
export async function importFrom(srcPath) {
|
|
208
|
+
const active = Backends.active();
|
|
209
|
+
return readCheckpointArchive(srcPath, async (metadata, artifactPath) => {
|
|
210
|
+
if (metadata.name !== null) {
|
|
211
|
+
requireValidCheckpointName(metadata.name);
|
|
212
|
+
}
|
|
213
|
+
if (metadata.backend !== active.name) {
|
|
214
|
+
throw new CheckpointBackendMismatchError(metadata.backend, active.name);
|
|
215
|
+
}
|
|
216
|
+
const effectiveRef = await active.importCheckpoint(artifactPath, metadata.ref);
|
|
217
|
+
const spec = fromCheckpointRegistryEntry({
|
|
218
|
+
name: metadata.name ?? "",
|
|
219
|
+
ref: effectiveRef,
|
|
220
|
+
backend: active.name,
|
|
221
|
+
createdIso: metadata.createdIso,
|
|
222
|
+
spec: metadata.spec,
|
|
223
|
+
});
|
|
224
|
+
if (metadata.name !== null) {
|
|
225
|
+
const dir = cacheDir();
|
|
226
|
+
const existing = await readCheckpointRegistry(dir, metadata.name);
|
|
227
|
+
if (existing.kind === "found" && existing.entry.ref !== effectiveRef && existing.entry.backend === active.name) {
|
|
228
|
+
await active.removeCheckpoint(existing.entry.ref).catch(() => { });
|
|
229
|
+
}
|
|
230
|
+
const entry = {
|
|
231
|
+
name: metadata.name,
|
|
232
|
+
ref: effectiveRef,
|
|
233
|
+
backend: active.name,
|
|
234
|
+
createdIso: metadata.createdIso,
|
|
235
|
+
spec: metadata.spec,
|
|
236
|
+
};
|
|
237
|
+
await writeCheckpointRegistryAtomic(dir, metadata.name, entry);
|
|
238
|
+
}
|
|
239
|
+
return { ref: effectiveRef, backend: active.name, spec };
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The library's entry point for rediscovering NAMED checkpoints across
|
|
244
|
+
* processes — see the [checkpoints guide](/guide/checkpoints#reusing-checkpoints-across-runs)
|
|
245
|
+
* for the `find(...) ?? seed()` first-run/later-run pattern this exists to
|
|
246
|
+
* support. Unnamed `checkpoint()` calls never appear here; only a
|
|
247
|
+
* `checkpoint(name)` call writes a registry entry these functions can find.
|
|
248
|
+
*/
|
|
249
|
+
export const Checkpoints = {
|
|
250
|
+
/** Rediscovers a named checkpoint — see `find` above. */
|
|
251
|
+
find,
|
|
252
|
+
/** Every named checkpoint currently in the registry — see `list` above. */
|
|
253
|
+
list,
|
|
254
|
+
/** Deletes a named checkpoint — see `remove` above. */
|
|
255
|
+
remove,
|
|
256
|
+
/** Bundles a checkpoint into a portable archive — see `exportTo` above. */
|
|
257
|
+
exportTo,
|
|
258
|
+
/** Materializes a portable archive on this machine — see `importFrom` above. */
|
|
259
|
+
importFrom,
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/core/checkpoint/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CheckpointRegistrySpec } from "./registry.js";
|
|
2
|
+
/** The one archive format version this library understands — pinned identically across every rightsize language implementation. */
|
|
3
|
+
export declare const CHECKPOINT_ARCHIVE_VERSION = 1;
|
|
4
|
+
/**
|
|
5
|
+
* `checkpoint.json`'s exact shape — pinned identically in every rightsize
|
|
6
|
+
* language implementation. `name` is `null` for an archive built from an
|
|
7
|
+
* unnamed (ephemeral) checkpoint; every other field mirrors
|
|
8
|
+
* `CheckpointRegistryEntry` plus the format version.
|
|
9
|
+
*/
|
|
10
|
+
export interface CheckpointArchiveMetadata {
|
|
11
|
+
readonly rightsizeArchive: 1;
|
|
12
|
+
readonly name: string | null;
|
|
13
|
+
readonly ref: string;
|
|
14
|
+
readonly backend: string;
|
|
15
|
+
readonly createdIso: string;
|
|
16
|
+
readonly spec: CheckpointRegistrySpec;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parses and validates `text` (the extracted archive's `checkpoint.json`
|
|
20
|
+
* content) into a `CheckpointArchiveMetadata`. Every failure — invalid JSON,
|
|
21
|
+
* a `rightsizeArchive` value other than `CHECKPOINT_ARCHIVE_VERSION` (named
|
|
22
|
+
* in the thrown error, per the pinned format's own contract), a malformed
|
|
23
|
+
* `name`/`ref`/`backend`/`createdIso`/`spec` — throws
|
|
24
|
+
* `MalformedCheckpointArchiveError` naming `archivePath` (the ORIGINAL
|
|
25
|
+
* archive the caller passed to `importFrom`, not the temp-extracted json
|
|
26
|
+
* path, so the error is actionable). Never touches a backend or the
|
|
27
|
+
* registry — pure parsing.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseCheckpointArchiveMetadata(text: string, archivePath: string): CheckpointArchiveMetadata;
|
|
30
|
+
/**
|
|
31
|
+
* Builds a checkpoint archive at `destPath`: stages `checkpoint.json` and an
|
|
32
|
+
* `artifact` file (written by `exportArtifact`, the backend's own
|
|
33
|
+
* `exportCheckpoint` call) in a fresh temp directory, then tars exactly
|
|
34
|
+
* those two members at the archive's root — never nested under the temp
|
|
35
|
+
* dir's own path. `destPath`'s parent directories are created first; a
|
|
36
|
+
* pre-existing file at `destPath` is overwritten (tar's own default
|
|
37
|
+
* behavior). The temp directory is removed in a `finally`, on success and on
|
|
38
|
+
* failure alike.
|
|
39
|
+
*/
|
|
40
|
+
export declare function writeCheckpointArchive(destPath: string, metadata: CheckpointArchiveMetadata, exportArtifact: (artifactPath: string) => Promise<void>): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Extracts `srcPath` into a fresh temp directory, parses and validates
|
|
43
|
+
* `checkpoint.json` (see `parseCheckpointArchiveMetadata`), confirms the
|
|
44
|
+
* `artifact` member is present, then hands both to `importArtifact` — the
|
|
45
|
+
* caller's own backend/registry validation and `importCheckpoint` call —
|
|
46
|
+
* before the temp directory is removed in a `finally`. `srcPath` not
|
|
47
|
+
* existing, or not being a valid tar, or missing its `checkpoint.json`
|
|
48
|
+
* member each throw `MalformedCheckpointArchiveError` naming `srcPath`
|
|
49
|
+
* itself, before any backend call.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readCheckpointArchive<T>(srcPath: string, importArtifact: (metadata: CheckpointArchiveMetadata, artifactPath: string) => Promise<T>): Promise<T>;
|
|
52
|
+
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/archive.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAG5D,mIAAmI;AACnI,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAU5C;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;CACvC;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,yBAAyB,CAwC3G;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,yBAAyB,EACnC,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,CAAC,QAAQ,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACxF,OAAO,CAAC,CAAC,CAAC,CAsCZ"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as fsp from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { BackendError, MalformedCheckpointArchiveError } from "../errors.js";
|
|
5
|
+
import { isCheckpointRegistrySpec } from "./registry.js";
|
|
6
|
+
import { TarCli, runTar } from "./tar-cli.js";
|
|
7
|
+
/** The one archive format version this library understands — pinned identically across every rightsize language implementation. */
|
|
8
|
+
export const CHECKPOINT_ARCHIVE_VERSION = 1;
|
|
9
|
+
/**
|
|
10
|
+
* An archive bundles a full backend artifact (a zstd-compressed msb snapshot
|
|
11
|
+
* or a saved docker image) — generous relative to a plain `msb copy`/`docker
|
|
12
|
+
* cp`, matching the backend's own export/import budget for the payload
|
|
13
|
+
* itself.
|
|
14
|
+
*/
|
|
15
|
+
const ARCHIVE_TAR_TIMEOUT_MS = 300_000;
|
|
16
|
+
async function withTempDir(prefix, fn) {
|
|
17
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
18
|
+
try {
|
|
19
|
+
return await fn(dir);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await fsp.rm(dir, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parses and validates `text` (the extracted archive's `checkpoint.json`
|
|
27
|
+
* content) into a `CheckpointArchiveMetadata`. Every failure — invalid JSON,
|
|
28
|
+
* a `rightsizeArchive` value other than `CHECKPOINT_ARCHIVE_VERSION` (named
|
|
29
|
+
* in the thrown error, per the pinned format's own contract), a malformed
|
|
30
|
+
* `name`/`ref`/`backend`/`createdIso`/`spec` — throws
|
|
31
|
+
* `MalformedCheckpointArchiveError` naming `archivePath` (the ORIGINAL
|
|
32
|
+
* archive the caller passed to `importFrom`, not the temp-extracted json
|
|
33
|
+
* path, so the error is actionable). Never touches a backend or the
|
|
34
|
+
* registry — pure parsing.
|
|
35
|
+
*/
|
|
36
|
+
export function parseCheckpointArchiveMetadata(text, archivePath) {
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is not valid JSON");
|
|
43
|
+
}
|
|
44
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
45
|
+
throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is not a JSON object");
|
|
46
|
+
}
|
|
47
|
+
const rec = parsed;
|
|
48
|
+
if (rec["rightsizeArchive"] !== CHECKPOINT_ARCHIVE_VERSION) {
|
|
49
|
+
throw new MalformedCheckpointArchiveError(archivePath, `unsupported rightsizeArchive version ${JSON.stringify(rec["rightsizeArchive"])} (this library reads version ${CHECKPOINT_ARCHIVE_VERSION})`);
|
|
50
|
+
}
|
|
51
|
+
const name = rec["name"];
|
|
52
|
+
if (name !== null && typeof name !== "string") {
|
|
53
|
+
throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json's 'name' field must be a string or null");
|
|
54
|
+
}
|
|
55
|
+
if (typeof rec["ref"] !== "string" || typeof rec["backend"] !== "string" || typeof rec["createdIso"] !== "string") {
|
|
56
|
+
throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is missing one of the required string fields 'ref', 'backend', 'createdIso'");
|
|
57
|
+
}
|
|
58
|
+
if (!isCheckpointRegistrySpec(rec["spec"])) {
|
|
59
|
+
throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json's 'spec' field is missing or malformed");
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
rightsizeArchive: 1,
|
|
63
|
+
name,
|
|
64
|
+
ref: rec["ref"],
|
|
65
|
+
backend: rec["backend"],
|
|
66
|
+
createdIso: rec["createdIso"],
|
|
67
|
+
spec: rec["spec"],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds a checkpoint archive at `destPath`: stages `checkpoint.json` and an
|
|
72
|
+
* `artifact` file (written by `exportArtifact`, the backend's own
|
|
73
|
+
* `exportCheckpoint` call) in a fresh temp directory, then tars exactly
|
|
74
|
+
* those two members at the archive's root — never nested under the temp
|
|
75
|
+
* dir's own path. `destPath`'s parent directories are created first; a
|
|
76
|
+
* pre-existing file at `destPath` is overwritten (tar's own default
|
|
77
|
+
* behavior). The temp directory is removed in a `finally`, on success and on
|
|
78
|
+
* failure alike.
|
|
79
|
+
*/
|
|
80
|
+
export async function writeCheckpointArchive(destPath, metadata, exportArtifact) {
|
|
81
|
+
await fsp.mkdir(path.dirname(destPath), { recursive: true });
|
|
82
|
+
await withTempDir("rightsize-checkpoint-export-", async (workDir) => {
|
|
83
|
+
const artifactPath = path.join(workDir, "artifact");
|
|
84
|
+
await exportArtifact(artifactPath);
|
|
85
|
+
await fsp.writeFile(path.join(workDir, "checkpoint.json"), JSON.stringify(metadata));
|
|
86
|
+
const result = await runTar(TarCli.create(path.basename(destPath), workDir, ["checkpoint.json", "artifact"]), ARCHIVE_TAR_TIMEOUT_MS, path.dirname(destPath));
|
|
87
|
+
if (result.exitCode !== 0) {
|
|
88
|
+
throw new BackendError(`tar could not create checkpoint archive '${destPath}' (exit ${result.exitCode}): ${result.stderr.trim()}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Extracts `srcPath` into a fresh temp directory, parses and validates
|
|
94
|
+
* `checkpoint.json` (see `parseCheckpointArchiveMetadata`), confirms the
|
|
95
|
+
* `artifact` member is present, then hands both to `importArtifact` — the
|
|
96
|
+
* caller's own backend/registry validation and `importCheckpoint` call —
|
|
97
|
+
* before the temp directory is removed in a `finally`. `srcPath` not
|
|
98
|
+
* existing, or not being a valid tar, or missing its `checkpoint.json`
|
|
99
|
+
* member each throw `MalformedCheckpointArchiveError` naming `srcPath`
|
|
100
|
+
* itself, before any backend call.
|
|
101
|
+
*/
|
|
102
|
+
export async function readCheckpointArchive(srcPath, importArtifact) {
|
|
103
|
+
const exists = await fsp
|
|
104
|
+
.stat(srcPath)
|
|
105
|
+
.then((s) => s.isFile())
|
|
106
|
+
.catch(() => false);
|
|
107
|
+
if (!exists) {
|
|
108
|
+
throw new MalformedCheckpointArchiveError(srcPath, "the file does not exist");
|
|
109
|
+
}
|
|
110
|
+
return withTempDir("rightsize-checkpoint-import-", async (workDir) => {
|
|
111
|
+
const extracted = await runTar(TarCli.extract(path.basename(srcPath), workDir), ARCHIVE_TAR_TIMEOUT_MS, path.dirname(srcPath));
|
|
112
|
+
if (extracted.exitCode !== 0) {
|
|
113
|
+
throw new MalformedCheckpointArchiveError(srcPath, `not a valid tar archive: ${extracted.stderr.trim()}`);
|
|
114
|
+
}
|
|
115
|
+
let text;
|
|
116
|
+
try {
|
|
117
|
+
text = await fsp.readFile(path.join(workDir, "checkpoint.json"), "utf8");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new MalformedCheckpointArchiveError(srcPath, "missing checkpoint.json");
|
|
121
|
+
}
|
|
122
|
+
const metadata = parseCheckpointArchiveMetadata(text, srcPath);
|
|
123
|
+
const artifactPath = path.join(workDir, "artifact");
|
|
124
|
+
const hasArtifact = await fsp
|
|
125
|
+
.stat(artifactPath)
|
|
126
|
+
.then(() => true)
|
|
127
|
+
.catch(() => false);
|
|
128
|
+
if (!hasArtifact) {
|
|
129
|
+
throw new MalformedCheckpointArchiveError(srcPath, "missing artifact");
|
|
130
|
+
}
|
|
131
|
+
return importArtifact(metadata, artifactPath);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/core/checkpoint/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned identically across every rightsize language implementation (see the
|
|
3
|
+
* named-checkpoints spec's own "Names and refs" section): lowercase letters,
|
|
4
|
+
* digits, and hyphens only, starting with a letter or digit, at most 41
|
|
5
|
+
* characters. This is also exactly the alphabet both backends' ref formats
|
|
6
|
+
* embed verbatim (`rz-ckpt-<name>`, `rightsize/checkpoint:<name>`), so a
|
|
7
|
+
* valid name is guaranteed to produce a valid ref on either backend.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CHECKPOINT_NAME_PATTERN: RegExp;
|
|
10
|
+
/** Fails fast — before any backend or filesystem call — on a `name` that doesn't match `CHECKPOINT_NAME_PATTERN`. */
|
|
11
|
+
export declare function requireValidCheckpointName(name: string): void;
|
|
12
|
+
//# sourceMappingURL=name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/name.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAA8B,CAAC;AAEnE,qHAAqH;AACrH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI7D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InvalidCheckpointNameError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pinned identically across every rightsize language implementation (see the
|
|
4
|
+
* named-checkpoints spec's own "Names and refs" section): lowercase letters,
|
|
5
|
+
* digits, and hyphens only, starting with a letter or digit, at most 41
|
|
6
|
+
* characters. This is also exactly the alphabet both backends' ref formats
|
|
7
|
+
* embed verbatim (`rz-ckpt-<name>`, `rightsize/checkpoint:<name>`), so a
|
|
8
|
+
* valid name is guaranteed to produce a valid ref on either backend.
|
|
9
|
+
*/
|
|
10
|
+
export const CHECKPOINT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
11
|
+
/** Fails fast — before any backend or filesystem call — on a `name` that doesn't match `CHECKPOINT_NAME_PATTERN`. */
|
|
12
|
+
export function requireValidCheckpointName(name) {
|
|
13
|
+
if (!CHECKPOINT_NAME_PATTERN.test(name)) {
|
|
14
|
+
throw new InvalidCheckpointNameError(name);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.js","sourceRoot":"","sources":["../../../src/core/checkpoint/name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;AAEnE,qHAAqH;AACrH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mints the backend-specific checkpoint ref: `rz-ckpt-<suffix>` on
|
|
3
|
+
* microsandbox, `rightsize/checkpoint:<suffix>` elsewhere. `name` present
|
|
4
|
+
* (a NAMED checkpoint) makes the suffix — and therefore the whole ref —
|
|
5
|
+
* deterministic: re-checkpointing the same name reproduces the exact same
|
|
6
|
+
* ref, which is what makes the registry's replace semantics (remove the old
|
|
7
|
+
* artifact under this ref, then create the new one) correct. `name`
|
|
8
|
+
* `undefined` mints a fresh random 12-hex suffix instead, byte-for-byte the
|
|
9
|
+
* pre-named-checkpoints behavior.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkpointRef(backendName: string, name: string | undefined): string;
|
|
12
|
+
//# sourceMappingURL=ref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/ref.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Mints the backend-specific checkpoint ref: `rz-ckpt-<suffix>` on
|
|
4
|
+
* microsandbox, `rightsize/checkpoint:<suffix>` elsewhere. `name` present
|
|
5
|
+
* (a NAMED checkpoint) makes the suffix — and therefore the whole ref —
|
|
6
|
+
* deterministic: re-checkpointing the same name reproduces the exact same
|
|
7
|
+
* ref, which is what makes the registry's replace semantics (remove the old
|
|
8
|
+
* artifact under this ref, then create the new one) correct. `name`
|
|
9
|
+
* `undefined` mints a fresh random 12-hex suffix instead, byte-for-byte the
|
|
10
|
+
* pre-named-checkpoints behavior.
|
|
11
|
+
*/
|
|
12
|
+
export function checkpointRef(backendName, name) {
|
|
13
|
+
const suffix = name ?? randomBytes(6).toString("hex");
|
|
14
|
+
return backendName === "microsandbox" ? `rz-ckpt-${suffix}` : `rightsize/checkpoint:${suffix}`;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref.js","sourceRoot":"","sources":["../../../src/core/checkpoint/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB,EAAE,IAAwB;IACzE,MAAM,MAAM,GAAG,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,OAAO,WAAW,KAAK,cAAc,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC,wBAAwB,MAAM,EAAE,CAAC;AACjG,CAAC"}
|