Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 4: a job can name the machine it needs

6c03e461ee409f4bd053ee552edd87b6188a5eb2 · Keenan Nemetz · 16d ago · parent b238ac24

Phase 4: a job can name the machine it needs

`runs-on: [linux, arm64]` on a job, `--label linux` on a runner, and the
lease hands a job only to a runner carrying every label it asked for. Extra
labels on the runner are not a mismatch — the rule is set inclusion, so a
machine more specific than the job needs still qualifies — and a job with no
runs-on matches every runner, which is what every job did before this
existed.

Both spellings are accepted (`runs-on: linux` and a list), because both are
the obvious way to write it and a dialect already separate from GitHub's
should not also fail on the spelling a person guessed. Labels are validated
in pre-receive by the same rule the runner applies to --label at startup: a
label no runner can carry leaves a job queued forever and looks exactly like
a busy pool, which is the failure nobody debugs because there is no error.

Labels are pinned at enqueue next to the steps and the artifact patterns. A
workflow edited after a push must not change where an already-queued job is
allowed to run. That also settles the derived-store question: run_labels is
queue state, not history, because a finished run's requirement is recoverable
from the pinned workflow blob, so `vole index rebuild` does not restore
labels and nothing needs it to.

A label is a claim by the runner about itself, unlike --repos, which the
server enforces. A runner that lies about its architecture only takes work it
will do badly; a runner that could name repositories it was not granted would
be an access-control hole. /runners now shows what each connected runner
takes, and says "any job" for an unlabelled one — the other half of "why has
my build not started", since queue depth alone cannot say that no runner
carrying the needed label is connected.

Enqueue takes labels as a second argument rather than a field on Run: they
are lease-time policy, not part of what a run is, and every reader of a Run
(a gate, a page, a record rebuild) would be looking at a field only a lease
ever consulted.

Measured: TestRunsOnWaitsForARunnerThatCarriesTheLabel — a runner carrying
only `linux` leaves a `[linux, fast]` job queued and the runner carrying both
builds it. TestLabelsDecideWhichRunnerMayTakeAJob covers no requirement,
exact match, one label missing, wrong labels and extra labels;
TestLabelsArePinnedAtEnqueue, TestForgettingARepositoryDropsItsLabels,
TestABadRunnerLabelIsRefusedBeforeConnecting,
TestRunsOnLabelsAreParsedAndValidated and
TestRunnersPageShowsWhatEachRunnerTakes cover the edges.

README.md +23−5

@@ -312,6 +312,7 @@ on:
312 312 branches: [main]
313 313 jobs:
314 314 build:
315 runs-on: [linux, arm64] # only a runner carrying both labels takes this job
315 316 steps:
316 317 - name: test
317 318 run: go test ./...
@@ -343,12 +344,29 @@ file), and `vole index rebuild` brings the finished history back from the record
343 344 each repository's `vole` branch. Pushing again cancels the *queued* runs of the commit
344 345 it replaced — never a running one — and one runner pool serves every repository: an
345 346 idle runner parks on the lease instead of asking again, and the lease prefers the
346 repository with the least work in flight.
347 repository with the least work in flight. `max_jobs_per_repo` bounds how much of the
348 pool one repository may occupy, when ordering is not enough.
347 349
348 `/runners` is the page for "why has my build not started": the connected runners, the job
349 each is building (or that it is waiting for one), and how much each repository has
350 queued and running. It follows the same visibility rule as everything else, so a runner
351 building a private repository is not shown.
350 `runs-on` names the machine a job needs, as one label or a list, and a job goes only to
351 a runner that carries *every* label it asks for (extra labels on the runner are fine).
352 A job with no `runs-on` runs anywhere, which is what every job did before targeting
353 existed. The labels are a claim by the runner about itself, not a permission:
354
355 ```sh
356 vole runner --remote ssh://git@host:2222 --label linux --label arm64
357 vole runner --remote ssh://git@host:2222 --repos demo --repos docs # only these
358 ```
359
360 `--repos` is the other direction and is *not* a claim: it narrows what the account's
361 runner allowlist already grants, and the server applies the intersection, so the worst a
362 runner can do is take less work than it was allowed. A filter that matches nothing is
363 refused at startup rather than presenting as a healthy idle runner.
364
365 `/runners` is the page for "why has my build not started": the connected runners and
366 what each takes, the job each is building (or that it is waiting for one), and how much
367 each repository has queued and running — including whether it is at its
368 `max_jobs_per_repo` cap. It follows the same visibility rule as everything else, so a
369 runner building a private repository is not shown.
352 370
353 371 ### Releases built by CI
354 372