From a96f706dc852970be63fcb12526554badcb0bd8d Mon Sep 17 00:00:00 2001 From: Gunju Kim Date: Mon, 10 Aug 2026 09:26:36 +0000 Subject: [PATCH] Use runner-owned job workspaces --- internal/controller/runner_controller.go | 14 ++++++++------ internal/controller/runner_controller_test.go | 11 ++++++++++- test/e2e/runner_test.go | 1 + test/fixture/github/main.go | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/internal/controller/runner_controller.go b/internal/controller/runner_controller.go index 4ad1719..f991d6e 100644 --- a/internal/controller/runner_controller.go +++ b/internal/controller/runner_controller.go @@ -27,10 +27,12 @@ import ( var errRunnerAlreadyAssigned = errors.New("runner is already assigned a WorkflowJob") const ( - jobPlanVolume = "open-actions-job" - workspaceVolume = "open-actions-workspace" - jobPlanMountPath = "/var/run/open-actions" - workspaceMountPath = "/workspace" + jobPlanVolume = "open-actions-job" + workspaceVolume = "open-actions-workspace" + jobPlanMountPath = "/var/run/open-actions" + workspaceVolumeMountPath = "/workspace" + // The repository lives below the volume root so the runner owns its Git worktree. + workspacePath = workspaceVolumeMountPath + "/repository" jobTTLSeconds = int32(3600) jobTimeoutSeconds = int64(50 * 60) jobStartTimeout = 5 * time.Minute @@ -596,7 +598,7 @@ func (r *RunnerReconciler) buildJob(workflowJob *actionsv1alpha1.WorkflowJob, ru Drop: []corev1.Capability{"ALL"}, }, }, - Args: []string{"--job-file=" + jobPlanMountPath + "/" + jobPlanKey, "--workspace=" + workspaceMountPath}, + Args: []string{"--job-file=" + jobPlanMountPath + "/" + jobPlanKey, "--workspace=" + workspacePath}, Env: []corev1.EnvVar{{ Name: "OPEN_ACTIONS_GITHUB_TOKEN", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{ @@ -606,7 +608,7 @@ func (r *RunnerReconciler) buildJob(workflowJob *actionsv1alpha1.WorkflowJob, ru }}, VolumeMounts: []corev1.VolumeMount{ {Name: jobPlanVolume, MountPath: jobPlanMountPath, ReadOnly: true}, - {Name: workspaceVolume, MountPath: workspaceMountPath}, + {Name: workspaceVolume, MountPath: workspaceVolumeMountPath}, }, }}, Volumes: []corev1.Volume{ diff --git a/internal/controller/runner_controller_test.go b/internal/controller/runner_controller_test.go index a9dbfa9..dc9f9b5 100644 --- a/internal/controller/runner_controller_test.go +++ b/internal/controller/runner_controller_test.go @@ -59,9 +59,18 @@ func TestRunnerBuildsOwnedJob(t *testing.T) { if container.Resources.Requests.Cpu().String() != "1" { t.Errorf("cpu request = %s", container.Resources.Requests.Cpu().String()) } - if strings.Join(container.Args, " ") != "--job-file=/var/run/open-actions/job.json --workspace=/workspace" { + if strings.Join(container.Args, " ") != "--job-file=/var/run/open-actions/job.json --workspace=/workspace/repository" { t.Errorf("args = %v", container.Args) } + workspaceMount := "" + for _, mount := range container.VolumeMounts { + if mount.Name == workspaceVolume { + workspaceMount = mount.MountPath + } + } + if workspaceMount == "" || !strings.HasPrefix(workspacePath, workspaceMount+"/") { + t.Fatalf("workspace path %q must be below volume mount %q", workspacePath, workspaceMount) + } if job.Labels[actionsv1alpha1.LabelWorkflowJob] != "build" { t.Errorf("workflow job label = %q", job.Labels[actionsv1alpha1.LabelWorkflowJob]) } diff --git a/test/e2e/runner_test.go b/test/e2e/runner_test.go index 1a4ea37..839a541 100644 --- a/test/e2e/runner_test.go +++ b/test/e2e/runner_test.go @@ -153,6 +153,7 @@ var _ = Describe("Runner", func() { Expect(output).To(ContainSubstring("external checkout main ran")) Expect(output).To(ContainSubstring("external setup-go main ran")) Expect(output).To(ContainSubstring("external composite run")) + Expect(output).To(ContainSubstring("runner workspace git works")) Expect(output).To(ContainSubstring("open actions e2e works")) Expect(output).To(ContainSubstring("external marker post ran")) Expect(output).To(ContainSubstring("external setup-go post ran")) diff --git a/test/fixture/github/main.go b/test/fixture/github/main.go index 82082a9..9e6e0ab 100644 --- a/test/fixture/github/main.go +++ b/test/fixture/github/main.go @@ -39,6 +39,8 @@ jobs: test "$GITHUB_REF_NAME" = "main" test "$EXTERNAL_SETUP_GO" = "ready" test "$COMPOSITE_VALUE" = "from composite" + git status --short + printf 'runner workspace git works\n' go test ./... printf 'open actions e2e works\n' ` @@ -85,9 +87,17 @@ if (process.env.STATE_checked_out === 'true') { const workspace = process.env.GITHUB_WORKSPACE; const repository = process.env['INPUT_REPOSITORY']; const remote = process.env.GITHUB_SERVER_URL + '/' + repository; -const run = (args) => childProcess.execFileSync('git', args, {stdio: 'inherit'}); +const gitEnvironment = { + ...process.env, + GIT_CONFIG_COUNT: '1', + GIT_CONFIG_KEY_0: 'safe.directory', + GIT_CONFIG_VALUE_0: workspace, +}; +const run = (args) => childProcess.execFileSync('git', args, { + env: gitEnvironment, + stdio: 'inherit', +}); fs.mkdirSync(workspace, {recursive: true}); -run(['config', '--global', '--add', 'safe.directory', workspace]); run(['init', '--quiet', workspace]); run(['-C', workspace, 'remote', 'add', 'origin', remote]); run(['-C', workspace, 'fetch', '--quiet', '--depth=1', 'origin', process.env.GITHUB_SHA]);