report_job_log live-smoke-test passed: live smoke test log line REGRESSION-CHECK-LIVE-SMOKE
report_job_log.py only ever commented on an open PR, so a push to
development/main -- the shape of a publish pipeline, which has no
pull_request trigger -- had nowhere to post: the branch pushed to is the
merge's target, not the already-closed PR that produced it. Combined with
job logs 500ing on this Gitea, that made a push-triggered failure completely
invisible (weblib-viewer run 1769, diagnosed only by hardcoding --pr on a
throwaway commit).
A GitHub-style commit-comment endpoint, the first thing tried, does not
exist on this Gitea: 404, and absent from /swagger.v1.json entirely. What
does exist and was verified live: GET commits/{sha}/pull resolves the PR
that produced a given commit, open or closed, across both merge styles this
org uses -- so the closed PR the push actually came from can still be found
and commented on as before. When even that finds nothing (a commit that was
never part of any PR), fall back to a commit status, since that endpoint is
already proven to work here.
No CLI changes; existing callers are unaffected.
Closes #14
Co-authored-by: bit <bit@das-labor.org>
156 lines
7.8 KiB
Markdown
156 lines
7.8 KiB
Markdown
# weblib-ci
|
|
|
|
The CI scripts shared by [cfbypass], [weblib-archive], [weblib-fs] and
|
|
[weblib-viewer]. Split out per weblib-archive#44, where they had been
|
|
hand-copied into each repo and had already drifted once.
|
|
|
|
**Public deliberately.** Nothing here is a secret or specific to the archive's
|
|
contents: a nixpkgs-pinning wrapper, a log poster and a label reconciler.
|
|
Public means a consumer needs no deploy key, no ssh setup and no secret to
|
|
fetch it — which was measured to be the difference between one step and three.
|
|
|
|
## What is here
|
|
|
|
| file | what it does |
|
|
|---|---|
|
|
| `with-nixpkgs.sh` | Runs a command with one nixpkgs package on PATH, pinned to the *consuming* repo's `flake.lock`. Avoids `nix shell nixpkgs#x`, which re-resolves the registry and refetches a channel tarball whenever the branch moves. |
|
|
| `report_job_log.py` | Posts the tail of a build log as a PR comment, falling back to a commit status when a `push`-triggered job has no PR to comment on. Exists because `actions/jobs/{id}/logs` returns 500 for every id on Gitea 1.25.2, so a red job otherwise says only that it failed. Takes `--status`; see below. |
|
|
| `sync_blocked_label.py` | Keeps `Status/Blocked` in step with Gitea's dependency graph. Resolves the label from the repo *or the organisation*, never touches an issue marked `Status/On Hold` or `Status/Abandoned`, and leaves the label alone (rather than guessing) on a blocker whose state a repo-scoped token cannot resolve — see weblib-ci#11. |
|
|
|
|
All three are standard library / plain bash only. They are *run*, not built, so
|
|
this repo has no flake.
|
|
|
|
`test_report_job_log.py` covers the reporter and `test_sync_blocked_label.py`
|
|
covers the reconciler. Both are standard library and offline — the forge each
|
|
talks to is an `http.server` on localhost that keeps what it is sent, so a
|
|
test reads state back rather than trusting an exit status of 0 (which the
|
|
reporter in particular returns even when its POST failed).
|
|
|
|
```bash
|
|
python3 test_report_job_log.py # 28 tests, ~3s, no network
|
|
python3 test_sync_blocked_label.py # 12 tests, no network
|
|
```
|
|
|
|
There is no workflow running it: this repo has no `.gitea/workflows` at all,
|
|
and no `flake.lock` for `with-nixpkgs.sh` to read. Run it by hand before
|
|
pushing.
|
|
|
|
## Using it
|
|
|
|
```yaml
|
|
- uses: actions/checkout@v4
|
|
- id: ci
|
|
uses: https://git.chaosbit.de/weblib/weblib-ci@main
|
|
- run: bash ${{ steps.ci.outputs.path }}/with-nixpkgs.sh python3 \
|
|
python3 ${{ steps.ci.outputs.path }}/report_job_log.py /tmp/build.log
|
|
```
|
|
|
|
No credentials anywhere: the repo is public, which is the point of it being so.
|
|
|
|
`with-nixpkgs.sh` reads the **consuming** repo's `flake.lock` relative to the
|
|
working directory, so it keeps working when invoked by absolute path from
|
|
outside the checkout.
|
|
|
|
### Use the full URL, not `weblib/weblib-ci@main`
|
|
|
|
Measured on weblib-archive#44 (2026-09-07), one job per form because Gitea
|
|
posts one commit status per job and job logs return 500:
|
|
|
|
| form | result |
|
|
|---|---|
|
|
| `uses: https://git.chaosbit.de/weblib/weblib-ci@main` | **works** |
|
|
| `uses: weblib/weblib-ci@main` | **fails** |
|
|
| `git clone https://…/weblib-ci.git` with no credentials | works |
|
|
| `steps.<id>.outputs.path`, then running a tool through it | works |
|
|
|
|
The bare `owner/repo` form resolves against the instance's default actions URL
|
|
rather than this host, so it has to be the full URL. Both forms failed while
|
|
this repo was **private**, which is the other half of why it is public — the
|
|
alternative was a deploy key and an ssh setup step in four repos.
|
|
|
|
The `outputs.path` row is listed separately on purpose: the action *running* and
|
|
its output *reaching the caller* are different claims, and a composite action
|
|
returning an empty string is exactly the sort of thing that looks green.
|
|
|
|
### `report_job_log.py --status`
|
|
|
|
The header used to be hardcoded to *failed*. That is true of every caller here,
|
|
because each guards the step with `if: failure()` — but a probe run under
|
|
`if: always()` posted a failure report for a job that had passed
|
|
(weblib-viewer#10, filed as #8).
|
|
|
|
**The default is still `failed`**, so a caller passing only the log path is
|
|
unchanged. A step that can run on success has to say so:
|
|
|
|
```yaml
|
|
- name: report the log
|
|
if: always()
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
bash "${{ steps.ci.outputs.path }}/with-nixpkgs.sh" python3 \
|
|
python3 "${{ steps.ci.outputs.path }}/report_job_log.py" /tmp/build.log \
|
|
--status "${{ job.status }}"
|
|
```
|
|
|
|
`${{ job.status }}` yields `success`/`failure`/`cancelled`/`skipped`, so those
|
|
spellings are accepted alongside `passed`/`failed`. `JOB_STATUS` in the
|
|
environment does the same thing if a flag is awkward. An **unrecognised**
|
|
status is put in the header verbatim rather than rejected: `argparse`'s
|
|
`choices=` would exit 2 on a value this list has not heard of, and the log —
|
|
the entire reason the script exists — would never be posted.
|
|
|
|
### `push`-triggered jobs: no open PR does not mean no channel
|
|
|
|
Found the hard way on weblib-ci#14: a `push` to `development`/`main` — the
|
|
common shape of a workflow like a publish pipeline, which has no
|
|
`pull_request` trigger at all — has no *open* PR for `report_job_log.py` to
|
|
comment on. The branch that was pushed to is the merge's **target**, not the
|
|
PR that produced it, and that PR is already closed. The old behaviour was to
|
|
print "no pull request for this run" and exit 0, so a failure there was
|
|
completely invisible: not the log (500 on this Gitea), not a comment
|
|
(nowhere to put it), nothing. That is how weblib-viewer run 1769's failure
|
|
(`publish.sh` refusing to overwrite an existing APK) went unnoticed until it
|
|
was reproduced by hand.
|
|
|
|
Two things changed, tried in order, neither needing a new flag:
|
|
|
|
1. **Ask Gitea which PR produced this exact commit.**
|
|
`GET repos/{repo}/commits/{sha}/pull` resolves the PR a commit came from —
|
|
open or closed — keyed on the commit itself rather than a branch name.
|
|
Verified against both merge styles used in this org: an explicit merge
|
|
commit, and a fast-forward promotion where the "merge commit" is just the
|
|
PR branch's own tip. This needs the **full** 40-character `GITHUB_SHA`; a
|
|
short sha gets the same 404 shape as a commit with no PR at all, so it
|
|
would silently look like "not found". This covers the ordinary case —
|
|
almost every push to a protected branch here is a merge landing.
|
|
2. **If that also finds nothing** — a commit that was never part of any
|
|
PR — post a **commit status** instead
|
|
(`POST repos/{repo}/statuses/{sha}`, context `report_job_log`). It is not
|
|
a substitute for the full log: this Gitea did not truncate a 3000-character
|
|
description in testing, but a status is UI chrome, not a page, so what
|
|
goes there is deliberately a short snippet (`STATUS_TAIL`, 500 characters),
|
|
not the whole tail. `state` follows the same outcome as the header phrase
|
|
(`failed → failure`, `passed → success`, `cancelled → warning`,
|
|
`skipped → skipped`; an unrecognised status maps to `error` rather than
|
|
guessing pass or fail), and `target_url` points at the commit.
|
|
|
|
**A commit *comment* API (GitHub-style, `commits/{sha}/comments`) does not
|
|
exist on this Gitea** — 404, and absent from `/swagger.v1.json` entirely, not
|
|
merely unauthorised. That ruled out the most direct-looking option; the
|
|
commit-status endpoint is the one that is actually there.
|
|
|
|
If a PR *is* found (by either the original lookups or the new sha lookup),
|
|
behaviour is identical to before: a comment, same format. The status path is
|
|
only reached when no PR exists anywhere for the commit.
|
|
|
|
### Why not a flake input
|
|
|
|
These are scripts a workflow runs, not derivations. A flake input would cost a
|
|
`flake.lock` bump in four repos every time one changes, and buys nothing.
|
|
|
|
[cfbypass]: https://git.chaosbit.de/weblib/cfbypass
|
|
[weblib-archive]: https://git.chaosbit.de/weblib/weblib-archive
|
|
[weblib-fs]: https://git.chaosbit.de/weblib/weblib-fs
|
|
[weblib-viewer]: https://git.chaosbit.de/weblib/weblib-viewer
|