a7711d2dc3
新增 pingora-gateway 独立二进制 crate,覆盖路由、静态资源、压缩、接流保护、TLS 直连和访问日志能力。 新增 Nginx canary、realpath canary、direct preflight、direct live、direct enable 和 rollback 脚本。 新增 Pingora 切流证据包、命令证据、manifest 验真、根目录总审计和 release readiness 聚合门禁。 完善 API release、Jenkins、systemd、health patrol、生产部署和发布包自包含校验。 更新 Pingora 试点文档、Nginx README 与 Hermes 共享记忆。
174 lines
4.9 KiB
JavaScript
174 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from 'node:child_process';
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
const BUILD_SCRIPT = 'scripts/build-production-release.sh';
|
|
const failures = [];
|
|
const tmpRoot = mkdtempSync(
|
|
path.join(tmpdir(), 'genarrative-pingora-production-release-build-'),
|
|
);
|
|
|
|
try {
|
|
main();
|
|
} finally {
|
|
rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error('[check:pingora-production-release-build] FAILED');
|
|
for (const failure of failures) {
|
|
console.error(`- ${failure}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('[check:pingora-production-release-build] OK');
|
|
|
|
function main() {
|
|
const fixture = prepareFixture();
|
|
const result = runBuild(fixture);
|
|
assertStatus(result, 0, 'Pingora production release 真实构建烟测应成功。');
|
|
if (result.status !== 0) {
|
|
return;
|
|
}
|
|
|
|
const releaseDir = path.join(process.cwd(), 'build', fixture.version);
|
|
try {
|
|
assertFileExists(
|
|
path.join(releaseDir, 'api-server'),
|
|
'发布包必须包含 api-server 占位 release binary。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'api-server.sha256'),
|
|
'发布包必须包含 api-server checksum。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'pingora-gateway'),
|
|
'真实 include Pingora 时发布包必须包含 pingora-gateway。',
|
|
);
|
|
assertExecutable(
|
|
path.join(releaseDir, 'pingora-gateway'),
|
|
'pingora-gateway 必须保留可执行权限。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'pingora-gateway.sha256'),
|
|
'真实 include Pingora 时发布包必须包含 pingora-gateway checksum。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'scripts/check-pingora-direct-live.mjs'),
|
|
'真实 include Pingora 的发布包仍必须携带 direct live smoke 脚本。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'scripts/ops/pingora-current-release-audit.mjs'),
|
|
'真实 include Pingora 的发布包仍必须携带 current release 自审脚本。',
|
|
);
|
|
assertFileExists(
|
|
path.join(releaseDir, 'deploy/pingora/nginx-route-parity.matrix.json'),
|
|
'真实 include Pingora 的发布包仍必须携带 Nginx/Pingora 路由矩阵。',
|
|
);
|
|
|
|
const manifest = readJson(path.join(releaseDir, 'release-manifest.json'));
|
|
if (manifest.component_type !== 'api-server') {
|
|
failures.push(
|
|
`release manifest component_type 应为 api-server,实际 ${manifest.component_type}`,
|
|
);
|
|
}
|
|
if (!manifest.artifacts?.some((item) => item.path === 'api-server')) {
|
|
failures.push('release manifest 必须登记 api-server artifact。');
|
|
}
|
|
if (!manifest.artifacts?.some((item) => item.path === 'pingora-gateway')) {
|
|
failures.push(
|
|
'真实 include Pingora 时 release manifest 必须登记 pingora-gateway artifact。',
|
|
);
|
|
}
|
|
} finally {
|
|
rmSync(releaseDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function prepareFixture() {
|
|
const cargoTargetDir = path.join(tmpRoot, 'cargo-target');
|
|
const binaryDir = path.join(
|
|
cargoTargetDir,
|
|
'x86_64-unknown-linux-gnu/release',
|
|
);
|
|
const version = `check-pingora-production-release-build-${process.pid}-${Date.now()}`;
|
|
mkdirSync(binaryDir, { recursive: true });
|
|
|
|
const apiBinary = path.join(binaryDir, 'api-server');
|
|
writeFileSync(apiBinary, '#!/usr/bin/env bash\nexit 0\n', 'utf8');
|
|
spawnSync('chmod', ['0755', apiBinary], { encoding: 'utf8' });
|
|
|
|
return { cargoTargetDir, version };
|
|
}
|
|
|
|
function runBuild(fixture) {
|
|
return spawnSync(
|
|
'bash',
|
|
[
|
|
BUILD_SCRIPT,
|
|
'--component',
|
|
'api-server',
|
|
'--name',
|
|
fixture.version,
|
|
'--skip-api-build',
|
|
'--include-pingora-gateway',
|
|
],
|
|
{
|
|
cwd: process.cwd(),
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
CARGO_TARGET_DIR: fixture.cargoTargetDir,
|
|
PATH: `${path.join(process.env.HOME || '', '.local', 'bin')}:${process.env.PATH || ''}`,
|
|
SOURCE_BRANCH: 'test-branch',
|
|
SOURCE_COMMIT: 'test-commit',
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
function readJson(filePath) {
|
|
try {
|
|
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
} catch (error) {
|
|
failures.push(`${filePath} 不是合法 JSON: ${error.message}`);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function assertFileExists(filePath, reason) {
|
|
if (!existsSync(filePath)) {
|
|
failures.push(`${reason} 缺少: ${filePath}`);
|
|
}
|
|
}
|
|
|
|
function assertExecutable(filePath, reason) {
|
|
if (!existsSync(filePath)) {
|
|
return;
|
|
}
|
|
if ((statSync(filePath).mode & 0o111) === 0) {
|
|
failures.push(`${reason} 文件不可执行: ${filePath}`);
|
|
}
|
|
}
|
|
|
|
function assertStatus(result, expected, reason) {
|
|
const actual = result.status ?? 0;
|
|
if (actual !== expected) {
|
|
failures.push(
|
|
`${reason} 预期退出码 ${expected},实际 ${actual}。\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
);
|
|
}
|
|
}
|