249 lines
6.8 KiB
JavaScript
249 lines
6.8 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
|
|
const jenkinsfile = readFileSync(
|
|
'jenkins/Jenkinsfile.preview-deployer',
|
|
'utf8',
|
|
);
|
|
const deployer = readFileSync('scripts/jenkins-preview-deployer.sh', 'utf8');
|
|
const resultWriter = readFileSync(
|
|
'scripts/preview-deployment-status.mjs',
|
|
'utf8',
|
|
);
|
|
const compose = readFileSync(
|
|
'deploy/container/docker-compose.loadtest.yml',
|
|
'utf8',
|
|
);
|
|
const webConfig = readFileSync(
|
|
'apps/preview-deployer-web/vite.config.ts',
|
|
'utf8',
|
|
);
|
|
const systemd = readFileSync(
|
|
'deploy/systemd/genarrative-preview-deployer.service',
|
|
'utf8',
|
|
);
|
|
const environmentExample = readFileSync(
|
|
'deploy/env/preview-deployer.env.example',
|
|
'utf8',
|
|
);
|
|
const server = readFileSync(
|
|
'server-rs/crates/preview-deployer-server/src/lib.rs',
|
|
'utf8',
|
|
);
|
|
const nginx = readFileSync(
|
|
'deploy/nginx/genarrative-preview-deployer-lan.conf',
|
|
'utf8',
|
|
);
|
|
const jobConfig = readFileSync(
|
|
'jenkins/preview-deployer-job-config.xml',
|
|
'utf8',
|
|
);
|
|
|
|
const failures = [];
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"choice(name: 'ACTION', choices: ['DEPLOY', 'STATUS', 'UNINSTALL']",
|
|
'Jenkins 参数必须固定为 DEPLOY/STATUS/UNINSTALL。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"string(name: 'SOURCE_BRANCH'",
|
|
'Jenkins 必须暴露 SOURCE_BRANCH。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"string(name: 'COMMIT_HASH'",
|
|
'Jenkins 必须暴露 COMMIT_HASH。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"string(name: 'DEPLOYMENT_ID'",
|
|
'Jenkins 必须暴露 DEPLOYMENT_ID。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"archiveArtifacts artifacts: 'preview-result.json'",
|
|
'Jenkins 必须归档 preview-result.json。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"writeFile file: 'preview-result.json'",
|
|
'Jenkins 失败路径也必须生成 preview-result.json。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
'disableConcurrentBuilds()',
|
|
'Jenkins Job 必须禁止并发构建。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
'COMMIT_HASH="${COMMIT_HASH:-}"',
|
|
'Jenkins Checkout 阶段必须允许 COMMIT_HASH 未导出或为空。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
'bash "${WORKSPACE}/scripts/jenkins-checkout-source.sh"',
|
|
'Jenkins Checkout 阶段必须显式使用 bash 执行源码检出脚本。',
|
|
);
|
|
assertExcludes(
|
|
jenkinsfile,
|
|
'COMMIT_HASH="${COMMIT_HASH}"',
|
|
'Jenkins Checkout 阶段不得在 set -u 下直接展开未导出的 COMMIT_HASH。',
|
|
);
|
|
assertIncludes(deployer, 'flock -x 9', '部署脚本必须使用跨进程独占锁。');
|
|
assertIncludes(
|
|
deployer,
|
|
'git check-ref-format --branch',
|
|
'部署脚本必须校验分支格式。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'genarrative-preview-[0-9a-f]{16}',
|
|
'卸载必须限制到预览 compose project 白名单。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'allocate_port webPort 8400 8499',
|
|
'Web 端口必须从 8400-8499 分配。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'ports: !reset []',
|
|
'预览 compose override 必须取消 SpacetimeDB 和 OTLP 宿主端口映射。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'spacetimedb:\n mem_limit: 2g\n ports: !reset []',
|
|
'预览 SpacetimeDB 必须覆盖压测基线内存限制,避免模块首次加载时 OOM。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'external-generation-worker:\n build:',
|
|
'预览 compose override 必须配置外部生成 worker。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'restart: on-failure',
|
|
'预览外部生成 worker 必须在运行时身份初始化竞态后自动重启。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'^preview-[0-9a-f]{16}$',
|
|
'部署 ID 必须固定为 preview- 加 16 位摘要。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'state.active === true && String(state[key]) === expected',
|
|
'只有活动实例才能占用端口租约。',
|
|
);
|
|
assertIncludes(
|
|
deployer,
|
|
'--yes=remote,migrate,break-clients',
|
|
'SpacetimeDB 预览发布必须显式确认受控容器网络目标,且不得默认删除实例数据。',
|
|
);
|
|
assertExcludes(
|
|
deployer,
|
|
'--delete-data',
|
|
'预览部署不得使用 SpacetimeDB 删除数据参数。',
|
|
);
|
|
assertIncludes(
|
|
jenkinsfile,
|
|
"GENARRATIVE_PREVIEW_WEB_HOST = '192.168.35.82'",
|
|
'Jenkins 必须固定输出可供同事访问的内网 Web 主机地址。',
|
|
);
|
|
assertIncludes(webConfig, "?? '/build/'", 'SPA 默认 base 必须固定为 /build/。');
|
|
assertIncludes(
|
|
systemd,
|
|
'User=jenkins',
|
|
'控制服务必须使用 Jenkins 运行用户以访问受控构建状态。',
|
|
);
|
|
assertIncludes(
|
|
nginx,
|
|
'location ^~ /build/',
|
|
'内网 Nginx 必须提供 /build/ 路由。',
|
|
);
|
|
assertIncludes(
|
|
server,
|
|
'.nest_service("/build/assets", ServeDir::new(static_dir.join("assets")))',
|
|
'控制服务必须原生托管 /build 子路径,不能依赖 Nginx 隐式改写。',
|
|
);
|
|
assertIncludes(
|
|
server,
|
|
'FAILED_RECORD_TTL_SECS',
|
|
'控制服务必须清理过期且不可卸载的失败/取消记录。',
|
|
);
|
|
assertIncludes(
|
|
server,
|
|
'STOPPED_RECORD_TTL_SECS',
|
|
'控制服务必须为已卸载记录配置有限审计保留期。',
|
|
);
|
|
assertIncludes(
|
|
environmentExample,
|
|
'GENARRATIVE_PREVIEW_DEPLOYER_JENKINS_PUBLIC_BASE_URL=http://192.168.35.82:8080/jenkins/',
|
|
'构建详情必须使用局域网 Jenkins 地址而非 loopback。',
|
|
);
|
|
assertIncludes(
|
|
jobConfig,
|
|
'<scriptPath>jenkins/Jenkinsfile.preview-deployer</scriptPath>',
|
|
'Jenkins Job 必须从受保护 master 读取固定流水线。',
|
|
);
|
|
assertIncludes(
|
|
jobConfig,
|
|
'<hudson.model.ChoiceParameterDefinition>',
|
|
'Jenkins Job 首次触发前必须已经声明 buildWithParameters 参数。',
|
|
);
|
|
assertIncludes(
|
|
nginx,
|
|
'location ^~ /api/preview-deployer/',
|
|
'内网 Nginx 必须代理预览控制 API。',
|
|
);
|
|
assertIncludes(
|
|
resultWriter,
|
|
'schemaVersion: 1',
|
|
'结果文件必须带版本化 schema。',
|
|
);
|
|
assertIncludes(resultWriter, 'healthStatus', '结果文件必须提供健康状态。');
|
|
assertIncludes(resultWriter, 'webUrl', '结果文件必须提供 Web URL。');
|
|
assertIncludes(
|
|
resultWriter,
|
|
'resolvedCommit',
|
|
'结果文件必须提供 resolvedCommit。',
|
|
);
|
|
assertIncludes(
|
|
resultWriter,
|
|
'status: publicStatus(phase)',
|
|
'结果文件必须提供小写 status 契约。',
|
|
);
|
|
assertIncludes(
|
|
resultWriter,
|
|
'health: publicHealth(healthStatus)',
|
|
'结果文件必须提供小写 health 契约。',
|
|
);
|
|
assertIncludes(
|
|
compose,
|
|
'test: ["CMD", "wget", "-qO-", "http://127.0.0.1/"]',
|
|
'Nginx 容器健康检查必须验证 Web 首页。',
|
|
);
|
|
assertExcludes(
|
|
compose,
|
|
'test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]',
|
|
'Nginx 容器健康检查不能访问公开拒绝的 /healthz。',
|
|
);
|
|
|
|
if (failures.length > 0) {
|
|
for (const failure of failures)
|
|
console.error(`[check:preview-deployer] ${failure}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(
|
|
'[check:preview-deployer] Jenkins 参数、隔离部署、卸载白名单、结果契约和 Nginx 健康检查通过。',
|
|
);
|
|
|
|
function assertIncludes(content, expected, message) {
|
|
if (!content.includes(expected)) failures.push(message);
|
|
}
|
|
|
|
function assertExcludes(content, expected, message) {
|
|
if (content.includes(expected)) failures.push(message);
|
|
}
|