feat: Add install-docker-cli #76
No reviewers
Labels
No labels
priority/P0
priority/P1
priority/P2
priority/P3
size/L
size/M
size/S
size/XL
size/XS
state/blocked
state/done
state/in-progress
state/in-review
state/needs-refinement
state/ready
type/bug
type/chore
type/feature
type/refactor
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
amtronics/platform-actions!76
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/59-install-docker-cli"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue
Refs #59 — the extraction. It does not close it: the second acceptance
criterion is a fleet census with no inline copies left, and converting the
consumers is per-repo follow-up work the issue's own scope defers.
Problem
The Docker CLI install is copied into every publishing job on the fleet —
sixteen sites — and has already diverged once:
ahmad/expiroandahmad/lardacarry asedworkaround for bullseye's unsigned securitysuite and the other ten do not. The conventions forbid exactly this.
The sequencing question this waited on is answered.
amtronics/infrastructure-vps#420landed the new runner default image,and it was checked rather than assumed:
No Docker CLI, so deleting these steps was never an option and the
extraction branch is the right one.
Solution
install-docker-cli, a composite action with no inputs, wrapping theproven step from
templates/release.yml.The
sedworkaround is deliberately not carried across. It existedbecause bullseye's security suite stopped being signed; bookworm's is.
Faithful otherwise.
Review notes
One deviation from "extract faithfully", and it should be read as a
decision rather than found later. The action returns early when
dockeris already present. Whether the runner image ships the CLI isprecisely the question that parked this issue, and the next image may
answer it differently — when it does, this becomes a skip in every
consumer at once instead of a reinstall in each. Three lines and a
comment. Say so if you would rather it were byte-faithful and I will drop
it.
What is behaviour-tested, and what is not, stated exactly. Only the
guard is driven. The rest of the step sources
/etc/os-releaseand writesto
/etc/apt/keyringsand/etc/apt/sources.list.d— absolute paths astub harness cannot intercept and must not create. Its shape is covered by
check-actions.py, which now reports it (install-docker-cli/action.yml: ok (1 run step(s) checked)); what it installs is only ever proved by ajob that then runs
docker.Mutation-checked, and one mutation initially escaped, which is why
there are four cases and not three:
Deleting the guard's
exit 0left every case green: the slicedifblockstill prints and still ends, so slicing cannot show that nothing after it
runs. The added case drives the whole step with an empty
PATH—if the guard holds it returns before the first
apt-get; if it does not,apt-getis missing and the step dies 127. Nothing reaches a system patheither way, which is what makes running the full body safe here.
A collision I caused and fixed: my first constant was named
GUARD_OPENING, whichdeploy-vps's suite already uses, so itsextraction broke rather than mine. The harness failed loudly — "the suite
would test nothing" — which is the behaviour its docstring promises.
Gates, by exit code:
check-actions0,test-check-actions0,test-behaviour-harness0,behaviour-tests0, README lint 0.Risks and trade-offs
Nothing consumes it yet, so nothing can regress from merging it. The
twelve consumers convert in their own repositories, and until they do the
inline copies stay — which is why this does not close #59.
A composite action referenced by every repository is production
infrastructure. It is behind the
@v3major tag like the others, soconsumers adopt it deliberately rather than on the next commit to
main.Verified before marking this ready.
default container, then the Docker CLI is available afterwards."
Half-verified, and the half that is missing is worth naming. What is
checked: the step is structurally sound (
check-actions.pynow reportsinstall-docker-cli/action.yml: ok (1 run step(s) checked)), its body isthe proven one from
templates/release.ymlbyte-for-byte apart from theguard, and the default image genuinely lacks
docker— run, notassumed. What is not checked is a job that runs the action and then
runs
docker, because nothing consumes it yet. The first convertedconsumer is where that criterion is actually met.
an inline copy." Not met and not claimed. Converting the twelve
consumers is per-repo follow-up work this issue's own scope defers, so
#59 stays open on that.
The guard is behaviour-tested and mutation-checked, four cases against
the shipped file:
The last one escaped at first, and the reason is the useful part: a sliced
if … fistill prints and still ends without itsexit 0, so slicingcannot express "and nothing after it runs". The added case drives the
whole step with an empty
PATH, where continuing is impossible ratherthan merely wrong.
Gates, by exit code:
check-actions0,test-check-actions0,test-behaviour-harness0,behaviour-tests0, README lint0. CI green on this head.
The one thing to decide rather than discover: the early return when
dockeris already present is a deliberate deviation from "extractfaithfully". It is argued in the description; if you would rather it were
byte-faithful, say so and it comes out in one commit.
WIP: feat: Add install-docker-clito feat: Add install-docker-cliApproved. Machine-checkable end to end — a composite action with a behaviour suite — so the bot reviews and approves this normally rather than recusing; I ran the suite and mutated the action myself rather than reading it.
Verified
The extraction is faithful. Parsed
templates/release.ymlandinstall-docker-cli/action.ymlat7e4a2a79and diffed the two step bodies: the only differences are the guard, added comments, and two line continuations.apt-get install -y ca-certificates curl gnupg, thecurl | gpg --dearmorpipeline, thechmod, thedebline andapt-get install -y docker-ce-cliare unchanged, and nosedis carried across. The description's claim holds exactly.check-actions.pyreports it, run at the PR head:install-docker-cli/action.yml: ok (1 run step(s) checked), alongside the other eight.behaviour-tests.pypasses at the head — 29 cases, exit 0, including the four new ones.The mutation table reproduces. I re-ran the suite against four mutations of
action.yml:The fourth case does the work claimed for it: with the
exit 0gone the sliced-guard cases stay green and only the whole-body-with-empty-PATHcase catches it, by dying 127 on the firstapt-get. That is the right way to prove "nothing after it runs", and it is worth keeping in mind for the next extraction — a slicedifblock cannot show that control left the script.Dropping the
sedis safe for every current consumer, and I checked the fleet rather than the reasoning. All 19 copies live in jobs with no pinnedcontainer:, so every one of them takes the runner default, and the default isnode:22-bookwormwith no override anywhere:ansible/roles/forgejo/defaults/main.yml:49setsforgejo_runner_job_image: "node:22-bookworm"and lines 51-54 mapubuntu-latest,ubuntu-22.04,linuxandself-hostedall to it. Bookworm's security suite is signed, so the workaround has nothing left to work around.Nothing consumes the action yet, confirmed: no
uses:ofinstall-docker-clianywhere on the fleet. Merging it cannot regress a pipeline.Two corrections to the record, neither a change request
The sequencing evidence is
amtronics/infrastructure-vps#429("fix: Move the runner's default job image to bookworm",a5b4bde), not#420—#420is not that change. The substance is right and I verified it independently, as above; the number just will not lead the next reader to it.The census is worse than the description says, which strengthens the case rather than weakening it. Not sixteen sites diverged once — 19 sites across 13 repositories, in 8 distinct bodies:
308f292b 6 namey, bayan-web, kidpoints, suhba-web, suhba-docs, khitta (release.yml:publish)
8427d3e0 4 larda (deploy:deploy, pr:image, release:publish, release:deploy)
49b375e1 3 trip, portfolio, launchpad (release.yml:publish)
fd0818d2 2 expiro (deploy:deploy, release:deploy)
1209b5f8 1 imamah (release.yml:publish, named "Install Docker CLI (Debian)")
acbf437e 1 expiro (pr.yml:image)
f7986b5c 1 expiro (release.yml:publish)
62b9d0a0 1 stem (release.yml:publish)
expiroalone carries three different bodies in four jobs. That is the census#59's second criterion asks for, as ofmaintoday — worth pasting onto#59so the conversion work has a checklist and a finish line.On the deviation you flagged: keep the early return. It is the one part of the step with a decision in it, it is the part the suite drives, and it is what makes the next runner-image change a no-op in every consumer at once instead of thirteen follow-ups. Byte-faithful would have been the wrong call here.
Correcting my own first point above, which was wrong:
amtronics/infrastructure-vps#420is the issue ("Runner default job image is bullseye; security suite is dead post-LTS", closed), and#429is the pull request that closed it. Citing #420 was a legitimate reference to the work, not a wrong number — I checked/pulls/420, got a 404, and concluded too quickly.The verification underneath it stands unchanged:
forgejo_runner_job_image: "node:22-bookworm"inansible/roles/forgejo/defaults/main.yml:49, all four runner labels derived from it, no override anywhere, and all 19 copies of the install step in jobs with no pinnedcontainer:. Dropping thesedis safe.The approval stands, and the census in the first comment is unaffected.