Skip to content

ci: dry-check the mirror list on PRs and write tags only after merge - #4544

Merged
bpamiri merged 2 commits into
developfrom
peter/mirror-pr-dry-run
Oct 9, 2026
Merged

bpamiri merged 2 commits into
developfrom
peter/mirror-pr-dry-run

Conversation

@bpamiri

@bpamiri bpamiri commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4540. Until now, a same-repo PR that touched the mirror files ran the PR head's mirror-images.sh with packages: write, against the same tags develop's CI pulls (for example mirror/ortussolutions/commandbox:latest). An unreviewed change could therefore repoint a live tag.

Now:

  • Pull requests, forks included, run a check job per image with a read-only token (contents: read, packages: read). mirror-images.sh check <ref> resolves the source on Docker Hub with a HEAD, which doesn't count toward the pull limit. It then compares that digest with the mirror's and reports one of up to date, would copy … (mirror has …) or would copy … (new image …). It fails only when a source doesn't resolve, or when a digest pin no longer exists upstream. Nothing is written.
  • Copies (the mirror job, packages: write) run only on a push to develop that changes the mirror files, on workflow_dispatch, and on the weekly schedule.

The cost is two steps for a new image: add it to tools/ci/mirror-images.txt and merge, which mirrors it, then point CI at it in a later PR.

Test plan

  • actionlint and shellcheck are clean
  • This PR's own run (37999195379) executes only list + check jobs; no mirror job runs, and each check reports up to date

🤖 Generated with Claude Code

A PR that changed the image list copied images with its own, unreviewed
copy of the script, writing the same tags develop's CI pulls. PRs, forks
included, now get a read-only check: each reference resolves on Docker Hub
and its digest is compared with the mirror's, reporting what the copy after
merge would do. Copies run only on push to develop, dispatch and the weekly
schedule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri

bpamiri commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

rev1-r3 review @ 1070b56: CHANGES (one line)

The split is right, apart from one gap in the write path.

Fork PRs are read-only and can't reach the write job.

  • On the pull_request event, a fork's GITHUB_TOKEN is read-only and it gets no secrets.
  • check declares only contents: read and packages: read.
  • mirror carries a job-level if: github.event_name != 'pull_request', so it is skipped for every PR, same-repo or fork. That is confirmed in run 37999195379: list, then 12 check jobs green, and mirror ${{ matrix.image }} skipped.

A crafted list file can't do harm beyond what a fork PR can already do.

  • Each list value reaches check only through the job name and env: IMAGE, never interpolated into a run: script. The script reads it as "$IMAGE", and parse rejects non-Docker-Hub names, missing tags and malformed digests.
  • A fork already runs its own mirror-images.sh in list and check. Under pull_request, that means a read-only token and no secrets.
  • At most it can read the private mirror, which only holds unchanged public upstream images.

check behaves as described.

  • It resolves the source with a HEAD and fails if a digest pin no longer resolves to itself.
  • It reports up to date, would copy (tag missing, new image, or mirror unreadable), or would copy (mirror differs), and writes nothing.
  • Seen in the run: up to date: …/commandbox:sha256-1f180edf… (sha256:1f180edf…).

The triggers are unchanged: dispatch, the 23 5 * * 1 schedule, push to develop and pull_request, all on the same three paths.

The gap: workflow_dispatch on any branch still writes the live tags

mirror now runs whenever the event isn't pull_request. That includes workflow_dispatch, which any repo writer can run with --ref <unmerged-branch>. That run executes the branch's mirror-images.sh and list with packages: write, against the same tags develop pulls. That is the unreviewed-write path this PR sets out to close, and the header comment now says "only reviewed code writes the tags".

Suggested fix:

  mirror:
    if: github.event_name != 'pull_request' && github.ref == 'refs/heads/develop'

schedule and the push trigger already run on develop, so they're unaffected, and only a dispatch from another branch is refused. If a branch dispatch is useful for trying the script, have it run the check job instead, with if: github.event_name == 'pull_request' || github.ref != 'refs/heads/develop'.

PR CI: 21 pass, 6 skipping, RustCFML (Linux) still pending (unrelated). Up to date with develop, MERGEABLE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri

bpamiri commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

rev1-r3 review @ 11d1e7f: APPROVE

The one change from my CHANGES at 1070b56 is in: mirror now has if: github.event_name != 'pull_request' && github.ref == 'refs/heads/develop', with a comment saying why.

  • schedule runs on the default branch (develop), and push only triggers for develop, so both still copy.
  • A workflow_dispatch on any other ref gets no write job.
  • PRs, forks included, get only the read-only check jobs.

Everything else is as I reviewed at 1070b56:

  • fork PRs run with read-only tokens and are skipped from the write job;
  • list values reach check only through env/name, and parse validates them;
  • the triggers are unchanged.

PR CI on this head: 25 pass, 4 skipping, 0 fail. That includes list and all 12 check <image> jobs; mirror is skipped on the PR, as intended. MERGEABLE.

@bpamiri
bpamiri merged commit 0fc0f7f into develop Oct 9, 2026
29 checks passed
@bpamiri
bpamiri deleted the peter/mirror-pr-dry-run branch October 9, 2026 22:41
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