Skip to content

Commit 236cdab

Browse files
authored
Create audit-pr.yml
1 parent 82ec707 commit 236cdab

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

.github/workflows/audit-pr.yml

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
name: Audit PR
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
types: [opened, synchronize, reopened, ready_for_review]
8+
issue_comment:
9+
types: [created]
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
issues: write
16+
17+
concurrency:
18+
group: audit-pr-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
audit-on-pr:
23+
name: Audit on pull request
24+
if: github.event_name == 'pull_request'
25+
runs-on: [self-hosted, Linux, ARM64, audit-linux]
26+
27+
steps:
28+
- name: Checkout PR head
29+
uses: actions/checkout@v4
30+
with:
31+
ref: ${{ github.event.pull_request.head.sha }}
32+
fetch-depth: 0
33+
34+
- name: Check required tools
35+
run: |
36+
which zip
37+
which jq
38+
which python3
39+
40+
- name: Create source archive
41+
id: archive
42+
shell: bash
43+
run: |
44+
ARCHIVE_NAME="${{ github.event.repository.name }}.zip"
45+
rm -f "/tmp/${ARCHIVE_NAME}"
46+
zip -r "/tmp/${ARCHIVE_NAME}" .
47+
echo "archive_name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"
48+
49+
- name: Submit audit job
50+
id: submit
51+
shell: bash
52+
run: |
53+
RESPONSE=$(curl -s -X POST "${{ vars.AUDIT_SERVICE_URL }}/audit-pr/${{ vars.AUDIT_PR_SKILL }}" \
54+
-H "X-API-Key: ${{ secrets.AUDIT_API_KEY }}" \
55+
-F "file=@/tmp/${{ steps.archive.outputs.archive_name }}" \
56+
-F "from_branch=${{ github.event.pull_request.base.ref }}" \
57+
-F "to_branch=${{ github.event.pull_request.head.ref }}")
58+
59+
echo "Submit response:"
60+
echo "$RESPONSE" | jq .
61+
62+
REPORT_URL=$(echo "$RESPONSE" | jq -r '.report_url // empty')
63+
64+
if [ -z "$REPORT_URL" ]; then
65+
echo "Audit service did not return report_url"
66+
exit 1
67+
fi
68+
69+
echo "report_url=$REPORT_URL" >> "$GITHUB_OUTPUT"
70+
71+
- name: Poll report until ready
72+
id: poll
73+
shell: bash
74+
run: |
75+
REPORT_PATH="${{ steps.submit.outputs.report_url }}"
76+
if [ -z "$REPORT_PATH" ]; then
77+
echo "Missing report_url"
78+
exit 1
79+
fi
80+
81+
echo "Polling report URL: ${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}"
82+
83+
for i in $(seq 1 20); do
84+
HTTP_CODE=$(curl -s -o /tmp/audit-report.html -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}")
85+
echo "Poll #$i HTTP_CODE=$HTTP_CODE"
86+
87+
if [ "$HTTP_CODE" = "200" ] && [ -s /tmp/audit-report.html ]; then
88+
echo "Report is ready"
89+
exit 0
90+
fi
91+
92+
sleep 60
93+
done
94+
95+
echo "Audit report did not become available within timeout"
96+
exit 1
97+
98+
- name: Fetch markdown report
99+
shell: bash
100+
run: |
101+
REPORT_PATH="${{ steps.submit.outputs.report_url }}"
102+
MD_PATH="${REPORT_PATH%audit-report.html}audit-report.md"
103+
104+
echo "Markdown path: ${MD_PATH}"
105+
HTTP_CODE=$(curl -s -o /tmp/audit-report.md -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${MD_PATH}")
106+
echo "Markdown fetch HTTP_CODE=$HTTP_CODE"
107+
108+
if [ "$HTTP_CODE" != "200" ] || [ ! -s /tmp/audit-report.md ]; then
109+
echo "Failed to fetch markdown report"
110+
exit 1
111+
fi
112+
113+
- name: Normalize markdown for GitHub comment
114+
shell: bash
115+
run: |
116+
python3 <<'PY'
117+
import re
118+
from pathlib import Path
119+
120+
p = Path("/tmp/audit-report.md")
121+
text = p.read_text(encoding="utf-8", errors="ignore")
122+
123+
# 去掉首尾空白,压缩多余空行
124+
text = re.sub(r'\n{3,}', '\n\n', text).strip() + '\n'
125+
126+
# 如果首行已经是一级标题,保留;否则不额外加头
127+
Path("/tmp/audit-report-clean.md").write_text(text, encoding="utf-8")
128+
PY
129+
130+
- name: Comment markdown report to PR
131+
uses: actions/github-script@v7
132+
with:
133+
script: |
134+
const fs = require('fs');
135+
const reportText = fs.readFileSync('/tmp/audit-report-clean.md', 'utf8').trim();
136+
137+
const limit = 60000;
138+
const chunks = [];
139+
140+
for (let i = 0; i < reportText.length; i += limit) {
141+
chunks.push(reportText.slice(i, i + limit));
142+
}
143+
144+
for (let i = 0; i < chunks.length; i++) {
145+
const body = chunks.length > 1
146+
? `${chunks[i]}\n\n---\nPart ${i + 1}/${chunks.length}`
147+
: chunks[i];
148+
149+
await github.rest.issues.createComment({
150+
owner: context.repo.owner,
151+
repo: context.repo.repo,
152+
issue_number: context.payload.pull_request.number,
153+
body
154+
});
155+
}
156+
157+
audit-by-comment:
158+
name: Audit by PR comment
159+
if: >
160+
github.event_name == 'issue_comment' &&
161+
github.event.issue.pull_request &&
162+
contains(github.event.comment.body, '/audit-pr') &&
163+
contains(format(',{0},', vars.AUDIT_ALLOWED_USERS), format(',{0},', github.event.comment.user.login))
164+
runs-on: [self-hosted, Linux, ARM64, audit-linux]
165+
166+
steps:
167+
- name: Get PR info
168+
id: pr
169+
uses: actions/github-script@v7
170+
with:
171+
script: |
172+
const pr = await github.rest.pulls.get({
173+
owner: context.repo.owner,
174+
repo: context.repo.repo,
175+
pull_number: context.issue.number
176+
});
177+
178+
core.setOutput('head_ref', pr.data.head.ref);
179+
core.setOutput('base_ref', pr.data.base.ref);
180+
core.setOutput('head_sha', pr.data.head.sha);
181+
182+
- name: Checkout PR head
183+
uses: actions/checkout@v4
184+
with:
185+
ref: ${{ steps.pr.outputs.head_sha }}
186+
fetch-depth: 0
187+
188+
- name: Check required tools
189+
run: |
190+
which zip
191+
which jq
192+
which python3
193+
194+
- name: Create source archive
195+
id: archive
196+
shell: bash
197+
run: |
198+
ARCHIVE_NAME="${{ github.event.repository.name }}.zip"
199+
rm -f "/tmp/${ARCHIVE_NAME}"
200+
zip -r "/tmp/${ARCHIVE_NAME}" .
201+
echo "archive_name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"
202+
203+
- name: Submit audit job
204+
id: submit
205+
shell: bash
206+
run: |
207+
RESPONSE=$(curl -s -X POST "${{ vars.AUDIT_SERVICE_URL }}/audit-pr/${{ vars.AUDIT_PR_SKILL }}" \
208+
-H "X-API-Key: ${{ secrets.AUDIT_API_KEY }}" \
209+
-F "file=@/tmp/${{ steps.archive.outputs.archive_name }}" \
210+
-F "from_branch=${{ steps.pr.outputs.base_ref }}" \
211+
-F "to_branch=${{ steps.pr.outputs.head_ref }}")
212+
213+
echo "Submit response:"
214+
echo "$RESPONSE" | jq .
215+
216+
REPORT_URL=$(echo "$RESPONSE" | jq -r '.report_url // empty')
217+
218+
if [ -z "$REPORT_URL" ]; then
219+
echo "Audit service did not return report_url"
220+
exit 1
221+
fi
222+
223+
echo "report_url=$REPORT_URL" >> "$GITHUB_OUTPUT"
224+
225+
- name: Poll report until ready
226+
id: poll
227+
shell: bash
228+
run: |
229+
REPORT_PATH="${{ steps.submit.outputs.report_url }}"
230+
if [ -z "$REPORT_PATH" ]; then
231+
echo "Missing report_url"
232+
exit 1
233+
fi
234+
235+
echo "Polling report URL: ${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}"
236+
237+
for i in $(seq 1 20); do
238+
HTTP_CODE=$(curl -s -o /tmp/audit-report.html -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${REPORT_PATH}")
239+
echo "Poll #$i HTTP_CODE=$HTTP_CODE"
240+
241+
if [ "$HTTP_CODE" = "200" ] && [ -s /tmp/audit-report.html ]; then
242+
echo "Report is ready"
243+
exit 0
244+
fi
245+
246+
sleep 60
247+
done
248+
249+
echo "Audit report did not become available within timeout"
250+
exit 1
251+
252+
- name: Fetch markdown report
253+
shell: bash
254+
run: |
255+
REPORT_PATH="${{ steps.submit.outputs.report_url }}"
256+
MD_PATH="${REPORT_PATH%audit-report.html}audit-report.md"
257+
258+
echo "Markdown path: ${MD_PATH}"
259+
HTTP_CODE=$(curl -s -o /tmp/audit-report.md -w "%{http_code}" "${{ vars.AUDIT_SERVICE_URL }}${MD_PATH}")
260+
echo "Markdown fetch HTTP_CODE=$HTTP_CODE"
261+
262+
if [ "$HTTP_CODE" != "200" ] || [ ! -s /tmp/audit-report.md ]; then
263+
echo "Failed to fetch markdown report"
264+
exit 1
265+
fi
266+
267+
- name: Normalize markdown for GitHub comment
268+
shell: bash
269+
run: |
270+
python3 <<'PY'
271+
import re
272+
from pathlib import Path
273+
274+
p = Path("/tmp/audit-report.md")
275+
text = p.read_text(encoding="utf-8", errors="ignore")
276+
277+
text = re.sub(r'\n{3,}', '\n\n', text).strip() + '\n'
278+
279+
Path("/tmp/audit-report-clean.md").write_text(text, encoding="utf-8")
280+
PY
281+
282+
- name: Comment markdown report to PR
283+
uses: actions/github-script@v7
284+
with:
285+
script: |
286+
const fs = require('fs');
287+
const reportText = fs.readFileSync('/tmp/audit-report-clean.md', 'utf8').trim();
288+
289+
const limit = 60000;
290+
const chunks = [];
291+
292+
for (let i = 0; i < reportText.length; i += limit) {
293+
chunks.push(reportText.slice(i, i + limit));
294+
}
295+
296+
for (let i = 0; i < chunks.length; i++) {
297+
const body = chunks.length > 1
298+
? `${chunks[i]}\n\n---\nPart ${i + 1}/${chunks.length}`
299+
: chunks[i];
300+
301+
await github.rest.issues.createComment({
302+
owner: context.repo.owner,
303+
repo: context.repo.repo,
304+
issue_number: context.issue.number,
305+
body
306+
});
307+
}

0 commit comments

Comments
 (0)