enhance: make shared tool cache safe at any capacity (#1401)

Co-authored-by: bircni <[email protected]>
This commit is contained in:
bircni authored and silverwind committed 2026-11-10 14:25:50 +00:00
1 parent 84c0fcd1fa
commit a021d3139d
9 files changed
+121 -11

No files matched your search

+13 -2
View File
@@ -273,7 +273,7 @@ func (rc *RunContext) validVolumes() []string {
volumes = append(volumes, hostEnv.Path, hostEnv.GetActPath())
}
if rc.Config.SharedToolCache {
volumes = append(volumes, sharedToolCacheVolume)
volumes = append(volumes, rc.toolCacheVolume())
}
if rc.Config.BindWorkdir {
volumes = append(volumes, rc.Config.Workdir, rc.workdirMountRoot())
@@ -333,6 +333,13 @@ func (rc *RunContext) startDockerProxy(ctx context.Context) {
rc.dockerProxy = container.NewDockerProxy(ctx, rc.jobContainerName())
}
func (rc *RunContext) toolCacheVolume() string {
if rc.Config.ToolCacheSlot == "" {
return sharedToolCacheVolume
}
return sharedToolCacheVolume + "-" + rc.Config.ToolCacheSlot
}
// toolCache returns the tool cache path the job sees, relocatable through RUNNER_TOOL_CACHE.
func (rc *RunContext) toolCache(fallback string) string {
return cmp.Or(rc.GetEnv()["RUNNER_TOOL_CACHE"], fallback)
@@ -393,7 +400,7 @@ func (rc *RunContext) GetBindsAndMounts(ctx context.Context) ([]string, map[stri
}
if rc.Config.SharedToolCache {
if toolCache := rc.toolCache(container.DefaultToolCache); !claimed[toolCache] {
mounts[sharedToolCacheVolume] = toolCache
mounts[rc.toolCacheVolume()] = toolCache
}
}
if hostEnv, ok := rc.JobContainer.(*container.HostEnvironment); ok {
@@ -453,6 +460,10 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
toolCacheParent = cacheDir
}
toolCache := rc.toolCache(filepath.Join(toolCacheParent, "tool_cache"))
if rc.Config.ToolCacheSlot != "" {
toolCache = filepath.Join(toolCache, rc.Config.ToolCacheSlot)
rc.Env["RUNNER_TOOL_CACHE"] = toolCache // overrides a RUNNER_TOOL_CACHE the slots would otherwise share
}
if err := os.MkdirAll(toolCache, 0o777); err != nil {
return err
}
+26
View File
@@ -559,6 +559,12 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
rc.containerSpec.Volumes = []string{"/unapproved:" + container.DefaultToolCache}
_, gotmount, _ = rc.GetBindsAndMounts(t.Context())
assert.Equal(t, container.DefaultToolCache, gotmount[sharedToolCacheVolume])
rc.Config.ToolCacheSlot = "1"
_, gotmount, _ = rc.GetBindsAndMounts(t.Context())
assert.Equal(t, container.DefaultToolCache, gotmount[sharedToolCacheVolume+"-1"])
assert.NotContains(t, gotmount, sharedToolCacheVolume)
assert.Contains(t, rc.validVolumes(), sharedToolCacheVolume+"-1")
})
t.Run("DaemonMountsAboveOwnerRepo", func(t *testing.T) {
@@ -1296,6 +1302,26 @@ func TestPrintStartJobContainerGroupGolden(t *testing.T) {
assert.Equal(t, want, buf.String())
}
func TestStartHostEnvironmentToolCacheSlotIsSubdirOfSharedCache(t *testing.T) {
cacheDir := t.TempDir()
override := filepath.Join(cacheDir, "override")
for sharedCache, env := range map[string]map[string]string{
filepath.Join(cacheDir, "tool_cache"): {},
override: {"RUNNER_TOOL_CACHE": override},
} {
rc := &RunContext{
Config: &Config{ActionCacheDir: cacheDir, SharedToolCache: true, ToolCacheSlot: "1"},
Env: env,
Run: &model.Run{Workflow: &model.Workflow{Name: "wf"}},
}
require.NoError(t, rc.startHostEnvironment()(t.Context()))
t.Cleanup(func() { require.NoError(t, rc.cleanUpJobContainer(context.Background())) })
want := filepath.Join(sharedCache, "1")
assert.Equal(t, want, rc.JobContainer.(*container.HostEnvironment).ToolCache)
assert.Equal(t, want, rc.Env["RUNNER_TOOL_CACHE"])
}
}
func TestRunContext_cleanupFailedStart(t *testing.T) {
type ctxKey string
const sentinel = ctxKey("sentinel")
+2 -1
View File
@@ -84,7 +84,8 @@ type Config struct {
JobLoggerLevel *logging.Level // the level of job logger
ValidVolumes []string // only volumes (and bind mounts) in this slice can be mounted on the job container or service containers
ContainerVolumes []string // mounted into every job, service and docker action container
SharedToolCache bool // one tool cache for all jobs instead of one per job
SharedToolCache bool // keeps the tool cache across jobs instead of one per job
ToolCacheSlot string // keeps concurrent tasks' tool caches apart, empty for slot 0 and in exec
InsecureSkipTLS bool // whether to skip verifying TLS certificate of the Gitea instance
ClientCertFile, ClientKeyFile string // PEM client certificate for the Gitea instance
CAFile string // PEM CA certificates the Gitea instance's certificate chains to