enhance: add runner.default_image for jobs matching no label (#1164)

A job whose `runs-on` matches none of the runner's labels, which includes every job that sets no `runs-on` at all, runs in `runner.default_image`. It defaults to `docker.gitea.com/runner-images:ubuntu-latest` as before, so a mirror can be pointed at instead.

A runner with no reachable docker daemon now runs such a job on the host, rather than failing on an image it cannot pull. Runners that use docker are unaffected and never probe for one.

This matters most to a host-mode runner, one whose labels are all `host`. Such a runner has no daemon to pull an image with, so a job matching none of its labels used to fail at container start. It now runs on the host, where that runner runs everything else anyway, and it takes no configuration to get there. A host-mode runner that does have a daemon within reach keeps using the image, unchanged.

Supersedes https://gitea.com/gitea/runner/pulls/642

---------

Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1164
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
This commit is contained in:
bircni
2026-08-19 18:36:00 +00:00
parent bc8161c673
commit 11ac12efa4
9 changed files with 86 additions and 22 deletions
+29
View File
@@ -12,6 +12,7 @@ import (
"gitea.com/gitea/runner/act/runner"
clientmocks "gitea.com/gitea/runner/internal/pkg/client/mocks"
"gitea.com/gitea/runner/internal/pkg/config"
"gitea.com/gitea/runner/internal/pkg/labels"
"gitea.com/gitea/runner/internal/pkg/ver"
"connectrpc.com/connect"
@@ -98,6 +99,34 @@ func TestNewRunnerInitializesLabelsAndEnvironment(t *testing.T) {
require.Empty(t, r.envs[runner.CacheServiceV2Env], "no cache server, nothing to serve v2 from")
}
func TestRunnerFallbackPlatform(t *testing.T) {
tests := []struct {
name string
label string
dockerRunning bool
want string
}{
{"a docker label needs no daemon probe", "ubuntu:docker://node:18", false, "mirror.example/ci:noble"},
{"host labels keep the image where docker runs", "ubuntu:host", true, "mirror.example/ci:noble"},
{"host labels without docker run on the host", "ubuntu:host", false, labels.SelfHostedPlatform},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reachable := dockerReachable
dockerReachable = func(context.Context) bool { return tt.dockerRunning }
t.Cleanup(func() { dockerReachable = reachable })
label, err := labels.Parse(tt.label)
require.NoError(t, err)
cfg := &config.Config{}
cfg.Runner.DefaultImage = "mirror.example/ci:noble"
r := &Runner{cfg: cfg, labels: labels.Labels{label}}
require.Equal(t, tt.want, r.fallbackPlatform(t.Context()))
})
}
}
// Proxy variables are assembled per task, because a job's service containers have to be
// reached directly and they are only known once the workflow is parsed.
func TestNewRunnerLeavesProxyToTheTask(t *testing.T) {