mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 10:32:40 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const IMAGE_PATTERN =
|
||||
/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}@sha256:[0-9a-f]{64}$/;
|
||||
|
||||
function fail(message) {
|
||||
throw new Error(`QingLong local Compose preflight failed: ${message}`);
|
||||
}
|
||||
|
||||
function argumentsValue(argv) {
|
||||
const values = Object.fromEntries(
|
||||
argv.map((argument) => {
|
||||
const separator = argument.indexOf('=');
|
||||
if (separator < 3 || !argument.startsWith('--')) {
|
||||
fail('arguments must use --name=value');
|
||||
}
|
||||
return [argument.slice(2, separator), argument.slice(separator + 1)];
|
||||
}),
|
||||
);
|
||||
if (
|
||||
Object.keys(values).length !== argv.length ||
|
||||
!IMAGE_PATTERN.test(values.image ?? '') ||
|
||||
(values.profile !== undefined &&
|
||||
values.profile !== 'edge' &&
|
||||
values.profile !== 'standalone') ||
|
||||
typeof values['docker-executable'] !== 'string' ||
|
||||
typeof values['docker-socket'] !== 'string'
|
||||
) {
|
||||
fail(
|
||||
'usage: --image=repository@sha256:digest --docker-executable=/absolute/docker --docker-socket=/absolute/docker.sock [--profile=edge|standalone]',
|
||||
);
|
||||
}
|
||||
const dockerExecutable = fs.realpathSync(values['docker-executable']);
|
||||
const dockerSocket = fs.realpathSync(values['docker-socket']);
|
||||
if (
|
||||
dockerExecutable !== values['docker-executable'] ||
|
||||
dockerSocket !== values['docker-socket']
|
||||
) {
|
||||
fail('Docker executable and socket must be canonical paths');
|
||||
}
|
||||
return Object.freeze({
|
||||
image: values.image,
|
||||
profile: values.profile ?? 'edge',
|
||||
dockerExecutable,
|
||||
dockerSocket,
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (process.versions.node.split('.')[0] !== '24') {
|
||||
fail('Node 24 is required');
|
||||
}
|
||||
if (
|
||||
typeof process.getuid !== 'function' ||
|
||||
typeof process.getgid !== 'function'
|
||||
) {
|
||||
fail('a POSIX identity is required');
|
||||
}
|
||||
const input = argumentsValue(process.argv.slice(2));
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const { prepareLocalDeployment } = require(path.join(
|
||||
root,
|
||||
'packages/ql3-local-owner-cli/dist/deployment/localDeployment.js',
|
||||
));
|
||||
const temporaryRoot = fs.realpathSync(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-compose-preflight-')),
|
||||
);
|
||||
fs.chmodSync(temporaryRoot, 0o700);
|
||||
const deploymentRoot = path.join(temporaryRoot, 'deployment');
|
||||
const commandPath = path.join(temporaryRoot, 'preflight.json');
|
||||
const uid = process.getuid();
|
||||
|
||||
try {
|
||||
const setup = await prepareLocalDeployment({
|
||||
schemaVersion: 1,
|
||||
operation: 'local.deployment.prepare',
|
||||
options: {
|
||||
deploymentRoot,
|
||||
profile: input.profile,
|
||||
instanceId: 'compose-preflight-live',
|
||||
busyTimeoutMs: 100,
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image: input.image,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
},
|
||||
request: {
|
||||
ownerPepperKeyId: 'owner-v1',
|
||||
registerMutationId: '019f8680-143d-4000-8000-000000000101',
|
||||
activateMutationId: '019f8680-143d-4000-8000-000000000102',
|
||||
registeredAtMs: 1_785_254_500_000,
|
||||
activatedAtMs: 1_785_254_500_001,
|
||||
},
|
||||
});
|
||||
if (setup.status !== 'prepared' || setup.profile !== input.profile) {
|
||||
fail('fresh deployment did not prepare');
|
||||
}
|
||||
const command = {
|
||||
schemaVersion: 1,
|
||||
operation: 'local.deployment.compose.preflight',
|
||||
options: {
|
||||
deploymentRoot,
|
||||
dockerExecutable: input.dockerExecutable,
|
||||
dockerSocketPath: input.dockerSocket,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
request: {
|
||||
expectedGeneration: 1,
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(commandPath, `${JSON.stringify(command)}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
const cli = path.join(
|
||||
root,
|
||||
'packages/ql3-local-owner-cli/dist/deployment/localDeploymentCli.js',
|
||||
);
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[cli, 'compose-preflight', '--command-file', commandPath],
|
||||
{
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 64 * 1024,
|
||||
timeout: 45_000,
|
||||
killSignal: 'SIGKILL',
|
||||
},
|
||||
);
|
||||
if (
|
||||
result.error ||
|
||||
result.status !== 0 ||
|
||||
result.stderr !== '' ||
|
||||
typeof result.stdout !== 'string' ||
|
||||
result.stdout.includes(deploymentRoot) ||
|
||||
/sha256|mutation|socket|executable/i.test(result.stdout)
|
||||
) {
|
||||
fail('private CLI did not return a low-sensitive ready result');
|
||||
}
|
||||
const report = JSON.parse(result.stdout);
|
||||
if (
|
||||
report.schemaVersion !== 1 ||
|
||||
report.operation !== 'local.deployment.compose.preflight' ||
|
||||
report.status !== 'ready' ||
|
||||
report.generation !== 1 ||
|
||||
report.profile !== input.profile ||
|
||||
report.sqlite?.contractVersion !== 43 ||
|
||||
(report.image?.architecture !== 'amd64' &&
|
||||
report.image?.architecture !== 'arm64') ||
|
||||
report.service?.kind !== 'compose'
|
||||
) {
|
||||
fail('preflight report is incompatible');
|
||||
}
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
profile: report.profile,
|
||||
generation: report.generation,
|
||||
sqliteContractVersion: report.sqlite.contractVersion,
|
||||
architecture: report.image.architecture,
|
||||
exactRepoDigest: true,
|
||||
composeMerge: true,
|
||||
compatible: true,
|
||||
})}\n`,
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(
|
||||
`${error instanceof Error ? error.message : String(error)}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user