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/agent/agent.go +26−3

@@ -76,6 +76,10 @@ type runnerJob struct {
76 76 Trigger string `json:"trigger,omitempty"`
77 77 TriggerAction string `json:"trigger_action,omitempty"`
78 78 EventPayload string `json:"event_payload,omitempty"`
79 // Network is the isolation the job asked for with `network:`, or empty when the
80 // workflow did not say. The runner applies it to the container it starts, so the
81 // job's request decides and the machine's default is what an unspecified job gets.
82 Network string `json:"network,omitempty"`
79 83 // Image is a registry reference or "file:<name>". For the latter, ImageDefinition
80 84 // is the pinned recipe and ImageSHA its digest; the runner builds it locally.
81 85 Image string `json:"image"`
@@ -152,6 +156,10 @@ type runnerRequest struct {
152 156 // Labels are what this machine is, sent with every lease so the server can match
153 157 // them against the runs-on a job asked for.
154 158 Labels []string `json:"labels,omitempty"`
159 // Network is the network this machine can give, sent with every lease so the
160 // server can match it against a job's `network:`. A runner started without
161 // --container-network bridge says none, which is the truth: it cannot provide one.
162 Network string `json:"network,omitempty"`
155 163 // Wait is how many seconds an idle lease may park before answering "nothing to
156 164 // do"; the server caps it. It is what makes an idle runner cost a parked channel
157 165 // rather than a query every couple of seconds.
@@ -299,6 +307,7 @@ func Run(ctx context.Context, opts Options) error {
299 307 }
300 308 sess.name = who.Name
301 309 sess.repos, sess.labels = opts.Repos, opts.Labels
310 sess.network = box.Network
302 311 sess.cacheRoot, sess.cacheMax, sess.cacheReuse = cacheRoot, cacheMax, !opts.NoCache
303 312 if opts.NoCache {
304 313 fmt.Fprintf(os.Stderr, "vole runner: job cache in %s, emptied after every job\n", cacheRoot)
@@ -318,7 +327,7 @@ func Run(ctx context.Context, opts Options) error {
318 327 // queued, not on a fixed beat, so an idle pool costs one open channel per
319 328 // runner instead of a query every couple of seconds. A one-shot runner asks
320 329 // once and leaves.
321 req := runnerRequest{Op: "lease", Repos: sess.repos, Labels: sess.labels}
330 req := runnerRequest{Op: "lease", Repos: sess.repos, Labels: sess.labels, Network: sess.network}
322 331 if !opts.Once {
323 332 req.Wait = int(leaseWait.Seconds())
324 333 }
@@ -392,6 +401,9 @@ type runnerSession struct {
392 401 // carries every label the job asked for, so an unlabelled runner only sees jobs
393 402 // that ask for nothing.
394 403 labels []string
404 // network is the network this runner can give, sent with every lease so the server
405 // only hands it a job whose `network:` it can satisfy.
406 network string
395 407 }
396 408
397 409 // describe is the one line a runner prints when it starts work, because "why is
@@ -406,6 +418,9 @@ func (s *runnerSession) describe(name string) string {
406 418 if len(s.repos) > 0 {
407 419 out += ", only in " + strings.Join(s.repos, ", ")
408 420 }
421 if s.network == ci.NetworkBridge {
422 out += ", able to give a job the network"
423 }
409 424 return out + "; waiting for work"
410 425 }
411 426
@@ -538,7 +553,15 @@ func (s *runnerSession) run(remote, cache string, job *runnerJob, lease time.Dur
538 553 s.finish(remote, co, job, log, 1, nil, nil, nil, box)
539 554 }
540 555 fmt.Fprintf(log, "=== %s · %s · %s @ %s\n", job.Repo, job.Job, job.Branch, format.SHA(job.SHA))
541 fmt.Fprintf(log, "environment: %s via %s\n", job.Image, box.Runtime)
556 // The job's network request is resolved before anything runs, so a runner that was
557 // handed a job it cannot isolate refuses it instead of leaking the network into a
558 // job that asked for none, or starving a job that asked for one.
559 boxConfig, err := box.networkFor(job)
560 if err != nil {
561 fail("%s", err)
562 return
563 }
564 fmt.Fprintf(log, "environment: %s via %s, network %s\n", job.Image, box.Runtime, boxConfig.Network)
542 565 // A declared secret the repository does not have is not a failure: the variable
543 566 // is left unset and the log says which one, because a workflow is allowed an
544 567 // optional secret and a required one is ${NAME:?} in the step.
@@ -618,7 +641,7 @@ func (s *runnerSession) run(remote, cache string, job *runnerJob, lease time.Dur
618 641 }
619 642 }()
620 643 }
621 sandbox, err := container.Start(box.Config, container.Spec{
644 sandbox, err := container.Start(boxConfig, container.Spec{
622 645 Image: image,
623 646 Name: containerName(box, job),
624 647 Work: co.Work,