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.

Let a job declare the network it needs

d79580247b8f33f7ffdf2f05847733cad12c187a · Keenan Nemetz · 6d ago · parent 4ac42680

Let a job declare the network it needs

A runner's --container-network decided the network for every job it took, so a
machine that could reach the network gave it to builds that never asked, and a
job that did need one could be handed to a machine that had none. The docs said
"add bridge only when a job needs it", which no workflow could express.

A job now says `network: none` or `network: bridge`. The request is pinned with
the run, and the lease only hands a bridge job to a runner that advertises the
capability, so a job waits for a capable machine instead of failing on one that
never could. The runner applies the job's request to its container, which means a
bridge runner can still withhold the network from a job that asked for none.

A job that declares nothing keeps the runner's own setting, which is what every
workflow meant before this field existed. The local run applies the same rule and
refuses a bridge job when it was started without one, so `vole workflow run` either
matches CI or says what to change on the command line.

The workflows page, the run page, and the runner's log line now say what a job
asked for and what it ran with. verify.yaml and release.yaml are deliberately not
in this change: the instance validates a pushed workflow with the binary it is
running, so they can declare the field once that binary knows it.

Closes #159.

ci/workflow_test.go +45

@@ -424,6 +424,51 @@ jobs:
424 424 }
425 425 }
426 426
427 // network asks for the isolation a job needs. It is one of two values, and a value
428 // no runner could satisfy fails the push rather than leaving the job queued behind an
429 // error nobody sees. A job that says nothing keeps the pre-network behaviour.
430 func TestJobNetworkIsParsedAndValidated(t *testing.T) {
431 t.Parallel()
432 // Saying nothing is not the same as saying none: the runner's own default applies.
433 wf, problems := Parse("ci.yaml", `version: 1
434 jobs:
435 build:
436 image: alpine:3.22
437 steps:
438 - run: true
439 `)
440 if len(problems) > 0 {
441 t.Fatalf("problems: %v", problems)
442 }
443 if got := wf.Jobs[0].Network; got != "" {
444 t.Fatalf("network = %q, want empty for a job that does not ask", got)
445 }
446
447 for _, want := range []string{NetworkNone, NetworkBridge} {
448 wf, problems := Parse("ci.yaml", "version: 1\njobs:\n build:\n network: "+want+"\n"+
449 " image: alpine:3.22\n steps:\n - run: true\n")
450 if len(problems) > 0 {
451 t.Fatalf("network %q: %v", want, problems)
452 }
453 if got := wf.Jobs[0].Network; got != want {
454 t.Fatalf("network = %q, want %q", got, want)
455 }
456 }
457
458 // Anything else is refused, and the message names the field so a typo is findable.
459 for _, bad := range []string{"Bridge", "host", "docker", "on"} {
460 src := "version: 1\njobs:\n build:\n network: " + bad + "\n" +
461 " image: alpine:3.22\n steps:\n - run: true\n"
462 _, problems := Parse("ci.yaml", src)
463 if len(problems) == 0 {
464 t.Fatalf("network %s was accepted", bad)
465 }
466 if !strings.Contains(strings.Join(problems, " "), "network") {
467 t.Fatalf("the problem does not name network: %v", problems)
468 }
469 }
470 }
471
427 472 // TestJobEnvironment: a job's env is how it opts in to things the runner provides
428 473 // but does not decide, such as the cache at /cache. It is validated like the image
429 474 // definition's env, and it may not replace what the sandbox itself sets.