@ornncompute/cli 1.0.72 → 1.0.75
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 +24 -20
- package/package.json +1 -1
- package/src/admin-capacity-reservations.mjs +2 -12
- package/src/admin-command-handlers.mjs +0 -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 +2 -36
- package/src/admin-node-lifecycle.mjs +25 -43
- package/src/admin-shell.mjs +39 -32
- package/src/external-bucket-commands.mjs +5 -73
- package/src/interaction-runtime.mjs +2 -37
- package/src/kubernetes-commands.mjs +9 -100
- package/src/launch-mode-handlers.mjs +0 -1
- package/src/mount-commands.mjs +25 -131
- package/src/storage-commands.mjs +2 -87
- package/src/vm-commands.mjs +9 -42
- package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
- package/vendor/capabilities/capabilities/admin.js +3 -3
- package/vendor/capabilities/capabilities/clusters.js +3 -3
- package/vendor/capabilities/capabilities/commerce-market.js +9 -9
- package/vendor/capabilities/capabilities/compute.js +7 -7
- package/vendor/capabilities/capabilities/credits.js +7 -2
- package/vendor/capabilities/capabilities/external-buckets.js +4 -4
- package/vendor/capabilities/capabilities/fleet.js +109 -29
- package/vendor/capabilities/capabilities/market-shared.js +1 -1
- package/vendor/capabilities/capabilities/markets.js +12 -12
- package/vendor/capabilities/capabilities/mounts.js +4 -4
- package/vendor/capabilities/capabilities/networks.js +5 -5
- package/vendor/capabilities/capabilities/observability.js +49 -14
- package/vendor/capabilities/capabilities/tokens.js +5 -9
- package/vendor/capabilities/capabilities/users.js +8 -10
- package/vendor/capabilities/capabilities/volumes.js +5 -5
package/src/mount-commands.mjs
CHANGED
|
@@ -640,7 +640,7 @@ async function show(input, runtime, command) {
|
|
|
640
640
|
return mountResult(runtime.interaction, await fetchMount(runtime, reservationId), reservationId);
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
-
export async function attachVolumeMount(input, runtime
|
|
643
|
+
export async function attachVolumeMount(input, runtime) {
|
|
644
644
|
const [rawReservation] = positionals(
|
|
645
645
|
input,
|
|
646
646
|
1,
|
|
@@ -684,50 +684,23 @@ export async function attachVolumeMount(input, runtime, { skipReview = false } =
|
|
|
684
684
|
}
|
|
685
685
|
);
|
|
686
686
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
687
|
+
const refreshedVolume = safeVolume(await resolveVolume(runtime, projectedVolume.id));
|
|
688
|
+
if (refreshedVolume.id !== projectedVolume.id || refreshedVolume.name !== projectedVolume.name) {
|
|
689
|
+
runtime.interaction.error("The selected managed volume changed before attachment.", {
|
|
690
|
+
code: "volume_selection_changed",
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
current = await fetchMount(runtime, reservationId);
|
|
694
|
+
if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
|
|
695
|
+
if (mountSatisfied(current, goal)) {
|
|
696
|
+
return wait && !mountReady(current)
|
|
697
|
+
? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
|
|
698
|
+
: mountResult(runtime.interaction, current, reservationId, { replayed: true });
|
|
699
|
+
}
|
|
700
|
+
if (current) {
|
|
701
|
+
runtime.interaction.error("The volume-mount relationship changed before attachment.", {
|
|
702
|
+
code: "volume_mount_conflict",
|
|
701
703
|
});
|
|
702
|
-
if (!accepted) {
|
|
703
|
-
return runtime.interaction.result({
|
|
704
|
-
errors: [{ code: "declined", message: "Volume attachment declined.", retryable: false }],
|
|
705
|
-
exitCode: 1,
|
|
706
|
-
outcome: { class: "ended", phase: "declined" },
|
|
707
|
-
presentation: { lines: ["Volume attachment declined. No relationship was created."] },
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
const refreshedVolume = safeVolume(await resolveVolume(runtime, projectedVolume.id));
|
|
711
|
-
if (
|
|
712
|
-
refreshedVolume.id !== projectedVolume.id ||
|
|
713
|
-
refreshedVolume.name !== projectedVolume.name
|
|
714
|
-
) {
|
|
715
|
-
runtime.interaction.error("The selected managed volume changed after review.", {
|
|
716
|
-
code: "volume_selection_changed",
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
current = await fetchMount(runtime, reservationId);
|
|
720
|
-
if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
|
|
721
|
-
if (mountSatisfied(current, goal)) {
|
|
722
|
-
return wait && !mountReady(current)
|
|
723
|
-
? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
|
|
724
|
-
: mountResult(runtime.interaction, current, reservationId, { replayed: true });
|
|
725
|
-
}
|
|
726
|
-
if (current) {
|
|
727
|
-
runtime.interaction.error("The volume-mount relationship changed after review.", {
|
|
728
|
-
code: "volume_mount_conflict",
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
704
|
}
|
|
732
705
|
|
|
733
706
|
try {
|
|
@@ -805,7 +778,7 @@ async function waitCommand(input, runtime, command) {
|
|
|
805
778
|
);
|
|
806
779
|
}
|
|
807
780
|
|
|
808
|
-
export async function detachVolumeMount(input, runtime
|
|
781
|
+
export async function detachVolumeMount(input, runtime) {
|
|
809
782
|
const [rawReservation] = positionals(
|
|
810
783
|
input,
|
|
811
784
|
1,
|
|
@@ -831,42 +804,13 @@ export async function detachVolumeMount(input, runtime, { skipReview = false } =
|
|
|
831
804
|
},
|
|
832
805
|
});
|
|
833
806
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
{ label: "Capacity reservation", value: reservationId },
|
|
839
|
-
{
|
|
840
|
-
label: "Managed volume",
|
|
841
|
-
value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
|
|
842
|
-
},
|
|
843
|
-
{
|
|
844
|
-
label: "Mounted machines",
|
|
845
|
-
value: `${current.readiness.ready} of ${current.readiness.expected}`,
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
label: "Consequence",
|
|
849
|
-
value: "Mounted access ends; the managed volume and its data remain",
|
|
850
|
-
},
|
|
851
|
-
],
|
|
852
|
-
prompt: "Detach this volume?",
|
|
807
|
+
const refreshed = await fetchMount(runtime, reservationId);
|
|
808
|
+
if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
|
|
809
|
+
runtime.interaction.error("The volume-mount relationship changed before detachment.", {
|
|
810
|
+
code: "volume_mount_changed",
|
|
853
811
|
});
|
|
854
|
-
if (!accepted) {
|
|
855
|
-
return runtime.interaction.result({
|
|
856
|
-
errors: [{ code: "declined", message: "Volume detachment declined.", retryable: false }],
|
|
857
|
-
exitCode: 1,
|
|
858
|
-
outcome: { class: "ended", phase: "declined" },
|
|
859
|
-
presentation: { lines: ["Volume detachment declined. No relationship was changed."] },
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
const refreshed = await fetchMount(runtime, reservationId);
|
|
863
|
-
if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
|
|
864
|
-
runtime.interaction.error("The volume-mount relationship changed after review.", {
|
|
865
|
-
code: "volume_mount_changed",
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
current = refreshed;
|
|
869
812
|
}
|
|
813
|
+
current = refreshed;
|
|
870
814
|
try {
|
|
871
815
|
await runtime.fetch(reservationAttachmentPath(reservationId), {
|
|
872
816
|
method: "DELETE",
|
|
@@ -933,28 +877,10 @@ async function deployFilesystem(input, runtime) {
|
|
|
933
877
|
? waitForMount(input, runtime, reservationId, current, "filesystems deploy")
|
|
934
878
|
: mountResult(runtime.interaction, current, reservationId, { replayed: true });
|
|
935
879
|
}
|
|
936
|
-
const accepted = await runtime.interaction.review({
|
|
937
|
-
title: "Deploy managed filesystem",
|
|
938
|
-
terms: [
|
|
939
|
-
{ label: "Capacity reservation", value: reservationId },
|
|
940
|
-
{ label: "Performance tier", value: performanceTier },
|
|
941
|
-
{ label: "Capacity", value: `${capacityGib} GiB` },
|
|
942
|
-
{ label: "Readiness", value: "Reported independently for every machine" },
|
|
943
|
-
],
|
|
944
|
-
prompt: "Deploy this filesystem?",
|
|
945
|
-
});
|
|
946
|
-
if (!accepted) {
|
|
947
|
-
return runtime.interaction.result({
|
|
948
|
-
errors: [{ code: "declined", message: "Filesystem deployment declined.", retryable: false }],
|
|
949
|
-
exitCode: 1,
|
|
950
|
-
outcome: { class: "ended", phase: "declined" },
|
|
951
|
-
presentation: { lines: ["Filesystem deployment declined. No deployment was created."] },
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
880
|
current = await fetchMount(runtime, reservationId);
|
|
955
881
|
if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
|
|
956
882
|
if (current && !filesystemSatisfied(current, performanceTier, capacityGib)) {
|
|
957
|
-
runtime.interaction.error("The managed filesystem relationship changed
|
|
883
|
+
runtime.interaction.error("The managed filesystem relationship changed before deployment.", {
|
|
958
884
|
code: "filesystem_deployment_conflict",
|
|
959
885
|
});
|
|
960
886
|
}
|
|
@@ -1032,41 +958,9 @@ async function deleteFilesystem(input, runtime) {
|
|
|
1032
958
|
},
|
|
1033
959
|
});
|
|
1034
960
|
}
|
|
1035
|
-
const accepted = await runtime.interaction.review({
|
|
1036
|
-
title: "Delete managed filesystem deployment",
|
|
1037
|
-
terms: [
|
|
1038
|
-
{ label: "Capacity reservation", value: reservationId },
|
|
1039
|
-
{
|
|
1040
|
-
label: "Managed volume",
|
|
1041
|
-
value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
|
|
1042
|
-
},
|
|
1043
|
-
{
|
|
1044
|
-
label: "Mounted machines",
|
|
1045
|
-
value: `${current.readiness.ready} of ${current.readiness.expected}`,
|
|
1046
|
-
},
|
|
1047
|
-
{
|
|
1048
|
-
label: "Consequence",
|
|
1049
|
-
value: "Filesystem access ends; the managed volume and its data remain",
|
|
1050
|
-
},
|
|
1051
|
-
],
|
|
1052
|
-
prompt: "Delete this filesystem deployment?",
|
|
1053
|
-
});
|
|
1054
|
-
if (!accepted) {
|
|
1055
|
-
return runtime.interaction.result({
|
|
1056
|
-
data: {
|
|
1057
|
-
filesystem_deployment: current,
|
|
1058
|
-
reservation_id: reservationId,
|
|
1059
|
-
volume_preserved: current.volume,
|
|
1060
|
-
},
|
|
1061
|
-
errors: [{ code: "declined", message: "Filesystem deletion declined.", retryable: false }],
|
|
1062
|
-
exitCode: 1,
|
|
1063
|
-
outcome: { class: "ended", phase: "declined" },
|
|
1064
|
-
presentation: { lines: ["Filesystem deletion declined. No relationship was changed."] },
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
961
|
const refreshed = await fetchMount(runtime, reservationId);
|
|
1068
962
|
if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
|
|
1069
|
-
runtime.interaction.error("The managed filesystem relationship changed
|
|
963
|
+
runtime.interaction.error("The managed filesystem relationship changed before deletion.", {
|
|
1070
964
|
code: "filesystem_deployment_changed",
|
|
1071
965
|
});
|
|
1072
966
|
}
|
package/src/storage-commands.mjs
CHANGED
|
@@ -21,11 +21,6 @@ import {
|
|
|
21
21
|
const DEFAULT_PAGE_LIMIT = 50;
|
|
22
22
|
const MAX_PAGE_LIMIT = 500;
|
|
23
23
|
const STORAGE_PREFIX = "/nodes/storage-drives";
|
|
24
|
-
const UNSAFE_CONTENT_COMMANDS = new Set([
|
|
25
|
-
"storage.volumes.clear",
|
|
26
|
-
"storage.volumes.files.delete",
|
|
27
|
-
"storage.volumes.folders.delete",
|
|
28
|
-
]);
|
|
29
24
|
|
|
30
25
|
/**
|
|
31
26
|
* Narrow CLI adapter expected by executeStorageCommand:
|
|
@@ -381,48 +376,11 @@ export async function resolveVolume(runtime, reference) {
|
|
|
381
376
|
return resolveFromRows(await fetchDrives(runtime), reference);
|
|
382
377
|
}
|
|
383
378
|
|
|
384
|
-
async function review(runtime, options) {
|
|
385
|
-
const accepted = await runtime.interaction.review(options);
|
|
386
|
-
if (accepted !== false) return null;
|
|
387
|
-
return runtime.interaction.result({
|
|
388
|
-
data: null,
|
|
389
|
-
exitCode: 1,
|
|
390
|
-
outcome: { class: "ended", phase: "declined" },
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function unsafeReview(command, input, runtime, reference, remotePath) {
|
|
395
|
-
if (!UNSAFE_CONTENT_COMMANDS.has(command)) return null;
|
|
396
|
-
const title =
|
|
397
|
-
command === "storage.volumes.clear"
|
|
398
|
-
? "Clear volume contents"
|
|
399
|
-
: command === "storage.volumes.files.delete"
|
|
400
|
-
? "Delete volume file"
|
|
401
|
-
: "Delete volume folder";
|
|
402
|
-
const terms = [
|
|
403
|
-
{ label: "Volume", value: reference },
|
|
404
|
-
...(remotePath ? [{ label: "Remote path", value: remotePath }] : []),
|
|
405
|
-
{
|
|
406
|
-
label: "Consequence",
|
|
407
|
-
value:
|
|
408
|
-
command === "storage.volumes.clear"
|
|
409
|
-
? "Every file and folder will be permanently deleted"
|
|
410
|
-
: "The selected content will be permanently deleted",
|
|
411
|
-
},
|
|
412
|
-
];
|
|
413
|
-
return review(runtime, {
|
|
414
|
-
prompt: "Continue?",
|
|
415
|
-
requireLive: true,
|
|
416
|
-
terms,
|
|
417
|
-
title,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
379
|
async function createVolume(input, runtime) {
|
|
422
380
|
positionals(
|
|
423
381
|
input,
|
|
424
382
|
0,
|
|
425
|
-
"Usage: ornn volumes create --name <name> [--source <volume>] [--
|
|
383
|
+
"Usage: ornn volumes create --name <name> [--source <volume>] [--idempotency-key <key>]"
|
|
426
384
|
);
|
|
427
385
|
requireMachineMutationKey(input, runtime);
|
|
428
386
|
const name = normalizeVolumeName(input.name);
|
|
@@ -439,21 +397,6 @@ async function createVolume(input, runtime) {
|
|
|
439
397
|
{ code: "volume_name_ambiguous", field: "name" }
|
|
440
398
|
);
|
|
441
399
|
}
|
|
442
|
-
const declined = await review(runtime, {
|
|
443
|
-
prompt: "Create this volume?",
|
|
444
|
-
terms: [
|
|
445
|
-
{ label: "Volume name", value: name },
|
|
446
|
-
{ label: "Source", value: source ? String(source.id) : "empty volume" },
|
|
447
|
-
{
|
|
448
|
-
label: "Consequence",
|
|
449
|
-
value: source
|
|
450
|
-
? "Create a durable managed volume copied from the selected source"
|
|
451
|
-
: "Create an empty durable managed volume",
|
|
452
|
-
},
|
|
453
|
-
],
|
|
454
|
-
title: "Create volume",
|
|
455
|
-
});
|
|
456
|
-
if (declined) return declined;
|
|
457
400
|
if (matches.length === 1) {
|
|
458
401
|
const existing = matches[0];
|
|
459
402
|
const existingSource = existing?.source_drive_id ?? existing?.source?.source_drive_id ?? null;
|
|
@@ -550,8 +493,6 @@ async function showVolume(input, runtime, { refresh = false } = {}) {
|
|
|
550
493
|
|
|
551
494
|
async function clearVolume(input, runtime) {
|
|
552
495
|
const [reference] = positionals(input, 1, "Usage: ornn volumes clear <volume>");
|
|
553
|
-
const declined = await unsafeReview("storage.volumes.clear", input, runtime, reference);
|
|
554
|
-
if (declined) return declined;
|
|
555
496
|
const drive = await resolveVolume(runtime, reference);
|
|
556
497
|
try {
|
|
557
498
|
const payload = await runtime.fetch(
|
|
@@ -590,15 +531,6 @@ async function clearVolume(input, runtime) {
|
|
|
590
531
|
async function deleteVolume(input, runtime) {
|
|
591
532
|
const [reference] = positionals(input, 1, "Usage: ornn volumes delete <volume>");
|
|
592
533
|
requireMachineMutationKey(input, runtime);
|
|
593
|
-
const declined = await review(runtime, {
|
|
594
|
-
prompt: "Delete this volume?",
|
|
595
|
-
terms: [
|
|
596
|
-
{ label: "Volume", value: reference },
|
|
597
|
-
{ label: "Consequence", value: "The volume and all data in it will be permanently deleted" },
|
|
598
|
-
],
|
|
599
|
-
title: "Delete volume",
|
|
600
|
-
});
|
|
601
|
-
if (declined) return declined;
|
|
602
534
|
const drive = await resolveVolume(runtime, reference);
|
|
603
535
|
try {
|
|
604
536
|
await runtime.fetch(
|
|
@@ -802,7 +734,7 @@ async function uploadFile(input, runtime) {
|
|
|
802
734
|
const [reference, localFile] = positionals(
|
|
803
735
|
input,
|
|
804
736
|
2,
|
|
805
|
-
"Usage: ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--
|
|
737
|
+
"Usage: ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--idempotency-key <key>]"
|
|
806
738
|
);
|
|
807
739
|
requireMachineMutationKey(input, runtime);
|
|
808
740
|
let fileInfo;
|
|
@@ -825,21 +757,6 @@ async function uploadFile(input, runtime) {
|
|
|
825
757
|
? requiredText(input.contentType, "--content-type")
|
|
826
758
|
: "application/octet-stream";
|
|
827
759
|
const drive = await resolveVolume(runtime, reference);
|
|
828
|
-
const declined = await review(runtime, {
|
|
829
|
-
prompt: "Upload this file?",
|
|
830
|
-
terms: [
|
|
831
|
-
{ label: "Volume", value: `${drive.name} (${drive.id})` },
|
|
832
|
-
{ label: "Local file", value: localFile },
|
|
833
|
-
{ label: "Remote path", value: remotePath },
|
|
834
|
-
{ label: "Bytes", value: String(fileInfo.size) },
|
|
835
|
-
{
|
|
836
|
-
label: "Consequence",
|
|
837
|
-
value: "Create or replace the selected path in the managed volume",
|
|
838
|
-
},
|
|
839
|
-
],
|
|
840
|
-
title: "Upload volume file",
|
|
841
|
-
});
|
|
842
|
-
if (declined) return declined;
|
|
843
760
|
const requestHash = createHash("sha256");
|
|
844
761
|
for await (const chunk of createReadStream(localFile)) requestHash.update(chunk);
|
|
845
762
|
const sha256 = requestHash.digest("hex");
|
|
@@ -1032,8 +949,6 @@ async function deleteContent(command, input, runtime) {
|
|
|
1032
949
|
`Usage: ornn volumes ${kind === "file" ? "files" : "folders"} delete <volume> <remote-path>`
|
|
1033
950
|
);
|
|
1034
951
|
const remotePath = normalizeRemotePath(rawRemotePath);
|
|
1035
|
-
const declined = await unsafeReview(command, input, runtime, reference, remotePath);
|
|
1036
|
-
if (declined) return declined;
|
|
1037
952
|
const drive = await resolveVolume(runtime, reference);
|
|
1038
953
|
const endpoint = computeEndpoint(
|
|
1039
954
|
`${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/${kind}s/${encodedRemotePath(remotePath)}`
|
package/src/vm-commands.mjs
CHANGED
|
@@ -303,7 +303,6 @@ function failedOperationRecoveryCommand(value, interaction) {
|
|
|
303
303
|
`--mode ${accessMode}`,
|
|
304
304
|
`--network ${networkMode}`,
|
|
305
305
|
accessMode !== sourceMode ? "--erase-local-data" : null,
|
|
306
|
-
"--yes",
|
|
307
306
|
`--idempotency-key ${requestId}`,
|
|
308
307
|
]
|
|
309
308
|
.filter(Boolean)
|
|
@@ -317,7 +316,6 @@ function failedOperationRecoveryCommand(value, interaction) {
|
|
|
317
316
|
`ornn machines ${command}`,
|
|
318
317
|
shellQuote(id),
|
|
319
318
|
command === "reset" ? "--erase-local-data" : null,
|
|
320
|
-
"--yes",
|
|
321
319
|
`--idempotency-key ${requestId}`,
|
|
322
320
|
]
|
|
323
321
|
.filter(Boolean)
|
|
@@ -392,7 +390,7 @@ function positional(input, expected, usage, interaction) {
|
|
|
392
390
|
return values;
|
|
393
391
|
}
|
|
394
392
|
|
|
395
|
-
function machineMutationPreflight(input, interaction
|
|
393
|
+
function machineMutationPreflight(input, interaction) {
|
|
396
394
|
if (!interaction.machine) return;
|
|
397
395
|
if (!text(input.idempotencyKey)) {
|
|
398
396
|
interaction.usage("--idempotency-key is required for automation.", {
|
|
@@ -400,12 +398,6 @@ function machineMutationPreflight(input, interaction, { requireYes = true } = {}
|
|
|
400
398
|
field: "idempotencyKey",
|
|
401
399
|
});
|
|
402
400
|
}
|
|
403
|
-
if (requireYes && input.yes !== true) {
|
|
404
|
-
interaction.usage("--yes is required for automation after reviewing the exact intent.", {
|
|
405
|
-
code: "consent_required",
|
|
406
|
-
field: "yes",
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
401
|
}
|
|
410
402
|
|
|
411
403
|
async function selectedOrganization(context, interaction, expectedId) {
|
|
@@ -821,12 +813,6 @@ function launchInputPreflight(input, interaction) {
|
|
|
821
813
|
field: "label",
|
|
822
814
|
});
|
|
823
815
|
}
|
|
824
|
-
if (input.yes !== true) {
|
|
825
|
-
interaction.usage("--yes is required for automation after reviewing the launch intent.", {
|
|
826
|
-
code: "consent_required",
|
|
827
|
-
field: "yes",
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
816
|
if (!text(input.idempotencyKey)) {
|
|
831
817
|
interaction.usage("--idempotency-key is required for automation.", {
|
|
832
818
|
code: "idempotency_key_required",
|
|
@@ -1199,7 +1185,7 @@ export const vmLaunchModeHandler = {
|
|
|
1199
1185
|
resourceId(key),
|
|
1200
1186
|
"--idempotency-key",
|
|
1201
1187
|
idempotencyKey,
|
|
1202
|
-
...(interaction.machine ? ["--
|
|
1188
|
+
...(interaction.machine ? ["--no-wait", "--format", "json"] : []),
|
|
1203
1189
|
].join(" ");
|
|
1204
1190
|
return uncertainMutationFailure(interaction, error, {
|
|
1205
1191
|
data: {
|
|
@@ -1559,7 +1545,6 @@ function durableWorkspaceAllocation(reservation, interaction) {
|
|
|
1559
1545
|
function workspaceStepContext(context, interaction, signal) {
|
|
1560
1546
|
const stepInteraction = Object.create(interaction);
|
|
1561
1547
|
stepInteraction.review = async () => true;
|
|
1562
|
-
stepInteraction.options = { ...interaction.options, yes: true };
|
|
1563
1548
|
return {
|
|
1564
1549
|
...context,
|
|
1565
1550
|
fetch: (path, init = {}) =>
|
|
@@ -1681,7 +1666,7 @@ function workspaceResumeCommand(input, goal, facts, id, interaction, localResume
|
|
|
1681
1666
|
if (goal.username) parts.push("--username", goal.username);
|
|
1682
1667
|
if (input.noWait) parts.push("--no-wait");
|
|
1683
1668
|
if (input.wait) parts.push("--wait", "--timeout", text(input.timeout));
|
|
1684
|
-
if (interaction.machine) parts.push("--
|
|
1669
|
+
if (interaction.machine) parts.push("--format", interaction.format);
|
|
1685
1670
|
return parts.map(shellQuote).join(" ");
|
|
1686
1671
|
}
|
|
1687
1672
|
|
|
@@ -1952,7 +1937,6 @@ async function launchDurableBareMetalWorkspace({
|
|
|
1952
1937
|
$positionals: [reservationId],
|
|
1953
1938
|
key: resourceId(key),
|
|
1954
1939
|
idempotencyKey: `${id}:ssh-key`,
|
|
1955
|
-
yes: true,
|
|
1956
1940
|
}
|
|
1957
1941
|
);
|
|
1958
1942
|
states.ssh_key_assignment = {
|
|
@@ -1988,7 +1972,6 @@ async function launchDurableBareMetalWorkspace({
|
|
|
1988
1972
|
idempotencyKey: `${id}:volume-mount`,
|
|
1989
1973
|
noWait: true,
|
|
1990
1974
|
volume: facts.volume.id,
|
|
1991
|
-
yes: true,
|
|
1992
1975
|
}
|
|
1993
1976
|
);
|
|
1994
1977
|
facts.volumeMount = mount.data.volume_mount;
|
|
@@ -2464,7 +2447,7 @@ export const bareMetalLaunchModeHandler = {
|
|
|
2464
2447
|
resourceId(key),
|
|
2465
2448
|
"--idempotency-key",
|
|
2466
2449
|
idempotencyKey,
|
|
2467
|
-
...(interaction.machine ? ["--
|
|
2450
|
+
...(interaction.machine ? ["--no-wait", "--format", "json"] : []),
|
|
2468
2451
|
].join(" ");
|
|
2469
2452
|
return uncertainMutationFailure(interaction, error, {
|
|
2470
2453
|
data: {
|
|
@@ -3002,24 +2985,12 @@ function validateWaitInput(input, interaction, { waitCommand = false } = {}) {
|
|
|
3002
2985
|
function requireMachineMutationSafety(input, interaction, { destructive = false } = {}) {
|
|
3003
2986
|
validateWaitInput(input, interaction);
|
|
3004
2987
|
if (destructive && input.eraseLocalData !== true) {
|
|
3005
|
-
interaction.usage(
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
field: "eraseLocalData",
|
|
3010
|
-
}
|
|
3011
|
-
);
|
|
2988
|
+
interaction.usage("machines reset requires --erase-local-data.", {
|
|
2989
|
+
code: "erase_local_data_required",
|
|
2990
|
+
field: "eraseLocalData",
|
|
2991
|
+
});
|
|
3012
2992
|
}
|
|
3013
2993
|
if (interaction.machine || !interaction.eligibleHuman) {
|
|
3014
|
-
if (input.yes !== true) {
|
|
3015
|
-
interaction.usage(
|
|
3016
|
-
"--yes is required outside an eligible terminal after reviewing the exact intent.",
|
|
3017
|
-
{
|
|
3018
|
-
code: "consent_required",
|
|
3019
|
-
field: "yes",
|
|
3020
|
-
}
|
|
3021
|
-
);
|
|
3022
|
-
}
|
|
3023
2994
|
if (!text(input.idempotencyKey)) {
|
|
3024
2995
|
interaction.usage("--idempotency-key is required outside an eligible terminal.", {
|
|
3025
2996
|
code: "idempotency_key_required",
|
|
@@ -3452,7 +3423,7 @@ async function switchMachine(input, context, interaction) {
|
|
|
3452
3423
|
const erasesLocalData = targetMode !== currentMode;
|
|
3453
3424
|
if (erasesLocalData && input.eraseLocalData !== true) {
|
|
3454
3425
|
interaction.usage(
|
|
3455
|
-
"Changing between VM and Bare Metal permanently erases source-local data and requires --erase-local-data;
|
|
3426
|
+
"Changing between VM and Bare Metal permanently erases source-local data and requires --erase-local-data; cannot infer destructive scope.",
|
|
3456
3427
|
{ code: "erase_local_data_required", field: "eraseLocalData" }
|
|
3457
3428
|
);
|
|
3458
3429
|
}
|
|
@@ -3561,7 +3532,6 @@ async function switchMachine(input, context, interaction) {
|
|
|
3561
3532
|
requestedMode ? `--mode ${requestedMode}` : null,
|
|
3562
3533
|
requestedNetwork ? `--network ${requestedNetwork}` : null,
|
|
3563
3534
|
erasesLocalData ? "--erase-local-data" : null,
|
|
3564
|
-
"--yes",
|
|
3565
3535
|
"--no-wait",
|
|
3566
3536
|
`--idempotency-key ${shellQuote(idempotencyKey)}`,
|
|
3567
3537
|
]
|
|
@@ -3663,7 +3633,6 @@ async function switchMachine(input, context, interaction) {
|
|
|
3663
3633
|
`--mode ${targetMode}`,
|
|
3664
3634
|
`--network ${targetNetwork}`,
|
|
3665
3635
|
erasesLocalData ? "--erase-local-data" : null,
|
|
3666
|
-
"--yes",
|
|
3667
3636
|
`--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
|
|
3668
3637
|
]
|
|
3669
3638
|
.filter(Boolean)
|
|
@@ -3781,7 +3750,6 @@ async function maintainMachine(command, input, context, interaction) {
|
|
|
3781
3750
|
`ornn machines ${command}`,
|
|
3782
3751
|
shellQuote(machineId(machine)),
|
|
3783
3752
|
destructive ? "--erase-local-data" : null,
|
|
3784
|
-
"--yes",
|
|
3785
3753
|
"--no-wait",
|
|
3786
3754
|
`--idempotency-key ${shellQuote(idempotencyKey)}`,
|
|
3787
3755
|
]
|
|
@@ -3874,7 +3842,6 @@ async function maintainMachine(command, input, context, interaction) {
|
|
|
3874
3842
|
`ornn machines ${command}`,
|
|
3875
3843
|
shellQuote(machineId(waitOutcome.value)),
|
|
3876
3844
|
destructive ? "--erase-local-data" : null,
|
|
3877
|
-
"--yes",
|
|
3878
3845
|
`--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
|
|
3879
3846
|
]
|
|
3880
3847
|
.filter(Boolean)
|
|
@@ -44,11 +44,13 @@ export const adminEnrollmentTokensListCapability = {
|
|
|
44
44
|
};
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
|
-
export const adminEnrollmentTokenCreateCapability = withVnextBinding(tokensCreateCapability, adminHelp(["admin", "enrollment-tokens", "create"], "Create one expiring enrollment token through an explicit secret export.", "ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] [
|
|
47
|
+
export const adminEnrollmentTokenCreateCapability = withVnextBinding(tokensCreateCapability, adminHelp(["admin", "enrollment-tokens", "create"], "Create one expiring enrollment token through an explicit secret export.", "ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] []", "Unsupported: token creation requires an eligible human terminal.", ["canonical supplier organization UUID"], "Review the supplier, expiry, and secret destination before minting the token."));
|
|
48
48
|
const tokenRevokeInput = z.object({
|
|
49
|
-
|
|
49
|
+
tokenId: z.string().min(1),
|
|
50
|
+
token_id: UUID.optional(),
|
|
50
51
|
expected_created_at: nonEmpty.optional(),
|
|
51
52
|
idempotency_key: nonEmpty.optional(),
|
|
53
|
+
confirm: z.boolean().optional(),
|
|
52
54
|
});
|
|
53
55
|
function rowsFromTokenList(payload) {
|
|
54
56
|
if (Array.isArray(payload))
|
|
@@ -104,22 +106,23 @@ async function readExactToken(ctx, tokenId, expectedCreatedAt) {
|
|
|
104
106
|
return { disposition: "apply", target };
|
|
105
107
|
}
|
|
106
108
|
export const adminEnrollmentTokenRevokeCapability = defineCapability({
|
|
107
|
-
id: "
|
|
109
|
+
id: "enrollment.token.revoke",
|
|
108
110
|
domain: "fleet",
|
|
109
111
|
roles: ["admin"],
|
|
110
|
-
description: "
|
|
112
|
+
description: "Internal staff only: revoke one exact enrollment-token generation after reconciling its durable goal.",
|
|
111
113
|
mutation: "preview-confirm",
|
|
112
|
-
http: { method: "DELETE", path: "/enrollment-tokens/{
|
|
114
|
+
http: { method: "DELETE", path: "/enrollment-tokens/{tokenId}" },
|
|
113
115
|
cli: {
|
|
114
116
|
command: ["admin", "enrollment-tokens", "revoke"],
|
|
115
|
-
vnext: adminHelp(["admin", "enrollment-tokens", "revoke"], "Revoke one exact enrollment-token generation.", "ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] [
|
|
117
|
+
vnext: adminHelp(["admin", "enrollment-tokens", "revoke"], "Revoke one exact enrollment-token generation.", "ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] []", "ornn admin enrollment-tokens revoke <token-id> --expected-created-at TIME --idempotency-key KEY --format json", ["canonical token UUID", "token created_at generation"], "Review the exact token generation and the loss of future enrollment access."),
|
|
116
118
|
},
|
|
117
|
-
mcp: false,
|
|
118
119
|
slack: false,
|
|
120
|
+
mcp: { name: "enrollment_token_revoke" },
|
|
119
121
|
input: tokenRevokeInput,
|
|
120
122
|
execute: async (ctx, input) => {
|
|
121
123
|
const parsed = tokenRevokeInput.parse(input);
|
|
122
|
-
const
|
|
124
|
+
const tokenId = parsed.tokenId ?? parsed.token_id;
|
|
125
|
+
const current = await readExactToken(ctx, tokenId, parsed.expected_created_at);
|
|
123
126
|
if (!ctx.confirmed)
|
|
124
127
|
return current;
|
|
125
128
|
if (current.disposition === "conflict") {
|
|
@@ -127,23 +130,21 @@ export const adminEnrollmentTokenRevokeCapability = defineCapability({
|
|
|
127
130
|
}
|
|
128
131
|
if (current.disposition === "satisfied") {
|
|
129
132
|
return {
|
|
130
|
-
id:
|
|
133
|
+
id: tokenId,
|
|
131
134
|
created_at: parsed.expected_created_at ?? current.target?.created_at ?? null,
|
|
132
135
|
status: "revoked",
|
|
133
136
|
reconciled: true,
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
if (!current.target.created_at) {
|
|
137
|
-
throw codedError("token_identity_unavailable", `Enrollment token ${
|
|
140
|
+
throw codedError("token_identity_unavailable", `Enrollment token ${tokenId} has no stable creation identity and cannot be revoked safely.`);
|
|
138
141
|
}
|
|
139
|
-
const revoked = (await ctx.fetch(`/enrollment-tokens/${encodeURIComponent(
|
|
142
|
+
const revoked = (await ctx.fetch(`/enrollment-tokens/${encodeURIComponent(tokenId)}`, {
|
|
140
143
|
method: "DELETE",
|
|
141
|
-
...(parsed.idempotency_key
|
|
142
|
-
? { headers: { "Idempotency-Key": parsed.idempotency_key } }
|
|
143
|
-
: {}),
|
|
144
|
+
...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
|
|
144
145
|
}));
|
|
145
146
|
return {
|
|
146
|
-
id:
|
|
147
|
+
id: tokenId,
|
|
147
148
|
created_at: current.target.created_at,
|
|
148
149
|
organization_id: current.target.organization_id,
|
|
149
150
|
expires_at: current.target.expires_at,
|
|
@@ -190,7 +191,7 @@ export const adminNodeEnrollCapability = defineCapability({
|
|
|
190
191
|
http: { method: "POST", path: "/remote" },
|
|
191
192
|
cli: {
|
|
192
193
|
command: ["admin", "nodes", "enroll"],
|
|
193
|
-
vnext: adminHelp(["admin", "nodes", "enroll"], "Install Ornn management on one explicitly assigned node.", "ornn admin nodes enroll <node-id> --host HOST --supplier UUID --facility UUID [--filter COMPARTMENT_IDS] --identity-file PATH --token-ttl SECONDS --replace-existing-management [--verbose] [
|
|
194
|
+
vnext: adminHelp(["admin", "nodes", "enroll"], "Install Ornn management on one explicitly assigned node.", "ornn admin nodes enroll <node-id> --host HOST --supplier UUID --facility UUID [--filter COMPARTMENT_IDS] --identity-file PATH --token-ttl SECONDS --replace-existing-management [--verbose] []", "Unsupported: remote enrollment requires an eligible human terminal.", ["canonical node UUID", "canonical supplier organization UUID", "canonical facility UUID"], "Review node identity, SSH target, assignment, compartment filter, and replacement of existing management."),
|
|
194
195
|
},
|
|
195
196
|
mcp: false,
|
|
196
197
|
slack: false,
|
|
@@ -291,7 +292,7 @@ export const adminNodeRotateKeyCapability = defineCapability({
|
|
|
291
292
|
http: { method: "POST", path: "/enrollment/admin-ssh-key" },
|
|
292
293
|
cli: {
|
|
293
294
|
command: ["admin", "nodes", "rotate-key"],
|
|
294
|
-
vnext: adminHelp(["admin", "nodes", "rotate-key"], "Rotate one node admin key and export the replacement deliberately.", "ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] [
|
|
295
|
+
vnext: adminHelp(["admin", "nodes", "rotate-key"], "Rotate one node admin key and export the replacement deliberately.", "ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] []", "Unsupported: key rotation requires an eligible human terminal.", ["canonical node UUID"], "Review the node and loss of access through the previous private key."),
|
|
295
296
|
},
|
|
296
297
|
mcp: false,
|
|
297
298
|
slack: false,
|