mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
fix: mount the job workspace above the repository (#1224)
Mount the job workspace volume, or the `bind_workdir` directory, above `<owner>/<repo>` instead of at the repository, like GitHub's work directory. With the repository as a mount point, pnpm 12 puts its store in `node_modules/.pnpm-store` (https://github.com/pnpm/pnpm/pull/13536), so a restored `setup-node` pnpm cache creates `node_modules` before install. Renames into `$RUNNER_WORKSPACE` fail with `EXDEV` and removing the workspace fails with `EBUSY`. Verified against GitHub hosted and container jobs with cold and warm caches, on volume and `bind_workdir` runners, covering checkout, cache, artifacts, github-script, the node, pnpm, bun, python, uv, go, rust, java, gradle, dotnet, ruby and terraform setup actions, docker and Dockerfile actions, buildx, compose via `GITEA_DOCKER_WORKSPACE`, services, host mode, `container.volumes` on the workspace and `exec`. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1224 Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com>
This commit is contained in:
committed by
bircni
co-authored by
bircni
parent
3d116eb0c2
commit
2cc3000369
@@ -18,6 +18,7 @@ import (
|
||||
maps0 "maps"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -255,7 +256,7 @@ func (rc *RunContext) validVolumes() []string {
|
||||
volumes = append(volumes, sharedToolCacheVolume)
|
||||
}
|
||||
if rc.Config.BindWorkdir {
|
||||
volumes = append(volumes, rc.Config.Workdir)
|
||||
volumes = append(volumes, rc.Config.Workdir, rc.workdirMountRoot())
|
||||
}
|
||||
if rc.dockerProxy != nil {
|
||||
volumes = append(volumes, rc.dockerProxy.Socket)
|
||||
@@ -265,6 +266,13 @@ func (rc *RunContext) validVolumes() []string {
|
||||
getDockerDaemonSocketMountPath(rc.containerDaemonSocket()))
|
||||
}
|
||||
|
||||
func (rc *RunContext) workdirMountRoot() string {
|
||||
if rc.Config.PresetGitHubContext != nil {
|
||||
return filepath.Dir(filepath.Dir(rc.Config.Workdir)) // only the daemon presets, its workdir is <parent>/<owner>/<repo>
|
||||
}
|
||||
return rc.Config.Workdir
|
||||
}
|
||||
|
||||
func (rc *RunContext) jobDockerSocket() string {
|
||||
if rc.dockerProxy != nil {
|
||||
return rc.dockerProxy.Socket
|
||||
@@ -359,6 +367,11 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string, error) {
|
||||
mounts[name+"-env"] = ext.GetActPath() // runner-internal, never overridable
|
||||
|
||||
if workdir := ext.ToContainerPath(rc.Config.Workdir); !claimed[workdir] {
|
||||
source := rc.workdirMountRoot()
|
||||
target := ext.ToContainerPath(source)
|
||||
if claimed[target] {
|
||||
source, target = rc.Config.Workdir, workdir
|
||||
}
|
||||
if rc.Config.BindWorkdir {
|
||||
bindModifiers := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
@@ -367,9 +380,9 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string, error) {
|
||||
if selinux.GetEnabled() {
|
||||
bindModifiers = ":z"
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, workdir, bindModifiers))
|
||||
binds = append(binds, fmt.Sprintf("%s:%s%s", source, target, bindModifiers))
|
||||
} else {
|
||||
mounts[name] = workdir
|
||||
mounts[name] = target
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,8 +992,12 @@ func (rc *RunContext) captureJobContainerInfo() common.Executor {
|
||||
return nil
|
||||
}
|
||||
rc.jobContainerID = info.ID
|
||||
if source := info.Mounts[rc.githubWorkspace()]; source != "" {
|
||||
rc.Env["GITEA_DOCKER_WORKSPACE"] = source
|
||||
workspace := rc.githubWorkspace()
|
||||
for dir := workspace; dir != "/" && dir != "."; dir = path.Dir(dir) {
|
||||
if source := info.Mounts[dir]; source != "" {
|
||||
rc.Env["GITEA_DOCKER_WORKSPACE"] = path.Join(source, strings.TrimPrefix(workspace, dir))
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -641,6 +641,30 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, container.DefaultToolCache, gotmount[sharedToolCacheVolume])
|
||||
})
|
||||
|
||||
t.Run("DaemonMountsAboveOwnerRepo", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("container paths are linux paths")
|
||||
}
|
||||
rc := &RunContext{
|
||||
Run: &model.Run{JobID: "job1", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"job1": {}}}},
|
||||
Config: &Config{BindWorkdir: true, Workdir: "/workspace/1/owner/repo", PresetGitHubContext: &model.GithubContext{}},
|
||||
}
|
||||
|
||||
gotbind, _, err := rc.GetBindsAndMounts()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, slices.ContainsFunc(gotbind, func(bind string) bool { return strings.HasPrefix(bind, "/workspace/1:/workspace/1") }), gotbind)
|
||||
|
||||
rc.Config.BindWorkdir = false
|
||||
_, gotmount, err := rc.GetBindsAndMounts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "/workspace/1", gotmount[rc.jobContainerName()])
|
||||
|
||||
require.NoError(t, rc.Run.Job().RawContainer.Encode(map[string][]string{"volumes": {"claimed:/workspace/1"}}))
|
||||
_, gotmount, err = rc.GetBindsAndMounts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "/workspace/1/owner/repo", gotmount[rc.jobContainerName()])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunContextValidVolumes(t *testing.T) {
|
||||
@@ -669,6 +693,8 @@ func TestRunContextValidVolumes(t *testing.T) {
|
||||
assert.NotContains(t, rc.validVolumes(), rc.Config.Workdir)
|
||||
rc.Config.BindWorkdir = true
|
||||
assert.Contains(t, rc.validVolumes(), rc.Config.Workdir)
|
||||
rc.Config.PresetGitHubContext = &model.GithubContext{}
|
||||
assert.Contains(t, rc.validVolumes(), filepath.FromSlash("/workspace/1"))
|
||||
}
|
||||
|
||||
func TestCleanupJobResourcesCleansServicesWithoutJobContainer(t *testing.T) {
|
||||
@@ -1523,7 +1549,7 @@ func TestCaptureJobContainerInfoExportsDockerWorkspace(t *testing.T) {
|
||||
job := &containerMock{}
|
||||
job.On("Inspect", mock.Anything).Return(&container.Info{
|
||||
ID: "job-container-id",
|
||||
Mounts: map[string]string{"/workspace/owner/repo": "/var/lib/docker/volumes/job/_data"},
|
||||
Mounts: map[string]string{"/workspace": "/var/lib/docker/volumes/job/_data"},
|
||||
}, nil)
|
||||
rc := &RunContext{
|
||||
Config: &Config{Workdir: "/workspace/owner/repo/"},
|
||||
@@ -1534,7 +1560,7 @@ func TestCaptureJobContainerInfoExportsDockerWorkspace(t *testing.T) {
|
||||
require.NoError(t, rc.captureJobContainerInfo()(context.Background()))
|
||||
|
||||
assert.Equal(t, "job-container-id", rc.jobContainerID)
|
||||
assert.Equal(t, "/var/lib/docker/volumes/job/_data", rc.Env["GITEA_DOCKER_WORKSPACE"])
|
||||
assert.Equal(t, "/var/lib/docker/volumes/job/_data/owner/repo", rc.Env["GITEA_DOCKER_WORKSPACE"])
|
||||
|
||||
rc.Config.Workdir = "/elsewhere"
|
||||
rc.Env = map[string]string{}
|
||||
|
||||
Reference in New Issue
Block a user