mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 23:14:33 +01:00
ci-audit: the weekly issue is the workflow bot's own (creator filter, newest first)
findIssue read the first 100 open issues oldest first and matched any issue with the title and marker, so an outsider's copy won the match and a repo with more than 100 open issues got a new audit issue every week. It now lists only github-actions[bot] issues, newest first, and requires that author. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
530fbf50e2
commit
080268fb43
3 files changed
+26
-6
No files matched your search
+3
-2
@@ -82,7 +82,8 @@ Turn it off with `gstack-config set ci_audit_ship off`.
|
||||
`lib/ci-audit/templates/ci-audit-weekly.yml` runs the full report every Saturday at 15:00 UTC
|
||||
and keeps one open "Weekly CI audit" issue current. A complete audit creates or replaces
|
||||
the issue, or closes it when nothing is left to fix. A partial or failed audit comments
|
||||
on the issue and keeps the last complete findings. The issue is matched by its title and
|
||||
a marker line, so an issue a person opened with the same title is left alone. It reads
|
||||
on the issue and keeps the last complete findings. The issue is matched by its author
|
||||
(`github-actions[bot]`, the workflow's own token), its title and a marker line, so an
|
||||
issue anyone else opened, even with the same title and marker, is left alone. It reads
|
||||
with the workflow's `GITHUB_TOKEN` (1,000 calls an hour), so it runs with
|
||||
`--max-calls 400`.
|
||||
@@ -45,11 +45,16 @@ export function issueBody(r: AuditReport, runUrl?: string): string {
|
||||
return head + (md.length > MAX_BODY ? md.slice(0, MAX_BODY) + '\n\n(truncated; the full report is the run artifact)' : md);
|
||||
}
|
||||
|
||||
/** The open issue this audit owns: its title and marker, so a person's issue with the same title is left alone. */
|
||||
export const ISSUE_AUTHOR = 'github-actions[bot]';
|
||||
|
||||
/**
|
||||
* The open issue this audit owns: opened by the workflow's own token, with its title and marker.
|
||||
* Anyone can copy the title and marker, so an issue another account opened is never touched.
|
||||
*/
|
||||
export async function findIssue(client: ApiClient, repo: string): Promise<Existing | null> {
|
||||
const res = await client.get(`/repos/${repo}/issues?state=open&per_page=100&sort=created&direction=asc`);
|
||||
const res = await client.get(`/repos/${repo}/issues?state=open&creator=${encodeURIComponent(ISSUE_AUTHOR)}&per_page=100&sort=created&direction=desc`);
|
||||
if (res.status !== 200) throw new AuditError('ci_audit_api_failed', `issues list returned HTTP ${res.status}`, 'now');
|
||||
const hit = ((res.json as any[]) ?? []).find(i => !i.pull_request && i.title === ISSUE_TITLE && String(i.body ?? '').includes(ISSUE_MARKER));
|
||||
const hit = ((res.json as any[]) ?? []).find(i => !i.pull_request && i.user?.login === ISSUE_AUTHOR && i.title === ISSUE_TITLE && String(i.body ?? '').includes(ISSUE_MARKER));
|
||||
return hit ? { number: Number(hit.number), body: String(hit.body) } : null;
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -7,7 +7,7 @@ import type { Run } from '../lib/ci-audit/collect';
|
||||
import { concurrencyFlag, lessonRunners } from '../lib/ci-audit/lessons';
|
||||
import { cohort, measure, phases } from '../lib/ci-audit/measure';
|
||||
import { priceRunner } from '../lib/ci-audit/prices';
|
||||
import { ISSUE_MARKER, planIssue } from '../lib/ci-audit/publish';
|
||||
import { ISSUE_MARKER, ISSUE_TITLE, findIssue, planIssue } from '../lib/ci-audit/publish';
|
||||
import type { AuditReport } from '../lib/ci-audit/report';
|
||||
import { CI_SHAPING, MIN_COHORT, comparable, judgeHead, staticChecks } from '../lib/ci-audit/ship-check';
|
||||
import { parseWorkflow, readStatic } from '../lib/ci-audit/static';
|
||||
@@ -263,6 +263,20 @@ describe('weekly issue', () => {
|
||||
expect(planIssue({ exit: 3, report: null, error: 'CI_AUDIT: unavailable x (ci_audit_deadline)' }, null).action).toBe('none');
|
||||
expect(planIssue({ exit: 4, report: { ...withFixes, partial: ['x'] } as AuditReport }, null).action).toBe('create');
|
||||
});
|
||||
|
||||
test('finds only the workflow bot\'s own issue, past 100 open issues and an outsider\'s copy', async () => {
|
||||
const issue = (number: number, login: string, title = 'other', body = '') => ({ number, title, body, user: { login } });
|
||||
const open = [issue(1, 'outsider', ISSUE_TITLE, `${ISSUE_MARKER}\nfake findings`),
|
||||
...Array.from({ length: 150 }, (_, i) => issue(i + 2, 'person')),
|
||||
issue(152, 'github-actions[bot]', ISSUE_TITLE, `${ISSUE_MARKER}\nreal findings`)];
|
||||
const client = { text: async () => null, get: async (apiPath: string) => {
|
||||
const q = new URL(apiPath, 'https://api.github.com').searchParams;
|
||||
const rows = open.filter(i => !q.get('creator') || i.user.login === q.get('creator'));
|
||||
if (q.get('direction') !== 'asc') rows.reverse();
|
||||
return { status: 200, json: rows.slice(0, Number(q.get('per_page') ?? 30)), headers: {} };
|
||||
} };
|
||||
expect(await findIssue(client as never, 'o/n')).toEqual({ number: 152, body: `${ISSUE_MARKER}\nreal findings` });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Saturday workflow template', () => {
|
||||
|
||||
Reference in new issue
Block a user