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.

Require an image for every CI job

70b702649b875d841e294f5a085ca784d43e5bc4 · Keenan Nemetz · 15d ago · parent 7da9111e

Require an image for every CI job

Runners now require an OCI runtime, and each workflow job must name either a registry image or an image-file definition under .vole/images. Definitions are validated at push time, pinned with the run, built locally by the runner, and cached by definition digest. Records and the run page carry the image and definition digest.

Split the CI guide into workflows and runners, and add reference pages for workflow and image definition fields.

docs/workflows.md +247

@@ -0,0 +1,247 @@
1 # Workflows
2
3 A workflow describes what CI runs: which branches and tags trigger it, which
4 steps each job executes, and what it publishes. Machines and execution belong to
5 [runners](runner.md); this page is about the project side.
6
7 For every field, limit, and default, see the [Workflow reference](reference/workflow-reference.md).
8
9 ## Add a workflow
10
11 Workflows live in `.vole/workflows/*.yaml`. When you push a new workflow, Vole
12 checks it before accepting the push, so a broken workflow is rejected early.
13
14 ```yaml
15 version: 1
16 on:
17 push:
18 branches: [main]
19 jobs:
20 build:
21 runs-on: [linux, arm64] # only a runner with both labels takes this job
22 image: golang:1.27-bookworm
23 steps:
24 - name: test
25 run: go test ./...
26 - name: build
27 run: make dist
28 artifacts:
29 - path: dist/*
30 pages:
31 path: site/ # publish this directory as the repository's site
32 ```
33
34 Every job must name the environment it runs in, with exactly one of `image:` or
35 `image-file:`. A workflow that names neither fails the push that introduced it.
36 See [Images](#images) for the two forms.
37
38 Triggers work like this:
39
40 - `on.push.branches` limits a workflow to certain branches.
41 - `on.push.tags` limits it to tag pushes.
42 - A workflow with neither runs on every branch.
43 - A workflow that names `tags:` runs only on tags.
44
45 `runs-on` asks for a runner with every label in the list. A job with no `runs-on`
46 can run anywhere. See [Runners § Start a runner](runner.md#start-a-runner) for what the
47 labels mean on the machine.
48
49 When a push starts a job, Vole remembers the exact commit, the exact workflow
50 file, and the exact image definition. Editing any of them later does not change a
51 job that is already queued.
52
53 ## Images
54
55 A job can name a registry image directly:
56
57 ```yaml
58 jobs:
59 build:
60 image: golang:1.27-bookworm@sha256:...
61 ```
62
63 Or keep a reusable definition under `.vole/images/` and reference it by name:
64
65 ```yaml
66 # .vole/images/go.yaml
67 version: 1
68 from: golang:1.27-bookworm
69 env:
70 CGO_ENABLED: "0"
71 run:
72 - apt-get update && apt-get install -y --no-install-recommends jq
73 ```
74
75 ```yaml
76 jobs:
77 build:
78 image-file: go
79 steps:
80 - run: go test ./...
81 ```
82
83 `image-file: go` resolves `.vole/images/go.yaml`. For the complete definition
84 schema, see the [Image reference](reference/image-reference.md). A definition has:
85
86 | Field | Meaning |
87 |---|---|
88 | `version` | Required; currently `1` |
89 | `from` | Required base image reference |
90 | `env` | Optional environment variables for the image |
91 | `run` | Optional build commands; each becomes a `RUN` |
92
93 Definitions are validated when the workflow is pushed, including definitions
94 that are not referenced, and they are pinned with the run. The runner builds the
95 image locally and tags it by the definition digest, so later jobs with the same
96 definition reuse the build. See [Runners § Images](runner.md#images-and-image-builds)
97 for the execution, caching, and security details.
98
99 An image definition is code: its build runs on the runner through the OCI
100 builder, so approving a contributor tip that changes a definition also approves
101 that build. Prefer rootless Podman and pin the base image by digest.
102
103 The image is the dependency manifest. It should contain the toolchains and system
104 packages the builds need, plus `/bin/sh` and a POSIX `sleep` (Vole runs shell steps
105 and keeps the container alive between them). Pin the base image by digest; a
106 mutable tag changes what a build means.
107
108 ## Contributor branches
109
110 A push to `refs/heads/contrib/<name>/...` queues no work by itself. Those
111 branches contain code from outside the project's maintainer group, and running a
112 workflow executes it on a runner. CI is held until a maintainer or admin opens the
113 repository page and chooses **Allow CI for this tip** next to the contributor
114 branch.
115
116 The approval is keyed by the exact commit, not by the branch. A new commit or a
117 force-push is a new decision, so the contributor cannot push once and inherit
118 approval forever. The approval is stored in the CI queue database, so a
119 reconciliation or a temporary queue outage does not silently run an unapproved
120 tip.
121
122 Allowing CI queues every workflow that applies to the branch. Workflows with no
123 `on.push.branches` filter run on every branch, including names with slashes such
124 as contributor branches.
125
126 ## Check and run a workflow locally
127
128 You can check workflows before pushing:
129
130 ```sh
131 vole workflow check
132 vole workflow list
133 ```
134
135 You can also run the steps without a server or a runner. From the repository root:
136
137 ```sh
138 vole workflow run # every job in every workflow
139 vole workflow run build # every job in build.yaml
140 vole workflow run ci -j test # one job in one workflow
141 vole workflow run -j test # the job named test in every workflow
142 ```
143
144 A workflow can be named with or without its extension (`ci` or `ci.yaml`). Steps
145 run in the working tree, in the order the workflow declares them. The command
146 stops at the first failure and exits with that step's status, so it can wrap a
147 script. It validates the workflow first, with the same checker a push uses.
148
149 A local run is a check of the commands, not a rehearsal of the push. It ignores
150 `on.push` filters, `runs-on`, and the job image, and it does not publish artifacts
151 or deploy a site. It sets `CI=true`, `VOLE_CI=1`, and `VOLE_CI_LOCAL=1`, gives
152 each step its declared timeout, and reads no standard input. Jobs share your
153 working tree, so a later job sees files an earlier one wrote; a pushed run starts
154 each job from a clean checkout in its image.
155
156 ## Artifacts
157
158 Use `artifacts:` to list files that a build should publish. Vole expands the
159 patterns in the checkout after the steps pass, then uploads the files to the same
160 storage used by LFS and CI logs.
161
162 ```yaml
163 artifacts:
164 - path: dist/*
165 ```
166
167 Artifacts appear on the run page with a download link:
168
169 ```sh
170 curl -OJ http://host/r/demo/runs/01M5.../artifacts/tool-linux
171 sha256sum tool-linux
172 ```
173
174 A pattern that matches nothing fails the job. A build that stopped producing the
175 declared file is exactly what the declaration is meant to catch. If two matches
176 have the same base name, Vole refuses them so nothing is published under an
177 unexpected name.
178
179 A run artifact belongs to its run. It is not a release artifact. Moving one into a
180 release is a separate step (`vole release attach`). Run artifacts are kept for as
181 long as the run exists.
182
183 ## Build a release
184
185 A tag push can build a release. Mark the artifacts `release: true`:
186
187 ```yaml
188 version: 1
189 on:
190 push:
191 tags: ['v*']
192 jobs:
193 release:
194 image: golang:1.27-bookworm
195 steps:
196 - name: build
197 run: make dist
198 artifacts:
199 - path: dist/*
200 release: true
201 ```
202
203 ```sh
204 git tag -a v1.0.0 -m "first release" # needs notes and a tagger
205 git push origin refs/tags/v1.0.0 # the runner builds and publishes
206 ```
207
208 The record on the `vole` branch names the build (`release: v1.0.0 artifacts from
209 run 01M5...`), so `git log vole` shows which run shipped the files. A lightweight
210 tag is refused: it has no release notes and no tagger. A tag run never counts as a
211 branch's required check, even if the tag points at that branch's tip.
212
213 See [Releases](releases.md) for the release page and artifact rules.
214
215 ## Publish a website
216
217 `pages:` publishes a directory as the repository's website, using the same rules
218 as `vole pages deploy`: the branch contains exactly those files, so a page removed
219 from the build stops being served.
220
221 ```yaml
222 pages:
223 path: site/
224 ```
225
226 The commit names the build (`site: main from run 01M5... (6beaa53)`), so
227 `git log pages` shows what put that tree there.
228
229 Vole refuses the deploy if the run is no longer the newest commit on its branch.
230 An older build finishing late must not silently roll the site back. The run log
231 explains why:
232
233 ```text
234 this run built f1f415ec but main is at 6beaa530 now; the site was not deployed
235 ```
236
237 Only one job per workflow can publish the site. The branch name is set by the
238 instance (`pages_ref`), not by the workflow.
239
240 ## What happens to a queued workflow
241
242 Pushing again cancels queued runs for the commit it replaced. It never cancels a
243 run that is already in progress. Finished runs become records on the repository's
244 `vole` branch.
245
246 For what happens when a runner disappears, how work is requeued, and how finished
247 history is rebuilt, see [Runners § Scheduling and the queue](runner.md#scheduling-and-the-queue).