Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26f4d354fb | |||
| fa55c7669d | |||
| 1daf8d946a | |||
| ef62aefda3 | |||
| 7d4d362846 | |||
| 55f28c7f77 | |||
| a79f393fb6 | |||
| 86a078cb71 | |||
| a4bf07a3de | |||
| f131dd5585 | |||
| 75321c7b9f | |||
| daa28b613c | |||
| 88e0215b02 |
@@ -35,7 +35,9 @@ concurrency:
|
||||
jobs:
|
||||
build-staging:
|
||||
name: Build Staging ${{ matrix.service_display }} Image
|
||||
runs-on: host
|
||||
runs-on:
|
||||
- ci-l2
|
||||
- host
|
||||
timeout-minutes: ${{ matrix.timeout }}
|
||||
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
|
||||
strategy:
|
||||
@@ -468,7 +470,9 @@ jobs:
|
||||
'
|
||||
build-production:
|
||||
name: Build Production ${{ matrix.service_display }} Image
|
||||
runs-on: host
|
||||
runs-on:
|
||||
- ci-l2
|
||||
- host
|
||||
timeout-minutes: ${{ matrix.timeout }}
|
||||
needs:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
+218
-59
@@ -33,10 +33,147 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Code Quality And Tests
|
||||
runs-on: host
|
||||
timeout-minutes: 10
|
||||
validate-code-quality:
|
||||
name: Validate - Code Quality
|
||||
runs-on:
|
||||
- ci-l1
|
||||
- host
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu
|
||||
python3 - <<'PY'
|
||||
import io, os, tarfile, time, urllib.request, urllib.error
|
||||
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
|
||||
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
|
||||
last_err = None
|
||||
for attempt in range(5):
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=120) as response:
|
||||
archive = response.read()
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
last_err = e
|
||||
if e.code >= 500 and attempt < 4:
|
||||
wait = 2 ** attempt
|
||||
print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||||
time.sleep(wait)
|
||||
continue
|
||||
raise
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
if attempt < 4:
|
||||
wait = 2 ** attempt
|
||||
print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||||
time.sleep(wait)
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
raise last_err
|
||||
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
|
||||
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
|
||||
for member in tar.getmembers():
|
||||
name = member.name
|
||||
if name == root_prefix[:-1]:
|
||||
continue
|
||||
if name.startswith(root_prefix):
|
||||
member.name = name[len(root_prefix):]
|
||||
if member.name:
|
||||
tar.extract(member, '.')
|
||||
PY
|
||||
|
||||
- name: Run all code quality checks
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu
|
||||
echo "=== Installing dependencies ==="
|
||||
python3 -m pip install -q -r requirements-base.txt
|
||||
python3 -m pip install -q -r requirements.txt
|
||||
python3 -m pip install -q -r requirements-dev.txt
|
||||
|
||||
echo ""
|
||||
echo "=== 1/5 Secret detection ==="
|
||||
python3 -m pip install -q detect-secrets
|
||||
detect-secrets scan --all-files --exclude-files '(^|/)(tests|test|e2e|__tests__|spec|docs|node_modules|site-packages|migrations|alembic|.gitea|.git|.pytest_cache|.next|dist|build)/' --exclude-files '\.(md|rst|txt|lock|example|sample|min\.js|min\.css|spec\.ts|test\.ts|test\.py)$' --exclude-files '(package-lock|yarn\.lock|poetry\.lock|Pipfile\.lock)$' --disable-plugin Base64HighEntropyString --disable-plugin HexHighEntropyString --disable-plugin BasicAuthDetector --disable-plugin KeywordDetector --disable-plugin IPPublicDetector 2>&1 | tee /tmp/secrets-scan.json
|
||||
FOUND=$(python3 -c "import json; d=json.load(open('/tmp/secrets-scan.json')); print(sum(len(v) for v in d.get('results',{}).values()))" 2>/dev/null || echo error)
|
||||
if [ "$FOUND" != "0" ] && [ "$FOUND" != "error" ]; then
|
||||
echo "❌ Secrets detected: $FOUND"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Secret scan passed"
|
||||
|
||||
echo ""
|
||||
echo "=== 2/5 Code quality (full scan) ==="
|
||||
python3 -m compileall -q alembic apps packages tests scripts
|
||||
python3 -m black --check --fast alembic apps packages tests scripts
|
||||
python3 -m isort --check-only alembic apps packages tests scripts
|
||||
python3 -m ruff check apps packages tests --statistics
|
||||
echo "✅ Code quality passed"
|
||||
|
||||
echo ""
|
||||
echo "=== 3/5 Type check (mypy) ==="
|
||||
bash scripts/ci/mypy_check.sh
|
||||
echo "✅ Type check passed"
|
||||
|
||||
echo ""
|
||||
echo "=== 4/5 Security scan (bandit) ==="
|
||||
bandit -r apps packages -q -ll
|
||||
echo "✅ Security scan passed"
|
||||
|
||||
echo ""
|
||||
echo "=== 5/5 Release scripts syntax ==="
|
||||
bash -n scripts/backup_postgres.sh
|
||||
bash -n scripts/restore_postgres_plan.sh
|
||||
bash -n scripts/init_production_env.sh
|
||||
echo "✅ Release scripts syntax OK"
|
||||
|
||||
echo ""
|
||||
echo "🎉 All code quality checks passed!"
|
||||
|
||||
echo ""
|
||||
echo "=== Reporting success status to Gitea ==="
|
||||
STATUS_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA}"
|
||||
curl -s -X POST "$STATUS_URL" \
|
||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state":"success","context":"CI/CD Pipeline / Validate - Code Quality","description":"Code quality checks passed"}' > /dev/null 2>&1
|
||||
echo "Status reported successfully"
|
||||
|
||||
- name: Report failure to Gitea
|
||||
continue-on-error: true
|
||||
if: failure()
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "Reporting failure status to Gitea..."
|
||||
STATUS_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA}"
|
||||
curl -s -X POST "$STATUS_URL" -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: application/json" -d '{"state":"failure","context":"CI/CD Pipeline / Validate - Code Quality","description":"Code quality checks failed"}' > /dev/null 2>&1
|
||||
echo "Failure status reported"
|
||||
|
||||
- name: Notify on failure
|
||||
continue-on-error: true
|
||||
if: failure()
|
||||
shell: sh
|
||||
env:
|
||||
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
|
||||
run: |
|
||||
set +e
|
||||
NOTIFY_MODE=failure JOB_NAME="Validate - Code Quality" python3 scripts/ci_notify.py
|
||||
|
||||
|
||||
validate-db-migrations:
|
||||
name: Validate - DB Migrations
|
||||
runs-on:
|
||||
- ci-l2
|
||||
- host
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/xiaoxia_saas
|
||||
USE_IN_MEMORY_DB: 'false'
|
||||
@@ -88,69 +225,64 @@ jobs:
|
||||
pytest --version
|
||||
|
||||
'
|
||||
- name: Secret detection (detect-secrets)
|
||||
- name: Start PostgreSQL for validate (isolated container)
|
||||
shell: sh
|
||||
run: "set -eu\necho \"=== Installing detect-secrets ===\"\npython3 -m pip install -q detect-secrets\ndetect-secrets --version\necho \"\"\necho \"=== Running secret scan ===\"\ndetect-secrets scan \\\n --all-files \\\n --exclude-files '(^|/)(tests|test|e2e|__tests__|spec|docs|node_modules|site-packages|migrations|alembic|.gitea|.git|.pytest_cache|.next|dist|build)/' \\\n --exclude-files '\\.(md|rst|txt|lock|example|sample|min\\.js|min\\.css|spec\\.ts|test\\.ts|test\\.py)$' \\\n --exclude-files '(package-lock|yarn\\.lock|poetry\\.lock|Pipfile\\.lock)$' \\\n --disable-plugin Base64HighEntropyString \\\n --disable-plugin HexHighEntropyString \\\n --disable-plugin BasicAuthDetector \\\n --disable-plugin KeywordDetector \\\n --disable-plugin IPPublicDetector \\\n 2>&1 | tee /tmp/secrets-scan.json\n\nFOUND=$(python3 -c \"\nimport json\ntry:\n with open('/tmp/secrets-scan.json') as f:\n data = json.load(f)\n results = data.get('results', {})\n total = sum(len(v) for\
|
||||
\ v in results.values())\n print(total)\nexcept Exception:\n print('error')\n\")\necho \"\"\necho \"Secrets detected: $FOUND\"\nif [ \"$FOUND\" != \"0\" ] && [ \"$FOUND\" != \"error\" ]; then\n echo \"\"\n echo \"=== Secret details ===\"\n python3 -c \"\nimport json\nwith open('/tmp/secrets-scan.json') as f:\n data = json.load(f)\nfor fpath, items in data.get('results', {}).items():\n for item in items:\n line = item.get('line_number', '?')\n stype = item.get('type', '?')\n hashed = item.get('hashed_secret', '')[:16]\n print(f' {fpath}:{line} [{stype}] {hashed}...')\n\"\n echo \"\"\n echo \"ERROR: Potential secrets detected in code!\"\n echo \"If these are false positives, add exclusions in the CI workflow.\"\n exit 1\nfi\necho \"Secret scan completed - no secrets detected\"\n"
|
||||
- name: Calculate changed Python files (incremental scan)
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: "set -eu\nSCAN_MODE=\"full\"\nCHANGED_PY_FILES=\"\"\n\nif [ \"${GITHUB_EVENT_NAME:-}\" = \"pull_request\" ] && [ -n \"${GITHUB_REF_NAME:-}\" ]; then\n echo \"PR mode (#${GITHUB_REF_NAME}) - fetching changed files from API\"\n\n PR_NUMBER=$(echo \"$GITHUB_REF\" | sed 's|refs/pull/||; s|/.*||')\n API_URL=\"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=100\"\n\n set +e\n RESPONSE=$(curl -s -w \"\\n%{http_code}\" -H \"Authorization: token ${GITHUB_TOKEN}\" \"${API_URL}\")\n HTTP_CODE=$(echo \"$RESPONSE\" | tail -n1)\n BODY=$(echo \"$RESPONSE\" | sed '$d')\n set -e\n\n if [ \"$HTTP_CODE\" = \"200\" ]; then\n CHANGED_PY_FILES=$(echo \"$BODY\" | python3 -c \"\nimport json, sys\ntry:\n files = json.load(sys.stdin)\n py_files = [f['filename'] for f in files\n if f['filename'].endswith('.py') and f['status'] != 'removed']\n print(' '.join(py_files))\nexcept Exception:\n print('')\n\")\n if [ -n \"$CHANGED_PY_FILES\" ]; then\n SCAN_MODE=\"incremental\"\n FILE_COUNT=$(echo \"$CHANGED_PY_FILES\" | wc -w)\n echo \"Changed Python files: ${FILE_COUNT}\"\n echo \"$CHANGED_PY_FILES\" | tr ' ' '\\n' | grep -v '^$'\n else\n SCAN_MODE=\"skip_py\"\n echo \"No Python files changed in this PR\"\n fi\n else\n echo \"WARN: API returned HTTP $HTTP_CODE, falling back to full scan\"\n fi\nelse\n echo \"Full scan mode (not a PR event)\"\nfi\n\necho \"SCAN_MODE=$SCAN_MODE\" >> $GITHUB_ENV\necho \"CHANGED_PY_FILES=$CHANGED_PY_FILES\" >> $GITHUB_ENV\n"
|
||||
- name: Run code quality checks
|
||||
shell: sh
|
||||
run: "set -eu\n\nif [ \"$SCAN_MODE\" = \"incremental\" ]; then\n echo \"=== Incremental scan mode ===\"\n\n python3 -m compileall -q $CHANGED_PY_FILES\n\n python3 -m black --check --fast $CHANGED_PY_FILES\n\n python3 -m isort --check-only $CHANGED_PY_FILES\n\n RUFF_FILES=$(echo \"$CHANGED_PY_FILES\" | tr ' ' '\\n' | grep -v '^scripts/' | tr '\\n' ' ')\n if [ -n \"$RUFF_FILES\" ]; then\n python3 -m ruff check $RUFF_FILES --statistics\n else\n echo \"No ruff-checkable files changed, skipping\"\n fi\n\nelif [ \"$SCAN_MODE\" = \"skip_py\" ]; then\n echo \"No Python files changed - skipping Python lint checks\"\n\nelse\n echo \"=== Full scan mode ===\"\n\n python3 -m compileall -q alembic apps packages tests scripts\n\n python3 -m black --check --fast alembic apps packages tests scripts\n\n python3 -m isort --check-only alembic apps packages tests scripts\n\n python3 -m ruff check apps packages tests --statistics\nfi\n"
|
||||
- name: Type check (mypy, hard gate)
|
||||
|
||||
shell: sh
|
||||
run: "bash scripts/ci/mypy_check.sh"
|
||||
- name: Run security scan (bandit)
|
||||
shell: sh
|
||||
run: 'set -eu
|
||||
|
||||
bandit -r apps packages -q -ll
|
||||
|
||||
'
|
||||
- name: Python dependency vulnerability scan (pip-audit)
|
||||
shell: sh
|
||||
run: "set -eu\necho \"=== Installing pip-audit ===\"\npython3 -m pip install -q pip-audit\npip-audit --version\necho \"\"\necho \"=== Scanning Python dependencies ===\"\nEXIT_CODE=0\nfor req_file in requirements.txt requirements-base.txt requirements-dev.txt; do\n if [ -f \"$req_file\" ]; then\n echo \"--- Scanning $req_file ---\"\n pip-audit -r \"$req_file\" --desc on 2>&1 | head -40 || EXIT_CODE=$?\n echo \"\"\n fi\ndone\necho \"pip-audit scan completed (advisory mode - warnings only, not blocking CI)\"\nif [ \"$EXIT_CODE\" != \"0\" ]; then\n echo \"WARNING: Potential vulnerabilities found in dependencies.\"\nfi\nexit 0\n"
|
||||
- name: Dead code detection (vulture)
|
||||
if: always()
|
||||
shell: sh
|
||||
run: "set +e\necho \"=== Installing vulture ===\"\npython3 -m pip install -q vulture\nvulture --version\necho \"\"\necho \"=== Running vulture dead code scan (confidence >= 70%) ===\"\necho \"告警模式,不阻断CI。置信度>=90%建议尽快确认。\"\necho \"\"\n# 按置信度从高到低输出,便于优先查看高价值条目\nvulture apps packages scripts \\\n --exclude \"tests,test,migrations,.gitea,docs,node_modules,site-packages,*/test_*.py,*/conftest.py\" \\\n --min-confidence 70 \\\n 2>&1 | sort -t'(' -k2 -rn | head -80\nEXIT_CODE=$?\necho \"\"\necho \"=== vulture scan summary ===\"\nif [ \"$EXIT_CODE\" != \"0\" ]; then\n echo \"发现潜在死代码(可能包含框架装饰器注册的函数,为误报)\"\n echo \"建议:定期人工审查高置信度(>=90%)条目\"\nelse\n echo \"未发现明显死代码 ✅\"\nfi\nexit 0\n"
|
||||
- name: Validate release scripts syntax
|
||||
shell: sh
|
||||
run: 'set -eu
|
||||
|
||||
bash -n scripts/backup_postgres.sh
|
||||
|
||||
bash -n scripts/restore_postgres_plan.sh
|
||||
|
||||
bash -n scripts/init_production_env.sh
|
||||
|
||||
'
|
||||
run: "set -eu\nPG_CONTAINER=\"ci-pg-validate-${GITHUB_RUN_ID:-$$}\"\necho \"PG_CONTAINER=$PG_CONTAINER\" >> \"$GITHUB_ENV\"\ndocker rm -f \"$PG_CONTAINER\" 2>/dev/null || true\ndocker run -d --name \"$PG_CONTAINER\" \\\n --shm-size=256m \\\n -e POSTGRES_USER=postgres \\\n -e POSTGRES_PASSWORD=postgres \\\n -e POSTGRES_DB=xiaoxia_saas \\\n -P \\\n --health-cmd \"pg_isready -U postgres\" \\\n --health-interval 3s \\\n --health-timeout 3s \\\n --health-retries 20 \\\n postgres:16\nPG_PORT=$(docker port \"$PG_CONTAINER\" 5432/tcp | cut -d: -f2)\necho \"PostgreSQL port: $PG_PORT\"\necho \"DATABASE_URL=postgresql+psycopg://postgres:postgres@127.0.0.1:$PG_PORT/xiaoxia_saas\" >> \"$GITHUB_ENV\"\nfor i in $(seq 1 30); do\n if docker inspect --format='{{.State.Health.Status}}' \"$PG_CONTAINER\" 2>/dev/null | grep -q healthy; then\n echo \"PostgreSQL is ready on port $PG_PORT\"\n break\n fi\n echo \"Waiting for PostgreSQL... ($i/30)\"\n sleep 2\ndone\ndocker inspect --format='{{.State.Health.Status}}' \"$PG_CONTAINER\" | grep -q healthy\n"
|
||||
- name: Validate Alembic migrations
|
||||
shell: sh
|
||||
run: 'set -eu
|
||||
|
||||
python3 -m alembic upgrade head --sql > /tmp/alembic-upgrade.sql
|
||||
|
||||
test -s /tmp/alembic-upgrade.sql
|
||||
|
||||
grep -q "Running upgrade" /tmp/alembic-upgrade.sql
|
||||
|
||||
python3 scripts/check_schema_metadata.py
|
||||
|
||||
'
|
||||
run: "set -eu\n\n# 调试:输出数据库连接信息(脱敏)\necho \"DATABASE_URL_HOST=$(echo $DATABASE_URL | sed 's|.*@||; s|/.*||')\"\necho \"PG_CONTAINER=${PG_CONTAINER:-not_set}\"\ndocker ps --filter \"name=${PG_CONTAINER:-none}\" --format '{{.Names}} {{.Status}} {{.Ports}}'\n\necho \"=== Running Alembic migrations (--sql mode) ===\"\nset +e\npython3 -m alembic upgrade head --sql > /tmp/alembic-upgrade.sql 2> /tmp/alembic-error.log\nALEMBIC_EXIT=$?\nset -e\n\nif [ $ALEMBIC_EXIT -ne 0 ]; then\n echo \"❌ Alembic failed with exit code $ALEMBIC_EXIT\"\n echo \"=== stderr output ===\"\n cat /tmp/alembic-error.log\n echo \"=== generated SQL (last 30 lines) ===\"\n tail -30 /tmp/alembic-upgrade.sql 2>/dev/null || echo \"(no SQL generated)\"\n exit $ALEMBIC_EXIT\nfi\n\necho \"✅ Alembic SQL generation succeeded\"\ntest -s /tmp/alembic-upgrade.sql\ngrep -q \"Running upgrade\" /tmp/alembic-upgrade.sql\n\npython3 scripts/check_schema_metadata.py\n"
|
||||
- name: Check migration safety
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: "set -eu\npython3 scripts/check_migration_safety.py --allow-medium-risk --diff-against origin/develop\n"
|
||||
- name: Cleanup PostgreSQL (validate)
|
||||
if: always()
|
||||
shell: sh
|
||||
run: 'docker rm -f "${PG_CONTAINER:-ci-pg-validate}" 2>/dev/null || true
|
||||
|
||||
echo "PostgreSQL container cleaned up"
|
||||
|
||||
'
|
||||
- name: Job duration summary
|
||||
if: always()
|
||||
shell: sh
|
||||
run: "set +eu\nif [ -n \"$JOB_START_TIME\" ]; then\n END_TIME=$(date +%s)\n DURATION=$((END_TIME - JOB_START_TIME))\n MINS=$((DURATION / 60))\n SECS=$((DURATION % 60))\n echo \"JOB_DURATION_SECONDS=$DURATION\" >> $GITHUB_ENV\n echo \"=== Job Duration: ${MINS}m${SECS}s ===\"\nelse\n echo \"JOB_DURATION_SECONDS=0\" >> $GITHUB_ENV\n echo \"=== Job Duration: unknown ===\"\nfi\n"
|
||||
- name: Report status to Gitea
|
||||
if: success()
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: "set -eu\n
|
||||
echo 'Reporting success status to Gitea...'\n
|
||||
STATE=success\n
|
||||
CONTEXT=\"CI/CD Pipeline / Validate - Code Quality\"\n
|
||||
API_URL=\"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA}\"\n
|
||||
set +e\n
|
||||
curl -s -X POST \"$API_URL\" \\\n
|
||||
-H \"Authorization: token ${GITHUB_TOKEN}\" \\\n
|
||||
-H \"Content-Type: application/json\" \\\n
|
||||
-d \"{\\\"state\\\":\\\"$STATE\\\",\\\"context\\\":\\\"$CONTEXT\\\",\\\"description\\\":\\\"Manual report\\\"}\"\n
|
||||
echo 'Status reported.'\n
|
||||
"
|
||||
|
||||
- name: Report failure status to Gitea
|
||||
if: failure()
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: "set +eu\n
|
||||
echo 'Reporting failure status to Gitea...'\n
|
||||
STATE=failure\n
|
||||
CONTEXT=\"CI/CD Pipeline / Validate - Code Quality\"\n
|
||||
API_URL=\"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA}\"\n
|
||||
curl -s -X POST \"$API_URL\" \\\n
|
||||
-H \"Authorization: token ${GITHUB_TOKEN}\" \\\n
|
||||
-H \"Content-Type: application/json\" \\\n
|
||||
-d \"{\\\"state\\\":\\\"$STATE\\\",\\\"context\\\":\\\"$CONTEXT\\\",\\\"description\\\":\\\"Manual report\\\"}\"\n
|
||||
echo 'Failure status reported.'\n
|
||||
"
|
||||
|
||||
- name: Notify on failure
|
||||
continue-on-error: true
|
||||
if: failure()
|
||||
@@ -159,12 +291,35 @@ jobs:
|
||||
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
|
||||
run: 'set +e
|
||||
|
||||
NOTIFY_MODE=failure JOB_NAME="Validate Code Quality And Tests" python3 scripts/ci_notify.py
|
||||
NOTIFY_MODE=failure JOB_NAME="Validate - Code Quality" python3 scripts/ci_notify.py
|
||||
|
||||
'
|
||||
|
||||
validate:
|
||||
name: Validate Code Quality And Tests
|
||||
needs: [validate-code-quality, validate-db-migrations]
|
||||
runs-on:
|
||||
- ci-l1
|
||||
- host
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Validate summary
|
||||
shell: sh
|
||||
run: 'set -eu
|
||||
|
||||
echo "All validate checks passed ✅"
|
||||
|
||||
echo " - Code Quality: PASSED"
|
||||
|
||||
echo " - DB Migrations: PASSED"
|
||||
|
||||
'
|
||||
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: host
|
||||
runs-on:
|
||||
- ci-l2
|
||||
- host
|
||||
timeout-minutes: 8
|
||||
env:
|
||||
USE_IN_MEMORY_DB: 'true'
|
||||
@@ -235,7 +390,9 @@ jobs:
|
||||
'
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: host
|
||||
runs-on:
|
||||
- ci-l2
|
||||
- host
|
||||
timeout-minutes: 30
|
||||
if: always()
|
||||
needs: validate
|
||||
@@ -352,7 +509,9 @@ jobs:
|
||||
'
|
||||
frontend-lint:
|
||||
name: Frontend Lint
|
||||
runs-on: host
|
||||
runs-on:
|
||||
- ci-l1
|
||||
- host
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
Reference in New Issue
Block a user