Repository navigation
399 lines (370 loc) · 15 KB
/
Copy path_backend.yml
File metadata and controls
399 lines (370 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# Reusable: backend gates — build, unit tests (default + feature-flag matrix),
# Flyway migration validation, baseline parity, migration filename lint.
# Path filtering happens in the caller (ci.yml); all jobs here run when called.
name: Backend
on:
workflow_call: {}
permissions:
contents: read
env:
JAVA_VERSION: '17'
jobs:
backend-build:
name: Backend Build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup JDK 17
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Grant Maven wrapper execute permission
run: chmod +x services/mvnw
- name: Build with Maven
run: cd services && ./mvnw compile -B
backend-test:
name: Backend Test
needs: backend-build
runs-on: ubuntu-latest
timeout-minutes: 40
services:
mysql:
image: mysql@sha256:0255b469f0135a0236d672d60e3154ae2f4538b146744966d96440318cc822c6
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ulticode
MYSQL_USER: ulticode
MYSQL_PASSWORD: ulticode
ports:
- 23306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost -u root -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=5
redis:
image: redis@sha256:ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf
ports:
- 26379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_HOST: localhost
DB_PORT: 23306
DB_USER: ulticode
DB_PASSWORD: ulticode
DB_NAME: ulticode
REDIS_HOST: localhost
REDIS_PORT: 26379
JWT_SECRET: test-jwt-secret-key-for-ci-minimum-32-characters-long
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup JDK 17
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Grant Maven wrapper execute permission
run: chmod +x services/mvnw
- name: Validate coverage gate contract
run: bash scripts/test/coverage-contract.sh
- name: Run tests with CI profile
run: cd services && ./mvnw verify -Dspring.profiles.active=ci -Dtest='!*IT' -B
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: test-results-backend
path: |
services/**/target/surefire-reports/
retention-days: 7
# --- Backend: Test under feature flag matrix (ADR-005 §4 #4) ---
# matrix 化: features=[off,on] 覆盖 "全关" 与 "产品全开" 路径, 防
# "默认 flag 路径 vs 全开/全关路径" 行为漂移. cutover flag 在所有变体保持
# false (走 cutover 守门, 不在 CI 强开). 第三个 default 路径由上面的
# backend-test job 覆盖.
backend-test-features:
name: Backend Test (features ${{ matrix.features }})
needs: backend-build
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
features: [off, on]
services:
mysql:
image: mysql@sha256:0255b469f0135a0236d672d60e3154ae2f4538b146744966d96440318cc822c6
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ulticode
MYSQL_USER: ulticode
MYSQL_PASSWORD: ulticode
ports:
- 23306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost -u root -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=5
redis:
image: redis@sha256:ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf
ports:
- 26379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_HOST: localhost
DB_PORT: 23306
DB_USER: ulticode
DB_PASSWORD: ulticode
DB_NAME: ulticode
REDIS_HOST: localhost
REDIS_PORT: 26379
JWT_SECRET: test-jwt-secret-key-for-ci-minimum-32-characters-long
# 5 cutover flag 全 false (matrix 无关, 走 cutover 守门)
USE_JUDGE_OUTBOX: 'false'
USE_GENERATION_FENCE: 'false'
JUDGE_QUEUE_USE_PORT: 'false'
JUDGE_QUEUE_CUTOVER_AT: '1970-01-01T00:00:00'
USE_NOTIFICATION_INTENT: 'false'
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup JDK 17
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Grant Maven wrapper execute permission
run: chmod +x services/mvnw
# 5 个产品 flag 由 matrix.features 决定 on/off, 通过 env 注入到 step.
# Spring profile 同时追加 features-${{ matrix.features }}, 与 yml profile
# 形成双保险 (yml 提供默认值, env 覆盖兜底).
- name: Inject feature flag env vars from matrix
env:
FEATURES: ${{ matrix.features }}
run: |
enabled=false
if [[ "$FEATURES" == "on" ]]; then
enabled=true
fi
for flag in USE_NEW_CONTEST_SYSTEM REALTIME_RANKING_ENABLED FIRST_SOLVE_NOTIFICATIONS_ENABLED ANTICHEAT_ENABLED CONTEST_ANALYTICS_ENABLED; do
printf '%s=%s\n' "$flag" "$enabled"
done >> "$GITHUB_ENV"
- name: Run tests with features-${{ matrix.features }} profile
run: cd services && ./mvnw verify -Dspring.profiles.active=ci,features-${{ matrix.features }} -Dtest='!*IT' -B
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: test-results-backend-features-${{ matrix.features }}
path: |
services/**/target/surefire-reports/
retention-days: 7
migrate-validate:
name: Validate Migrations
runs-on: ubuntu-latest
timeout-minutes: 30
services:
mysql:
image: mysql@sha256:0255b469f0135a0236d672d60e3154ae2f4538b146744966d96440318cc822c6
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ulticode
MYSQL_USER: ulticode
MYSQL_PASSWORD: ulticode
ports:
- 23306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost -u root -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=5
redis:
image: redis@sha256:ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf
ports:
- 26379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_HOST: localhost
DB_PORT: 23306
DB_USER: ulticode
DB_PASSWORD: ulticode
DB_NAME: ulticode
REDIS_HOST: localhost
REDIS_PORT: 26379
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup mise for repository toolchain
uses: jdx/mise-action@fc42bcc7ddbf51124c30f95a877a0c5168a3bfde # v5.2.1
- name: Setup JDK 17 for registry contracts
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
- name: Grant Maven wrapper execute permission
run: chmod +x services/mvnw
- name: Wait for MySQL
run: |
for i in $(seq 1 30); do
if mysqladmin ping -h localhost -P 23306 -u ulticode -pulticode 2>/dev/null; then
echo "MySQL is ready"
break
fi
echo "Waiting for MySQL... ($i/30)"
sleep 2
done
- name: Run and validate migrations
run: |
docker run --rm --network host \
-v "$PWD/init-db/migrations:/flyway/sql:ro" \
flyway/flyway@sha256:354e5d5aec18d8d9d57ede196f02fdc66f6ee86a50e3bddfe1b001265817eadb \
-url="jdbc:mysql://127.0.0.1:23306/ulticode?allowPublicKeyRetrieval=true&useSSL=false" \
-user=root -password=root -locations="filesystem:/flyway/sql/*.sql" -connectRetries=10 migrate
docker run --rm --network host \
-v "$PWD/init-db/migrations:/flyway/sql:ro" \
flyway/flyway@sha256:354e5d5aec18d8d9d57ede196f02fdc66f6ee86a50e3bddfe1b001265817eadb \
-url="jdbc:mysql://127.0.0.1:23306/ulticode?allowPublicKeyRetrieval=true&useSSL=false" \
-user=root -password=root -locations="filesystem:/flyway/sql/*.sql" validate
- name: Run registry-derived dynamic architecture contracts
run: |
set -euo pipefail
# The registry is the sole authority for dynamic contract names.
# HA remains a separately prepared invocation below.
dynamic_children="$(bash scripts/dev/architecture-contract-test.sh --list-qualified dynamic)"
while IFS= read -r child; do
[[ -n "$child" ]] || continue
if [[ "$child" == */ha-profile-contract.sh ]]; then
continue
fi
echo "[architecture] running dynamic child: $child"
bash "$child"
done <<< "$dynamic_children"
- name: Validate stateful HA reference profile
run: |
set -euo pipefail
dynamic_children="$(bash scripts/dev/architecture-contract-test.sh --list-qualified dynamic)"
ha_child=""
while IFS= read -r child; do
if [[ "$child" == */ha-profile-contract.sh ]]; then
ha_child="$child"
break
fi
done <<< "$dynamic_children"
[[ -n "$ha_child" ]] || {
echo 'HA contract is missing from the dynamic architecture registry' >&2
exit 1
}
config_dir="$(mktemp -d)"
env_file="$(mktemp)"
cleanup() {
rm -rf "$config_dir" "$env_file"
}
trap cleanup EXIT
cp .env.example "$env_file"
replication_password="$(openssl rand -hex 16)"
sentinel_password="$(openssl rand -hex 16)"
printf '\nNACOS_DB_USER=nacos_rw\nNACOS_DB_PASSWORD=ci-ha-config\nNACOS_SERVERS=nacos:8848 nacos-2:8848 nacos-3:8848\nREDIS_HA_CONFIG_DIR=%s\nREDIS_REPLICATION_PASSWORD=%s\nREDIS_SENTINEL_PASSWORD=%s\n' \
"$config_dir" "$replication_password" "$sentinel_password" >> "$env_file"
rustfs_access_key="ci-rustfs-root-$(openssl rand -hex 12)"
rustfs_secret_key="ci-rustfs-root-$(openssl rand -hex 24)"
rustfs_app_access_key="ci-rustfs-app-$(openssl rand -hex 12)"
rustfs_app_secret_key="ci-rustfs-app-$(openssl rand -hex 24)"
rustfs_admin_access_key="ci-rustfs-admin-$(openssl rand -hex 12)"
rustfs_admin_secret_key="ci-rustfs-admin-$(openssl rand -hex 24)"
printf 'RUSTFS_ACCESS_KEY=%s\nRUSTFS_SECRET_KEY=%s\nRUSTFS_APP_ACCESS_KEY=%s\nRUSTFS_APP_SECRET_KEY=%s\nRUSTFS_ADMIN_ACCESS_KEY=%s\nRUSTFS_ADMIN_SECRET_KEY=%s\nAPP_STORAGE_S3_ACCESS_KEY=%s\nAPP_STORAGE_S3_SECRET_KEY=%s\n' \
"$rustfs_access_key" "$rustfs_secret_key" \
"$rustfs_app_access_key" "$rustfs_app_secret_key" \
"$rustfs_admin_access_key" "$rustfs_admin_secret_key" \
"$rustfs_app_access_key" "$rustfs_app_secret_key" >> "$env_file"
printf 'replicaof redis 6379\nreplica-read-only yes\nappendonly yes\nmasteruser ulticode-replication\nmasterauth %s\n' \
"$replication_password" > "$config_dir/redis-replica.conf"
for sentinel in 1 2 3; do
printf 'sentinel monitor mymaster redis 6379 2\nsentinel auth-user mymaster ulticode-sentinel\nsentinel auth-pass mymaster %s\n' \
"$sentinel_password" > "$config_dir/sentinel-$sentinel.conf"
done
HA_COMPOSE_ENV_FILE="$env_file" \
HA_COMPOSE_PROD=0 \
HA_COMPOSE_REQUIRED=1 \
HA_RECONNECT_DRILL=1 \
bash "$ha_child"
# --- Baseline Parity (init-db deep module) ---
# CI gate for 派生制品:baseline.sql 必须与增量迁移 per-schema 一致.
# 本地等价: ./init-db/scripts/validate-baseline.sh && ./init-db/scripts/baseline-adopt.sh
baseline-parity:
name: Baseline Parity
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Validate baseline parity
run: |
set -euo pipefail
echo "[ci] validate-baseline.sh"
./init-db/scripts/validate-baseline.sh 2>&1 | tee /tmp/validate-baseline.log
echo "[ci] baseline-adopt.sh disposable"
./init-db/scripts/baseline-adopt.sh 2>&1 | tee /tmp/baseline-adopt.log
grep -q "PASS" /tmp/validate-baseline.log
grep -q "PASS" /tmp/baseline-adopt.log
echo "[ci] baseline parity PASS (scripts exit 0 + PASS found)"
flyway-filename-lint:
name: Flyway Filename Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# ADR-005 §4 #5: 迁移文件名遵守 V{ts}__{description}.sql.
# 正则兼容两种时间戳写法 (V20260604_110000 与 V20260604110000).
- name: Lint migration filenames
run: |
set -e
PATTERN='^V[0-9]{8}_?[0-9_]*__[A-Za-z0-9_]+\.sql$'
cd init-db/migrations
fails=0
total=0
for f in V*.sql; do
[[ -e "$f" ]] || continue
total=$((total+1))
if [[ ! "$f" =~ $PATTERN ]]; then
echo "::error file=$f::must match $PATTERN (V<14-digit-ts>__<desc>.sql)"
fails=$((fails+1))
fi
done
if [[ $fails -gt 0 ]]; then
echo "::error::$fails of $total migration file(s) violate naming convention"
exit 1
fi
echo "✅ all $total migration filenames conform to V<14-digit-ts>__<desc>.sql"