Files
qinglong/scripts/ql3-local-compose-preflight-live-contract.cjs
T

190 lines
5.8 KiB
JavaScript

#!/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 {
writeSyntheticLocalReleaseSelection,
} = require('./lib/ql3-local-release-selection-test-fixture.cjs');
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();
const releaseSelection = writeSyntheticLocalReleaseSelection({
directory: temporaryRoot,
image: input.image,
allowRootService: uid === 0,
sourceRevision: process.env.GITHUB_SHA,
});
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',
releaseSelection,
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 !== 44 ||
(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;
});