@ornncompute/cli 0.1.5 → 0.1.6
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 +13 -7
- package/package.json +1 -1
- package/src/cli.mjs +40 -11
package/README.md
CHANGED
|
@@ -56,9 +56,9 @@ ornn nodes keys list <node-id> [--json]
|
|
|
56
56
|
ornn node health <node-id> [--json]
|
|
57
57
|
ornn node diagnose <node-id> [--json]
|
|
58
58
|
ornn node deenroll <node-id> [--reason <text>] [--keep-record] [--json]
|
|
59
|
-
ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--ssh-user ubuntu|admin|ornn] [--json]
|
|
60
|
-
ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run [--json]
|
|
61
|
-
ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash> [--json]
|
|
59
|
+
ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... [--ssh-user ubuntu|admin|ornn] [--json]
|
|
60
|
+
ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... [--json]
|
|
61
|
+
ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash> [--source-user <user>]... [--preserve-user <user>]... [--json]
|
|
62
62
|
ornn fleet enroll <fleet-id> --identity-file <path> [--confirm-takeover <ip[,ip...]>] [--json]
|
|
63
63
|
ornn fleet deploy <fleet-id> --tenant <contact-email> --user <email-or-id> [--commerce-reservation <id>] [--network public|private] [--json]
|
|
64
64
|
ornn ssh <node-or-reservation-id> [--print] [--identity-file <path>] [--user <name>] [--json]
|
|
@@ -212,11 +212,17 @@ Fleet handoff is an ordered, Compute-backed workflow:
|
|
|
212
212
|
not trust a key first presented during cleanup.
|
|
213
213
|
- `fleet clean ... --dry-run` tries `ubuntu`, `admin`, then `ornn`, records each
|
|
214
214
|
host's hardware identity and exact deletion plan, and prints one fleet plan
|
|
215
|
-
hash.
|
|
215
|
+
hash. Repeat `--source-user` for Linux accounts owned by the departing tenant
|
|
216
|
+
(including an explicitly reviewed orphaned `/home/<user>` directory) and
|
|
217
|
+
`--preserve-user` for reviewed host accounts. Package/service-owned
|
|
218
|
+
accounts are protected automatically; any other account stops planning for
|
|
219
|
+
explicit review. Re-running with `--confirm-clean <plan-hash>` and the same
|
|
220
|
+
account flags removes the approved tenant
|
|
216
221
|
users, keys, containers/images/volumes/networks/build cache, workload paths,
|
|
217
|
-
mounts, services, and logs
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
mounts, services, and tenant-scoped logs. Apply refuses plans with running
|
|
223
|
+
containers, locks the management/root passwords, enforces key-only SSH, trims
|
|
224
|
+
supported filesystems, preserves existing root and management keys and host
|
|
225
|
+
logs, and stores per-node proof in Compute. A failed
|
|
220
226
|
cleanup can be dry-run again with the same fleet ID; only failed members are
|
|
221
227
|
replanned. Residual GPU
|
|
222
228
|
consumers or tenant/platform listeners fail verification instead of being
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -130,9 +130,9 @@ Usage:
|
|
|
130
130
|
ornn tokens list [--json]
|
|
131
131
|
ornn tokens create --operator <id> [--facility <id>] [--expires-in <seconds>] [--mode bare-metal|vm] [--ip <addr>] [--force] [--json]
|
|
132
132
|
ornn tokens revoke <token-id> [--json]
|
|
133
|
-
ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--ssh-user ubuntu|admin|ornn] [--json]
|
|
134
|
-
ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run [--json]
|
|
135
|
-
ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash> [--json]
|
|
133
|
+
ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... [--ssh-user ubuntu|admin|ornn] [--json]
|
|
134
|
+
ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... [--json]
|
|
135
|
+
ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash> [--source-user <user>]... [--preserve-user <user>]... [--json]
|
|
136
136
|
ornn fleet enroll <fleet-id> --identity-file <path> [--confirm-takeover <ip[,ip...]>] [--json]
|
|
137
137
|
ornn fleet deploy <fleet-id> --tenant <email> --user <email-or-id> [--commerce-reservation <id>] [--network public|private] [--json]
|
|
138
138
|
ornn reservations withdraw <reservation-id> [--json]
|
|
@@ -2193,6 +2193,18 @@ const FLEET_DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
2193
2193
|
const FLEET_RESULT_ERROR_MAX_LENGTH = 4000;
|
|
2194
2194
|
const FLEET_MANAGEMENT_USERS = ["ubuntu", "admin", "ornn"];
|
|
2195
2195
|
|
|
2196
|
+
function fleetAccountOptions(value, optionName) {
|
|
2197
|
+
if (!optionProvided(value)) return [];
|
|
2198
|
+
const values = Array.isArray(value) ? value : [value];
|
|
2199
|
+
const normalized = values.map((entry) => String(entry || "").trim());
|
|
2200
|
+
for (const user of normalized) {
|
|
2201
|
+
if (!/^[a-z_][a-z0-9_-]{0,30}\$?$/i.test(user)) {
|
|
2202
|
+
throw new Error(`${optionName} must be a valid Linux account name.`);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
return [...new Set(normalized)].sort();
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2196
2208
|
async function fleet(args, context) {
|
|
2197
2209
|
const [subcommand, ...rest] = args;
|
|
2198
2210
|
if (subcommand === "clean") {
|
|
@@ -2209,7 +2221,7 @@ async function fleet(args, context) {
|
|
|
2209
2221
|
|
|
2210
2222
|
async function fleetClean(args, context) {
|
|
2211
2223
|
const usage =
|
|
2212
|
-
"Usage: ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--ssh-user ubuntu|admin|ornn] OR ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run OR ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash>";
|
|
2224
|
+
"Usage: ornn fleet clean <ip>... --operator <id-or-slug> --ib-island <name> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... [--ssh-user ubuntu|admin|ornn] OR ornn fleet clean <failed-fleet-id> --identity-file <path> --dry-run [--source-user <user>]... [--preserve-user <user>]... OR ornn fleet clean <fleet-id> --identity-file <path> --confirm-clean <plan-hash> [--source-user <user>]... [--preserve-user <user>]...";
|
|
2213
2225
|
const { options, positionals } = parseOptions(args, {
|
|
2214
2226
|
boolean: ["dry-run", "json"],
|
|
2215
2227
|
value: [
|
|
@@ -2217,6 +2229,8 @@ async function fleetClean(args, context) {
|
|
|
2217
2229
|
"ib-island",
|
|
2218
2230
|
"identity-file",
|
|
2219
2231
|
"confirm-clean",
|
|
2232
|
+
"source-user",
|
|
2233
|
+
"preserve-user",
|
|
2220
2234
|
"ssh-user",
|
|
2221
2235
|
"ssh-port",
|
|
2222
2236
|
"parallel",
|
|
@@ -2240,6 +2254,12 @@ async function fleetClean(args, context) {
|
|
|
2240
2254
|
const timeoutSeconds = optionProvided(options.timeout)
|
|
2241
2255
|
? positiveIntegerOption(options.timeout, "--timeout")
|
|
2242
2256
|
: FLEET_DEFAULT_TIMEOUT_SECONDS;
|
|
2257
|
+
const sourceUsers = fleetAccountOptions(options.sourceUser, "--source-user");
|
|
2258
|
+
const preserveUsers = fleetAccountOptions(options.preserveUser, "--preserve-user");
|
|
2259
|
+
const accountOverlap = sourceUsers.filter((user) => preserveUsers.includes(user));
|
|
2260
|
+
if (accountOverlap.length) {
|
|
2261
|
+
throw new Error(`Users cannot be both source and preserved: ${accountOverlap.join(", ")}.`);
|
|
2262
|
+
}
|
|
2243
2263
|
const confirmHash = optionalStringOption(options.confirmClean);
|
|
2244
2264
|
if (confirmHash) {
|
|
2245
2265
|
if (positionals.length !== 1 || options.dryRun || options.operator || options.ibIsland) {
|
|
@@ -2250,7 +2270,11 @@ async function fleetClean(args, context) {
|
|
|
2250
2270
|
}
|
|
2251
2271
|
const fleetId = positionals[0];
|
|
2252
2272
|
const approved = await operatorRequest({
|
|
2253
|
-
body: {
|
|
2273
|
+
body: {
|
|
2274
|
+
plan_hash: confirmHash,
|
|
2275
|
+
source_users: sourceUsers,
|
|
2276
|
+
preserve_users: preserveUsers,
|
|
2277
|
+
},
|
|
2254
2278
|
endpoint: `/internal/fleets/${encodeURIComponent(fleetId)}/cleanup/approve`,
|
|
2255
2279
|
env: context.env,
|
|
2256
2280
|
fetchImpl: context.fetchImpl,
|
|
@@ -2415,21 +2439,22 @@ async function fleetClean(args, context) {
|
|
|
2415
2439
|
requestedUser: requestedUser || node.ssh_user || null,
|
|
2416
2440
|
timeoutSeconds: Math.min(timeoutSeconds, 30),
|
|
2417
2441
|
});
|
|
2442
|
+
const accountArguments = [
|
|
2443
|
+
...sourceUsers.map((user) => `--source-user ${shellQuote(user)}`),
|
|
2444
|
+
...preserveUsers.map((user) => `--preserve-user ${shellQuote(user)}`),
|
|
2445
|
+
].join(" ");
|
|
2418
2446
|
const result = await runFleetSshCapture({
|
|
2419
2447
|
context,
|
|
2420
2448
|
identityFile,
|
|
2421
2449
|
ip: node.ip_address,
|
|
2422
2450
|
port: sshPort,
|
|
2423
|
-
remoteCommand: `sudo -n python3 - plan --management-ip ${node.ip_address} --management-user ${sshUser} --management-public-key-b64 ${managementPublicKeyB64}`,
|
|
2451
|
+
remoteCommand: `sudo -n python3 - plan --management-ip ${node.ip_address} --management-user ${sshUser} --management-public-key-b64 ${managementPublicKeyB64}${accountArguments ? ` ${accountArguments}` : ""}`,
|
|
2424
2452
|
stdin: runner.script,
|
|
2425
2453
|
timeoutSeconds,
|
|
2426
2454
|
user: sshUser,
|
|
2427
2455
|
});
|
|
2428
|
-
if (result.exitCode !== 0) {
|
|
2429
|
-
throw new Error(result.stderr || "Cleanup plan failed.");
|
|
2430
|
-
}
|
|
2431
2456
|
const payload = fleetCleanupJson(result.stdout);
|
|
2432
|
-
if (!payload.plan || !payload.plan_hash) {
|
|
2457
|
+
if (result.exitCode !== 0 || !payload.plan || !payload.plan_hash) {
|
|
2433
2458
|
throw new Error(payload.error || result.stderr || "Cleanup plan failed.");
|
|
2434
2459
|
}
|
|
2435
2460
|
return {
|
|
@@ -2484,6 +2509,10 @@ async function fleetClean(args, context) {
|
|
|
2484
2509
|
`${planningError} Fleet ${fleetRecord.id} was marked failed, but its local manifest could not be saved: ${fleetResultError(manifestError)}`,
|
|
2485
2510
|
);
|
|
2486
2511
|
}
|
|
2512
|
+
if (options.json) {
|
|
2513
|
+
writeJson(context.stdout, { error: planningError, fleet: failedFleet });
|
|
2514
|
+
return 1;
|
|
2515
|
+
}
|
|
2487
2516
|
throw new Error(`${planningError} Fleet ${fleetRecord.id} was marked failed.`);
|
|
2488
2517
|
}
|
|
2489
2518
|
await saveFleetManifest(recorded.fleet, context.env);
|
|
@@ -2499,7 +2528,7 @@ async function fleetClean(args, context) {
|
|
|
2499
2528
|
}
|
|
2500
2529
|
context.stdout.write(`Cleanup plan: ${recorded.plan_hash}\n`);
|
|
2501
2530
|
context.stdout.write(
|
|
2502
|
-
`Approve exactly this plan with: ornn fleet clean ${fleetRecord.id} --identity-file ${shellQuote(identityFile)} --confirm-clean ${recorded.plan_hash}\n`
|
|
2531
|
+
`Approve exactly this plan with: ornn fleet clean ${fleetRecord.id} --identity-file ${shellQuote(identityFile)} --confirm-clean ${recorded.plan_hash}${sourceUsers.map((user) => ` --source-user ${shellQuote(user)}`).join("")}${preserveUsers.map((user) => ` --preserve-user ${shellQuote(user)}`).join("")}\n`
|
|
2503
2532
|
);
|
|
2504
2533
|
}
|
|
2505
2534
|
return 0;
|