@ornncompute/cli 1.0.67 → 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 +35 -27
- 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/catalog-shell.mjs +4 -18
- package/src/external-bucket-commands.mjs +5 -73
- package/src/interaction-runtime.mjs +2 -37
- package/src/kubernetes-commands.mjs +11 -102
- 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 +11 -44
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +12 -5
- 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/listings.js +44 -16
- package/vendor/capabilities/capabilities/locality.js +42 -0
- package/vendor/capabilities/capabilities/market-shared.js +1 -1
- package/vendor/capabilities/capabilities/markets.js +14 -14
- package/vendor/capabilities/capabilities/mounts.js +4 -4
- package/vendor/capabilities/capabilities/native-listing.js +51 -15
- 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
|
@@ -875,8 +875,8 @@ async function selectedOrganization(context, runtime) {
|
|
|
875
875
|
const selected = record(record(result.data).selected_organization);
|
|
876
876
|
const id = text(selected.id);
|
|
877
877
|
const name = text(selected.name);
|
|
878
|
-
if (!id || !name
|
|
879
|
-
runtime.usage("
|
|
878
|
+
if (!id || !name) {
|
|
879
|
+
runtime.usage("Join an organization before launching compute.", {
|
|
880
880
|
code: "organization_required",
|
|
881
881
|
field: "organization",
|
|
882
882
|
});
|
|
@@ -914,12 +914,6 @@ function clusterMachineLaunchPreflight(runtime, input, membershipCount) {
|
|
|
914
914
|
});
|
|
915
915
|
}
|
|
916
916
|
if (runtime.eligibleHuman) return;
|
|
917
|
-
if (input.yes !== true) {
|
|
918
|
-
runtime.usage("--yes is required for automation after reviewing the exact intent.", {
|
|
919
|
-
code: "consent_required",
|
|
920
|
-
field: "yes",
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
917
|
const references = requestedMachineReferences(input);
|
|
924
918
|
if (membershipCount === null || references.length !== membershipCount) {
|
|
925
919
|
runtime.usage("Automation requires --machines with exactly the --nodes membership count.", {
|
|
@@ -1257,47 +1251,13 @@ export async function launchClusterMode(
|
|
|
1257
1251
|
accessMode
|
|
1258
1252
|
);
|
|
1259
1253
|
if (eligibilitySignature(reviewEligibility) !== eligibilitySignature(initialEligibility)) {
|
|
1260
|
-
runtime.error(`${label} eligibility changed before
|
|
1254
|
+
runtime.error(`${label} eligibility changed before launch; no cluster was created.`, {
|
|
1261
1255
|
code: "cluster_eligibility_changed",
|
|
1262
1256
|
});
|
|
1263
1257
|
}
|
|
1264
|
-
const confirmed = await runtime.review({
|
|
1265
|
-
title: `Launch ${label} cluster`,
|
|
1266
|
-
terms: [
|
|
1267
|
-
{ label: "Organization", value: `${organization.name} (${organization.id})` },
|
|
1268
|
-
{ label: "Capacity reservation", value: reservationId },
|
|
1269
|
-
{ label: "Access mode", value: label },
|
|
1270
|
-
{ label: "Network", value: networkMode },
|
|
1271
|
-
...(keyPlan
|
|
1272
|
-
? [
|
|
1273
|
-
{
|
|
1274
|
-
label: "SSH key",
|
|
1275
|
-
value: `${text(reviewedKey.label) ?? "SSH key"} (${text(reviewedKey.fingerprint) ?? text(reviewedKey.id) ?? "new key"})`,
|
|
1276
|
-
},
|
|
1277
|
-
]
|
|
1278
|
-
: []),
|
|
1279
|
-
{ label: "Cluster membership", value: String(membershipCount) },
|
|
1280
|
-
...selected.map((machine, index) => ({
|
|
1281
|
-
label: `Machine ${index + 1}`,
|
|
1282
|
-
value: `${machine.hostname} (${machine.id})`,
|
|
1283
|
-
})),
|
|
1284
|
-
...virtualMachineIds.map((virtualMachineId, index) => ({
|
|
1285
|
-
label: `VM ${index + 1}`,
|
|
1286
|
-
value: `${virtualMachineId} (SSH keys are set aside while it is in the cluster)`,
|
|
1287
|
-
})),
|
|
1288
|
-
],
|
|
1289
|
-
});
|
|
1290
|
-
if (!confirmed) {
|
|
1291
|
-
return runtime.result({
|
|
1292
|
-
data: { created: false, requested },
|
|
1293
|
-
exitCode: 1,
|
|
1294
|
-
outcome: { class: "ended", phase: "declined" },
|
|
1295
|
-
presentation: { lines: ["No cluster was created."] },
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
1258
|
const currentOrganization = await selectedOrganization(context, runtime);
|
|
1299
1259
|
if (currentOrganization.id !== organization.id) {
|
|
1300
|
-
runtime.error("The selected organization changed
|
|
1260
|
+
runtime.error("The selected organization changed before launch; no cluster was created.", {
|
|
1301
1261
|
code: "organization_changed",
|
|
1302
1262
|
exitCode: 2,
|
|
1303
1263
|
});
|
|
@@ -1312,20 +1272,20 @@ export async function launchClusterMode(
|
|
|
1312
1272
|
if (racedExisting) {
|
|
1313
1273
|
const current = await fetchClusterDetail(context.fetch, runtime, racedExisting);
|
|
1314
1274
|
if (current.network_mode && current.network_mode !== networkMode) {
|
|
1315
|
-
runtime.error(`A different ${label} network goal was accepted
|
|
1275
|
+
runtime.error(`A different ${label} network goal was accepted before launch.`, {
|
|
1316
1276
|
code: "cluster_launch_goal_conflict",
|
|
1317
1277
|
});
|
|
1318
1278
|
}
|
|
1319
1279
|
const currentIds = memberIds(current);
|
|
1320
1280
|
if (currentIds.length === 0) {
|
|
1321
1281
|
if (current.lifecycle.phase !== "provisioning") {
|
|
1322
|
-
runtime.error(`A non-recoverable empty ${label} cluster appeared
|
|
1282
|
+
runtime.error(`A non-recoverable empty ${label} cluster appeared before launch.`, {
|
|
1323
1283
|
code: "cluster_launch_goal_conflict",
|
|
1324
1284
|
});
|
|
1325
1285
|
}
|
|
1326
1286
|
} else {
|
|
1327
1287
|
if (!sameMembers(currentIds, goalIds)) {
|
|
1328
|
-
runtime.error(`A different ${label} membership goal was accepted
|
|
1288
|
+
runtime.error(`A different ${label} membership goal was accepted before launch.`, {
|
|
1329
1289
|
code: "cluster_launch_goal_conflict",
|
|
1330
1290
|
});
|
|
1331
1291
|
}
|
|
@@ -1618,21 +1578,6 @@ async function addNode(input, deps) {
|
|
|
1618
1578
|
if (!machine)
|
|
1619
1579
|
deps.interaction.usage("Choose one exact eligible machine.", { code: "machine_not_eligible" });
|
|
1620
1580
|
const goalIds = [...initialMembers, machine.id].sort();
|
|
1621
|
-
const confirmed = await deps.interaction.review({
|
|
1622
|
-
title: `Add ${clusterModeLabel(prepared.cluster.access_mode)} cluster node`,
|
|
1623
|
-
terms: [
|
|
1624
|
-
{ label: "Cluster", value: prepared.cluster.id },
|
|
1625
|
-
{ label: "Machine", value: `${machine.hostname} (${machine.id})` },
|
|
1626
|
-
{ label: "Membership goal", value: String(goalIds.length) },
|
|
1627
|
-
],
|
|
1628
|
-
});
|
|
1629
|
-
if (!confirmed)
|
|
1630
|
-
return deps.interaction.result({
|
|
1631
|
-
data: { changed: false, cluster: prepared.cluster },
|
|
1632
|
-
exitCode: 1,
|
|
1633
|
-
outcome: { class: "ended", phase: "declined" },
|
|
1634
|
-
presentation: { lines: ["Cluster membership was not changed."] },
|
|
1635
|
-
});
|
|
1636
1581
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
|
|
1637
1582
|
const currentIds = memberIds(current);
|
|
1638
1583
|
if (sameMembers(currentIds, goalIds)) {
|
|
@@ -1643,7 +1588,7 @@ async function addNode(input, deps) {
|
|
|
1643
1588
|
});
|
|
1644
1589
|
}
|
|
1645
1590
|
if (!sameMembers(currentIds, initialMembers)) {
|
|
1646
|
-
deps.interaction.error("Cluster membership changed
|
|
1591
|
+
deps.interaction.error("Cluster membership changed before adding the node.", {
|
|
1647
1592
|
code: "cluster_membership_changed",
|
|
1648
1593
|
});
|
|
1649
1594
|
}
|
|
@@ -1654,7 +1599,7 @@ async function addNode(input, deps) {
|
|
|
1654
1599
|
current.access_mode
|
|
1655
1600
|
);
|
|
1656
1601
|
if (eligibilitySignature(currentEligibility) !== eligibilitySignature(initialEligibility)) {
|
|
1657
|
-
deps.interaction.error("Eligible machines changed
|
|
1602
|
+
deps.interaction.error("Eligible machines changed before adding the node.", {
|
|
1658
1603
|
code: "cluster_eligibility_changed",
|
|
1659
1604
|
});
|
|
1660
1605
|
}
|
|
@@ -1736,22 +1681,6 @@ async function removeNode(input, deps) {
|
|
|
1736
1681
|
code: "cluster_member_not_found",
|
|
1737
1682
|
});
|
|
1738
1683
|
const goalIds = initialMembers.filter((id) => id !== machine.id);
|
|
1739
|
-
const confirmed = await deps.interaction.review({
|
|
1740
|
-
title: `Remove ${clusterModeLabel(prepared.cluster.access_mode)} cluster node`,
|
|
1741
|
-
terms: [
|
|
1742
|
-
{ label: "Cluster", value: prepared.cluster.id },
|
|
1743
|
-
{ label: "Machine", value: `${machine.hostname} (${machine.id})` },
|
|
1744
|
-
{ label: "Membership goal", value: String(goalIds.length) },
|
|
1745
|
-
{ label: "Consequence", value: "Workloads and native access on this member stop." },
|
|
1746
|
-
],
|
|
1747
|
-
});
|
|
1748
|
-
if (!confirmed)
|
|
1749
|
-
return deps.interaction.result({
|
|
1750
|
-
data: { changed: false, cluster: prepared.cluster },
|
|
1751
|
-
exitCode: 1,
|
|
1752
|
-
outcome: { class: "ended", phase: "declined" },
|
|
1753
|
-
presentation: { lines: ["Cluster membership was not changed."] },
|
|
1754
|
-
});
|
|
1755
1684
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
|
|
1756
1685
|
const currentIds = memberIds(current);
|
|
1757
1686
|
if (sameMembers(currentIds, goalIds)) {
|
|
@@ -1762,7 +1691,7 @@ async function removeNode(input, deps) {
|
|
|
1762
1691
|
});
|
|
1763
1692
|
}
|
|
1764
1693
|
if (!sameMembers(currentIds, initialMembers)) {
|
|
1765
|
-
deps.interaction.error("Cluster membership changed
|
|
1694
|
+
deps.interaction.error("Cluster membership changed before removing the node.", {
|
|
1766
1695
|
code: "cluster_membership_changed",
|
|
1767
1696
|
});
|
|
1768
1697
|
}
|
|
@@ -1836,29 +1765,9 @@ async function teardownCluster(input, deps) {
|
|
|
1836
1765
|
});
|
|
1837
1766
|
}
|
|
1838
1767
|
const reviewedMembers = memberIds(reviewed);
|
|
1839
|
-
const confirmed = await deps.interaction.review({
|
|
1840
|
-
title: `Tear down ${clusterModeLabel(reviewed.access_mode)} cluster`,
|
|
1841
|
-
terms: [
|
|
1842
|
-
{ label: "Cluster", value: reviewed.id },
|
|
1843
|
-
{ label: "Members", value: String(reviewedMembers.length) },
|
|
1844
|
-
{
|
|
1845
|
-
label: "Native access",
|
|
1846
|
-
value: `${clusterNativeAccess(reviewed.access_mode)} access ends.`,
|
|
1847
|
-
},
|
|
1848
|
-
{ label: "Capacity reservation", value: `${reviewed.reservation_id} remains active.` },
|
|
1849
|
-
{ label: "Charges", value: "Capacity charges continue after cluster teardown." },
|
|
1850
|
-
],
|
|
1851
|
-
});
|
|
1852
|
-
if (!confirmed)
|
|
1853
|
-
return deps.interaction.result({
|
|
1854
|
-
data: { changed: false, cluster: reviewed },
|
|
1855
|
-
exitCode: 1,
|
|
1856
|
-
outcome: { class: "ready", phase: "unchanged" },
|
|
1857
|
-
presentation: { lines: ["The cluster remains active. Capacity charges continue."] },
|
|
1858
|
-
});
|
|
1859
1768
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, reviewed);
|
|
1860
1769
|
if (!sameMembers(memberIds(current), reviewedMembers)) {
|
|
1861
|
-
deps.interaction.error("Cluster membership changed
|
|
1770
|
+
deps.interaction.error("Cluster membership changed before teardown was submitted.", {
|
|
1862
1771
|
code: "cluster_membership_changed",
|
|
1863
1772
|
});
|
|
1864
1773
|
}
|
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)}`
|