@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getReservation } from "../vendor/api-client/commerce.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
|
|
9
10
|
import { waitForGoal as waitForReadGoal } from "./wait-runtime.mjs";
|
|
10
11
|
import { gpuLabeler } from "./gpu-catalog.mjs";
|
|
11
|
-
import { reservationLabeler } from "./machine-reference.mjs";
|
|
12
|
+
import { listActiveReservations, reservationLabeler } from "./machine-reference.mjs";
|
|
12
13
|
import { keyInputShapePreflight, keyPlanFromInput, materializeKey } from "./vm-commands.mjs";
|
|
13
14
|
|
|
14
15
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -51,8 +52,7 @@ function clusterNativeAccess(accessMode) {
|
|
|
51
52
|
return accessMode === "slurm" ? "SSH login" : "kubeconfig";
|
|
52
53
|
}
|
|
53
54
|
function reservationAccessMode(value) {
|
|
54
|
-
|
|
55
|
-
return mode === "virtual-machine" ? "vm" : mode;
|
|
55
|
+
return text(record(value).provisioningType);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function clusterConnectCommand(cluster) {
|
|
@@ -736,17 +736,13 @@ async function waitForClusterGoal({
|
|
|
736
736
|
}
|
|
737
737
|
|
|
738
738
|
async function fetchReservation(fetch, runtime, reservationId, accessMode = "kubernetes") {
|
|
739
|
-
const reservation = record(
|
|
740
|
-
|
|
741
|
-
method: "GET",
|
|
742
|
-
})
|
|
743
|
-
);
|
|
744
|
-
if (text(reservation.id) !== reservationId) {
|
|
739
|
+
const reservation = record(await getReservation(fetch, reservationId));
|
|
740
|
+
if (text(reservation.reservationId) !== reservationId) {
|
|
745
741
|
runtime.error("The gateway returned a different capacity reservation.", {
|
|
746
742
|
code: "reservation_identity_changed",
|
|
747
743
|
});
|
|
748
744
|
}
|
|
749
|
-
const status = cleanState(reservation.status
|
|
745
|
+
const status = cleanState(reservation.status);
|
|
750
746
|
if (reservationAccessMode(reservation) !== accessMode) {
|
|
751
747
|
runtime.error(
|
|
752
748
|
`The selected capacity reservation is not reserved for ${clusterModeLabel(accessMode)}.`,
|
|
@@ -766,50 +762,6 @@ async function fetchReservation(fetch, runtime, reservationId, accessMode = "kub
|
|
|
766
762
|
return reservation;
|
|
767
763
|
}
|
|
768
764
|
|
|
769
|
-
async function fetchActiveReservations(fetch, runtime) {
|
|
770
|
-
const reservations = [];
|
|
771
|
-
const seen = new Set();
|
|
772
|
-
let cursor;
|
|
773
|
-
for (let page = 0; page < 20; page += 1) {
|
|
774
|
-
const query = new URLSearchParams({ limit: "500", state: "active" });
|
|
775
|
-
if (cursor) query.set("cursor", cursor);
|
|
776
|
-
const payload = await fetch(`/v1/workflows/commerce/reservations?${query}`, {
|
|
777
|
-
method: "GET",
|
|
778
|
-
});
|
|
779
|
-
const source = record(payload);
|
|
780
|
-
const rows = Array.isArray(payload)
|
|
781
|
-
? payload
|
|
782
|
-
: Array.isArray(source.reservations)
|
|
783
|
-
? source.reservations
|
|
784
|
-
: null;
|
|
785
|
-
if (!rows) {
|
|
786
|
-
runtime.error("Capacity reservations returned an unsafe response.", {
|
|
787
|
-
code: "reservation_projection_invalid",
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
reservations.push(
|
|
791
|
-
...rows.filter((value) => {
|
|
792
|
-
const reservation = record(value);
|
|
793
|
-
return (
|
|
794
|
-
UUID.test(text(reservation.id) ?? "") &&
|
|
795
|
-
cleanState(reservation.status ?? reservation.state) === "active"
|
|
796
|
-
);
|
|
797
|
-
})
|
|
798
|
-
);
|
|
799
|
-
const next = text(source.next_cursor);
|
|
800
|
-
if (!next) return reservations;
|
|
801
|
-
if (seen.has(next))
|
|
802
|
-
runtime.error("Capacity-reservation pagination did not advance.", {
|
|
803
|
-
code: "reservation_pagination_invalid",
|
|
804
|
-
});
|
|
805
|
-
seen.add(next);
|
|
806
|
-
cursor = next;
|
|
807
|
-
}
|
|
808
|
-
runtime.error("Capacity-reservation pagination exceeded its safety bound.", {
|
|
809
|
-
code: "reservation_pagination_invalid",
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
|
|
813
765
|
async function resolveLaunchReservation(context, runtime, input, accessMode) {
|
|
814
766
|
const positionals = input.$positionals ?? [];
|
|
815
767
|
if (positionals.length > 1) {
|
|
@@ -838,7 +790,7 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
|
|
|
838
790
|
field: "reservation",
|
|
839
791
|
});
|
|
840
792
|
}
|
|
841
|
-
const active = (await
|
|
793
|
+
const active = (await listActiveReservations(context, runtime)).filter(
|
|
842
794
|
(reservation) => reservationAccessMode(reservation) === accessMode
|
|
843
795
|
);
|
|
844
796
|
if (active.length === 0) {
|
|
@@ -850,14 +802,14 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
|
|
|
850
802
|
);
|
|
851
803
|
}
|
|
852
804
|
if (active.length === 1) {
|
|
853
|
-
return fetchReservation(context.fetch, runtime, text(
|
|
805
|
+
return fetchReservation(context.fetch, runtime, text(active[0].reservationId), accessMode);
|
|
854
806
|
}
|
|
855
807
|
const reservationLabel = await reservationLabeler(context.fetch);
|
|
856
808
|
const selected = await runtime.ask({
|
|
857
809
|
label: "Capacity reservation",
|
|
858
810
|
choices: active.flatMap((value) => {
|
|
859
811
|
const reservation = record(value);
|
|
860
|
-
const id = text(reservation.
|
|
812
|
+
const id = text(reservation.reservationId);
|
|
861
813
|
return id ? [{ label: reservationLabel(reservation), value: id }] : [];
|
|
862
814
|
}),
|
|
863
815
|
});
|
|
@@ -914,12 +866,6 @@ function clusterMachineLaunchPreflight(runtime, input, membershipCount) {
|
|
|
914
866
|
});
|
|
915
867
|
}
|
|
916
868
|
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
869
|
const references = requestedMachineReferences(input);
|
|
924
870
|
if (membershipCount === null || references.length !== membershipCount) {
|
|
925
871
|
runtime.usage("Automation requires --machines with exactly the --nodes membership count.", {
|
|
@@ -1130,7 +1076,7 @@ export async function launchClusterMode(
|
|
|
1130
1076
|
const organization = await selectedOrganization(context, runtime);
|
|
1131
1077
|
const keyPlan = accessMode === "slurm" ? await keyPlanFromInput(context, input, runtime) : null;
|
|
1132
1078
|
const reservation = await resolveLaunchReservation(context, runtime, input, accessMode);
|
|
1133
|
-
const reservationId = text(reservation.
|
|
1079
|
+
const reservationId = text(reservation.reservationId);
|
|
1134
1080
|
const initialExisting = await existingReservationCluster(
|
|
1135
1081
|
context.fetch,
|
|
1136
1082
|
runtime,
|
|
@@ -1257,47 +1203,13 @@ export async function launchClusterMode(
|
|
|
1257
1203
|
accessMode
|
|
1258
1204
|
);
|
|
1259
1205
|
if (eligibilitySignature(reviewEligibility) !== eligibilitySignature(initialEligibility)) {
|
|
1260
|
-
runtime.error(`${label} eligibility changed before
|
|
1206
|
+
runtime.error(`${label} eligibility changed before launch; no cluster was created.`, {
|
|
1261
1207
|
code: "cluster_eligibility_changed",
|
|
1262
1208
|
});
|
|
1263
1209
|
}
|
|
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
1210
|
const currentOrganization = await selectedOrganization(context, runtime);
|
|
1299
1211
|
if (currentOrganization.id !== organization.id) {
|
|
1300
|
-
runtime.error("The selected organization changed
|
|
1212
|
+
runtime.error("The selected organization changed before launch; no cluster was created.", {
|
|
1301
1213
|
code: "organization_changed",
|
|
1302
1214
|
exitCode: 2,
|
|
1303
1215
|
});
|
|
@@ -1312,20 +1224,20 @@ export async function launchClusterMode(
|
|
|
1312
1224
|
if (racedExisting) {
|
|
1313
1225
|
const current = await fetchClusterDetail(context.fetch, runtime, racedExisting);
|
|
1314
1226
|
if (current.network_mode && current.network_mode !== networkMode) {
|
|
1315
|
-
runtime.error(`A different ${label} network goal was accepted
|
|
1227
|
+
runtime.error(`A different ${label} network goal was accepted before launch.`, {
|
|
1316
1228
|
code: "cluster_launch_goal_conflict",
|
|
1317
1229
|
});
|
|
1318
1230
|
}
|
|
1319
1231
|
const currentIds = memberIds(current);
|
|
1320
1232
|
if (currentIds.length === 0) {
|
|
1321
1233
|
if (current.lifecycle.phase !== "provisioning") {
|
|
1322
|
-
runtime.error(`A non-recoverable empty ${label} cluster appeared
|
|
1234
|
+
runtime.error(`A non-recoverable empty ${label} cluster appeared before launch.`, {
|
|
1323
1235
|
code: "cluster_launch_goal_conflict",
|
|
1324
1236
|
});
|
|
1325
1237
|
}
|
|
1326
1238
|
} else {
|
|
1327
1239
|
if (!sameMembers(currentIds, goalIds)) {
|
|
1328
|
-
runtime.error(`A different ${label} membership goal was accepted
|
|
1240
|
+
runtime.error(`A different ${label} membership goal was accepted before launch.`, {
|
|
1329
1241
|
code: "cluster_launch_goal_conflict",
|
|
1330
1242
|
});
|
|
1331
1243
|
}
|
|
@@ -1618,21 +1530,6 @@ async function addNode(input, deps) {
|
|
|
1618
1530
|
if (!machine)
|
|
1619
1531
|
deps.interaction.usage("Choose one exact eligible machine.", { code: "machine_not_eligible" });
|
|
1620
1532
|
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
1533
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
|
|
1637
1534
|
const currentIds = memberIds(current);
|
|
1638
1535
|
if (sameMembers(currentIds, goalIds)) {
|
|
@@ -1643,7 +1540,7 @@ async function addNode(input, deps) {
|
|
|
1643
1540
|
});
|
|
1644
1541
|
}
|
|
1645
1542
|
if (!sameMembers(currentIds, initialMembers)) {
|
|
1646
|
-
deps.interaction.error("Cluster membership changed
|
|
1543
|
+
deps.interaction.error("Cluster membership changed before adding the node.", {
|
|
1647
1544
|
code: "cluster_membership_changed",
|
|
1648
1545
|
});
|
|
1649
1546
|
}
|
|
@@ -1654,7 +1551,7 @@ async function addNode(input, deps) {
|
|
|
1654
1551
|
current.access_mode
|
|
1655
1552
|
);
|
|
1656
1553
|
if (eligibilitySignature(currentEligibility) !== eligibilitySignature(initialEligibility)) {
|
|
1657
|
-
deps.interaction.error("Eligible machines changed
|
|
1554
|
+
deps.interaction.error("Eligible machines changed before adding the node.", {
|
|
1658
1555
|
code: "cluster_eligibility_changed",
|
|
1659
1556
|
});
|
|
1660
1557
|
}
|
|
@@ -1736,22 +1633,6 @@ async function removeNode(input, deps) {
|
|
|
1736
1633
|
code: "cluster_member_not_found",
|
|
1737
1634
|
});
|
|
1738
1635
|
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
1636
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
|
|
1756
1637
|
const currentIds = memberIds(current);
|
|
1757
1638
|
if (sameMembers(currentIds, goalIds)) {
|
|
@@ -1762,7 +1643,7 @@ async function removeNode(input, deps) {
|
|
|
1762
1643
|
});
|
|
1763
1644
|
}
|
|
1764
1645
|
if (!sameMembers(currentIds, initialMembers)) {
|
|
1765
|
-
deps.interaction.error("Cluster membership changed
|
|
1646
|
+
deps.interaction.error("Cluster membership changed before removing the node.", {
|
|
1766
1647
|
code: "cluster_membership_changed",
|
|
1767
1648
|
});
|
|
1768
1649
|
}
|
|
@@ -1836,29 +1717,9 @@ async function teardownCluster(input, deps) {
|
|
|
1836
1717
|
});
|
|
1837
1718
|
}
|
|
1838
1719
|
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
1720
|
const current = await fetchClusterDetail(deps.fetch, deps.interaction, reviewed);
|
|
1860
1721
|
if (!sameMembers(memberIds(current), reviewedMembers)) {
|
|
1861
|
-
deps.interaction.error("Cluster membership changed
|
|
1722
|
+
deps.interaction.error("Cluster membership changed before teardown was submitted.", {
|
|
1862
1723
|
code: "cluster_membership_changed",
|
|
1863
1724
|
});
|
|
1864
1725
|
}
|
|
@@ -1901,44 +1762,25 @@ export async function executeKubernetesClusterCommand(command, input, deps) {
|
|
|
1901
1762
|
return handler(input, deps);
|
|
1902
1763
|
}
|
|
1903
1764
|
|
|
1904
|
-
function
|
|
1905
|
-
const reservation =
|
|
1906
|
-
|
|
1907
|
-
if (id !== expectedId) {
|
|
1765
|
+
export async function reservationFactsForCluster(fetch, runtime, cluster) {
|
|
1766
|
+
const reservation = await getReservation(fetch, cluster.reservation_id);
|
|
1767
|
+
if (reservation.reservationId !== cluster.reservation_id) {
|
|
1908
1768
|
runtime.error("The gateway returned facts for a different capacity reservation.", {
|
|
1909
1769
|
code: "reservation_identity_changed",
|
|
1910
1770
|
});
|
|
1911
1771
|
}
|
|
1912
|
-
return
|
|
1913
|
-
id,
|
|
1914
|
-
facility:
|
|
1915
|
-
text(reservation.facility_name) ??
|
|
1916
|
-
text(reservation.site_nickname) ??
|
|
1917
|
-
text(reservation.facility),
|
|
1918
|
-
hardware: text(reservation.gpu_type) ?? text(reservation.hardware),
|
|
1919
|
-
machine_count: integer(reservation.node_count) ?? integer(reservation.machines),
|
|
1920
|
-
region: text(reservation.region),
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
export async function reservationFactsForCluster(fetch, runtime, cluster) {
|
|
1925
|
-
const payload = await fetch(
|
|
1926
|
-
`/v1/workflows/commerce/reservations/${encodeURIComponent(cluster.reservation_id)}`,
|
|
1927
|
-
{ method: "GET" }
|
|
1928
|
-
);
|
|
1929
|
-
return projectReservationFacts(payload, cluster.reservation_id, runtime);
|
|
1772
|
+
return reservation;
|
|
1930
1773
|
}
|
|
1931
1774
|
|
|
1932
1775
|
export function clusterFactLabel(cluster, reservation, gpuLabel) {
|
|
1933
|
-
const
|
|
1934
|
-
const gpu = reservation.hardware ?? cluster.gpu_type;
|
|
1776
|
+
const gpu = reservation.gpuSlug ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
|
|
1935
1777
|
const hardware = gpu ? gpuLabel(gpu) : "hardware unknown";
|
|
1936
1778
|
const nodeCount =
|
|
1937
1779
|
integer(cluster.membership_count) ??
|
|
1938
1780
|
integer(cluster.node_count) ??
|
|
1939
1781
|
(Array.isArray(cluster.machines) ? cluster.machines.length : 0);
|
|
1940
1782
|
const lifecycle = text(record(cluster.lifecycle).class) ?? text(cluster.state) ?? "unknown";
|
|
1941
|
-
return `${
|
|
1783
|
+
return `${clusterModeLabel(cluster.access_mode)} · ${hardware} · ${nodeCount} nodes · ${lifecycle}`;
|
|
1942
1784
|
}
|
|
1943
1785
|
const clusterConnectCandidatesCache = new WeakMap();
|
|
1944
1786
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { listAllReservations } from "../vendor/api-client/commerce.js";
|
|
1
2
|
import { gpuLabeler } from "./gpu-catalog.mjs";
|
|
2
3
|
|
|
3
4
|
const CANONICAL_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
-
const PAGE_SIZE = 500;
|
|
5
|
-
const MAX_RESERVATION_PAGES = 20;
|
|
6
5
|
const TERMINAL_MACHINE_STATES = new Set([
|
|
7
6
|
"canceled",
|
|
8
7
|
"cancelled",
|
|
@@ -31,18 +30,12 @@ function rows(payload, field) {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
function reservationId(value) {
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
text(source.id) ??
|
|
37
|
-
text(source.ref) ??
|
|
38
|
-
text(source.reservation_id) ??
|
|
39
|
-
text(source.commerce_reservation_id)
|
|
40
|
-
);
|
|
33
|
+
return text(value.reservationId);
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
function reservationMode(value) {
|
|
44
37
|
const source = record(value);
|
|
45
|
-
const mode = text(source.
|
|
38
|
+
const mode = text(source.provisioningType);
|
|
46
39
|
return mode?.toLowerCase() === "virtual-machine" ? "vm" : (mode?.toLowerCase() ?? null);
|
|
47
40
|
}
|
|
48
41
|
|
|
@@ -63,21 +56,16 @@ function reservationModeLabel(value) {
|
|
|
63
56
|
|
|
64
57
|
export function reservationChoiceLabel(value, gpuLabel) {
|
|
65
58
|
const reservation = record(value);
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
59
|
+
// CPU capacity has no GPU type and no GPUs; the labeler names it CPU.
|
|
60
|
+
const gpu = text(reservation.gpuSlug) ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
|
|
61
|
+
const hardware = gpu ? gpuLabel(gpu) : "GPU capacity";
|
|
62
|
+
const quantity = reservation.reservedNodeCount;
|
|
69
63
|
const machines = Number.isInteger(quantity) && quantity > 0 ? quantity : 1;
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
text(reservation.location) ??
|
|
74
|
-
text(reservation.region) ??
|
|
75
|
-
"location unreported";
|
|
76
|
-
const start = text(reservation.start_at) ?? text(reservation.start_date);
|
|
77
|
-
const end = text(reservation.end_at) ?? text(reservation.end_date);
|
|
78
|
-
const createdAt = text(reservation.created_at);
|
|
64
|
+
const start = text(reservation.startsAt);
|
|
65
|
+
const end = text(reservation.endsAt);
|
|
66
|
+
const createdAt = text(reservation.createdAt);
|
|
79
67
|
return [
|
|
80
|
-
`${hardware}, ${machines} machine${machines === 1 ? "" : "s"}
|
|
68
|
+
`${hardware}, ${machines} machine${machines === 1 ? "" : "s"}`,
|
|
81
69
|
reservationModeLabel(reservation),
|
|
82
70
|
start && end ? `${start} to ${end}` : null,
|
|
83
71
|
createdAt ? `reserved ${createdAt}` : null,
|
|
@@ -91,13 +79,6 @@ export async function reservationLabeler(fetch) {
|
|
|
91
79
|
return (reservation) => reservationChoiceLabel(reservation, gpuLabel);
|
|
92
80
|
}
|
|
93
81
|
|
|
94
|
-
function isActiveDirectReservation(value) {
|
|
95
|
-
const source = record(value);
|
|
96
|
-
const status = (text(source.status) ?? text(source.state) ?? "").toLowerCase();
|
|
97
|
-
const mode = reservationMode(source);
|
|
98
|
-
return status === "active" && (!mode || mode === "vm" || mode === "bare-metal");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
82
|
export function canonicalMachineId(value) {
|
|
102
83
|
const source = record(value);
|
|
103
84
|
return (
|
|
@@ -130,35 +111,31 @@ function invalidGateway(interaction, message) {
|
|
|
130
111
|
});
|
|
131
112
|
}
|
|
132
113
|
|
|
133
|
-
export async function
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
export async function listActiveReservations(context, interaction) {
|
|
115
|
+
const session = context.cli?.session
|
|
116
|
+
? (await context.cli.session("context", {})).data
|
|
117
|
+
: await context.fetch("/v1/cli/session");
|
|
118
|
+
const customerId = text((session.selectedOrganization ?? session.selected_organization)?.id);
|
|
119
|
+
if (!customerId) {
|
|
120
|
+
interaction.usage("Select an organization before choosing a reservation.", {
|
|
121
|
+
code: "organization_required",
|
|
122
|
+
field: "organization",
|
|
142
123
|
});
|
|
143
|
-
const pageRows = rows(payload, "reservations");
|
|
144
|
-
if (!pageRows)
|
|
145
|
-
invalidGateway(interaction, "The gateway returned an invalid capacity-reservation page.");
|
|
146
|
-
reservations.push(...pageRows.map(record).filter(isActiveDirectReservation));
|
|
147
|
-
|
|
148
|
-
const source = record(payload);
|
|
149
|
-
const next = text(source.next_cursor) ?? text(source.nextCursor);
|
|
150
|
-
if (!next || Array.isArray(payload)) return reservations;
|
|
151
|
-
if (seen.has(next)) {
|
|
152
|
-
invalidGateway(interaction, "Capacity-reservation pagination repeated a cursor.");
|
|
153
|
-
}
|
|
154
|
-
seen.add(next);
|
|
155
|
-
cursor = next;
|
|
156
124
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
125
|
+
// Native Commerce permits staff to read all organizations. Customer pickers
|
|
126
|
+
// retain the selected-organization scope of the published CLI workflow.
|
|
127
|
+
const { reservations } = await listAllReservations(context.fetch, {
|
|
128
|
+
customerId,
|
|
129
|
+
status: "active",
|
|
161
130
|
});
|
|
131
|
+
return reservations.filter((reservation) => reservation.status === "active");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function listActiveDirectReservations(context, interaction) {
|
|
135
|
+
return (await listActiveReservations(context, interaction)).filter(
|
|
136
|
+
({ provisioningType }) =>
|
|
137
|
+
provisioningType === "virtual-machine" || provisioningType === "bare-metal"
|
|
138
|
+
);
|
|
162
139
|
}
|
|
163
140
|
|
|
164
141
|
export async function listOwnedActiveMachines(context, interaction) {
|