Sync/upstream 2026 07 #128
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Audit PR | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| issue_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: audit-pr-${{ github.event.pull_request.number || github.event.issue.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| audit-on-pr: | |
| name: Audit on pull request | |
| if: github.event_name == 'pull_request' | |
| runs-on: [self-hosted, Linux, ARM64, audit-linux] | |
| steps: | |
| - name: Checkout PR head | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| fetch-depth: 0 | |
| - name: Check required tools | |
| run: | | |
| which zip | |
| which jq | |
| which python3 | |
| - name: Create source archive | |
| id: archive | |
| shell: bash | |
| run: | | |
| ARCHIVE_NAME="${{ github.event.repository.name }}.zip" | |
| rm -f "/tmp/${ARCHIVE_NAME}" | |
| zip -r "/tmp/${ARCHIVE_NAME}" . | |
| echo "archive_name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT" | |
| - name: Submit audit job | |
| id: submit | |
| shell: bash | |
| run: | | |
| RESPONSE=$(curl -s -X POST "${{ vars.AUDIT_SERVICE_URL }}/audit-pr/${{ vars.AUDIT_PR_SKILL }}" \ | |
| -H "X-API-Key: ${{ secrets.AUDIT_API_KEY }}" \ | |
| -F "file=@/tmp/${{ steps.archive.outputs.archive_name }}" \ | |
| -F "from_branch=${{ github.event.pull_request.base.ref }}" \ | |
| -F "to_branch=${{ github.event.pull_request.head.ref }}") | |
| echo "Submit response:" | |
| echo "$RESPONSE" | jq . | |
| REPORT_URL=$(echo "$RESPONSE" | jq -r '.report_url // empty') | |
| if [ -z "$REPORT_URL" ]; then | |
| echo "Audit service did not return report_url" | |
| exit 1 | |
| fi | |
| echo "report_url=$REPORT_URL" >> "$GITHUB_OUTPUT" | |
| - name: Poll report until ready | |
| id: poll | |
| shell: bash | |
| run: | | |
| REPORT_PATH="${{ steps.submit.outputs.report_url }}" | |
| if [ -z "$REPORT_PATH" ]; then | |
| echo "Missing report_url" | |
| exit 1 | |
| fi | |
| echo "Polling report URL: ${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}" | |
| for i in $(seq 1 20); do | |
| HTTP_CODE=$(curl -s -o /tmp/audit-report.html -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}") | |
| echo "Poll #$i HTTP_CODE=$HTTP_CODE" | |
| if [ "$HTTP_CODE" = "200" ] && [ -s /tmp/audit-report.html ]; then | |
| echo "Report is ready" | |
| exit 0 | |
| fi | |
| sleep 60 | |
| done | |
| echo "Audit report did not become available within timeout" | |
| exit 1 | |
| - name: Fetch markdown report | |
| shell: bash | |
| run: | | |
| REPORT_PATH="${{ steps.submit.outputs.report_url }}" | |
| MD_PATH="${REPORT_PATH%audit-report.html}audit-report.md" | |
| echo "Markdown path: ${MD_PATH}" | |
| HTTP_CODE=$(curl -s -o /tmp/audit-report.md -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${MD_PATH}") | |
| echo "Markdown fetch HTTP_CODE=$HTTP_CODE" | |
| if [ "$HTTP_CODE" != "200" ] || [ ! -s /tmp/audit-report.md ]; then | |
| echo "Failed to fetch markdown report" | |
| exit 1 | |
| fi | |
| - name: Normalize markdown for GitHub comment | |
| shell: bash | |
| run: | | |
| python3 <<'PY' | |
| import re | |
| from pathlib import Path | |
| p = Path("/tmp/audit-report.md") | |
| text = p.read_text(encoding="utf-8", errors="ignore") | |
| # 去掉首尾空白,压缩多余空行 | |
| text = re.sub(r'\n{3,}', '\n\n', text).strip() + '\n' | |
| # 如果首行已经是一级标题,保留;否则不额外加头 | |
| Path("/tmp/audit-report-clean.md").write_text(text, encoding="utf-8") | |
| PY | |
| - name: Comment markdown report to PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const reportText = fs.readFileSync('/tmp/audit-report-clean.md', 'utf8').trim(); | |
| const limit = 60000; | |
| const chunks = []; | |
| for (let i = 0; i < reportText.length; i += limit) { | |
| chunks.push(reportText.slice(i, i + limit)); | |
| } | |
| for (let i = 0; i < chunks.length; i++) { | |
| const body = chunks.length > 1 | |
| ? `${chunks[i]}\n\n---\nPart ${i + 1}/${chunks.length}` | |
| : chunks[i]; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body | |
| }); | |
| } | |
| audit-by-comment: | |
| name: Audit by PR comment | |
| if: > | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '/audit-pr') && | |
| contains(format(',{0},', vars.AUDIT_ALLOWED_USERS), format(',{0},', github.event.comment.user.login)) | |
| runs-on: [self-hosted, Linux, ARM64, audit-linux] | |
| steps: | |
| - name: Get PR info | |
| id: pr | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| core.setOutput('head_ref', pr.data.head.ref); | |
| core.setOutput('base_ref', pr.data.base.ref); | |
| core.setOutput('head_sha', pr.data.head.sha); | |
| - name: Checkout PR head | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.pr.outputs.head_sha }} | |
| fetch-depth: 0 | |
| - name: Check required tools | |
| run: | | |
| which zip | |
| which jq | |
| which python3 | |
| - name: Create source archive | |
| id: archive | |
| shell: bash | |
| run: | | |
| ARCHIVE_NAME="${{ github.event.repository.name }}.zip" | |
| rm -f "/tmp/${ARCHIVE_NAME}" | |
| zip -r "/tmp/${ARCHIVE_NAME}" . | |
| echo "archive_name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT" | |
| - name: Submit audit job | |
| id: submit | |
| shell: bash | |
| run: | | |
| RESPONSE=$(curl -s -X POST "${{ vars.AUDIT_SERVICE_URL }}/audit-pr/${{ vars.AUDIT_PR_SKILL }}" \ | |
| -H "X-API-Key: ${{ secrets.AUDIT_API_KEY }}" \ | |
| -F "file=@/tmp/${{ steps.archive.outputs.archive_name }}" \ | |
| -F "from_branch=${{ steps.pr.outputs.base_ref }}" \ | |
| -F "to_branch=${{ steps.pr.outputs.head_ref }}") | |
| echo "Submit response:" | |
| echo "$RESPONSE" | jq . | |
| REPORT_URL=$(echo "$RESPONSE" | jq -r '.report_url // empty') | |
| if [ -z "$REPORT_URL" ]; then | |
| echo "Audit service did not return report_url" | |
| exit 1 | |
| fi | |
| echo "report_url=$REPORT_URL" >> "$GITHUB_OUTPUT" | |
| - name: Poll report until ready | |
| id: poll | |
| shell: bash | |
| run: | | |
| REPORT_PATH="${{ steps.submit.outputs.report_url }}" | |
| if [ -z "$REPORT_PATH" ]; then | |
| echo "Missing report_url" | |
| exit 1 | |
| fi | |
| echo "Polling report URL: ${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}" | |
| for i in $(seq 1 20); do | |
| HTTP_CODE=$(curl -s -o /tmp/audit-report.html -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}") | |
| echo "Poll #$i HTTP_CODE=$HTTP_CODE" | |
| if [ "$HTTP_CODE" = "200" ] && [ -s /tmp/audit-report.html ]; then | |
| echo "Report is ready" | |
| exit 0 | |
| fi | |
| sleep 60 | |
| done | |
| echo "Audit report did not become available within timeout" | |
| exit 1 | |
| - name: Fetch markdown report | |
| shell: bash | |
| run: | | |
| REPORT_PATH="${{ steps.submit.outputs.report_url }}" | |
| MD_PATH="${REPORT_PATH%audit-report.html}audit-report.md" | |
| echo "Markdown path: ${MD_PATH}" | |
| HTTP_CODE=$(curl -s -o /tmp/audit-report.md -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${MD_PATH}") | |
| echo "Markdown fetch HTTP_CODE=$HTTP_CODE" | |
| if [ "$HTTP_CODE" != "200" ] || [ ! -s /tmp/audit-report.md ]; then | |
| echo "Failed to fetch markdown report" | |
| exit 1 | |
| fi | |
| - name: Normalize markdown for GitHub comment | |
| shell: bash | |
| run: | | |
| python3 <<'PY' | |
| import re | |
| from pathlib import Path | |
| p = Path("/tmp/audit-report.md") | |
| text = p.read_text(encoding="utf-8", errors="ignore") | |
| text = re.sub(r'\n{3,}', '\n\n', text).strip() + '\n' | |
| Path("/tmp/audit-report-clean.md").write_text(text, encoding="utf-8") | |
| PY | |
| - name: Comment markdown report to PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const reportText = fs.readFileSync('/tmp/audit-report-clean.md', 'utf8').trim(); | |
| const limit = 60000; | |
| const chunks = []; | |
| for (let i = 0; i < reportText.length; i += limit) { | |
| chunks.push(reportText.slice(i, i + limit)); | |
| } | |
| for (let i = 0; i < chunks.length; i++) { | |
| const body = chunks.length > 1 | |
| ? `${chunks[i]}\n\n---\nPart ${i + 1}/${chunks.length}` | |
| : chunks[i]; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body | |
| }); | |
| } |