fix: allow relocating the tool cache and mounting over runner paths (#1122)

1. Docker rejects two mounts on one target, so a `container.volumes:` or `--volume` aimed at `/opt/hostedtoolcache` failed the job with `Duplicate mount point`. Job and service volumes now displace the mount on the same path, and `name:/target:ro` no longer mounts read-write at the literal path `/target:ro`.
1. Setting `RUNNER_TOOL_CACHE` only changed what the variable said, the cache stayed where it was, so tools writing to it landed outside the mount and `${{ runner.tool_cache }}` disagreed with the variable. It now relocates the cache. Leaving it unset behaves as before.
1. Unknown `config.yaml` keys now warn instead of being dropped without a trace.

Fixes https://gitea.com/gitea/runner/issues/813

---------

Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1122
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
silverwind
2026-07-29 21:32:28 +00:00
committed by silverwind
parent e6c7ba3a15
commit 0192861155
10 changed files with 210 additions and 72 deletions

View File

@@ -471,8 +471,7 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
logger.Debugf("Custom container.HostConfig from options ==> %+v", containerConfig.HostConfig)
hostConfig.Binds = append(hostConfig.Binds, containerConfig.HostConfig.Binds...)
hostConfig.Mounts = append(hostConfig.Mounts, containerConfig.HostConfig.Mounts...)
overlayVolumes(hostConfig, containerConfig.HostConfig)
binds := hostConfig.Binds
mounts := hostConfig.Mounts
networkMode := hostConfig.NetworkMode
@@ -1108,6 +1107,34 @@ func (cr *containerReference) sanitizeConfig(ctx context.Context, config *contai
return config, hostConfig
}
// bindTarget returns the container path a bind mounts onto, empty if it cannot be parsed.
func bindTarget(bind string) string {
parsed, err := loader.ParseVolume(bind)
if err != nil {
return ""
}
return parsed.Target
}
// overlayVolumes appends src's volumes to dst, dropping the dst ones they mount over. Docker
// rejects two mounts on one target, so the volumes declared last have to win.
func overlayVolumes(dst, src *container.HostConfig) {
claimed := map[string]bool{}
for _, bind := range src.Binds {
if target := bindTarget(bind); target != "" {
claimed[target] = true
}
}
for _, mt := range src.Mounts {
claimed[mt.Target] = true
}
dst.Binds = append(slices.DeleteFunc(slices.Clone(dst.Binds),
func(bind string) bool { return claimed[bindTarget(bind)] }), src.Binds...)
dst.Mounts = append(slices.DeleteFunc(slices.Clone(dst.Mounts),
func(mt mount.Mount) bool { return claimed[mt.Target] }), src.Mounts...)
}
type validVolumeMatcher struct {
allowAll bool
named []glob.Glob