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.

ci/workflow_test.go +27−14

@@ -13,6 +13,7 @@ on:
13 13 branches: [main, "release/*"]
14 14 jobs:
15 15 test:
16 image: alpine:3.22
16 17 steps:
17 18 - name: build
18 19 run: go build ./...
@@ -52,7 +53,7 @@ func TestValidWorkflowParses(t *testing.T) {
52 53
53 54 func TestBranchesDefaultToAll(t *testing.T) {
54 55 t.Parallel()
55 wf, problems := Parse("w.yaml", "version: 1\njobs:\n j:\n steps:\n - run: echo hi\n")
56 wf, problems := Parse("w.yaml", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo hi\n")
56 57 if len(problems) > 0 {
57 58 t.Fatalf("rejected: %v", problems)
58 59 }
@@ -71,17 +72,17 @@ func TestInvalidWorkflowsRejected(t *testing.T) {
71 72 src string
72 73 want string
73 74 }{
74 {"no version", "jobs:\n j:\n steps:\n - run: echo\n", "version is required"},
75 {"bad version", "version: 2\njobs:\n j:\n steps:\n - run: echo\n", "not supported"},
75 {"no version", "jobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n", "version is required"},
76 {"bad version", "version: 2\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n", "not supported"},
76 77 {"no jobs", "version: 1\njobs: {}\n", "at least one job"},
77 {"no steps", "version: 1\njobs:\n j:\n steps: []\n", "at least one step"},
78 {"no run", "version: 1\njobs:\n j:\n steps:\n - name: x\n", "run is required"},
79 {"bad job id", "version: 1\njobs:\n Bad Job:\n steps:\n - run: echo\n", "job names are"},
80 {"bad timeout", "version: 1\njobs:\n j:\n steps:\n - run: echo\n timeout: soon\n", "not a duration"},
81 {"huge timeout", "version: 1\njobs:\n j:\n steps:\n - run: echo\n timeout: 5h\n", "exceeds"},
82 {"typo key", "version: 1\njobs:\n j:\n run-on: x\n steps:\n - run: echo\n", "is not a known key"},
78 {"no steps", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps: []\n", "at least one step"},
79 {"no run", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps:\n - name: x\n", "run is required"},
80 {"bad job id", "version: 1\njobs:\n Bad Job:\n image: alpine:3.22\n steps:\n - run: echo\n", "job names are"},
81 {"bad timeout", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n timeout: soon\n", "not a duration"},
82 {"huge timeout", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n timeout: 5h\n", "exceeds"},
83 {"typo key", "version: 1\njobs:\n j:\n run-on: x\n image: alpine:3.22\n steps:\n - run: echo\n", "is not a known key"},
83 84 {"empty", "", "empty"},
84 {"bad glob", "version: 1\non:\n push:\n branches: [\"a b\"]\njobs:\n j:\n steps:\n - run: echo\n", "not a branch pattern"},
85 {"bad glob", "version: 1\non:\n push:\n branches: [\"a b\"]\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n", "not a branch pattern"},
85 86 }
86 87 for _, tc := range cases {
87 88 t.Run(tc.name, func(t *testing.T) {
@@ -122,6 +123,7 @@ func TestEveryProblemIsReported(t *testing.T) {
122 123 _, problems := Parse(".vole/workflows/x.yaml", `version: 2
123 124 jobs:
124 125 A:
126 image: alpine:3.22
125 127 steps:
126 128 - name: no command
127 129 - run: echo
@@ -140,6 +142,7 @@ func TestArtifactsAreValidatedAtPush(t *testing.T) {
140 142 ok := `version: 1
141 143 jobs:
142 144 build:
145 image: alpine:3.22
143 146 steps:
144 147 - run: make dist
145 148 artifacts:
@@ -164,7 +167,7 @@ jobs:
164 167 " - pathx: dist/*\n", // unknown key
165 168 }
166 169 for _, decl := range bad {
167 body := "version: 1\njobs:\n build:\n steps:\n - run: true\n artifacts:\n" + decl
170 body := "version: 1\njobs:\n build:\n image: alpine:3.22\n steps:\n - run: true\n artifacts:\n" + decl
168 171 if _, problems := Parse("ci.yaml", body); len(problems) == 0 {
169 172 t.Errorf("declaration %q was accepted", strings.TrimSpace(decl))
170 173 }
@@ -179,6 +182,7 @@ func TestPagesDeclarationIsValidated(t *testing.T) {
179 182 ok := `version: 1
180 183 jobs:
181 184 site:
185 image: alpine:3.22
182 186 steps:
183 187 - run: make site
184 188 pages:
@@ -200,7 +204,7 @@ jobs:
200 204 " pathx: dist\n", // unknown key
201 205 }
202 206 for _, decl := range bad {
203 body := "version: 1\njobs:\n site:\n steps:\n - run: true\n pages:\n" + decl
207 body := "version: 1\njobs:\n site:\n image: alpine:3.22\n steps:\n - run: true\n pages:\n" + decl
204 208 if _, problems := Parse("ci.yaml", body); len(problems) == 0 {
205 209 t.Errorf("pages declaration %q was accepted", strings.TrimSpace(decl))
206 210 }
@@ -210,11 +214,13 @@ jobs:
210 214 two := `version: 1
211 215 jobs:
212 216 a:
217 image: alpine:3.22
213 218 steps:
214 219 - run: true
215 220 pages:
216 221 path: site
217 222 b:
223 image: alpine:3.22
218 224 steps:
219 225 - run: true
220 226 pages:
@@ -236,6 +242,7 @@ on:
236 242 tags: ['v*']
237 243 jobs:
238 244 release:
245 image: alpine:3.22
239 246 steps:
240 247 - run: make dist
241 248 artifacts:
@@ -260,6 +267,7 @@ jobs:
260 267 plain, problems := Parse("ci.yaml", `version: 1
261 268 jobs:
262 269 test:
270 image: alpine:3.22
263 271 steps:
264 272 - run: true
265 273 `)
@@ -271,6 +279,7 @@ jobs:
271 279 if _, problems := Parse("ci.yaml", `version: 1
272 280 jobs:
273 281 build:
282 image: alpine:3.22
274 283 steps:
275 284 - run: true
276 285 artifacts:
@@ -288,6 +297,7 @@ on:
288 297 tags: ['v*']
289 298 jobs:
290 299 build:
300 image: alpine:3.22
291 301 steps:
292 302 - run: true
293 303 `)
@@ -302,6 +312,7 @@ on:
302 312 tags: ['../x']
303 313 jobs:
304 314 build:
315 image: alpine:3.22
305 316 steps:
306 317 - run: true
307 318 `); len(problems) == 0 {
@@ -318,6 +329,7 @@ func TestRunsOnLabelsAreParsedAndValidated(t *testing.T) {
318 329 wf, problems := Parse("ci.yaml", `version: 1
319 330 jobs:
320 331 build:
332 image: alpine:3.22
321 333 steps:
322 334 - run: true
323 335 `)
@@ -330,8 +342,8 @@ jobs:
330 342
331 343 // One label as a scalar, several as a list.
332 344 for _, src := range []string{
333 "version: 1\njobs:\n build:\n runs-on: linux\n steps:\n - run: true\n",
334 "version: 1\njobs:\n build:\n runs-on: [linux]\n steps:\n - run: true\n",
345 "version: 1\njobs:\n build:\n runs-on: linux\n image: alpine:3.22\n steps:\n - run: true\n",
346 "version: 1\njobs:\n build:\n runs-on: [linux]\n image: alpine:3.22\n steps:\n - run: true\n",
335 347 } {
336 348 wf, problems := Parse("ci.yaml", src)
337 349 if len(problems) > 0 {
@@ -346,6 +358,7 @@ jobs:
346 358 jobs:
347 359 build:
348 360 runs-on: [linux, arm64, linux]
361 image: alpine:3.22
349 362 steps:
350 363 - run: true
351 364 `)