diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e529d58..82794b8 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -9,7 +9,7 @@ name: build on: push: - branches: [ main ] + branches: [ main, codex/core-rc-20261023 ] workflow_dispatch: inputs: cache_bust: @@ -42,6 +42,8 @@ jobs: git clone --depth 1 --branch main "http://x-access-token:${GITHUB_TOKEN}@gitea.yunhaixingtong.com/${{ github.repository }}.git" /tmp/src git -C /tmp/src fetch --depth 1 origin "$GITHUB_SHA" git -C /tmp/src checkout --detach FETCH_HEAD + test "$(git -C /tmp/src rev-parse HEAD)" = "$GITHUB_SHA" + echo "CI_SOURCE_SHA=$GITHUB_SHA" cp -a /tmp/src/. . rm -rf /tmp/src # 取提交备注首行,脱去换行/双引号/反斜杠(避免破坏 webhook JSON),截断到 200 字符 @@ -80,7 +82,19 @@ jobs: docker exec "$BUILD_CONTAINER" sh -c "printf '%s' 'nexus-reponexus-internalhttp://nexus:8081/repository/maven-public/truetruealwaysnexus-repo' > /root/.m2/settings.xml" # 为真实 MySQL 测试创建本次 job 独占的临时数据库;不映射宿主端口、不连接业务库。 DB_CONTAINER="${BUILD_CONTAINER}-db" - trap 'docker rm -f "$BUILD_CONTAINER" "$DB_CONTAINER" >/dev/null 2>&1 || true' EXIT + cleanup_ci() { + result=$? + trap - EXIT + docker rm -fv "$BUILD_CONTAINER" "$DB_CONTAINER" >/dev/null 2>&1 || true + docker info >/dev/null 2>&1 || { echo 'CI_CONTAINER_CLEANUP=UNVERIFIED' >&2; exit 1; } + if docker inspect "$BUILD_CONTAINER" >/dev/null 2>&1 || docker inspect "$DB_CONTAINER" >/dev/null 2>&1; then + echo 'CI_CONTAINER_CLEANUP=FAILED' >&2 + exit 1 + fi + echo 'CI_CONTAINER_CLEANUP=PASS' + exit "$result" + } + trap cleanup_ci EXIT export MYSQL_ROOT_PASSWORD=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n') export MYSQL_PASSWORD=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n') docker create --name "$DB_CONTAINER" --network infra-infra-net \ @@ -96,6 +110,17 @@ jobs: [ "$ready" -eq 1 ] || { echo 'Isolated CI MySQL did not become ready' >&2; exit 1; } export CI_TEST_DB_URL="jdbc:mysql://${DB_CONTAINER}:3306/flowcenter_ci?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" export CI_TEST_DB_USER=flow_ci CI_TEST_DB_PASSWORD="$MYSQL_PASSWORD" + tables=$(docker exec "$DB_CONTAINER" sh -c 'MYSQL_PWD="$MYSQL_PASSWORD" mysql -h127.0.0.1 -u"$MYSQL_USER" "$MYSQL_DATABASE" -Nse "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE()"') + [ "$tables" = 14 ] || { echo "Expected 14 CI schema tables, got $tables" >&2; exit 1; } + echo "CI_SCHEMA_TABLES=$tables" + # 先在独立空 schema 验证真实 validate 失败会阻断打包;随后再生成正向候选 JAR。 + printf '%s\n' 'CREATE DATABASE flowcenter_ci_invalid;' "GRANT ALL ON flowcenter_ci_invalid.* TO 'flow_ci'@'%';" | \ + docker exec -i "$DB_CONTAINER" sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql -uroot' + positive_db_url="$CI_TEST_DB_URL" + export CI_TEST_DB_URL="jdbc:mysql://${DB_CONTAINER}:3306/flowcenter_ci_invalid?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" + docker exec -e CI_TEST_DB_URL -e CI_TEST_DB_USER -e CI_TEST_DB_PASSWORD \ + "$BUILD_CONTAINER" sh -c 'cd /build && sh ci/verify-failure-guard.sh' + export CI_TEST_DB_URL="$positive_db_url" docker exec -e CI_TEST_DB_URL -e CI_TEST_DB_USER -e CI_TEST_DB_PASSWORD \ "$BUILD_CONTAINER" sh -c 'cd /build && sh ci/test-gate.sh clean package' docker exec "$BUILD_CONTAINER" sh -c 'cd /build/target && cp *.jar app.jar' @@ -104,7 +129,7 @@ jobs: docker rm -f "$BUILD_CONTAINER" - name: 构建并推送镜像 - if: ${{ inputs.tests_only != 'true' }} + if: ${{ success() && github.ref == 'refs/heads/main' && inputs.tests_only != 'true' }} run: | set -e TAG=$(echo "$GITHUB_SHA" | cut -c1-8) @@ -115,7 +140,7 @@ jobs: # 通知 projman:webhook 收到后只建档 Release(status=pending),部署由运维在 DeployTab 手动触发(App.deployMode=manual) - name: 通知 projman 建档(部署由运维手动触发) - if: ${{ inputs.tests_only != 'true' }} + if: ${{ success() && github.ref == 'refs/heads/main' && inputs.tests_only != 'true' }} run: | curl -sS -X POST "http://192.168.10.160:8090/projman/api/deploy/webhook" \ -H "Content-Type: application/json" \ diff --git a/ci/README.md b/ci/README.md index a98425c..dcd0d17 100644 --- a/ci/README.md +++ b/ci/README.md @@ -27,3 +27,13 @@ CI 场景必须实际连接 `flowcenter_ci*`,不能悄悄改用默认开发库 不能将等价 Maven 证据描述为实际 Gitea workflow 已通过。 实体结构变更时应显式刷新 SQL 快照并重新验证;不能把 validate 改为 update 来绕过缺表或缺列。 + + +## 核心 RC 功能分支实跑 + +`codex/core-rc-20261023` 的 push 只执行测试/JAR;镜像推送与通知同时受 main ref 与 tests_only 条件约束。 +检出后核验 `CI_SOURCE_SHA` 等于事件 SHA,不能使用随后更新的 main。 +FL 在同一独立 MySQL 容器额外创建空 `flowcenter_ci_invalid`,先运行 `verify-failure-guard.sh`: +要求 11 项真实数据库测试因 validate 缺表报错、Maven 非零且无 JAR;随后对 14 表正常 schema 执行正向门禁。 +空库检查的预期失败不属于 380 项正向用例。回收检查打印 `CI_CONTAINER_CLEANUP=PASS`,同时删除本次容器的匿名卷。 +本文件是运行约定;只有取得对应远端准确 SHA 的实际日志,才能宣称 runner 已完成验证。 diff --git a/ci/verify-failure-guard.sh b/ci/verify-failure-guard.sh new file mode 100644 index 0000000..cadef0c --- /dev/null +++ b/ci/verify-failure-guard.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu +cd "$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +: "${CI_TEST_DB_URL:?isolated empty MySQL schema URL is required}" +case "$CI_TEST_DB_URL" in */flowcenter_ci_invalid\?*|*/flowcenter_ci_invalid_*\?*) ;; *) echo 'Negative guard requires its own invalid test schema' >&2; exit 2 ;; esac +log=$(mktemp /tmp/flow-ci-negative.XXXXXX) +trap 'rm -f "$log"' EXIT +set +e +sh ci/test-gate.sh clean package >"$log" 2>&1 +code=$? +set -e +cat "$log" +echo "NEGATIVE_MVN_EXIT=$code" +[ "$code" -ne 0 ] || { echo 'Empty schema unexpectedly passed' >&2; exit 1; } +grep -q 'Schema-validation: missing table' "$log" +grep -q 'Tests run: 92, Failures: 0, Errors: 11, Skipped: 0' "$log" +[ -z "$(find target -maxdepth 1 -name '*.jar' -print -quit 2>/dev/null)" ] || { echo 'Failed gate produced a JAR' >&2; exit 1; } +echo 'CI_EMPTY_SCHEMA_FAILURE_GUARD=PASS'