@ornncompute/cli 1.0.121 → 1.0.123
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 +8 -0
- package/package.json +1 -1
- package/src/admin-node-diagnostics.mjs +2 -4
- package/src/admin-node-lifecycle.mjs +4 -8
- package/src/admin-ssh.mjs +2 -1
- package/src/api-client.mjs +5 -2
- package/src/evidence-commands.mjs +10 -6
- package/src/external-bucket-commands.mjs +25 -46
- package/src/interaction-runtime.mjs +2 -0
- package/src/machine-reference.mjs +7 -17
- package/src/mount-commands.mjs +3 -9
- package/src/storage-commands.mjs +54 -95
- package/src/vm-commands.mjs +796 -427
- package/vendor/api-client/commerce.js +109 -0
- package/vendor/api-client/orchestrator.js +437 -1
- package/vendor/capabilities/cli.js +307 -200
package/src/storage-commands.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as storageApi from "../vendor/api-client/orchestrator.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { createReadStream } from "node:fs";
|
|
3
4
|
import { stat } from "node:fs/promises";
|
|
@@ -6,7 +7,6 @@ import { Readable } from "node:stream";
|
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
CliApiError,
|
|
9
|
-
computeEndpoint,
|
|
10
10
|
loadRequiredSession,
|
|
11
11
|
raiseForFailedResponse,
|
|
12
12
|
resolveApiBaseUrl,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_PAGE_LIMIT = 50;
|
|
22
22
|
const MAX_PAGE_LIMIT = 500;
|
|
23
|
-
const STORAGE_PREFIX = "/nodes/storage-drives";
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Narrow CLI adapter expected by executeStorageCommand:
|
|
@@ -143,13 +142,6 @@ function normalizeRemotePath(value, { allowEmpty = false, field = "path" } = {})
|
|
|
143
142
|
return segments.join("/");
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
function encodedRemotePath(path) {
|
|
147
|
-
return path
|
|
148
|
-
.split("/")
|
|
149
|
-
.map((segment) => encodeURIComponent(segment))
|
|
150
|
-
.join("/");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
145
|
function isManagedVolume(drive) {
|
|
154
146
|
const provider = String(drive?.provider ?? "").toLocaleLowerCase("en-US");
|
|
155
147
|
const sourceProvider = String(
|
|
@@ -255,19 +247,6 @@ export function codeOf(error) {
|
|
|
255
247
|
return nested && /^[a-z][a-z0-9_]*$/.test(nested) ? nested : null;
|
|
256
248
|
}
|
|
257
249
|
|
|
258
|
-
const SAFE_ERROR_MESSAGES = {
|
|
259
|
-
storage_content_busy: "The volume content is still changing. Inspect the volume and try again.",
|
|
260
|
-
storage_drive_attached_to_reservation:
|
|
261
|
-
"This volume still has a reservation relationship. Detach every volume mount before deleting it.",
|
|
262
|
-
storage_drive_mounted: "This volume is mounted. Detach it before changing its files.",
|
|
263
|
-
storage_drive_not_found: "The requested volume was not found.",
|
|
264
|
-
storage_file_not_found: "The requested volume file was not found.",
|
|
265
|
-
storage_folder_not_found: "The requested volume folder was not found.",
|
|
266
|
-
storage_object_file_api_unsupported: "This volume does not support nested file operations.",
|
|
267
|
-
storage_pending_uploads:
|
|
268
|
-
"This volume has an upload still being finalized. Inspect it and try again.",
|
|
269
|
-
};
|
|
270
|
-
|
|
271
250
|
function safeStorageError(error, fallback, { action } = {}) {
|
|
272
251
|
if (error instanceof StorageCommandError) return error;
|
|
273
252
|
if (error instanceof AtomicOutputExistsError) {
|
|
@@ -285,8 +264,7 @@ function safeStorageError(error, fallback, { action } = {}) {
|
|
|
285
264
|
const unavailable = code === "service_unavailable";
|
|
286
265
|
const ownerMessage = detailOf(error)?.message;
|
|
287
266
|
return new StorageCommandError(
|
|
288
|
-
|
|
289
|
-
(typeof ownerMessage === "string" && ownerMessage.trim() ? ownerMessage : fallback),
|
|
267
|
+
typeof ownerMessage === "string" && ownerMessage.trim() ? ownerMessage : fallback,
|
|
290
268
|
{
|
|
291
269
|
action,
|
|
292
270
|
code,
|
|
@@ -313,11 +291,11 @@ export async function fetchDrivePage(runtime, { limit = 25, cursor, scope = "all
|
|
|
313
291
|
if (!Number.isInteger(Number(limit)) || Number(limit) < 1 || Number(limit) > 100) {
|
|
314
292
|
usage("--limit must be an integer from 1 through 100.", "limit");
|
|
315
293
|
}
|
|
316
|
-
const params = new URLSearchParams({ limit: String(limit), scope });
|
|
317
|
-
if (cursor) params.set("cursor", cursor);
|
|
318
294
|
try {
|
|
319
|
-
const payload = await runtime.fetch
|
|
320
|
-
|
|
295
|
+
const payload = await storageApi.listDrives(runtime.fetch, {
|
|
296
|
+
limit: Number(limit),
|
|
297
|
+
scope,
|
|
298
|
+
...(cursor ? { cursor } : {}),
|
|
321
299
|
});
|
|
322
300
|
return { drives: volumeRows(payload), next_cursor: payload?.next_cursor ?? null };
|
|
323
301
|
} catch (error) {
|
|
@@ -367,10 +345,7 @@ function resolveFromRows(rows, reference, { source = false } = {}) {
|
|
|
367
345
|
|
|
368
346
|
export async function resolveVolume(runtime, reference) {
|
|
369
347
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(reference)) {
|
|
370
|
-
const drive = await runtime.fetch
|
|
371
|
-
computeEndpoint(`${STORAGE_PREFIX}/${encodeURIComponent(reference)}`),
|
|
372
|
-
{ method: "GET" }
|
|
373
|
-
);
|
|
348
|
+
const drive = await storageApi.getDrive(runtime.fetch, reference);
|
|
374
349
|
return resolveFromRows([drive], reference);
|
|
375
350
|
}
|
|
376
351
|
return resolveFromRows(await fetchDrives(runtime), reference);
|
|
@@ -418,10 +393,8 @@ async function createVolume(input, runtime) {
|
|
|
418
393
|
|
|
419
394
|
const body = { name, source_drive_id: source ? String(source.id) : null };
|
|
420
395
|
try {
|
|
421
|
-
const created = await runtime.fetch
|
|
422
|
-
body,
|
|
396
|
+
const created = await storageApi.createDrive(runtime.fetch, body, {
|
|
423
397
|
headers: idempotencyHeaders(input),
|
|
424
|
-
method: "POST",
|
|
425
398
|
});
|
|
426
399
|
return runtime.interaction.result({
|
|
427
400
|
data: { effective_input: effective, replayed: false, volume: safeVolume(created) },
|
|
@@ -477,10 +450,7 @@ async function showVolume(input, runtime, { refresh = false } = {}) {
|
|
|
477
450
|
return runtime.interaction.result({ data: { volume: safeVolume(drive) } });
|
|
478
451
|
}
|
|
479
452
|
try {
|
|
480
|
-
const refreshed = await runtime.fetch(
|
|
481
|
-
computeEndpoint(`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/refresh`),
|
|
482
|
-
{ method: "POST" }
|
|
483
|
-
);
|
|
453
|
+
const refreshed = await storageApi.getDrive(runtime.fetch, String(drive.id), { refresh: true });
|
|
484
454
|
return runtime.interaction.result({
|
|
485
455
|
data: { volume: safeVolume(refreshed) },
|
|
486
456
|
outcome: { class: "ready", phase: "refreshed" },
|
|
@@ -495,10 +465,7 @@ async function clearVolume(input, runtime) {
|
|
|
495
465
|
const [reference] = positionals(input, 1, "Usage: ornn volumes clear <volume>");
|
|
496
466
|
const drive = await resolveVolume(runtime, reference);
|
|
497
467
|
try {
|
|
498
|
-
const payload = await runtime.fetch(
|
|
499
|
-
computeEndpoint(`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/contents`),
|
|
500
|
-
{ method: "DELETE" }
|
|
501
|
-
);
|
|
468
|
+
const payload = await storageApi.clearDriveContents(runtime.fetch, String(drive.id));
|
|
502
469
|
return runtime.interaction.result({
|
|
503
470
|
data: {
|
|
504
471
|
cleared: true,
|
|
@@ -533,12 +500,11 @@ async function deleteVolume(input, runtime) {
|
|
|
533
500
|
requireMachineMutationKey(input, runtime);
|
|
534
501
|
const drive = await resolveVolume(runtime, reference);
|
|
535
502
|
try {
|
|
536
|
-
await
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
503
|
+
await storageApi.deleteDrive(
|
|
504
|
+
runtime.fetch,
|
|
505
|
+
String(drive.id),
|
|
506
|
+
{},
|
|
507
|
+
{ headers: idempotencyHeaders(input) }
|
|
542
508
|
);
|
|
543
509
|
return runtime.interaction.result({
|
|
544
510
|
data: { deleted: true, volume: safeVolume(drive) },
|
|
@@ -590,17 +556,13 @@ async function fetchFileRows(runtime, drive, prefix) {
|
|
|
590
556
|
const entries = [];
|
|
591
557
|
let cursor = 0;
|
|
592
558
|
while (cursor !== null) {
|
|
593
|
-
const query = new URLSearchParams({ recursive: "true" });
|
|
594
|
-
if (folder) query.set("path", folder);
|
|
595
|
-
if (cursor) query.set("cursor", String(cursor));
|
|
596
559
|
let page;
|
|
597
560
|
try {
|
|
598
|
-
page = await runtime.fetch(
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
),
|
|
602
|
-
|
|
603
|
-
);
|
|
561
|
+
page = await storageApi.listDriveContents(runtime.fetch, String(drive.id), {
|
|
562
|
+
recursive: true,
|
|
563
|
+
...(folder ? { path: folder } : {}),
|
|
564
|
+
...(cursor ? { cursor } : {}),
|
|
565
|
+
});
|
|
604
566
|
} catch (error) {
|
|
605
567
|
throw safeStorageError(error, "Volume files could not be listed safely.");
|
|
606
568
|
}
|
|
@@ -719,12 +681,7 @@ async function putSignedUpload({
|
|
|
719
681
|
|
|
720
682
|
async function cancelUpload(runtime, driveId, uploadId) {
|
|
721
683
|
try {
|
|
722
|
-
await runtime.fetch
|
|
723
|
-
computeEndpoint(
|
|
724
|
-
`${STORAGE_PREFIX}/${encodeURIComponent(driveId)}/file-uploads/${encodeURIComponent(uploadId)}`
|
|
725
|
-
),
|
|
726
|
-
{ method: "DELETE" }
|
|
727
|
-
);
|
|
684
|
+
await storageApi.cancelDriveUpload(runtime.fetch, driveId, uploadId);
|
|
728
685
|
} catch {
|
|
729
686
|
// Preserve the transfer error. Finalizing sessions are owner-reconciled.
|
|
730
687
|
}
|
|
@@ -762,13 +719,11 @@ async function uploadFile(input, runtime) {
|
|
|
762
719
|
const sha256 = requestHash.digest("hex");
|
|
763
720
|
let session;
|
|
764
721
|
try {
|
|
765
|
-
session = await
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
method: "POST",
|
|
771
|
-
}
|
|
722
|
+
session = await storageApi.startDriveUpload(
|
|
723
|
+
runtime.fetch,
|
|
724
|
+
String(drive.id),
|
|
725
|
+
{ content_type: contentType, path: remotePath, size_bytes: fileInfo.size, sha256 },
|
|
726
|
+
{ headers: idempotencyHeaders(input) }
|
|
772
727
|
);
|
|
773
728
|
} catch (error) {
|
|
774
729
|
throw safeStorageError(error, "The upload could not be staged safely.");
|
|
@@ -803,24 +758,25 @@ async function uploadFile(input, runtime) {
|
|
|
803
758
|
sizeBytes: fileInfo.size,
|
|
804
759
|
uploadUrl: session.upload_url,
|
|
805
760
|
});
|
|
806
|
-
const completionPath = computeEndpoint(
|
|
807
|
-
`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/file-uploads/${encodeURIComponent(uploadId)}/complete`
|
|
808
|
-
);
|
|
809
761
|
let completed;
|
|
810
762
|
try {
|
|
811
|
-
completed = await
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
763
|
+
completed = await storageApi.completeDriveUpload(
|
|
764
|
+
runtime.fetch,
|
|
765
|
+
String(drive.id),
|
|
766
|
+
uploadId,
|
|
767
|
+
{ uploaded_generation: uploaded.generation },
|
|
768
|
+
{ headers: idempotencyHeaders(input) }
|
|
769
|
+
);
|
|
816
770
|
} catch (error) {
|
|
817
771
|
if (!(error instanceof CliApiError) || (error.status !== undefined && error.status < 500))
|
|
818
772
|
throw error;
|
|
819
|
-
completed = await
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
773
|
+
completed = await storageApi.completeDriveUpload(
|
|
774
|
+
runtime.fetch,
|
|
775
|
+
String(drive.id),
|
|
776
|
+
uploadId,
|
|
777
|
+
{ uploaded_generation: uploaded.generation },
|
|
778
|
+
{ headers: idempotencyHeaders(input) }
|
|
779
|
+
);
|
|
824
780
|
}
|
|
825
781
|
const file = safeFile(completed?.file ?? { path: remotePath, size_bytes: fileInfo.size });
|
|
826
782
|
return runtime.interaction.result({
|
|
@@ -888,16 +844,19 @@ async function downloadFile(input, runtime) {
|
|
|
888
844
|
const drive = await resolveVolume(runtime, reference);
|
|
889
845
|
const session = await loadRequiredSession({ env: runtime.env });
|
|
890
846
|
const baseUrl = resolveApiBaseUrl({ env: runtime.env, session });
|
|
891
|
-
const endpoint =
|
|
892
|
-
`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/files/${encodedRemotePath(remotePath)}`
|
|
893
|
-
);
|
|
847
|
+
const endpoint = storageApi.driveFilePath(String(drive.id), remotePath);
|
|
894
848
|
const url = new URL(endpoint, `${baseUrl}/`);
|
|
895
849
|
let response;
|
|
896
850
|
try {
|
|
897
|
-
response = await
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
851
|
+
response = await storageApi.downloadDriveFile(
|
|
852
|
+
(path, init) =>
|
|
853
|
+
runtime.fetchImpl(new URL(path, `${baseUrl}/`), {
|
|
854
|
+
...init,
|
|
855
|
+
headers: userCredentialHeaders(session),
|
|
856
|
+
}),
|
|
857
|
+
String(drive.id),
|
|
858
|
+
remotePath
|
|
859
|
+
);
|
|
901
860
|
} catch {
|
|
902
861
|
throw new StorageCommandError("The volume file could not be downloaded safely.", {
|
|
903
862
|
code: "service_unavailable",
|
|
@@ -950,11 +909,11 @@ async function deleteContent(command, input, runtime) {
|
|
|
950
909
|
);
|
|
951
910
|
const remotePath = normalizeRemotePath(rawRemotePath);
|
|
952
911
|
const drive = await resolveVolume(runtime, reference);
|
|
953
|
-
|
|
954
|
-
`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/${kind}s/${encodedRemotePath(remotePath)}`
|
|
955
|
-
);
|
|
912
|
+
|
|
956
913
|
try {
|
|
957
|
-
const payload = await
|
|
914
|
+
const payload = await (
|
|
915
|
+
kind === "file" ? storageApi.deleteDriveFile : storageApi.deleteDriveFolder
|
|
916
|
+
)(runtime.fetch, String(drive.id), remotePath);
|
|
958
917
|
return runtime.interaction.result({
|
|
959
918
|
data: {
|
|
960
919
|
deleted: true,
|