Skip to content

feat(ci): gate release pins and propose bumps by pull request - #13

Merged
VizzleTF merged 1 commit into
mainfrom
feat/pins-workflow
Sep 4, 2026
Merged

feat(ci): gate release pins and propose bumps by pull request#13
VizzleTF merged 1 commit into
mainfrom
feat/pins-workflow

Conversation

@VizzleTF

@VizzleTF VizzleTF commented Sep 4, 2026

Copy link
Copy Markdown
Member

images/owlab.yaml is the source of truth for which point releases this repository
runs, and owlab context --list already resolves it weekly into the images.yml
matrix. What nothing did was notice when a copy of a release literal fell behind
the pin — so ci.yml and images/Dockerfile have been naming 25.12.4 since
25.12.5 shipped, testing a release nobody runs, with nothing going red to say so.

The gate

tools/pins.sh check requires every NN.NN.N literal in .github/workflows/*.yml and
images/Dockerfile to be a release images/owlab.yaml pins. Comment lines are skipped:
the ones there record measured facts about specific releases, and rewriting those would
be forging a measurement.

Prose is deliberately out of scope — README*.md, docs/, examples/ and
action/action.yml name a release as illustration, and the README's sample
owlab releases output shows a stale pin on purpose.

On main the gate reports exactly the three forgotten copies (ci.yml:155,
ci.yml:207, images/Dockerfile:27) and nothing else. They are bumped to 25.12.5
here.

The proposal

pins.yml runs on a cron, builds owlab, and reads owlab releases --json — the
owlab.releases/v1 schema it has always emitted and nobody consumed. With stale == 0
it prints one line and exits, touching nothing. With stale > 0 it derives the
pinned -> newest pairs, rewrites them with tools/pins.sh bump, re-validates with
pins.sh check and owlab context --list, and opens a pull request. It proposes; it
does not apply — the same shape check-updates.sh has in owfeed-packages.

An already-open proposal for the same release is detected and not duplicated.

Two failure modes it avoids by construction

Every gh call passes -R "$GITHUB_REPOSITORY", and results are captured into a
variable rather than piped into grep: a pipeline reports the status of its last
command, so a failed gh reaching grep as empty output reads as a valid answer.

The pull request is authored by app/github-actions, and this repository's
fork-pr-contributor-approval policy is first_time_contributors, so its
pull_request run would sit in action_required until a person approves it. The job
therefore dispatches ci.yml on the pin branch — workflow_dispatch is GitHub's own
documented exception, and check runs bind to a commit, so the same contexts are
reported. A refusal to dispatch prints the manual command instead of failing the job.

Note on 25.12.5

The rootfs tarball exists and answers 200, but the openwrt/rootfs:x86_64-25.12.5
container tag is not on Docker Hub yet. That does not block anything: owlab context asks the registry and falls back to the tarball — measured locally, and
confirmed green by the images.yml run for openwrt-2512-x86-64 at 25.12.5. The
ARG carries that as a comment.

The release number is copied out of images/owlab.yaml into every place that
starts a router, and a stale copy fails nothing: an old release still builds,
it just stops being the one anybody tests. Measured on 2026-09-04 -- ARG
BASE_IMAGE and both release literals in ci.yml still said 25.12.4 while the
config had been on 25.12.5 since it was written, and every job was green.

tools/pins.sh holds the list of files this repository runs a release number out
of, once, and both directions over it: `check` refuses a literal the config does
not pin, `bump` moves every copy of one release. ci.yml runs `check` in the
shell job. Comments, examples/, the READMEs and docs/ stay out of both, because
a release number there is an illustration -- the README's sample `owlab
releases` output is a pin one release behind on purpose.

pins.yml is the consumer the report never had. `owlab releases --json` has
carried a `stale` field since 0.2.0 and images.yml pipes the table into a log
nobody opens. Daily, this reads it, rewrites the pins when stale is non-zero,
validates with `pins.sh check` and `owlab context --list`, and opens a pull
request. It never pushes to main: what proves a new release still builds a
router is the CI run on that pull request.

It then dispatches ci.yml on the pin branch. A pull request opened under
GITHUB_TOKEN is authored by app/github-actions, and this repository's approval
policy is first_time_contributors, so its pull_request run is created and held
in action_required until a person presses a button; workflow_dispatch is
GitHub's own documented exception, and check runs bind to a commit rather than
to an event.

The two lagging copies move to 25.12.5. openwrt/rootfs:x86_64-25.12.5 was not
on Docker Hub yet -- upstream tags the download server first -- and that is not
a problem: `owlab context` asks the registry and falls back to the rootfs
tarball, which is the path the published 25.12.5 images were already built
through.
@VizzleTF
VizzleTF merged commit c9d914a into main Sep 4, 2026
14 checks passed
@VizzleTF
VizzleTF deleted the feat/pins-workflow branch September 4, 2026 16:20
VizzleTF added a commit that referenced this pull request Sep 4, 2026
`docker compose build` puts every router in one buildkit solve, and buildkit
cancels the solve on the first target that fails. The extras step ran under
`set -eu` with no guard, so one unusable staged file turned into a failed lab:
measured on a three-router stand, an .ipk with an unsatisfiable dependency
exited 255 and took `#16 CANCELED`, `#13 CANCELED` and `owlab: build failed`
with it. Both release lines did it -- it is `set -eu`, not the package manager:
the same stand with a .apk apk answers `unable to select packages` exited 27
and cancelled its neighbour. Neither `docker compose build` nor
`docker buildx bake` has a --keep-going, so the tolerance has to live in the
RUN.

The extras step now installs the set as before and, only if that fails,
installs each file on its own in the staged order, so the good ones still land.
The successful path is unchanged.

Tolerance without a report would be worse than the cancelled builds it
replaces, so what still fails is named once per package and recorded in
/etc/owlab/extras-failed. `owlab up` reads that back off each running router
and prints it under the ready table, exiting non-zero; `owlab test` gains an
`extra_packages` step that fails the router before any assertion runs against a
box missing what it was told to have.

Fixes #12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant