fix(ci): close clean runner gaps

This commit is contained in:
whyour
2026-08-22 12:17:11 +08:00
parent 1a4acaa56f
commit ca8ddf34b8
4 changed files with 74 additions and 23 deletions
+5 -1
View File
@@ -45,7 +45,9 @@ jobs:
- name: Install dependencies without lifecycle scripts - name: Install dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts run: pnpm install --frozen-lockfile --ignore-scripts
- name: Initialize the public test environment - name: Initialize the public test environment
run: cp .env.example .env run: |
cp .env.example .env
mkdir -p data/db
- name: Rebuild the reviewed native SQLite binding - name: Rebuild the reviewed native SQLite binding
run: pnpm rebuild @whyour/sqlite3 run: pnpm rebuild @whyour/sqlite3
- name: Build backend and QL3 workspace from source - name: Build backend and QL3 workspace from source
@@ -951,6 +953,8 @@ jobs:
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_runtime LOGIN PASSWORD 'ql3_runtime_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_runtime LOGIN PASSWORD 'ql3_runtime_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_admin LOGIN PASSWORD 'ql3_admin_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_admin LOGIN PASSWORD 'ql3_admin_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_automation_manager LOGIN PASSWORD 'ql3_automation_manager_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_automation_manager LOGIN PASSWORD 'ql3_automation_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_approval_manager LOGIN PASSWORD 'ql3_approval_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_run_manager LOGIN PASSWORD 'ql3_run_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_manager LOGIN PASSWORD 'ql3_package_manager_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_manager LOGIN PASSWORD 'ql3_package_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_executor LOGIN PASSWORD 'ql3_package_executor_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_executor LOGIN PASSWORD 'ql3_package_executor_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_worker_credential_manager LOGIN PASSWORD 'ql3_worker_credential_manager_test'" docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_worker_credential_manager LOGIN PASSWORD 'ql3_worker_credential_manager_test'"
+41 -19
View File
@@ -54,11 +54,21 @@ function docker(args, options = {}) {
} }
function runCertificateTool(directory, args) { function runCertificateTool(directory, args) {
const uid = process.getuid?.();
const gid = process.getgid?.();
if (
!Number.isSafeInteger(uid) ||
uid < 0 ||
!Number.isSafeInteger(gid) ||
gid < 0
) {
throw new Error('cannot resolve the host certificate owner');
}
docker([ docker([
'run', 'run',
'--rm', '--rm',
'--user', '--user',
'0:0', `${uid}:${gid}`,
'--volume', '--volume',
`${directory}:/work`, `${directory}:/work`,
'--workdir', '--workdir',
@@ -187,6 +197,7 @@ function preparePostgresFileOwnership(directory) {
'chown', 'chown',
`${identity[0]}:${identity[1]}`, `${identity[0]}:${identity[1]}`,
'/work/server.key', '/work/server.key',
'/work/active.crt',
]); ]);
} }
@@ -221,24 +232,34 @@ async function waitFor(operation, description, timeoutMs = WAIT_TIMEOUT_MS) {
} }
async function waitForPostgres(containerName) { async function waitForPostgres(containerName) {
await waitFor( try {
() => await waitFor(
docker( () =>
[ docker(
'exec', [
containerName, 'exec',
'pg_isready', containerName,
'-h', 'pg_isready',
'127.0.0.1', '-h',
'-U', '127.0.0.1',
USER, '-U',
'-d', USER,
DATABASE, '-d',
], DATABASE,
{ allowFailure: true }, ],
).status === 0, { allowFailure: true },
'TLS PostgreSQL readiness', ).status === 0,
); 'TLS PostgreSQL readiness',
);
} catch (error) {
const logs = docker(['logs', containerName], { allowFailure: true });
const detail = [logs.stderr, logs.stdout].filter(Boolean).join('\n');
throw new Error(
`${error instanceof Error ? error.message : String(error)}${
detail ? `\nPostgreSQL container logs:\n${detail}` : ''
}`,
);
}
} }
function databaseUrl(port) { function databaseUrl(port) {
@@ -340,6 +361,7 @@ async function expectTlsRejection(
async function reloadCertificate(containerName, directory, generation) { async function reloadCertificate(containerName, directory, generation) {
activateServerCertificate(directory, generation); activateServerCertificate(directory, generation);
preparePostgresFileOwnership(directory);
docker(['kill', '--signal', 'HUP', containerName]); docker(['kill', '--signal', 'HUP', containerName]);
await delay(500); await delay(500);
} }
+14 -3
View File
@@ -39,6 +39,7 @@ test('pins backend CI to the released Node line and bootstraps a clean checkout'
assertOrdered(backend, [ assertOrdered(backend, [
'pnpm install --frozen-lockfile --ignore-scripts', 'pnpm install --frozen-lockfile --ignore-scripts',
'cp .env.example .env', 'cp .env.example .env',
'mkdir -p data/db',
'pnpm rebuild @whyour/sqlite3', 'pnpm rebuild @whyour/sqlite3',
'pnpm build:back', 'pnpm build:back',
'pnpm run build:packages:ql3', 'pnpm run build:packages:ql3',
@@ -47,6 +48,18 @@ test('pins backend CI to the released Node line and bootstraps a clean checkout'
assert.doesNotMatch(backend, /if: matrix\.node == '24'/); assert.doesNotMatch(backend, /if: matrix\.node == '24'/);
}); });
test('provisions every role required by the PostgreSQL migration stream', () => {
const postgres = jobSource('cluster-postgres', 'cluster-postgres-ha');
for (const role of [
'ql3_automation_manager',
'ql3_approval_manager',
'ql3_run_manager',
'ql3_package_manager',
]) {
assert.match(postgres, new RegExp(`CREATE ROLE ${role} LOGIN PASSWORD`));
}
});
test('bootstraps jobs that previously depended on local modules or artifacts', () => { test('bootstraps jobs that previously depended on local modules or artifacts', () => {
const serviceManager = jobSource( const serviceManager = jobSource(
'service-manager-bridge', 'service-manager-bridge',
@@ -99,9 +112,7 @@ test('rebuilds the only native legacy binding used by resource evidence', () =>
}); });
test('does not forward a literal separator into recovery evidence CLIs', () => { test('does not forward a literal separator into recovery evidence CLIs', () => {
const recovery = jobSource( const recovery = jobSource('cluster-plugin-package-recovery-e2e');
'cluster-plugin-package-recovery-e2e',
);
assert.doesNotMatch( assert.doesNotMatch(
recovery, recovery,
/pnpm (?:test|audit):plugin-package-recovery-e2e:ql3 -- \\/, /pnpm (?:test|audit):plugin-package-recovery-e2e:ql3 -- \\/,
@@ -60,7 +60,21 @@ test('builds cluster-postgres before the standalone TLS rotation contract', () =
'pg_stat_ssl', 'pg_stat_ssl',
'pg_is_in_recovery()', 'pg_is_in_recovery()',
"current_setting('transaction_read_only')", "current_setting('transaction_read_only')",
'PostgreSQL container logs:',
]) { ]) {
assert.ok(source.includes(required), `missing TLS evidence: ${required}`); assert.ok(source.includes(required), `missing TLS evidence: ${required}`);
} }
assert.match(source, /const uid = process\.getuid\?\.\(\)/);
assert.match(source, /const gid = process\.getgid\?\.\(\)/);
const certificateTool = source.match(
/function runCertificateTool[\s\S]*?\n}\n/,
)?.[0];
assert.ok(certificateTool);
assert.match(certificateTool, /'--user',\s*`\$\{uid}:\$\{gid}`/);
assert.doesNotMatch(certificateTool, /'0:0'/);
assert.match(source, /'\/work\/server\.key',\s*'\/work\/active\.crt'/);
assert.match(
source,
/activateServerCertificate\(directory, generation\);\s*preparePostgresFileOwnership\(directory\);\s*docker\(\['kill'/,
);
}); });