Lint findings 115 linters, 18 refused with reasons
0
0%
Statement coverage
86%
Uncovered functions
1
Cloud drivers
2
Container image
19 MB
Go, hand-written
0 lines
The end-to-end tests drive a real Forgejo, a real GitLab and a real
GitHub repository, and fail if a single machine or runner registration
survives the run. The one uncovered function is main,
which calls os.Exit.
Ephemeral CI runners: one throwaway machine per job, for GitHub
Actions, GitLab CI and Forgejo Actions, on any cloud.
All three forges solved the same problem and none of them the same way.
GitHub hands out a config good for exactly one job. Forgejo hands out a
runner that deletes itself. GitLab hands out a token that does neither
and a registration it will never clean up. The fourth surprise came
from OVHcloud: a create response with no creation time, which the
reaper reads as a machine born in 1970 and long overdue for deletion.
It would have emptied the account on its first pass.
Run it
docker run -v ./data:/data -p 8080:8080 \
ghcr.io/slop-place/runnerforge
Every machine records what it cost, billed from when the provider
reported it running rather than from when the row was created.
Time spent booting is not charged, and counting it would overstate
every figure here.
Machines still running show a bill that is still growing, which is
the number worth watching. Sub-cent amounts are not rounded to
$0.00: a one-minute job genuinely costs less than a
cent, and hiding that defeats the point.
02 / 04
Forms, not a JSON textarea
Drivers declare their settings as typed fields, so the form has
labels, help text and validation. Providers that can list what an
account offers turn the flavor and image inputs into pickers of
real values — smallest first, because the cheapest thing that
works is usually the right choice.
Secrets are never rendered back. The form says one is stored and
keeps it when you leave the field blank.
03 / 04
The UI is not a dead end
Anything configured by clicking renders itself as Terraform,
referencing the other resources properly rather than hard-coding
ids that mean nothing in another deployment.
Secrets become var. references, so what you paste
into a repository never carries a credential.
04 / 04
Or let the cluster own it
The same configuration renders as a Kubernetes custom resource.
Run with -k8s and objects are reconciled into the
database, their status reports back, and those records go
read-only in the UI.
Credentials come from a secretRef in both directions.
Anything made in the UI is left alone, so both ways of working
coexist in one deployment.
How it stays honest
The reaper does not trust the database
The failure mode of a runner controller is leaking machines and
billing you. So it asks each cloud what it is actually
running, rather than what it believes — and can lose its database
entirely without leaking.
Nothing important is mocked
The tests drive a real Forgejo, a real GitLab and a real GitHub
repository. Runners are containers rather than cloud VMs, which is the
only substitution; every run asserts that nothing survived it.
It says when it is not safe
The console holds cloud credentials. Configure an OIDC issuer and it is
gated; leave it unset and it says so at startup and on every page,
rather than looking secure when it is not.
Read it before you run it. Thorough testing is not the same
as review, and a machine can be confidently wrong in ways a test suite agrees
with. This one provisions cloud machines that cost money. Point it at a
disposable project first.