Rename design r2, "Selector scopes and parser matrix". packages/business/src/selector.mjs and scripts/selector.sh carry one rule set: in a parser's scope each spelling appears once, and a second spelling must carry exactly the same value; a repeated spelling, a missing or flag-shaped value, an invalid id and unequal values refuse. The file family (--business-file/--org-file) compares paths as given. Every CLI parser that took --business now takes --org through orgSelector, and the old global --org rewrite in main() is gone, so a value another option consumes is never read as a selector. The id is checked while parsing (OrgError org-invalid, exit 3), so grants admin set no longer reads the token and proxy init no longer prompts for the key before an invalid id refuses. access gains the same local id check.
58 lines
3.1 KiB
JavaScript
58 lines
3.1 KiB
JavaScript
// `mosaic hosts` (row 234, #1740, r6 E4): the broker's hosts.status, hosts.check and hosts.remove. The
|
|
// human runs all three. With --tester the tester's session runs them. check logs in as mosaic-installer
|
|
// and runs `list` on the host, then marks the host ready. remove drops a host no grant uses. Nothing here
|
|
// reads a key: the answers carry fingerprints only. Row 235 (#1741, r6 section 6, X3) adds revoke, which
|
|
// takes an agent's grant on a host away at once, and sweep, which removes the lines a failure left.
|
|
import { CliError } from "./errors.mjs";
|
|
import { orgSelector } from "./org-selector.mjs";
|
|
|
|
export const HOSTS_USAGE =
|
|
"mosaic hosts [status] [--json] [--tester] [--business ID] | check|remove|sweep HOST [--tester] [--business ID] | revoke AGENT HOST --reason R [--tester] [--business ID]";
|
|
|
|
function parse(args) {
|
|
const out = { positional: [], flags: new Set(), values: {} };
|
|
// --org and --business are one selector in this parser's scope (rename design r2).
|
|
const selector = orgSelector(() => new CliError(HOSTS_USAGE, 4));
|
|
for (let i = 0; i < args.length; i++) {
|
|
const arg = args[i];
|
|
if (selector.owns(arg)) { selector.take(arg, args[i + 1]); i++; }
|
|
else if (arg === "--json" || arg === "--tester") out.flags.add(arg);
|
|
else if (arg === "--reason") {
|
|
if (!args[i + 1] || Object.hasOwn(out.values, arg)) throw new CliError(HOSTS_USAGE, 4);
|
|
out.values[arg] = args[++i];
|
|
} else if (arg.startsWith("--")) throw new CliError(HOSTS_USAGE, 4);
|
|
else out.positional.push(arg);
|
|
}
|
|
if (selector.given) out.values["--business"] = selector.value;
|
|
return out;
|
|
}
|
|
|
|
const line = (h) =>
|
|
[h.host, h.state, `${h.user}@${h.address}:${h.port}`, h.sudo ? "sudo" : "no-sudo", h.checkedAt ?? "unchecked",
|
|
...(h.hostLineLeft.length ? [`line left: ${h.hostLineLeft.join(" ")}`] : []),
|
|
...(h.revoking?.length ? [`revoking: ${h.revoking.join(" ")}`] : [])].join("\t");
|
|
|
|
export async function hostsCommand(args, io, callFor) {
|
|
const parsed = parse(args);
|
|
const [verb = "status", first, second, ...extra] = parsed.positional;
|
|
const one = first !== undefined && second === undefined;
|
|
const shape = { status: first === undefined, check: one, remove: one, sweep: one, revoke: second !== undefined };
|
|
// N10: own keys only, so `constructor` or `toString` is a usage error, not a verb.
|
|
if (extra.length || !Object.hasOwn(shape, verb) || !shape[verb] || (parsed.flags.has("--json") && verb !== "status") ||
|
|
(verb === "revoke") !== Object.hasOwn(parsed.values, "--reason"))
|
|
throw new CliError(HOSTS_USAGE, 4);
|
|
try {
|
|
const call = callFor(parsed);
|
|
if (verb === "status") {
|
|
const rows = await call("hosts.status", {});
|
|
io.stdout.write(parsed.flags.has("--json") ? JSON.stringify(rows, null, 2) + "\n" : rows.map(line).join("\n") + (rows.length ? "\n" : ""));
|
|
return;
|
|
}
|
|
const args = verb === "revoke" ? { agent: first, host: second, reason: parsed.values["--reason"] } : { host: first };
|
|
io.stdout.write(JSON.stringify(await call(`hosts.${verb}`, args), null, 2) + "\n");
|
|
} catch (error) {
|
|
if (error instanceof CliError) throw error;
|
|
throw new CliError(error.message, 2);
|
|
}
|
|
}
|