From 45b5fe8f6f7d68061fa56ed8ec402c010fedcdf2 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 6 Nov 2026 13:59:16 +0800 Subject: [PATCH] fix(ci): reject TEST revisions without memory protection --- Jenkinsfile.service | 8 +++ ...est-environment-pipeline-contract.test.mjs | 60 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile.service b/Jenkinsfile.service index 7693a915d..fd3348739 100644 --- a/Jenkinsfile.service +++ b/Jenkinsfile.service @@ -125,6 +125,14 @@ pipeline { git cat-file -e "${RESOLVED_SOURCE_COMMIT}^{commit}" git checkout --detach "${RESOLVED_SOURCE_COMMIT}" test "$(git rev-parse HEAD)" = "$(git rev-parse "${RESOLVED_SOURCE_COMMIT}^{commit}")" + # TEST 重发旧提交也须包含限额文件及部署入口,避免重建时恢复无限制容器。 + if [ "$RESOLVED_ENVIRONMENT" = "test" ]; then + if ! git cat-file -e "${RESOLVED_SOURCE_COMMIT}:deploy/docker-compose.test-limits.yml" 2>/dev/null || + ! git grep -q -F 'docker-compose.test-limits.yml' "$RESOLVED_SOURCE_COMMIT" -- deploy/scripts/deploy-built-service.sh; then + echo "目标 TEST 提交缺少完整内存保护,请选择包含限额修复的 test 提交" >&2 + exit 1 + fi + fi if [ "$RESOLVED_ENVIRONMENT" = "production" ]; then git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main git merge-base --is-ancestor "$RESOLVED_SOURCE_COMMIT" origin/main || { diff --git a/scripts/test-environment-pipeline-contract.test.mjs b/scripts/test-environment-pipeline-contract.test.mjs index 9ad321f05..392624169 100644 --- a/scripts/test-environment-pipeline-contract.test.mjs +++ b/scripts/test-environment-pipeline-contract.test.mjs @@ -1,5 +1,9 @@ import assert from 'node:assert/strict' -import { readFile } from 'node:fs/promises' +import { existsSync } from 'node:fs' +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' import test from 'node:test' const pipeline = (await readFile(new URL('../Jenkinsfile.service', import.meta.url), 'utf8')) @@ -15,6 +19,60 @@ const serviceDockerfile = (await readFile(new URL('../deploy/docker/Dockerfile', const serviceCompose = (await readFile(new URL('../deploy/docker-compose.yml', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') +test('TEST rejects old source commits even when the workspace contains newer limits files', { + skip: process.platform === 'win32' && !existsSync('C:/Program Files/Git/bin/bash.exe') +}, async () => { + const directory = await mkdtemp(join(tmpdir(), 'test-memory-source-')) + const bash = process.platform === 'win32' ? 'C:/Program Files/Git/bin/bash.exe' : 'bash' + const guardStart = pipeline.indexOf('if [ "$RESOLVED_ENVIRONMENT" = "test" ]; then') + const guardEnd = pipeline.indexOf('if [ "$RESOLVED_ENVIRONMENT" = "production" ]; then', guardStart) + assert.ok(guardStart >= 0 && guardEnd > guardStart) + assert.ok(guardStart < pipeline.indexOf("stage('Maven 测试与打包')")) + const guard = pipeline.slice(guardStart, guardEnd) + // 在微型 Git 夹具中验证目标提交本身,避免工作区残留文件误放行旧版本。 + const git = (...args) => { + const result = spawnSync('git', args, { cwd: directory, encoding: 'utf8' }) + assert.equal(result.status, 0, result.stderr) + return result.stdout.trim() + } + // 构建提交沿用正常身份设置,但禁止夹具触发全局签名及提交钩子。 + const commit = (message) => { + git('add', '.') + git('-c', 'user.name=Resource Contract', '-c', 'user.email=resource@example.invalid', + '-c', 'core.hooksPath=/dev/null', 'commit', '--no-gpg-sign', '-m', message) + return git('rev-parse', 'HEAD') + } + try { + git('init') + await writeFile(join(directory, 'README.md'), 'Source guard fixture\n') + const oldCommit = commit('old source') + await mkdir(join(directory, 'deploy/scripts'), { recursive: true }) + await writeFile(join(directory, 'deploy/docker-compose.test-limits.yml'), 'services: {}\n') + await writeFile(join(directory, 'deploy/scripts/deploy-built-service.sh'), '#!/bin/sh\nexit 0\n') + const incompleteCommit = commit('limits file without deployment integration') + await writeFile(join(directory, 'deploy/scripts/deploy-built-service.sh'), directDeployScript) + const protectedCommit = commit('complete memory protection') + git('checkout', '--detach', oldCommit) + await mkdir(join(directory, 'deploy/scripts'), { recursive: true }) + await writeFile(join(directory, 'deploy/docker-compose.test-limits.yml'), 'services: {}\n') + await writeFile(join(directory, 'deploy/scripts/deploy-built-service.sh'), directDeployScript) + for (const [environment, sourceCommit, status] of [ + ['test', oldCommit, 1], ['test', incompleteCommit, 1], + ['test', protectedCommit, 0], ['production', oldCommit, 0] + ]) { + const result = spawnSync(bash, ['-c', guard], { + cwd: directory, encoding: 'utf8', timeout: 10000, + env: { ...process.env, RESOLVED_ENVIRONMENT: environment, RESOLVED_SOURCE_COMMIT: sourceCommit } + }) + assert.equal(result.status, status, result.stderr) + if (status !== 0) assert.match(result.stderr, /目标 TEST 提交缺少完整内存保护/) + } + } finally { + assert.equal(dirname(resolve(directory)), resolve(tmpdir())) + await rm(directory, { recursive: true, force: true }) + } +}) + test('both pipelines reject the placeholder registry from failed build 662 before Maven or Docker', () => { for (const source of [coordinatorPipeline, pipeline]) { assert.match(source, /string\(name: 'IMAGE_REPOSITORY', defaultValue: params\.IMAGE_REPOSITORY \?: ''/,