@ornncompute/cli 1.0.122 → 1.0.123
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 +8 -0
- package/package.json +1 -1
- package/src/admin-node-diagnostics.mjs +2 -4
- package/src/admin-node-lifecycle.mjs +4 -8
- package/src/admin-ssh.mjs +2 -1
- package/src/api-client.mjs +5 -2
- package/src/evidence-commands.mjs +10 -6
- package/src/interaction-runtime.mjs +2 -0
- package/src/machine-reference.mjs +7 -17
- package/src/vm-commands.mjs +796 -427
- package/vendor/api-client/orchestrator.js +231 -1
- package/vendor/capabilities/cli.js +266 -209
package/README.md
CHANGED
|
@@ -69,6 +69,10 @@ The CLI uses Authorization's newest membership on every request, regardless of
|
|
|
69
69
|
approval. `ornn context` displays that current organization; login and launch
|
|
70
70
|
use the same context. Reserve browsing and purchasing still require approval.
|
|
71
71
|
|
|
72
|
+
Node and VM requests use `@ornn/api-client/orchestrator`; CLI selection, polling,
|
|
73
|
+
and SSH remain here. VM readiness comes from the guest resource, separately from
|
|
74
|
+
physical-host readiness. See [machine behavior](../../docs/cli.md#customer-workflows).
|
|
75
|
+
|
|
72
76
|
Advanced customer resources are registered under one noun and one verb:
|
|
73
77
|
|
|
74
78
|
```text
|
|
@@ -271,6 +275,10 @@ Global interaction modifiers are `--format`, `--guided`, `--wait`,
|
|
|
271
275
|
as `--erase-local-data` or `--delete-node-record`. Machine retries require a
|
|
272
276
|
caller-stable idempotency key and reconcile against durable owner state.
|
|
273
277
|
|
|
278
|
+
Access-mode changes preserve the reported network mode and require an
|
|
279
|
+
explicit `--network` when it is unreported. A same-mode request stays read-only,
|
|
280
|
+
including its bounded wait; an existing request key reconciles its owner receipt.
|
|
281
|
+
|
|
274
282
|
Permanent deletion and teardown requests execute directly when explicitly
|
|
275
283
|
requested. Private networks, detached volumes, and
|
|
276
284
|
Kubernetes or Slurm clusters use their command arguments to express the exact
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as orchestrator from "../vendor/api-client/orchestrator.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
|
|
@@ -140,10 +141,7 @@ function reviewFields(target) {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
async function prepareTarget(input, runtime, fetch) {
|
|
143
|
-
const payload = await fetch
|
|
144
|
-
method: "GET",
|
|
145
|
-
...(runtime.signal ? { signal: runtime.signal } : {}),
|
|
146
|
-
});
|
|
144
|
+
const payload = await orchestrator.getNode(fetch, input.node_id, runtime.signal);
|
|
147
145
|
return nodeContext(payload, input.node_id);
|
|
148
146
|
}
|
|
149
147
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as orchestrator from "../vendor/api-client/orchestrator.js";
|
|
1
2
|
import { getReservation } from "../vendor/api-client/commerce.js";
|
|
2
3
|
import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
|
|
3
4
|
import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
|
|
@@ -210,10 +211,7 @@ function isNotFound(error) {
|
|
|
210
211
|
|
|
211
212
|
async function readNodeById(fetch, id, { allowMissing = false, signal } = {}) {
|
|
212
213
|
try {
|
|
213
|
-
return await fetch
|
|
214
|
-
method: "GET",
|
|
215
|
-
...(signal ? { signal } : {}),
|
|
216
|
-
});
|
|
214
|
+
return await orchestrator.getNode(fetch, id, signal);
|
|
217
215
|
} catch (error) {
|
|
218
216
|
if (allowMissing && isNotFound(error)) return null;
|
|
219
217
|
throw error;
|
|
@@ -238,7 +236,7 @@ async function resolveNode(fetch, reference, { allowMissing = false, machine = f
|
|
|
238
236
|
});
|
|
239
237
|
}
|
|
240
238
|
const folded = reference.toLocaleLowerCase("en-US");
|
|
241
|
-
const matches = nodeRows(await fetch
|
|
239
|
+
const matches = nodeRows(await orchestrator.listStaffNodes(fetch)).filter((node) => {
|
|
242
240
|
const names = [node?.hostname, node?.k8s_node_name]
|
|
243
241
|
.map(text)
|
|
244
242
|
.filter(Boolean)
|
|
@@ -282,9 +280,7 @@ async function resolveLiveMachine(fetch, node) {
|
|
|
282
280
|
);
|
|
283
281
|
}
|
|
284
282
|
const rows = machineRows(
|
|
285
|
-
await fetch
|
|
286
|
-
method: "GET",
|
|
287
|
-
})
|
|
283
|
+
await orchestrator.listStaffNodes(fetch, { reservation_id: reservation })
|
|
288
284
|
);
|
|
289
285
|
const refs = new Set([id, text(node?.id), text(node?.k8s_node_name)].filter(Boolean));
|
|
290
286
|
const matches = rows.filter((machine) => refs.has(text(machine?.node_id)));
|
package/src/admin-ssh.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as orchestrator from "../vendor/api-client/orchestrator.js";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
2
3
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { constants, tmpdir } from "node:os";
|
|
@@ -21,7 +22,7 @@ function run(program, args, signal) {
|
|
|
21
22
|
|
|
22
23
|
export async function openAdminSsh({ input, fetch, execute, runtime }, { runProcess = run } = {}) {
|
|
23
24
|
// Read the existing node route; issuance returns only its own certificate.
|
|
24
|
-
const node = await fetch
|
|
25
|
+
const node = await orchestrator.getNode(fetch, input.node_id);
|
|
25
26
|
const host = node.ip;
|
|
26
27
|
const port = node.port ?? 22;
|
|
27
28
|
if (
|
package/src/api-client.mjs
CHANGED
|
@@ -251,9 +251,12 @@ export function resolveInstallerBaseUrl({ env = process.env } = {}) {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
function operatorProxyEndpoint(endpoint) {
|
|
254
|
-
// Shared clients use canonical staff or
|
|
254
|
+
// Shared clients use canonical staff or owner paths. Keep the existing
|
|
255
255
|
// staff envelope, including its admin-role and impersonation checks.
|
|
256
|
-
const path = normalizeComputePath(endpoint).replace(
|
|
256
|
+
const path = normalizeComputePath(endpoint).replace(
|
|
257
|
+
/^\/v1\/(?:staff|benchmark|orchestrator)(?=\/|$)/,
|
|
258
|
+
""
|
|
259
|
+
);
|
|
257
260
|
return `/v1/staff${path}`;
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as gatewayApi from "../vendor/api-client/gateway.js";
|
|
2
2
|
import { gpuLabeler } from "./gpu-catalog.mjs";
|
|
3
3
|
import { clusterFactLabel, reservationFactsForCluster } from "./kubernetes-commands.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
canonicalMachineId as machineId,
|
|
6
|
+
listOwnedActiveMachines,
|
|
7
|
+
resolveMachineReference,
|
|
8
|
+
} from "./machine-reference.mjs";
|
|
5
9
|
import { machineReachability } from "./vm-commands.mjs";
|
|
6
10
|
|
|
7
11
|
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;
|
|
@@ -106,11 +110,6 @@ function containsUnsafeFields(value) {
|
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
function machineId(value) {
|
|
110
|
-
const source = record(value);
|
|
111
|
-
return text(source.id) ?? text(source.machine_id) ?? text(source.instance_id);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
113
|
function reservationId(value) {
|
|
115
114
|
const source = record(value);
|
|
116
115
|
return text(source.reservation_id) ?? text(source.reservationId);
|
|
@@ -250,6 +249,11 @@ function lifecycle(value, kind) {
|
|
|
250
249
|
if (["failed", "error", "unavailable"].includes(state)) {
|
|
251
250
|
return { class: "failed", phase: "failed" };
|
|
252
251
|
}
|
|
252
|
+
if (state === "bare_metal") {
|
|
253
|
+
return source.health_readiness?.ready === true
|
|
254
|
+
? { class: "ready", phase: "active" }
|
|
255
|
+
: { class: "working", phase: "provisioning" };
|
|
256
|
+
}
|
|
253
257
|
if (["active", "online", "ready", "running", "provisioned"].includes(state)) {
|
|
254
258
|
return { class: "ready", phase: kind === "cluster" ? "access_ready" : "active" };
|
|
255
259
|
}
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
|
|
4
4
|
import { resolveAuthBaseUrl } from "./device-auth.mjs";
|
|
5
|
+
import { CliApiError } from "./api-client.mjs";
|
|
5
6
|
|
|
6
7
|
const BOOLEAN_OPTIONS = new Set([
|
|
7
8
|
"acknowledge-retained-os",
|
|
@@ -53,6 +54,7 @@ export class InteractionError extends Error {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export function isRecoverableWaitFailure(error) {
|
|
57
|
+
if (error instanceof CliApiError && error.status === undefined) return true;
|
|
56
58
|
const status = Number(error?.status);
|
|
57
59
|
if (status === 401 || status === 403 || (Number.isFinite(status) && status >= 500)) {
|
|
58
60
|
return true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as orchestrator from "../vendor/api-client/orchestrator.js";
|
|
1
2
|
import { getCliSession } from "../vendor/api-client/authorization.js";
|
|
2
3
|
import { listAllReservations } from "../vendor/api-client/commerce.js";
|
|
3
4
|
import { gpuLabeler } from "./gpu-catalog.mjs";
|
|
@@ -139,8 +140,9 @@ export async function listActiveDirectReservations(context, interaction) {
|
|
|
139
140
|
);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
export async function listOwnedActiveMachines(context, interaction) {
|
|
143
|
-
const reservations =
|
|
143
|
+
export async function listOwnedActiveMachines(context, interaction, activeReservations) {
|
|
144
|
+
const reservations =
|
|
145
|
+
activeReservations ?? (await listActiveDirectReservations(context, interaction));
|
|
144
146
|
const pages = await Promise.all(
|
|
145
147
|
reservations.map(async (reservation) => {
|
|
146
148
|
const ref = reservationId(reservation);
|
|
@@ -150,21 +152,13 @@ export async function listOwnedActiveMachines(context, interaction) {
|
|
|
150
152
|
"The gateway returned a capacity reservation without a canonical identifier."
|
|
151
153
|
);
|
|
152
154
|
}
|
|
153
|
-
const payload = await context.fetch
|
|
154
|
-
`/v1/workflows/compute/nodes/reservations/${encodeURIComponent(ref)}/machines`,
|
|
155
|
-
{ method: "GET" }
|
|
156
|
-
);
|
|
155
|
+
const payload = await orchestrator.listNodes(context.fetch, { reservation_id: ref });
|
|
157
156
|
const machineRows = rows(payload, "machines");
|
|
158
157
|
if (!machineRows)
|
|
159
158
|
invalidGateway(interaction, "The gateway returned an invalid machine collection.");
|
|
160
159
|
return machineRows
|
|
161
160
|
.map(record)
|
|
162
|
-
.filter((machine) => !TERMINAL_MACHINE_STATES.has(machineState(machine)))
|
|
163
|
-
.map((machine) => ({
|
|
164
|
-
...machine,
|
|
165
|
-
reservation_id:
|
|
166
|
-
text(machine.reservation_id) ?? text(machine.commerce_reservation_id) ?? ref,
|
|
167
|
-
}));
|
|
161
|
+
.filter((machine) => !TERMINAL_MACHINE_STATES.has(machineState(machine)));
|
|
168
162
|
})
|
|
169
163
|
);
|
|
170
164
|
|
|
@@ -185,11 +179,7 @@ export async function listOwnedActiveMachines(context, interaction) {
|
|
|
185
179
|
|
|
186
180
|
async function fetchCanonicalMachine(reference, context, interaction, { allowMiss = false } = {}) {
|
|
187
181
|
try {
|
|
188
|
-
const machine = record(
|
|
189
|
-
await context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(reference)}`, {
|
|
190
|
-
method: "GET",
|
|
191
|
-
})
|
|
192
|
-
);
|
|
182
|
+
const machine = record(await orchestrator.getNode(context.fetch, reference));
|
|
193
183
|
if (canonicalMachineId(machine)?.toLowerCase() !== reference.toLowerCase()) {
|
|
194
184
|
invalidGateway(
|
|
195
185
|
interaction,
|