mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
165 lines
5.3 KiB
JavaScript
165 lines
5.3 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { spawnSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
|
|
const ROOT = path.resolve(__dirname, '../..');
|
|
const launcher = path.join(
|
|
ROOT,
|
|
'deploy/console/ql3-cluster-copilot/docker-loopback.sh',
|
|
);
|
|
const image = `ghcr.io/example/qinglong3-cluster-admin@sha256:${'a'.repeat(
|
|
64,
|
|
)}`;
|
|
|
|
function fixture(t) {
|
|
const directory = fs.realpathSync(
|
|
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-copilot-console-launcher-')),
|
|
);
|
|
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
|
const privateRoot = path.join(directory, 'private');
|
|
const bin = path.join(directory, 'bin');
|
|
fs.mkdirSync(privateRoot, { mode: 0o700 });
|
|
fs.mkdirSync(bin, { mode: 0o700 });
|
|
const capture = path.join(directory, 'docker-args');
|
|
fs.writeFileSync(
|
|
path.join(bin, 'docker'),
|
|
'#!/bin/sh\nprintf \'%s\\n\' "$@" > "$QL3_TEST_DOCKER_ARGS"\n',
|
|
{ mode: 0o700 },
|
|
);
|
|
return {
|
|
privateRoot: fs.realpathSync(privateRoot),
|
|
capture,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
QL3_TEST_DOCKER_ARGS: capture,
|
|
QL3_COPILOT_CONSOLE_IMAGE: image,
|
|
QL3_COPILOT_CONSOLE_PRIVATE_ROOT: fs.realpathSync(privateRoot),
|
|
QL3_COPILOT_CONSOLE_NETWORK: 'qinglong3-console-egress',
|
|
QL3_COPILOT_CONSOLE_PORT: '5701',
|
|
QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'compact',
|
|
},
|
|
};
|
|
}
|
|
|
|
function invoke(mode, env) {
|
|
return spawnSync(launcher, [mode], {
|
|
cwd: ROOT,
|
|
env,
|
|
encoding: 'utf8',
|
|
});
|
|
}
|
|
|
|
test('runs compact preflight without opening or publishing the Console', (t) => {
|
|
assert.equal(fs.statSync(launcher).mode & 0o777, 0o755);
|
|
const value = fixture(t);
|
|
const result = invoke('check', value.env);
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout, '');
|
|
assert.equal(result.stderr, '');
|
|
assert.deepEqual(
|
|
fs.readFileSync(value.capture, 'utf8').trimEnd().split('\n'),
|
|
[
|
|
'run',
|
|
'--rm',
|
|
'--pull',
|
|
'never',
|
|
'--init',
|
|
'--read-only',
|
|
'--network',
|
|
'qinglong3-console-egress',
|
|
'--cap-drop',
|
|
'ALL',
|
|
'--security-opt',
|
|
'no-new-privileges',
|
|
'--user',
|
|
'10001:10001',
|
|
'--pids-limit',
|
|
'32',
|
|
'--memory',
|
|
'192m',
|
|
'--cpus',
|
|
'0.25',
|
|
'--stop-timeout',
|
|
'3',
|
|
'--tmpfs',
|
|
'/tmp:rw,noexec,nosuid,nodev,size=8m,mode=700,uid=10001,gid=10001',
|
|
'--mount',
|
|
`type=bind,src=${value.privateRoot},dst=/var/run/secrets/qinglong3/copilot-console,readonly`,
|
|
image,
|
|
'copilot-console',
|
|
'--container-published-loopback',
|
|
'--port=5701',
|
|
'--config',
|
|
'/var/run/secrets/qinglong3/copilot-console/client.json',
|
|
'--credential',
|
|
'/var/run/secrets/qinglong3/copilot-console/credential',
|
|
'--session',
|
|
'/var/run/secrets/qinglong3/copilot-console/session',
|
|
'--check',
|
|
],
|
|
);
|
|
});
|
|
|
|
test('publishes standard serve only on host loopback', (t) => {
|
|
const value = fixture(t);
|
|
const result = invoke('serve', {
|
|
...value.env,
|
|
QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'standard',
|
|
});
|
|
assert.equal(result.status, 0, result.stderr);
|
|
const args = fs.readFileSync(value.capture, 'utf8').trimEnd().split('\n');
|
|
assert.equal(args.includes('--check'), false);
|
|
assert.equal(args[args.indexOf('--memory') + 1], '512m');
|
|
assert.equal(args[args.indexOf('--cpus') + 1], '1');
|
|
assert.equal(args[args.indexOf('--pids-limit') + 1], '64');
|
|
assert.equal(args[args.indexOf('--publish') + 1], '127.0.0.1:5701:5701/tcp');
|
|
});
|
|
|
|
test('adds optional Run management files only after an explicit enabled switch', (t) => {
|
|
const value = fixture(t);
|
|
const result = invoke('check', {
|
|
...value.env,
|
|
QL3_COPILOT_CONSOLE_RUN_MANAGEMENT: 'enabled',
|
|
});
|
|
assert.equal(result.status, 0, result.stderr);
|
|
const args = fs.readFileSync(value.capture, 'utf8').trimEnd().split('\n');
|
|
assert.equal(
|
|
args[args.indexOf('--run-management-config') + 1],
|
|
'/var/run/secrets/qinglong3/copilot-console/run-management-client.json',
|
|
);
|
|
assert.equal(
|
|
args[args.indexOf('--run-management-assertion') + 1],
|
|
'/var/run/secrets/qinglong3/copilot-console/run-management-assertion.jwt',
|
|
);
|
|
});
|
|
|
|
test('rejects mutable, ambient and malformed host inputs before Docker', (t) => {
|
|
const value = fixture(t);
|
|
for (const environment of [
|
|
{ ...value.env, QL3_COPILOT_CONSOLE_IMAGE: 'ghcr.io/example/admin:latest' },
|
|
{ ...value.env, QL3_COPILOT_CONSOLE_NETWORK: 'host' },
|
|
{ ...value.env, QL3_COPILOT_CONSOLE_PORT: '80' },
|
|
{
|
|
...value.env,
|
|
QL3_COPILOT_CONSOLE_PRIVATE_ROOT: `${value.privateRoot}:rw`,
|
|
},
|
|
{ ...value.env, QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'unbounded' },
|
|
{ ...value.env, QL3_COPILOT_CONSOLE_RUN_MANAGEMENT: 'ambient' },
|
|
]) {
|
|
const rejected = invoke('serve', environment);
|
|
assert.equal(rejected.status, 78);
|
|
assert.equal(rejected.stdout, '');
|
|
assert.deepEqual(JSON.parse(rejected.stderr), {
|
|
schemaVersion: 1,
|
|
component: 'qinglong3-cluster-copilot-console-launcher',
|
|
event: 'launch_failed',
|
|
});
|
|
assert.equal(fs.existsSync(value.capture), false);
|
|
assert.doesNotMatch(rejected.stderr, /latest|host|private|unbounded/);
|
|
}
|
|
});
|