feat(ql3): add unified cluster operator cli

This commit is contained in:
whyour
2026-08-12 21:42:41 +08:00
parent bd0621c9f2
commit 2934123155
18 changed files with 1010 additions and 5 deletions
@@ -0,0 +1,142 @@
#!/usr/bin/env node
import { spawn, type ChildProcess } from 'node:child_process';
import { constants } from 'node:os';
import { resolveQingLong3ClusterProductCommand } from './productCommand';
const FORWARDED_SIGNALS = Object.freeze([
'SIGINT',
'SIGTERM',
'SIGHUP',
] as const);
export interface QingLong3ClusterProductSignalChild {
readonly exitCode: number | null;
readonly signalCode: NodeJS.Signals | null;
kill(signal: NodeJS.Signals): boolean;
}
export interface QingLong3ClusterProductSignalHost {
on(signal: NodeJS.Signals, handler: () => void): unknown;
off(signal: NodeJS.Signals, handler: () => void): unknown;
}
export function clusterProductSignalExitCode(
signal: NodeJS.Signals | null,
): number {
if (signal === null) return 1;
const number = constants.signals[signal];
return typeof number === 'number' ? 128 + number : 1;
}
function lowSensitivityFailure(
code: string,
message: string,
): Readonly<Record<string, string | number>> {
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-cluster-product-cli',
code,
message,
});
}
export function forwardClusterProductSignals(
child: QingLong3ClusterProductSignalChild,
signalHost: QingLong3ClusterProductSignalHost = process,
): () => void {
const handlers = FORWARDED_SIGNALS.map((signal) => {
const handler = (): void => {
if (child.exitCode === null && child.signalCode === null) {
child.kill(signal);
}
};
signalHost.on(signal, handler);
return Object.freeze({ signal, handler });
});
return () => {
for (const { signal, handler } of handlers) {
signalHost.off(signal, handler);
}
};
}
function invoke(targetFilePath: string, argv: readonly string[]): void {
let child: ChildProcess;
try {
child = spawn(process.execPath, [targetFilePath, ...argv], {
stdio: 'inherit',
env: process.env,
shell: false,
windowsHide: true,
});
} catch {
process.stderr.write(
`${JSON.stringify(
lowSensitivityFailure(
'QL3_CLUSTER_PRODUCT_COMMAND_START_FAILED',
'QingLong 3.0 Cluster product command could not start',
),
)}\n`,
);
process.exitCode = 1;
return;
}
const removeSignalHandlers = forwardClusterProductSignals(child);
let settled = false;
const settle = (exitCode: number): void => {
if (settled) return;
settled = true;
removeSignalHandlers();
process.exitCode = exitCode;
};
child.once('error', () => {
process.stderr.write(
`${JSON.stringify(
lowSensitivityFailure(
'QL3_CLUSTER_PRODUCT_COMMAND_START_FAILED',
'QingLong 3.0 Cluster product command could not start',
),
)}\n`,
);
settle(1);
});
child.once('close', (code, signal) => {
settle(code ?? clusterProductSignalExitCode(signal));
});
}
function main(argv: readonly string[]): void {
try {
const resolution = resolveQingLong3ClusterProductCommand(argv, __dirname);
if (resolution.kind === 'help' || resolution.kind === 'version') {
process.stdout.write(`${resolution.output}\n`);
return;
}
if (resolution.kind === 'invalid') {
process.stderr.write(
`${JSON.stringify(
lowSensitivityFailure(resolution.code, resolution.message),
)}\n`,
);
process.exitCode = 64;
return;
}
invoke(resolution.targetFilePath, resolution.argv);
} catch {
process.stderr.write(
`${JSON.stringify(
lowSensitivityFailure(
'QL3_CLUSTER_PRODUCT_CLI_INSTALLATION_INVALID',
'QingLong 3.0 Cluster product command installation is invalid',
),
)}\n`,
);
process.exitCode = 1;
}
}
if (require.main === module) {
main(process.argv.slice(2));
}
@@ -0,0 +1,218 @@
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
import { isAbsolute, relative, resolve, sep } from 'node:path';
export interface QingLong3ClusterProductCommandDefinition {
readonly name: string;
readonly binary: string;
readonly target: string;
readonly description: string;
}
export type QingLong3ClusterProductCommandResolution =
| Readonly<{ kind: 'help'; output: string }>
| Readonly<{ kind: 'version'; output: string }>
| Readonly<{
kind: 'invoke';
command: QingLong3ClusterProductCommandDefinition;
targetFilePath: string;
argv: readonly string[];
}>
| Readonly<{
kind: 'invalid';
code: 'QL3_CLUSTER_PRODUCT_CLI_USAGE_INVALID';
message: string;
}>;
const PACKAGE_NAME = '@qinglong/cluster-admin';
const MAXIMUM_PACKAGE_MANIFEST_BYTES = 64 * 1024;
const SEMVER_PATTERN =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
export const QINGLONG3_CLUSTER_PRODUCT_COMMANDS: readonly QingLong3ClusterProductCommandDefinition[] =
Object.freeze([
Object.freeze({
name: 'package',
binary: 'ql3-plugin-package-client',
target: 'plugin-package/management/pluginPackageManagementClientCli.js',
description: 'manage Plugin Packages through the authenticated API',
}),
Object.freeze({
name: 'package-kubernetes',
binary: 'ql3-plugin-package-client-kubernetes',
target:
'plugin-package/management/pluginPackageManagementKubernetesClientCli.js',
description: 'manage Plugin Packages through a bounded Kubernetes tunnel',
}),
Object.freeze({
name: 'worker-credential',
binary: 'ql3-worker-credential-client',
target: 'worker-credential/workerCredentialManagementClientCli.js',
description: 'manage Worker credentials through the authenticated API',
}),
Object.freeze({
name: 'approval',
binary: 'ql3-approval-client',
target: 'approval-management/approvalManagementClientCli.js',
description: 'inspect and decide human approvals',
}),
Object.freeze({
name: 'run',
binary: 'ql3-run-client',
target: 'run-management/runManagementClientCli.js',
description: 'retry or stop Runs under strong authentication',
}),
Object.freeze({
name: 'automation',
binary: 'ql3-automation-client',
target: 'automation-management/automationManagementClientCli.js',
description: 'manage Task and Trigger definitions',
}),
Object.freeze({
name: 'model-credential',
binary: 'ql3-provider-credential-client',
target:
'model-provider-credential/modelProviderCredentialManagementClientCli.js',
description: 'manage model provider credentials',
}),
]);
function installationPaths(moduleDirectory: string): Readonly<{
distRoot: string;
packageRoot: string;
packageManifestPath: string;
}> {
const distRoot = resolve(moduleDirectory, '..');
const packageRoot = resolve(distRoot, '..');
return Object.freeze({
distRoot,
packageRoot,
packageManifestPath: resolve(packageRoot, 'package.json'),
});
}
function isInside(parent: string, candidate: string): boolean {
const pathFromParent = relative(parent, candidate);
return (
pathFromParent !== '' &&
pathFromParent !== '..' &&
!pathFromParent.startsWith(`..${sep}`) &&
!isAbsolute(pathFromParent)
);
}
function resolveInstalledTarget(
distRoot: string,
definition: QingLong3ClusterProductCommandDefinition,
): string {
const lexicalTarget = resolve(distRoot, definition.target);
if (!isInside(distRoot, lexicalTarget)) {
throw new Error('Cluster product command target escapes package dist root');
}
const targetStatus = lstatSync(lexicalTarget, { throwIfNoEntry: false });
if (
targetStatus === undefined ||
!targetStatus.isFile() ||
targetStatus.isSymbolicLink()
) {
throw new Error('Cluster product command target is unavailable');
}
const canonicalDistRoot = realpathSync(distRoot);
const canonicalTarget = realpathSync(lexicalTarget);
if (!isInside(canonicalDistRoot, canonicalTarget)) {
throw new Error(
'Cluster product command target escapes canonical package root',
);
}
return lexicalTarget;
}
export function loadQingLong3ClusterProductVersion(
moduleDirectory: string,
): string {
const { packageRoot, packageManifestPath } =
installationPaths(moduleDirectory);
const status = lstatSync(packageManifestPath, { throwIfNoEntry: false });
if (
status === undefined ||
!status.isFile() ||
status.isSymbolicLink() ||
status.size <= 0 ||
status.size > MAXIMUM_PACKAGE_MANIFEST_BYTES ||
realpathSync(packageManifestPath) !==
resolve(realpathSync(packageRoot), 'package.json')
) {
throw new Error('Cluster product package manifest is unavailable');
}
const manifest = JSON.parse(readFileSync(packageManifestPath, 'utf8')) as {
readonly name?: unknown;
readonly version?: unknown;
};
if (
manifest.name !== PACKAGE_NAME ||
typeof manifest.version !== 'string' ||
!SEMVER_PATTERN.test(manifest.version)
) {
throw new Error('Cluster product package identity is invalid');
}
return manifest.version;
}
export function qingLong3ClusterProductHelp(): string {
const longestName = Math.max(
...QINGLONG3_CLUSTER_PRODUCT_COMMANDS.map(({ name }) => name.length),
);
const commands = QINGLONG3_CLUSTER_PRODUCT_COMMANDS.map(
({ name, description }) => ` ${name.padEnd(longestName)} ${description}`,
).join('\n');
return [
'Usage: ql3-cluster-admin <command> [arguments]',
'',
'Remote client commands:',
commands,
'',
'Use `ql3-cluster-admin <command> --help` for command-specific usage.',
'Server, migration, recovery, executor and key-custody authorities remain isolated.',
].join('\n');
}
export function resolveQingLong3ClusterProductCommand(
argv: readonly string[],
moduleDirectory: string,
): QingLong3ClusterProductCommandResolution {
if (
argv.length === 0 ||
(argv.length === 1 &&
(argv[0] === '--help' || argv[0] === '-h' || argv[0] === 'help'))
) {
return Object.freeze({
kind: 'help',
output: qingLong3ClusterProductHelp(),
});
}
if (
argv.length === 1 &&
(argv[0] === '--version' || argv[0] === '-V' || argv[0] === 'version')
) {
return Object.freeze({
kind: 'version',
output: loadQingLong3ClusterProductVersion(moduleDirectory),
});
}
const command = QINGLONG3_CLUSTER_PRODUCT_COMMANDS.find(
(candidate) => candidate.name === argv[0],
);
if (command === undefined) {
return Object.freeze({
kind: 'invalid',
code: 'QL3_CLUSTER_PRODUCT_CLI_USAGE_INVALID',
message: 'unknown QingLong 3.0 Cluster product command',
});
}
const { distRoot } = installationPaths(moduleDirectory);
return Object.freeze({
kind: 'invoke',
command,
targetFilePath: resolveInstalledTarget(distRoot, command),
argv: Object.freeze(argv.slice(1)),
});
}