@ornncompute/cli 0.2.22 → 0.2.23
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 +0 -3
- package/package.json +1 -1
- package/src/cli.mjs +152 -122
- package/vendor/capabilities/capabilities/fleet.js +2 -2
package/README.md
CHANGED
|
@@ -137,9 +137,6 @@ ornn networks show <network-id> [--json]
|
|
|
137
137
|
ornn networks create --name <name> [--cidr <cidr>] [--description <text>] [--json]
|
|
138
138
|
ornn networks update <network-id> [--name <name>] [--description <text>] [--clear-description] [--json]
|
|
139
139
|
ornn networks delete <network-id> [--yes] [--json]
|
|
140
|
-
ornn networks reservation <reservation-id> [--json]
|
|
141
|
-
ornn networks attach <reservation-id> --network <network-id> [--json]
|
|
142
|
-
ornn networks detach <reservation-id> [--json]
|
|
143
140
|
ornn storage volumes list [--json]
|
|
144
141
|
ornn storage volumes show <drive-id> [--json]
|
|
145
142
|
ornn storage volumes create --name <name> [--source <drive-id>] [--json]
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -214,9 +214,6 @@ Usage:
|
|
|
214
214
|
ornn networks create --name <name> [--cidr <cidr>] [--description <text>] [--json]
|
|
215
215
|
ornn networks update <network-id> [--name <name>] [--description <text>] [--clear-description] [--json]
|
|
216
216
|
ornn networks delete <network-id> [--yes] [--json]
|
|
217
|
-
ornn networks reservation <reservation-id> [--json]
|
|
218
|
-
ornn networks attach <reservation-id> --network <network-id> [--json]
|
|
219
|
-
ornn networks detach <reservation-id> [--json]
|
|
220
217
|
ornn storage volumes list [--json]
|
|
221
218
|
ornn storage files ls <drive-id> [--prefix <path>] [--json]
|
|
222
219
|
ornn storage files upload <drive-id> <local-file> [--destination <path>] [--content-type <type>] [--json]
|
|
@@ -3002,7 +2999,7 @@ function nodeOpUsage(subcommand) {
|
|
|
3002
2999
|
return `Usage: ornn node ${subcommand} <node-id> [--json]`;
|
|
3003
3000
|
}
|
|
3004
3001
|
|
|
3005
|
-
// Catalog: @ornn/capabilities fleet.nodes.list → GET /
|
|
3002
|
+
// Catalog: @ornn/capabilities fleet.nodes.list → GET /nodes
|
|
3006
3003
|
async function nodeOpList(rest, context) {
|
|
3007
3004
|
const options = parseCommandOptions(
|
|
3008
3005
|
rest,
|
|
@@ -3133,11 +3130,15 @@ async function resolveNodeInstanceId(nodeId, context) {
|
|
|
3133
3130
|
throw new CliApiError(`Node ${nodeId} has no assigned reservation to reboot.`);
|
|
3134
3131
|
}
|
|
3135
3132
|
const machines = await operatorRequest({
|
|
3136
|
-
endpoint: `/v1/orchestrator/nodes
|
|
3133
|
+
endpoint: `/v1/orchestrator/internal/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
|
|
3137
3134
|
env: context.env,
|
|
3138
3135
|
fetchImpl: context.fetchImpl,
|
|
3139
3136
|
});
|
|
3140
|
-
const rows = Array.isArray(machines?.
|
|
3137
|
+
const rows = Array.isArray(machines?.nodes)
|
|
3138
|
+
? machines.nodes
|
|
3139
|
+
: Array.isArray(machines?.machines)
|
|
3140
|
+
? machines.machines
|
|
3141
|
+
: [];
|
|
3141
3142
|
const nodeRefs = new Set(
|
|
3142
3143
|
[nodeId, node?.id, node?.k8s_node_name]
|
|
3143
3144
|
.filter((value) => value !== null && value !== undefined && String(value) !== "")
|
|
@@ -3151,7 +3152,7 @@ async function resolveNodeInstanceId(nodeId, context) {
|
|
|
3151
3152
|
`Multiple live machines found for node ${nodeId} (reservation ${reservationId}).`
|
|
3152
3153
|
);
|
|
3153
3154
|
}
|
|
3154
|
-
const instanceId = matches[0]?.id;
|
|
3155
|
+
const instanceId = matches[0]?.node_id ?? matches[0]?.id;
|
|
3155
3156
|
if (!instanceId) {
|
|
3156
3157
|
throw new CliApiError(
|
|
3157
3158
|
`No live machine found for node ${nodeId} (reservation ${reservationId}).`
|
|
@@ -3370,7 +3371,7 @@ function writeNodeHealth(stdout, node) {
|
|
|
3370
3371
|
|
|
3371
3372
|
stdout.write(`Validation: ${node?.validation_status || "unknown"}\n`);
|
|
3372
3373
|
writeOptionalStatusLine(stdout, "Validation checked", node?.validation_checked_at);
|
|
3373
|
-
writeOptionalStatusLine(stdout, "Last heartbeat", node?.
|
|
3374
|
+
writeOptionalStatusLine(stdout, "Last heartbeat", node?.observed_at);
|
|
3374
3375
|
}
|
|
3375
3376
|
|
|
3376
3377
|
async function nodes(args, context) {
|
|
@@ -3706,10 +3707,11 @@ async function clusters(args, context) {
|
|
|
3706
3707
|
"Usage: ornn clusters list [--json]"
|
|
3707
3708
|
);
|
|
3708
3709
|
const rows = await fetchTenantClusters(context);
|
|
3710
|
+
const clusters = withNumberedClusterNames(requireArrayPayload(rows, "clusters"));
|
|
3709
3711
|
if (options.json) {
|
|
3710
|
-
writeJson(context.stdout,
|
|
3712
|
+
writeJson(context.stdout, clusters);
|
|
3711
3713
|
} else {
|
|
3712
|
-
writeClusterList(context.stdout,
|
|
3714
|
+
writeClusterList(context.stdout, clusters);
|
|
3713
3715
|
}
|
|
3714
3716
|
return 0;
|
|
3715
3717
|
}
|
|
@@ -3802,9 +3804,16 @@ async function clusters(args, context) {
|
|
|
3802
3804
|
const wait = options.wait
|
|
3803
3805
|
? await waitForClusterActive(selectedReservationId, type, waitOptions(options), context)
|
|
3804
3806
|
: null;
|
|
3805
|
-
const result = {
|
|
3807
|
+
const result = {
|
|
3808
|
+
cluster: withNumberedClusterName(launch, [launch]),
|
|
3809
|
+
reservation_id: selectedReservationId,
|
|
3810
|
+
type,
|
|
3811
|
+
wait: wait
|
|
3812
|
+
? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
|
|
3813
|
+
: null,
|
|
3814
|
+
};
|
|
3806
3815
|
if (options.json) {
|
|
3807
|
-
writeJson(context.stdout, result);
|
|
3816
|
+
writeJson(context.stdout, omitClusterName(result));
|
|
3808
3817
|
} else {
|
|
3809
3818
|
writeClusterLaunchResult(context.stdout, result);
|
|
3810
3819
|
writeNextStep(
|
|
@@ -3825,8 +3834,20 @@ async function clusters(args, context) {
|
|
|
3825
3834
|
{ boolean: ["json"], value: ["type", "mode"] },
|
|
3826
3835
|
"Usage: ornn clusters show <reservation-id> [--type kubernetes|slurm] [--json]"
|
|
3827
3836
|
);
|
|
3828
|
-
const
|
|
3829
|
-
|
|
3837
|
+
const listed = requireArrayPayload(await fetchTenantClusters(context), "clusters");
|
|
3838
|
+
let type;
|
|
3839
|
+
if (options.type || options.mode) {
|
|
3840
|
+
type = normalizeClusterType(options.type || options.mode);
|
|
3841
|
+
} else {
|
|
3842
|
+
const listedType = listed.find(
|
|
3843
|
+
(candidate) => String(candidate.reservation_id || "") === String(reservationId)
|
|
3844
|
+
)?.access_mode;
|
|
3845
|
+
if (!listedType) {
|
|
3846
|
+
throw new Error("--type is required when the cluster is not listed yet.");
|
|
3847
|
+
}
|
|
3848
|
+
type = normalizeClusterType(listedType);
|
|
3849
|
+
}
|
|
3850
|
+
const cluster = withNumberedClusterName(await fetchCluster(reservationId, type, context), listed);
|
|
3830
3851
|
if (options.json) {
|
|
3831
3852
|
writeJson(context.stdout, cluster);
|
|
3832
3853
|
} else {
|
|
@@ -3843,10 +3864,14 @@ async function clusters(args, context) {
|
|
|
3843
3864
|
);
|
|
3844
3865
|
const type = await resolveClusterTypeForReservation(reservationId, options, context);
|
|
3845
3866
|
const result = await waitForClusterActive(reservationId, type, waitOptions(options), context);
|
|
3867
|
+
const named = {
|
|
3868
|
+
...result,
|
|
3869
|
+
cluster: withNumberedClusterName(result.cluster, [result.cluster]),
|
|
3870
|
+
};
|
|
3846
3871
|
if (options.json) {
|
|
3847
|
-
writeJson(context.stdout,
|
|
3872
|
+
writeJson(context.stdout, omitClusterName(named));
|
|
3848
3873
|
} else {
|
|
3849
|
-
writeClusterWaitResult(context.stdout,
|
|
3874
|
+
writeClusterWaitResult(context.stdout, named);
|
|
3850
3875
|
}
|
|
3851
3876
|
return 0;
|
|
3852
3877
|
}
|
|
@@ -3952,13 +3977,14 @@ async function clusters(args, context) {
|
|
|
3952
3977
|
subcommand === "add-node"
|
|
3953
3978
|
? await addClusterNode(reservationId, nodeId, context)
|
|
3954
3979
|
: await removeClusterNode(reservationId, nodeId, context);
|
|
3980
|
+
const named = withNumberedClusterName(cluster, [cluster]);
|
|
3955
3981
|
if (options.json) {
|
|
3956
|
-
writeJson(context.stdout,
|
|
3982
|
+
writeJson(context.stdout, named);
|
|
3957
3983
|
} else {
|
|
3958
3984
|
context.stdout.write(
|
|
3959
3985
|
`Cluster node ${subcommand === "add-node" ? "add" : "remove"} queued.\n`
|
|
3960
3986
|
);
|
|
3961
|
-
writeClusterDetail(context.stdout,
|
|
3987
|
+
writeClusterDetail(context.stdout, named);
|
|
3962
3988
|
}
|
|
3963
3989
|
return 0;
|
|
3964
3990
|
}
|
|
@@ -3971,7 +3997,7 @@ async function clusters(args, context) {
|
|
|
3971
3997
|
);
|
|
3972
3998
|
if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
|
|
3973
3999
|
const type = await resolveClusterTypeForReservation(reservationId, options, context);
|
|
3974
|
-
const cluster = await teardownCluster(reservationId, type, context);
|
|
4000
|
+
const cluster = withNumberedClusterName(await teardownCluster(reservationId, type, context), []);
|
|
3975
4001
|
if (options.json) {
|
|
3976
4002
|
writeJson(context.stdout, cluster);
|
|
3977
4003
|
} else {
|
|
@@ -4002,9 +4028,16 @@ async function slurm(args, context) {
|
|
|
4002
4028
|
const wait = options.wait
|
|
4003
4029
|
? await waitForClusterActive(reservationId, "slurm", waitOptions(options), context)
|
|
4004
4030
|
: null;
|
|
4005
|
-
const result = {
|
|
4031
|
+
const result = {
|
|
4032
|
+
cluster: withNumberedClusterName(launch, [launch]),
|
|
4033
|
+
reservation_id: reservationId,
|
|
4034
|
+
type: "slurm",
|
|
4035
|
+
wait: wait
|
|
4036
|
+
? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
|
|
4037
|
+
: null,
|
|
4038
|
+
};
|
|
4006
4039
|
if (options.json) {
|
|
4007
|
-
writeJson(context.stdout, result);
|
|
4040
|
+
writeJson(context.stdout, omitClusterName(result));
|
|
4008
4041
|
} else {
|
|
4009
4042
|
writeClusterLaunchResult(context.stdout, result);
|
|
4010
4043
|
}
|
|
@@ -4018,7 +4051,10 @@ async function slurm(args, context) {
|
|
|
4018
4051
|
"Usage: ornn slurm teardown <reservation-id> [--yes] [--json]"
|
|
4019
4052
|
);
|
|
4020
4053
|
if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
|
|
4021
|
-
const cluster =
|
|
4054
|
+
const cluster = withNumberedClusterName(
|
|
4055
|
+
await teardownCluster(reservationId, "slurm", context),
|
|
4056
|
+
[]
|
|
4057
|
+
);
|
|
4022
4058
|
if (options.json) {
|
|
4023
4059
|
writeJson(context.stdout, cluster);
|
|
4024
4060
|
} else {
|
|
@@ -4034,7 +4070,7 @@ async function slurm(args, context) {
|
|
|
4034
4070
|
{ boolean: ["json"] },
|
|
4035
4071
|
"Usage: ornn slurm status <reservation-id> [--json]"
|
|
4036
4072
|
);
|
|
4037
|
-
const cluster = await fetchCluster(reservationId, "slurm", context);
|
|
4073
|
+
const cluster = withNumberedClusterName(await fetchCluster(reservationId, "slurm", context), []);
|
|
4038
4074
|
if (options.json) {
|
|
4039
4075
|
writeJson(context.stdout, cluster);
|
|
4040
4076
|
} else {
|
|
@@ -4100,9 +4136,16 @@ async function kubernetes(args, context) {
|
|
|
4100
4136
|
const wait = options.wait
|
|
4101
4137
|
? await waitForClusterActive(reservationId, "kubernetes", waitOptions(options), context)
|
|
4102
4138
|
: null;
|
|
4103
|
-
const result = {
|
|
4139
|
+
const result = {
|
|
4140
|
+
cluster: withNumberedClusterName(launch, [launch]),
|
|
4141
|
+
reservation_id: reservationId,
|
|
4142
|
+
type: "kubernetes",
|
|
4143
|
+
wait: wait
|
|
4144
|
+
? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
|
|
4145
|
+
: null,
|
|
4146
|
+
};
|
|
4104
4147
|
if (options.json) {
|
|
4105
|
-
writeJson(context.stdout, result);
|
|
4148
|
+
writeJson(context.stdout, omitClusterName(result));
|
|
4106
4149
|
} else {
|
|
4107
4150
|
writeClusterLaunchResult(context.stdout, result);
|
|
4108
4151
|
}
|
|
@@ -4116,7 +4159,10 @@ async function kubernetes(args, context) {
|
|
|
4116
4159
|
"Usage: ornn kubernetes teardown <reservation-id> [--yes] [--json]"
|
|
4117
4160
|
);
|
|
4118
4161
|
if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
|
|
4119
|
-
const cluster =
|
|
4162
|
+
const cluster = withNumberedClusterName(
|
|
4163
|
+
await teardownCluster(reservationId, "kubernetes", context),
|
|
4164
|
+
[]
|
|
4165
|
+
);
|
|
4120
4166
|
if (options.json) {
|
|
4121
4167
|
writeJson(context.stdout, cluster);
|
|
4122
4168
|
} else {
|
|
@@ -4132,7 +4178,10 @@ async function kubernetes(args, context) {
|
|
|
4132
4178
|
{ boolean: ["json"] },
|
|
4133
4179
|
"Usage: ornn kubernetes status <reservation-id> [--json]"
|
|
4134
4180
|
);
|
|
4135
|
-
const cluster =
|
|
4181
|
+
const cluster = withNumberedClusterName(
|
|
4182
|
+
await fetchCluster(reservationId, "kubernetes", context),
|
|
4183
|
+
[]
|
|
4184
|
+
);
|
|
4136
4185
|
if (options.json) {
|
|
4137
4186
|
writeJson(context.stdout, cluster);
|
|
4138
4187
|
} else {
|
|
@@ -4326,71 +4375,11 @@ async function networks(args, context) {
|
|
|
4326
4375
|
return 0;
|
|
4327
4376
|
}
|
|
4328
4377
|
|
|
4329
|
-
if (subcommand === "reservation"
|
|
4330
|
-
|
|
4331
|
-
rest,
|
|
4332
|
-
{ boolean: ["json"] },
|
|
4333
|
-
"Usage: ornn networks reservation <reservation-id> [--json]"
|
|
4334
|
-
);
|
|
4335
|
-
const payload = await cliRequest({
|
|
4336
|
-
endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
|
|
4337
|
-
env: context.env,
|
|
4338
|
-
fetchImpl: context.fetchImpl,
|
|
4339
|
-
});
|
|
4340
|
-
if (options.json) {
|
|
4341
|
-
writeJson(context.stdout, payload);
|
|
4342
|
-
} else {
|
|
4343
|
-
writeReservationNetwork(context.stdout, payload);
|
|
4344
|
-
}
|
|
4345
|
-
return 0;
|
|
4346
|
-
}
|
|
4347
|
-
|
|
4348
|
-
if (subcommand === "attach" && id) {
|
|
4349
|
-
const options = parseCommandOptions(
|
|
4350
|
-
rest,
|
|
4351
|
-
{ boolean: ["json"], value: ["network", "network-id"] },
|
|
4352
|
-
"Usage: ornn networks attach <reservation-id> --network <network-id> [--json]"
|
|
4353
|
-
);
|
|
4354
|
-
const networkId = requiredOption(options.network || options.networkId, "--network");
|
|
4355
|
-
const payload = await cliRequest({
|
|
4356
|
-
body: { network_id: networkId },
|
|
4357
|
-
endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
|
|
4358
|
-
env: context.env,
|
|
4359
|
-
fetchImpl: context.fetchImpl,
|
|
4360
|
-
method: "POST",
|
|
4361
|
-
});
|
|
4362
|
-
if (options.json) {
|
|
4363
|
-
writeJson(context.stdout, payload);
|
|
4364
|
-
} else {
|
|
4365
|
-
writeColored(context.stdout, "green", "Reservation network attached.");
|
|
4366
|
-
writeReservationNetwork(context.stdout, payload);
|
|
4367
|
-
}
|
|
4368
|
-
return 0;
|
|
4369
|
-
}
|
|
4370
|
-
|
|
4371
|
-
if (subcommand === "detach" && id) {
|
|
4372
|
-
const options = parseCommandOptions(
|
|
4373
|
-
rest,
|
|
4374
|
-
{ boolean: ["json"] },
|
|
4375
|
-
"Usage: ornn networks detach <reservation-id> [--json]"
|
|
4376
|
-
);
|
|
4377
|
-
const payload = await cliRequest({
|
|
4378
|
-
body: { network_id: null },
|
|
4379
|
-
endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
|
|
4380
|
-
env: context.env,
|
|
4381
|
-
fetchImpl: context.fetchImpl,
|
|
4382
|
-
method: "POST",
|
|
4383
|
-
});
|
|
4384
|
-
if (options.json) {
|
|
4385
|
-
writeJson(context.stdout, payload);
|
|
4386
|
-
} else {
|
|
4387
|
-
writeColored(context.stdout, "green", "Reservation network detached.");
|
|
4388
|
-
writeReservationNetwork(context.stdout, payload);
|
|
4389
|
-
}
|
|
4390
|
-
return 0;
|
|
4378
|
+
if (subcommand === "reservation" || subcommand === "attach" || subcommand === "detach") {
|
|
4379
|
+
throw new Error("network_attach_removed");
|
|
4391
4380
|
}
|
|
4392
4381
|
|
|
4393
|
-
throw new Error("Usage: ornn networks list|show|create|update|delete
|
|
4382
|
+
throw new Error("Usage: ornn networks list|show|create|update|delete");
|
|
4394
4383
|
}
|
|
4395
4384
|
|
|
4396
4385
|
function normalizeStorageDestination(value) {
|
|
@@ -6061,6 +6050,8 @@ function inventoryAvailableGpuCount(record) {
|
|
|
6061
6050
|
function normalizeInventoryListing(record) {
|
|
6062
6051
|
const gpuCount = inventoryAvailableGpuCount(record);
|
|
6063
6052
|
const facility = record.site_nickname ?? record.facility ?? record.site ?? "Unknown facility";
|
|
6053
|
+
// Customer availability never exposes real supplier/operator identity.
|
|
6054
|
+
// Display branding stays in the text formatter, not on the JSON envelope.
|
|
6064
6055
|
return {
|
|
6065
6056
|
id: record.id,
|
|
6066
6057
|
kind: "inventory",
|
|
@@ -6855,13 +6846,21 @@ async function resolveStorageMountUsers(attachment, context) {
|
|
|
6855
6846
|
return users.size ? users : null;
|
|
6856
6847
|
}
|
|
6857
6848
|
|
|
6849
|
+
function reservationHostsFromPayload(payload) {
|
|
6850
|
+
if (Array.isArray(payload)) return payload;
|
|
6851
|
+
if (Array.isArray(payload?.nodes)) return payload.nodes;
|
|
6852
|
+
if (Array.isArray(payload?.machines)) return payload.machines;
|
|
6853
|
+
return [];
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6858
6856
|
async function getReservationMachines(reservationId, context) {
|
|
6859
6857
|
const payload = await cliRequest({
|
|
6860
|
-
endpoint: `/v1/workflows/compute/nodes
|
|
6858
|
+
endpoint: `/v1/workflows/compute/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
|
|
6861
6859
|
env: context.env,
|
|
6862
6860
|
fetchImpl: context.fetchImpl,
|
|
6863
6861
|
});
|
|
6864
|
-
|
|
6862
|
+
const nodes = reservationHostsFromPayload(payload);
|
|
6863
|
+
return { ...(payload && typeof payload === "object" ? payload : {}), machines: nodes };
|
|
6865
6864
|
}
|
|
6866
6865
|
|
|
6867
6866
|
async function fetchBillingInvoices(context) {
|
|
@@ -6967,13 +6966,6 @@ async function launchReservationAccess(
|
|
|
6967
6966
|
"--storage-save-drive-id"
|
|
6968
6967
|
);
|
|
6969
6968
|
}
|
|
6970
|
-
const access = await cliRequest({
|
|
6971
|
-
body: { access_mode: mode, image_id: null },
|
|
6972
|
-
endpoint: `/v1/workflows/commerce/reservations/${reservationId}/access-mode`,
|
|
6973
|
-
env: context.env,
|
|
6974
|
-
fetchImpl: context.fetchImpl,
|
|
6975
|
-
method: "POST",
|
|
6976
|
-
});
|
|
6977
6969
|
const launch = await cliRequest({
|
|
6978
6970
|
body: launchPayload,
|
|
6979
6971
|
endpoint: `/v1/workflows/compute/nodes/reservations/${reservationId}/launch`,
|
|
@@ -6992,7 +6984,7 @@ async function launchReservationAccess(
|
|
|
6992
6984
|
? await waitForSshReady(reservationId, waitOptions(options), context)
|
|
6993
6985
|
: null;
|
|
6994
6986
|
return {
|
|
6995
|
-
access,
|
|
6987
|
+
access: launch,
|
|
6996
6988
|
launch,
|
|
6997
6989
|
mode,
|
|
6998
6990
|
opened,
|
|
@@ -7027,7 +7019,7 @@ async function switchReservationAccess(reservationId, options, context) {
|
|
|
7027
7019
|
fetchImpl: context.fetchImpl,
|
|
7028
7020
|
method: "POST",
|
|
7029
7021
|
});
|
|
7030
|
-
const machines =
|
|
7022
|
+
const machines = reservationHostsFromPayload(switched);
|
|
7031
7023
|
const wait = options.wait
|
|
7032
7024
|
? await waitForSshReady(reservationId, waitOptions(options), context)
|
|
7033
7025
|
: null;
|
|
@@ -7045,7 +7037,7 @@ async function switchReservationAccess(reservationId, options, context) {
|
|
|
7045
7037
|
|
|
7046
7038
|
function accessActivateJsonResult(result) {
|
|
7047
7039
|
return {
|
|
7048
|
-
access: result.
|
|
7040
|
+
access: { access_mode: result.mode },
|
|
7049
7041
|
launch: result.launch,
|
|
7050
7042
|
opened: Boolean(result.opened?.opened),
|
|
7051
7043
|
url: result.opened?.url ?? null,
|
|
@@ -7062,11 +7054,11 @@ async function fetchTenantClusters(context) {
|
|
|
7062
7054
|
|
|
7063
7055
|
async function fetchEligibleClusterNodes(reservationId, type, networkMode, context) {
|
|
7064
7056
|
const payload = await cliRequest({
|
|
7065
|
-
endpoint: `/v1/workflows/compute/nodes
|
|
7057
|
+
endpoint: `/v1/workflows/compute/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
|
|
7066
7058
|
env: context.env,
|
|
7067
7059
|
fetchImpl: context.fetchImpl,
|
|
7068
7060
|
});
|
|
7069
|
-
const machines =
|
|
7061
|
+
const machines = reservationHostsFromPayload(payload);
|
|
7070
7062
|
return {
|
|
7071
7063
|
reservation_id: reservationId,
|
|
7072
7064
|
access_mode: type,
|
|
@@ -7803,7 +7795,7 @@ function writeAccessDetail(stdout, payload = {}) {
|
|
|
7803
7795
|
}
|
|
7804
7796
|
|
|
7805
7797
|
function writeAccessLaunchSummary(stdout, launch = {}) {
|
|
7806
|
-
const machines =
|
|
7798
|
+
const machines = reservationHostsFromPayload(launch);
|
|
7807
7799
|
if (!machines.length) {
|
|
7808
7800
|
stdout.write("Provisioning response received.\n");
|
|
7809
7801
|
return;
|
|
@@ -8011,18 +8003,6 @@ function writeNetworkDetail(stdout, network = {}) {
|
|
|
8011
8003
|
writeOptionalStatusLine(stdout, "Updated", network.updated_at);
|
|
8012
8004
|
}
|
|
8013
8005
|
|
|
8014
|
-
function writeReservationNetwork(stdout, payload = {}) {
|
|
8015
|
-
stdout.write(`${payload.reservation_id || "reservation"}\n`);
|
|
8016
|
-
if (!payload.network_id || !payload.network) {
|
|
8017
|
-
stdout.write("Network: shared\n");
|
|
8018
|
-
return;
|
|
8019
|
-
}
|
|
8020
|
-
stdout.write(`Network: ${payload.network.name || payload.network_id}\n`);
|
|
8021
|
-
stdout.write(`Network ID: ${payload.network_id}\n`);
|
|
8022
|
-
writeOptionalStatusLine(stdout, "CIDR", payload.network.cidr);
|
|
8023
|
-
writeOptionalStatusLine(stdout, "Status", payload.network.status);
|
|
8024
|
-
}
|
|
8025
|
-
|
|
8026
8006
|
function writeReservationStorageAttachment(stdout, attachment = {}, options = {}) {
|
|
8027
8007
|
if (!attachment) {
|
|
8028
8008
|
stdout.write("No volume is attached to this reservation.\n");
|
|
@@ -8950,10 +8930,10 @@ function metricSnapshotFromLivePayload(fallbackMachine, payload = {}) {
|
|
|
8950
8930
|
function metricSnapshotFromMachine(machine, extras = {}) {
|
|
8951
8931
|
const resourceMetrics = machine.resource_metrics || machine.resourceMetrics || {};
|
|
8952
8932
|
const lastHeartbeatAt =
|
|
8933
|
+
machine.observed_at ||
|
|
8934
|
+
resourceMetrics.observed_at ||
|
|
8953
8935
|
resourceMetrics.last_heartbeat_at ||
|
|
8954
8936
|
resourceMetrics.lastHeartbeatAt ||
|
|
8955
|
-
machine.last_heartbeat_at ||
|
|
8956
|
-
machine.lastHeartbeatAt ||
|
|
8957
8937
|
null;
|
|
8958
8938
|
return {
|
|
8959
8939
|
connection_status:
|
|
@@ -9033,6 +9013,51 @@ function writeWaitResult(stdout, result) {
|
|
|
9033
9013
|
stdout.write(`SSH: ${result.command}\n`);
|
|
9034
9014
|
}
|
|
9035
9015
|
|
|
9016
|
+
function clusterIdentity(cluster) {
|
|
9017
|
+
return String(cluster?.controller_id || cluster?.id || cluster?.reservation_id || "").trim();
|
|
9018
|
+
}
|
|
9019
|
+
|
|
9020
|
+
function numberedClusterName(clusters, target) {
|
|
9021
|
+
const ordered = [...clusters].sort((left, right) => {
|
|
9022
|
+
const leftCreatedAt = Date.parse(left.created_at ?? "");
|
|
9023
|
+
const rightCreatedAt = Date.parse(right.created_at ?? "");
|
|
9024
|
+
const leftTime = Number.isFinite(leftCreatedAt) ? leftCreatedAt : Number.MAX_SAFE_INTEGER;
|
|
9025
|
+
const rightTime = Number.isFinite(rightCreatedAt) ? rightCreatedAt : Number.MAX_SAFE_INTEGER;
|
|
9026
|
+
return leftTime - rightTime || clusterIdentity(left).localeCompare(clusterIdentity(right));
|
|
9027
|
+
});
|
|
9028
|
+
const index = ordered.findIndex((cluster) => clusterIdentity(cluster) === clusterIdentity(target));
|
|
9029
|
+
return `Cluster ${index >= 0 ? index + 1 : 1}`;
|
|
9030
|
+
}
|
|
9031
|
+
|
|
9032
|
+
function omitClusterName(value) {
|
|
9033
|
+
if (Array.isArray(value)) {
|
|
9034
|
+
return value.map(omitClusterName);
|
|
9035
|
+
}
|
|
9036
|
+
if (!value || typeof value !== "object") {
|
|
9037
|
+
return value;
|
|
9038
|
+
}
|
|
9039
|
+
const { cluster_name: _ignored, ...rest } = value;
|
|
9040
|
+
if ("cluster" in rest) {
|
|
9041
|
+
rest.cluster = omitClusterName(rest.cluster);
|
|
9042
|
+
}
|
|
9043
|
+
if (rest.wait && typeof rest.wait === "object") {
|
|
9044
|
+
rest.wait = omitClusterName(rest.wait);
|
|
9045
|
+
}
|
|
9046
|
+
return rest;
|
|
9047
|
+
}
|
|
9048
|
+
|
|
9049
|
+
function withNumberedClusterName(cluster, siblings) {
|
|
9050
|
+
const rows = siblings.length ? siblings : [cluster];
|
|
9051
|
+
return {
|
|
9052
|
+
...omitClusterName(cluster),
|
|
9053
|
+
name: numberedClusterName(rows, cluster),
|
|
9054
|
+
};
|
|
9055
|
+
}
|
|
9056
|
+
|
|
9057
|
+
function withNumberedClusterNames(clusters) {
|
|
9058
|
+
return clusters.map((cluster) => withNumberedClusterName(cluster, clusters));
|
|
9059
|
+
}
|
|
9060
|
+
|
|
9036
9061
|
function writeClusterList(stdout, clusters) {
|
|
9037
9062
|
if (!clusters.length) {
|
|
9038
9063
|
stdout.write("No clusters found.\n");
|
|
@@ -9067,7 +9092,7 @@ function writeClusterList(stdout, clusters) {
|
|
|
9067
9092
|
stdout.write("Clusters:\n");
|
|
9068
9093
|
for (const cluster of clusters) {
|
|
9069
9094
|
stdout.write(
|
|
9070
|
-
`- ${cluster.reservation_id || cluster.id || "cluster"} ${displayClusterType(cluster.access_mode)} ${cluster.state || "unknown"}`
|
|
9095
|
+
`- ${cluster.name || numberedClusterName(clusters, cluster)} ${cluster.reservation_id || cluster.id || "cluster"} ${displayClusterType(cluster.access_mode)} ${cluster.state || "unknown"}`
|
|
9071
9096
|
);
|
|
9072
9097
|
stdout.write(` ${cluster.node_count ?? "?"} nodes/${cluster.gpu_count ?? "?"} GPUs`);
|
|
9073
9098
|
stdout.write(` network=${cluster.network_mode || "public"}`);
|
|
@@ -9157,13 +9182,13 @@ function writeEligibleClusterNodes(stdout, payload) {
|
|
|
9157
9182
|
|
|
9158
9183
|
function writeClusterDetail(stdout, cluster = {}) {
|
|
9159
9184
|
stdout.write(`${cluster.reservation_id || cluster.id || "cluster"}\n`);
|
|
9185
|
+
stdout.write(`Name: ${cluster.name || numberedClusterName([cluster], cluster)}\n`);
|
|
9160
9186
|
stdout.write(`Type: ${displayClusterType(cluster.access_mode)}\n`);
|
|
9161
9187
|
stdout.write(`State: ${cluster.state || "unknown"}\n`);
|
|
9162
9188
|
stdout.write(`Network: ${cluster.network_mode || "public"}\n`);
|
|
9163
9189
|
stdout.write(`Nodes: ${cluster.node_count ?? "unknown"}\n`);
|
|
9164
9190
|
stdout.write(`GPUs: ${cluster.gpu_count ?? "unknown"}\n`);
|
|
9165
9191
|
writeOptionalStatusLine(stdout, "GPU type", cluster.gpu_type);
|
|
9166
|
-
writeOptionalStatusLine(stdout, "Name", cluster.cluster_name);
|
|
9167
9192
|
writeOptionalStatusLine(stdout, "Endpoint", cluster.endpoint);
|
|
9168
9193
|
writeOptionalStatusLine(stdout, "Namespace", cluster.namespace);
|
|
9169
9194
|
writeOptionalStatusLine(stdout, "Credentials", cluster.credential_state);
|
|
@@ -9635,7 +9660,12 @@ function looksLikePublicKey(value) {
|
|
|
9635
9660
|
function looksLikePath(value) {
|
|
9636
9661
|
const text = String(value || "").trim();
|
|
9637
9662
|
return (
|
|
9638
|
-
text.startsWith("~") ||
|
|
9663
|
+
text.startsWith("~") ||
|
|
9664
|
+
text.startsWith(".") ||
|
|
9665
|
+
text.includes("/") ||
|
|
9666
|
+
text.includes("\\") ||
|
|
9667
|
+
/^[A-Za-z]:/.test(text) ||
|
|
9668
|
+
text.endsWith(".pub")
|
|
9639
9669
|
);
|
|
9640
9670
|
}
|
|
9641
9671
|
|
|
@@ -79,7 +79,7 @@ export const listNodesCapability = defineCapability({
|
|
|
79
79
|
roles: ["user", "reviewer", "admin"],
|
|
80
80
|
description: "List nodes. Users see their own reserved machines. Reviewers and admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional operator_id, facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Never invent nodes. Never print private keys.",
|
|
81
81
|
mutation: "read",
|
|
82
|
-
http: { method: "GET", path: "/
|
|
82
|
+
http: { method: "GET", path: "/nodes" },
|
|
83
83
|
slack: {
|
|
84
84
|
parameters: {
|
|
85
85
|
type: "object",
|
|
@@ -107,7 +107,7 @@ export const listNodesCapability = defineCapability({
|
|
|
107
107
|
if (ctx.role === "user") {
|
|
108
108
|
return ctx.fetch("/nodes", { method: "GET" });
|
|
109
109
|
}
|
|
110
|
-
return ctx.fetch(queryPath("/
|
|
110
|
+
return ctx.fetch(queryPath("/nodes", {
|
|
111
111
|
operator_id: parsed.operator_id ?? parsed.operatorId ?? parsed.operator,
|
|
112
112
|
facility_id: parsed.facility_id ?? parsed.facilityId,
|
|
113
113
|
status: parsed.status,
|