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"
|
maps0 "maps"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -255,7 +256,7 @@ func (rc *RunContext) validVolumes() []string {
|
|||||||
volumes = append(volumes, sharedToolCacheVolume)
|
volumes = append(volumes, sharedToolCacheVolume)
|
||||||
}
|
}
|
||||||
if rc.Config.BindWorkdir {
|
if rc.Config.BindWorkdir {
|
||||||
volumes = append(volumes, rc.Config.Workdir)
|
volumes = append(volumes, rc.Config.Workdir, rc.workdirMountRoot())
|
||||||
}
|
}
|
||||||
if rc.dockerProxy != nil {
|
if rc.dockerProxy != nil {
|
||||||
volumes = append(volumes, rc.dockerProxy.Socket)
|
volumes = append(volumes, rc.dockerProxy.Socket)
|
||||||
@@ -265,6 +266,13 @@ func (rc *RunContext) validVolumes() []string {
|
|||||||
getDockerDaemonSocketMountPath(rc.containerDaemonSocket()))
|
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 {
|
func (rc *RunContext) jobDockerSocket() string {
|
||||||
if rc.dockerProxy != nil {
|
if rc.dockerProxy != nil {
|
||||||
return rc.dockerProxy.Socket
|
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
|
mounts[name+"-env"] = ext.GetActPath() // runner-internal, never overridable
|
||||||
|
|
||||||
if workdir := ext.ToContainerPath(rc.Config.Workdir); !claimed[workdir] {
|
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 {
|
if rc.Config.BindWorkdir {
|
||||||
bindModifiers := ""
|
bindModifiers := ""
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
@@ -367,9 +380,9 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string, error) {
|
|||||||
if selinux.GetEnabled() {
|
if selinux.GetEnabled() {
|
||||||
bindModifiers = ":z"
|
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 {
|
} else {
|
||||||
mounts[name] = workdir
|
mounts[name] = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,8 +992,12 @@ func (rc *RunContext) captureJobContainerInfo() common.Executor {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rc.jobContainerID = info.ID
|
rc.jobContainerID = info.ID
|
||||||
if source := info.Mounts[rc.githubWorkspace()]; source != "" {
|
workspace := rc.githubWorkspace()
|
||||||
rc.Env["GITEA_DOCKER_WORKSPACE"] = source
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,6 +641,30 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, container.DefaultToolCache, gotmount[sharedToolCacheVolume])
|
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) {
|
func TestRunContextValidVolumes(t *testing.T) {
|
||||||
@@ -669,6 +693,8 @@ func TestRunContextValidVolumes(t *testing.T) {
|
|||||||
assert.NotContains(t, rc.validVolumes(), rc.Config.Workdir)
|
assert.NotContains(t, rc.validVolumes(), rc.Config.Workdir)
|
||||||
rc.Config.BindWorkdir = true
|
rc.Config.BindWorkdir = true
|
||||||
assert.Contains(t, rc.validVolumes(), rc.Config.Workdir)
|
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) {
|
func TestCleanupJobResourcesCleansServicesWithoutJobContainer(t *testing.T) {
|
||||||
@@ -1523,7 +1549,7 @@ func TestCaptureJobContainerInfoExportsDockerWorkspace(t *testing.T) {
|
|||||||
job := &containerMock{}
|
job := &containerMock{}
|
||||||
job.On("Inspect", mock.Anything).Return(&container.Info{
|
job.On("Inspect", mock.Anything).Return(&container.Info{
|
||||||
ID: "job-container-id",
|
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)
|
}, nil)
|
||||||
rc := &RunContext{
|
rc := &RunContext{
|
||||||
Config: &Config{Workdir: "/workspace/owner/repo/"},
|
Config: &Config{Workdir: "/workspace/owner/repo/"},
|
||||||
@@ -1534,7 +1560,7 @@ func TestCaptureJobContainerInfoExportsDockerWorkspace(t *testing.T) {
|
|||||||
require.NoError(t, rc.captureJobContainerInfo()(context.Background()))
|
require.NoError(t, rc.captureJobContainerInfo()(context.Background()))
|
||||||
|
|
||||||
assert.Equal(t, "job-container-id", rc.jobContainerID)
|
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.Config.Workdir = "/elsewhere"
|
||||||
rc.Env = map[string]string{}
|
rc.Env = map[string]string{}
|
||||||
|
|||||||
Reference in New Issue
Block a user