@ornncompute/cli 1.0.72 → 1.0.97
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 +55 -24
- package/package.json +4 -2
- package/src/admin-capacity-reservations.mjs +6 -13
- package/src/admin-command-handlers.mjs +2 -1
- package/src/admin-credits.mjs +1 -2
- package/src/admin-enrollment.mjs +22 -30
- package/src/admin-mutations.mjs +6 -56
- package/src/admin-node-diagnostics.mjs +3 -41
- package/src/admin-node-lifecycle.mjs +40 -160
- package/src/admin-node-sanitize.mjs +243 -0
- package/src/admin-shell.mjs +39 -32
- package/src/catalog-shell.mjs +6 -6
- package/src/evidence-commands.mjs +4 -6
- package/src/external-bucket-commands.mjs +76 -96
- package/src/interaction-runtime.mjs +5 -37
- package/src/kubernetes-commands.mjs +25 -183
- package/src/launch-mode-handlers.mjs +0 -1
- package/src/machine-reference.mjs +33 -56
- package/src/mount-commands.mjs +25 -131
- package/src/slurm-commands.mjs +13 -2
- package/src/storage-commands.mjs +2 -87
- package/src/vm-commands.mjs +48 -101
- package/vendor/api-client/commerce.js +743 -0
- package/vendor/capabilities/cli.js +7688 -7
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
- package/vendor/capabilities/capabilities/admin-enrollment.js +0 -321
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -235
- package/vendor/capabilities/capabilities/admin.js +0 -91
- package/vendor/capabilities/capabilities/cli.js +0 -156
- package/vendor/capabilities/capabilities/clusters.js +0 -248
- package/vendor/capabilities/capabilities/commerce-market.js +0 -405
- package/vendor/capabilities/capabilities/compute.js +0 -523
- package/vendor/capabilities/capabilities/credits.js +0 -78
- package/vendor/capabilities/capabilities/evidence.js +0 -145
- package/vendor/capabilities/capabilities/external-buckets.js +0 -191
- package/vendor/capabilities/capabilities/fleet.js +0 -306
- package/vendor/capabilities/capabilities/identity.js +0 -129
- package/vendor/capabilities/capabilities/listings.js +0 -398
- package/vendor/capabilities/capabilities/locality.js +0 -42
- package/vendor/capabilities/capabilities/market-shared.js +0 -46
- package/vendor/capabilities/capabilities/markets.js +0 -482
- package/vendor/capabilities/capabilities/mounts.js +0 -176
- package/vendor/capabilities/capabilities/native-listing.js +0 -99
- package/vendor/capabilities/capabilities/networks.js +0 -1264
- package/vendor/capabilities/capabilities/observability.js +0 -218
- package/vendor/capabilities/capabilities/organization-invites.js +0 -53
- package/vendor/capabilities/capabilities/reservation-list.js +0 -68
- package/vendor/capabilities/capabilities/staff-shared.js +0 -64
- package/vendor/capabilities/capabilities/tokens.js +0 -165
- package/vendor/capabilities/capabilities/users.js +0 -159
- package/vendor/capabilities/capabilities/volumes.js +0 -178
- package/vendor/capabilities/capability.js +0 -99
- package/vendor/capabilities/cli-catalog.js +0 -73
- package/vendor/capabilities/cli-migrations.js +0 -264
- package/vendor/capabilities/names.js +0 -6
- package/vendor/capabilities/software-catalog.js +0 -82
- package/vendor/capabilities/spec-catalog.js +0 -151
- package/vendor/capabilities/supplier-directory.js +0 -20
|
@@ -5,6 +5,7 @@ import { CliApiError, computeEndpoint } from "./api-client.mjs";
|
|
|
5
5
|
import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
|
|
6
6
|
import {
|
|
7
7
|
codeOf,
|
|
8
|
+
detailOf,
|
|
8
9
|
fetchDrives,
|
|
9
10
|
fetchDrivePage,
|
|
10
11
|
idempotencyHeaders,
|
|
@@ -83,6 +84,26 @@ function verificationCode(source) {
|
|
|
83
84
|
return typeof code === "string" && /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(code) ? code : null;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const TRANSFER_ACCOUNT_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]@[a-z][a-z0-9-]{4,28}[a-z0-9]\.iam\.gserviceaccount\.com$/;
|
|
88
|
+
|
|
89
|
+
function safeTransferAccount(value) {
|
|
90
|
+
return typeof value === "string" && TRANSFER_ACCOUNT_PATTERN.test(value) ? value : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function gcsGrantPresentation(safe) {
|
|
94
|
+
const account = safe.access.transfer_account;
|
|
95
|
+
if (safe.kind !== "gcs" || !account) return null;
|
|
96
|
+
const grant = (role) =>
|
|
97
|
+
` gcloud storage buckets add-iam-policy-binding gs://${safe.location.bucket} --member=serviceAccount:${account} --role=${role}`;
|
|
98
|
+
return [
|
|
99
|
+
`Your organization's Ornn transfer account: ${account}`,
|
|
100
|
+
"Grant it read access to the bucket:",
|
|
101
|
+
grant("roles/storage.objectViewer"),
|
|
102
|
+
grant("roles/storage.legacyBucketReader"),
|
|
103
|
+
"Then deploy the volume to a reservation from the Ornn console.",
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
function safeExternalBucket(drive, { forceUnverified = false } = {}) {
|
|
87
108
|
const source = drive?.source ?? {};
|
|
88
109
|
const kind = externalKind(drive);
|
|
@@ -110,6 +131,9 @@ function safeExternalBucket(drive, { forceUnverified = false } = {}) {
|
|
|
110
131
|
access: {
|
|
111
132
|
mode: source.read_only === false ? "read-write" : "read-only",
|
|
112
133
|
credentials_configured: kind === "gcs" ? false : source.credentials_configured === true,
|
|
134
|
+
...(kind === "gcs"
|
|
135
|
+
? { transfer_account: safeTransferAccount(source.transfer_service_account) }
|
|
136
|
+
: {}),
|
|
113
137
|
},
|
|
114
138
|
verification: {
|
|
115
139
|
phase,
|
|
@@ -402,7 +426,7 @@ function connectionEquivalent(drive, intent) {
|
|
|
402
426
|
);
|
|
403
427
|
}
|
|
404
428
|
|
|
405
|
-
function requireMutationPreflight(input, runtime
|
|
429
|
+
function requireMutationPreflight(input, runtime) {
|
|
406
430
|
const noninteractive = runtime.interaction.machine || runtime.interaction.redirected;
|
|
407
431
|
if (noninteractive && !String(input.idempotencyKey ?? "").trim()) {
|
|
408
432
|
usage(
|
|
@@ -410,23 +434,6 @@ function requireMutationPreflight(input, runtime, { consent = false } = {}) {
|
|
|
410
434
|
"idempotency-key"
|
|
411
435
|
);
|
|
412
436
|
}
|
|
413
|
-
if (noninteractive && consent && input.yes !== true) {
|
|
414
|
-
usage(
|
|
415
|
-
"Consent is required. Review the exact intent and supply --yes.",
|
|
416
|
-
"yes",
|
|
417
|
-
"consent_required"
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function reviewed(runtime, options) {
|
|
423
|
-
const accepted = await runtime.interaction.review(options);
|
|
424
|
-
if (accepted) return null;
|
|
425
|
-
return runtime.interaction.result({
|
|
426
|
-
data: null,
|
|
427
|
-
exitCode: 1,
|
|
428
|
-
outcome: { class: "ended", phase: "declined" },
|
|
429
|
-
});
|
|
430
437
|
}
|
|
431
438
|
|
|
432
439
|
const SAFE_EXTERNAL_ERRORS = {
|
|
@@ -465,13 +472,18 @@ function safeExternalError(error, fallback, { action, forcePartial = false } = {
|
|
|
465
472
|
}
|
|
466
473
|
const code = codeOf(error);
|
|
467
474
|
const unavailable = error?.status === undefined || error?.status >= 500;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
code
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
+
const ownerMessage = detailOf(error)?.message;
|
|
476
|
+
return new StorageCommandError(
|
|
477
|
+
SAFE_EXTERNAL_ERRORS[code] ??
|
|
478
|
+
(typeof ownerMessage === "string" && ownerMessage.trim() ? ownerMessage : fallback),
|
|
479
|
+
{
|
|
480
|
+
action,
|
|
481
|
+
code: code ?? (unavailable ? "service_unavailable" : "external_bucket_operation_failed"),
|
|
482
|
+
exitCode: forcePartial ? 4 : unavailable ? 6 : 1,
|
|
483
|
+
outcomeClass: forcePartial ? "partial" : unavailable ? "failed" : "failed",
|
|
484
|
+
retryable: forcePartial || unavailable,
|
|
485
|
+
}
|
|
486
|
+
);
|
|
475
487
|
}
|
|
476
488
|
|
|
477
489
|
async function listBuckets(input, runtime) {
|
|
@@ -493,28 +505,11 @@ async function showBucket(input, runtime) {
|
|
|
493
505
|
|
|
494
506
|
async function connectBucket(input, runtime) {
|
|
495
507
|
const intent = await connectionIntent(input);
|
|
496
|
-
requireMutationPreflight(input, runtime
|
|
508
|
+
requireMutationPreflight(input, runtime);
|
|
497
509
|
const fingerprint =
|
|
498
510
|
intent.accessKeyId && intent.secretAccessKey
|
|
499
511
|
? credentialFingerprint(intent.accessKeyId, intent.secretAccessKey)
|
|
500
512
|
: null;
|
|
501
|
-
const declined = await reviewed(runtime, {
|
|
502
|
-
title: "Connect external bucket",
|
|
503
|
-
terms: [
|
|
504
|
-
{ label: "Kind", value: providerName(intent.kind) },
|
|
505
|
-
{ label: "Name", value: intent.name },
|
|
506
|
-
{ label: "Bucket", value: intent.bucket },
|
|
507
|
-
{ label: "Prefix", value: intent.prefix ?? "(bucket root)" },
|
|
508
|
-
{ label: "Access", value: intent.readOnly ? "read-only" : "read-write" },
|
|
509
|
-
{
|
|
510
|
-
label: "Credentials",
|
|
511
|
-
value: fingerprint ? `fingerprint ${fingerprint.slice(0, 12)}` : "ambient GCS access",
|
|
512
|
-
},
|
|
513
|
-
{ label: "Verification", value: "separate command required" },
|
|
514
|
-
],
|
|
515
|
-
prompt: "Connect this external bucket?",
|
|
516
|
-
});
|
|
517
|
-
if (declined) return declined;
|
|
518
513
|
|
|
519
514
|
const rows = await fetchDrives(runtime);
|
|
520
515
|
const sameName = bucketRows(rows).filter(
|
|
@@ -544,13 +539,20 @@ async function connectBucket(input, runtime) {
|
|
|
544
539
|
class: safe.verification.phase === "failed" ? "failed" : "ready",
|
|
545
540
|
phase: "replayed",
|
|
546
541
|
},
|
|
547
|
-
presentation:
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
542
|
+
presentation: gcsGrantPresentation(safe)
|
|
543
|
+
? {
|
|
544
|
+
lines: [
|
|
545
|
+
`External bucket connection replayed: ${safe.name} (${safe.id})`,
|
|
546
|
+
...gcsGrantPresentation(safe),
|
|
547
|
+
],
|
|
548
|
+
}
|
|
549
|
+
: {
|
|
550
|
+
lines: [
|
|
551
|
+
`External bucket connection replayed: ${safe.name} (${safe.id})`,
|
|
552
|
+
"Connection and verification are separate operations.",
|
|
553
|
+
],
|
|
554
|
+
next: `ornn external-buckets verify ${safe.id}`,
|
|
555
|
+
},
|
|
554
556
|
});
|
|
555
557
|
}
|
|
556
558
|
|
|
@@ -581,13 +583,20 @@ async function connectBucket(input, runtime) {
|
|
|
581
583
|
return runtime.interaction.result({
|
|
582
584
|
data: { external_bucket: safe, replayed: false, verification_required: true },
|
|
583
585
|
outcome: { class: "ready", phase: "connected" },
|
|
584
|
-
presentation:
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
586
|
+
presentation: gcsGrantPresentation(safe)
|
|
587
|
+
? {
|
|
588
|
+
lines: [
|
|
589
|
+
`External bucket connected: ${safe.name} (${safe.id})`,
|
|
590
|
+
...gcsGrantPresentation(safe),
|
|
591
|
+
],
|
|
592
|
+
}
|
|
593
|
+
: {
|
|
594
|
+
lines: [
|
|
595
|
+
`External bucket connected: ${safe.name} (${safe.id})`,
|
|
596
|
+
"Connection recorded; access has not been verified.",
|
|
597
|
+
],
|
|
598
|
+
next: `ornn external-buckets verify ${safe.id}`,
|
|
599
|
+
},
|
|
591
600
|
});
|
|
592
601
|
}
|
|
593
602
|
|
|
@@ -644,7 +653,7 @@ async function updateCredentials(input, runtime) {
|
|
|
644
653
|
);
|
|
645
654
|
const accessKeyId = requiredText(input.accessKeyId, "--access-key-id", "access-key-id");
|
|
646
655
|
const secret = await readCredentialFile(input.secretAccessKeyFile);
|
|
647
|
-
requireMutationPreflight(input, runtime
|
|
656
|
+
requireMutationPreflight(input, runtime);
|
|
648
657
|
const fingerprint = credentialFingerprint(accessKeyId, secret);
|
|
649
658
|
const source = resolveExternalBucket(await fetchDrives(runtime), reference);
|
|
650
659
|
const kind = externalKind(source);
|
|
@@ -655,17 +664,6 @@ async function updateCredentials(input, runtime) {
|
|
|
655
664
|
);
|
|
656
665
|
}
|
|
657
666
|
const id = safeIdentifier(source.id);
|
|
658
|
-
const declined = await reviewed(runtime, {
|
|
659
|
-
title: "Update external bucket credentials",
|
|
660
|
-
terms: [
|
|
661
|
-
{ label: "External bucket", value: `${safeName(source.name)} (${id})` },
|
|
662
|
-
{ label: "Kind", value: providerName(kind) },
|
|
663
|
-
{ label: "Credential fingerprint", value: fingerprint.slice(0, 12) },
|
|
664
|
-
{ label: "Verification", value: "must be run again" },
|
|
665
|
-
],
|
|
666
|
-
prompt: "Replace these credentials?",
|
|
667
|
-
});
|
|
668
|
-
if (declined) return declined;
|
|
669
667
|
let updated;
|
|
670
668
|
try {
|
|
671
669
|
updated = await runtime.fetch(
|
|
@@ -698,20 +696,9 @@ async function updateCredentials(input, runtime) {
|
|
|
698
696
|
|
|
699
697
|
async function disconnectBucket(input, runtime) {
|
|
700
698
|
const [reference] = positions(input, 1, "ornn external-buckets disconnect <external-bucket>");
|
|
701
|
-
requireMutationPreflight(input, runtime
|
|
699
|
+
requireMutationPreflight(input, runtime);
|
|
702
700
|
const source = resolveExternalBucket(await fetchDrives(runtime), reference);
|
|
703
701
|
const safe = safeExternalBucket(source);
|
|
704
|
-
const declined = await reviewed(runtime, {
|
|
705
|
-
title: "Disconnect external bucket",
|
|
706
|
-
terms: [
|
|
707
|
-
{ label: "External bucket", value: `${safe.name} (${safe.id})` },
|
|
708
|
-
{ label: "Kind", value: providerName(safe.kind) },
|
|
709
|
-
{ label: "Source writeback", value: "disabled" },
|
|
710
|
-
{ label: "Consequence", value: "source access is removed after all mounts are detached" },
|
|
711
|
-
],
|
|
712
|
-
prompt: "Disconnect this external bucket?",
|
|
713
|
-
});
|
|
714
|
-
if (declined) return declined;
|
|
715
702
|
const attachedReservations = activeMountReservations(source);
|
|
716
703
|
if (attachedReservations.length > 0) {
|
|
717
704
|
throw new StorageCommandError(
|
|
@@ -1159,7 +1146,7 @@ async function importBucket(input, runtime) {
|
|
|
1159
1146
|
usage("Noninteractive --wait requires --timeout <duration>.", "timeout");
|
|
1160
1147
|
}
|
|
1161
1148
|
const timeoutMs = parseTimeout(input.timeout);
|
|
1162
|
-
requireMutationPreflight(input, runtime
|
|
1149
|
+
requireMutationPreflight(input, runtime);
|
|
1163
1150
|
|
|
1164
1151
|
const source = resolveExternalBucket(await fetchDrives(runtime), reference);
|
|
1165
1152
|
const bucket = safeExternalBucket(source);
|
|
@@ -1229,18 +1216,6 @@ async function importBucket(input, runtime) {
|
|
|
1229
1216
|
: null;
|
|
1230
1217
|
const replayed = attachment !== null;
|
|
1231
1218
|
if (!attachment) {
|
|
1232
|
-
const declined = await reviewed(runtime, {
|
|
1233
|
-
title: "Import external bucket",
|
|
1234
|
-
terms: [
|
|
1235
|
-
{ label: "Source", value: `${providerName(bucket.kind)} ${bucket.location.bucket}` },
|
|
1236
|
-
{ label: "Working volume", value: `${bucket.name} (${bucket.id})` },
|
|
1237
|
-
{ label: "Reservation", value: reservationId },
|
|
1238
|
-
{ label: "Isolation", value: "one Ornn-managed working copy" },
|
|
1239
|
-
{ label: "Source writeback", value: "disabled" },
|
|
1240
|
-
],
|
|
1241
|
-
prompt: "Create this managed working copy?",
|
|
1242
|
-
});
|
|
1243
|
-
if (declined) return declined;
|
|
1244
1219
|
try {
|
|
1245
1220
|
const payload = await runtime.fetch(
|
|
1246
1221
|
computeEndpoint(
|
|
@@ -1350,7 +1325,9 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
|
|
|
1350
1325
|
}
|
|
1351
1326
|
for (const bucket of rows) {
|
|
1352
1327
|
stdout.write(
|
|
1353
|
-
|
|
1328
|
+
bucket.kind === "gcs"
|
|
1329
|
+
? `${bucket.name} ${bucket.kind} ${bucket.access.mode} ${bucket.id}\n`
|
|
1330
|
+
: `${bucket.name} ${bucket.kind} ${bucket.verification.phase} ${bucket.access.mode} ${bucket.id}\n`
|
|
1354
1331
|
);
|
|
1355
1332
|
}
|
|
1356
1333
|
if (execution.page?.next_cursor) {
|
|
@@ -1368,7 +1345,10 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
|
|
|
1368
1345
|
stdout.write(`Bucket: ${bucket.location.bucket}\n`);
|
|
1369
1346
|
if (bucket.location.prefix) stdout.write(`Prefix: ${bucket.location.prefix}\n`);
|
|
1370
1347
|
stdout.write(`Access: ${bucket.access.mode}\n`);
|
|
1371
|
-
|
|
1348
|
+
if (bucket.access.transfer_account) {
|
|
1349
|
+
stdout.write(`Transfer account: ${bucket.access.transfer_account}\n`);
|
|
1350
|
+
}
|
|
1351
|
+
if (bucket.kind !== "gcs") stdout.write(`Verification: ${bucket.verification.phase}\n`);
|
|
1372
1352
|
}
|
|
1373
1353
|
for (const error of execution.errors ?? []) stderr.write(`${error.message}\n`);
|
|
1374
1354
|
}
|
|
@@ -4,7 +4,9 @@ import { createInterface } from "node:readline/promises";
|
|
|
4
4
|
import { resolveAuthBaseUrl } from "./device-auth.mjs";
|
|
5
5
|
|
|
6
6
|
const BOOLEAN_OPTIONS = new Set([
|
|
7
|
+
"acknowledge-retained-os",
|
|
7
8
|
"all-nodes",
|
|
9
|
+
"data-drives-sanitized",
|
|
8
10
|
"erase-local-data",
|
|
9
11
|
"force",
|
|
10
12
|
"follow",
|
|
@@ -17,8 +19,8 @@ const BOOLEAN_OPTIONS = new Set([
|
|
|
17
19
|
"read-only",
|
|
18
20
|
"read-write",
|
|
19
21
|
"recursive",
|
|
22
|
+
"tenant-access-revoked",
|
|
20
23
|
"wait",
|
|
21
|
-
"yes",
|
|
22
24
|
]);
|
|
23
25
|
|
|
24
26
|
const OPTIONAL_VALUE_OPTIONS = new Set(["resume"]);
|
|
@@ -230,42 +232,8 @@ export function createInteractionRuntime({
|
|
|
230
232
|
return answer;
|
|
231
233
|
}
|
|
232
234
|
|
|
233
|
-
async function review(
|
|
234
|
-
|
|
235
|
-
if (!requireLive && options.yes === true) return true;
|
|
236
|
-
usage(
|
|
237
|
-
requireLive
|
|
238
|
-
? "This operation is supported only in an eligible interactive terminal."
|
|
239
|
-
: "Consent is required. Review the terms and supply --yes to continue.",
|
|
240
|
-
{
|
|
241
|
-
code: requireLive ? "unsupported_in_machine_mode" : "consent_required",
|
|
242
|
-
...(!requireLive ? { field: "yes" } : {}),
|
|
243
|
-
}
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
if (requireLive && !eligibleHuman) {
|
|
247
|
-
usage("This operation is supported only in an eligible interactive terminal.", {
|
|
248
|
-
code: "unsupported_in_machine_mode",
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
stderr.write(`${title}\n`);
|
|
252
|
-
for (const term of terms) stderr.write(` ${term.label}: ${term.value}\n`);
|
|
253
|
-
|
|
254
|
-
if (!requireLive && options.yes === true) return true;
|
|
255
|
-
if (!eligibleHuman) {
|
|
256
|
-
usage("Consent is required. Review the terms and supply --yes to continue.", {
|
|
257
|
-
code: "consent_required",
|
|
258
|
-
field: "yes",
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
let answer;
|
|
262
|
-
try {
|
|
263
|
-
answer = await readline().question(`${prompt} [y/N] `);
|
|
264
|
-
} catch (caught) {
|
|
265
|
-
if (caught?.code === "ERR_USE_AFTER_CLOSE") return false;
|
|
266
|
-
throw caught;
|
|
267
|
-
}
|
|
268
|
-
return /^(y|yes)$/i.test(String(answer ?? "").trim());
|
|
235
|
+
async function review() {
|
|
236
|
+
return true;
|
|
269
237
|
}
|
|
270
238
|
|
|
271
239
|
function webUrl(path) {
|