diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/pod.go b/pkg/reconciler/v1alpha1/taskrun/resources/pod.go index a02860caf56..df10a9029f5 100644 --- a/pkg/reconciler/v1alpha1/taskrun/resources/pod.go +++ b/pkg/reconciler/v1alpha1/taskrun/resources/pod.go @@ -93,9 +93,14 @@ const ( unnamedInitContainerPrefix = "build-step-unnamed-" // Name of the credential initialization container. credsInit = "credential-initializer" + // Name of the working dir initialization container. + workingDirInit = "working-dir-initializer" ) var ( + // The container used to initialize working directories before the build runs. + bashWorkingDirImage = flag.String("bash-working-dir-image", "override-with-bash-noop:latest", + "The container image for preparing our Build's working directories.") // The container used to initialize credentials before the build runs. credsImage = flag.String("creds-image", "override-with-creds:latest", "The container image for preparing our Build's credentials.") @@ -162,6 +167,45 @@ func makeCredentialInitializer(serviceAccountName, namespace string, kubeclient }, volumes, nil } +func makeWorkingDirScript(workingDirs map[string]bool) string { + script := "" + for wd := range workingDirs { + if wd != "" { + p := filepath.Clean(wd) + if rel, err := filepath.Rel(workspaceDir, p); err == nil && !strings.HasPrefix(rel, ".") { + if script == "" { + script = fmt.Sprintf("mkdir -p %s", p) + } else { + script = fmt.Sprintf("%s %s", script, p) + } + } + } + } + + return script +} + +func makeWorkingDirInitializer(steps []corev1.Container) *corev1.Container { + workingDirs := make(map[string]bool) + for _, step := range steps { + workingDirs[step.WorkingDir] = true + } + + if script := makeWorkingDirScript(workingDirs); script != "" { + return &corev1.Container{ + Name: names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(containerPrefix + workingDirInit), + Image: *bashWorkingDirImage, + Command: []string{"/ko-app/bash"}, + Args: []string{"-args", script}, + VolumeMounts: implicitVolumeMounts, + Env: implicitEnvVars, + WorkingDir: workspaceDir, + } + } + + return nil +} + // GetPod returns the Pod for the given pod name type GetPod func(string, metav1.GetOptions) (*corev1.Pod, error) @@ -197,6 +241,10 @@ func MakePod(taskRun *v1alpha1.TaskRun, taskSpec v1alpha1.TaskSpec, kubeclient k initContainers := []corev1.Container{*cred} podContainers := []corev1.Container{} + if workingDir := makeWorkingDirInitializer(taskSpec.Steps); workingDir != nil { + initContainers = append(initContainers, *workingDir) + } + maxIndicesByResource := findMaxResourceRequest(taskSpec.Steps, corev1.ResourceCPU, corev1.ResourceMemory, corev1.ResourceEphemeralStorage) for i := range taskSpec.Steps { diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/pod_test.go b/pkg/reconciler/v1alpha1/taskrun/resources/pod_test.go index 6f3bbcbcb39..121926eaee5 100644 --- a/pkg/reconciler/v1alpha1/taskrun/resources/pod_test.go +++ b/pkg/reconciler/v1alpha1/taskrun/resources/pod_test.go @@ -19,6 +19,7 @@ package resources import ( "crypto/rand" "fmt" + "path/filepath" "strings" "testing" @@ -314,6 +315,52 @@ func TestMakePod(t *testing.T) { }, Volumes: implicitVolumes, }, + }, { + desc: "working-dir-in-workspace-dir", + ts: v1alpha1.TaskSpec{ + Steps: []corev1.Container{{ + Name: "name", + Image: "image", + WorkingDir: filepath.Join(workspaceDir, "test"), + }}, + }, + want: &corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + InitContainers: []corev1.Container{{ + Name: containerPrefix + credsInit + "-9l9zj", + Image: *credsImage, + Command: []string{"/ko-app/creds-init"}, + Args: []string{}, + Env: implicitEnvVars, + VolumeMounts: implicitVolumeMounts, + WorkingDir: workspaceDir, + }, { + Name: containerPrefix + workingDirInit + "-mz4c7", + Image: *bashWorkingDirImage, + Command: []string{"/ko-app/bash"}, + Args: []string{"-args", fmt.Sprintf("mkdir -p %s", filepath.Join(workspaceDir, "test"))}, + Env: implicitEnvVars, + VolumeMounts: implicitVolumeMounts, + WorkingDir: workspaceDir, + }}, + Containers: []corev1.Container{{ + Name: "build-step-name", + Image: "image", + Env: implicitEnvVars, + VolumeMounts: implicitVolumeMounts, + WorkingDir: filepath.Join(workspaceDir, "test"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("0"), + corev1.ResourceMemory: resource.MustParse("0"), + corev1.ResourceEphemeralStorage: resource.MustParse("0"), + }, + }, + }, + nopContainer, + }, + Volumes: implicitVolumes, + }, }} { t.Run(c.desc, func(t *testing.T) { names.TestingSeed() @@ -374,3 +421,29 @@ func TestMakePod(t *testing.T) { }) } } + +func TestMakeWorkingDirScript(t *testing.T) { + for _, c := range []struct { + desc string + workingDirs map[string]bool + want string + }{{ + desc: "default", + workingDirs: map[string]bool{"/workspace": true}, + want: "", + }, { + desc: "simple", + workingDirs: map[string]bool{"/workspace/foo": true, "/workspace/bar": true, "/baz": true}, + want: "mkdir -p /workspace/foo /workspace/bar", + }, { + desc: "empty", + workingDirs: map[string]bool{"/workspace": true, "": true, "/baz": true, "/workspacedir": true}, + want: "", + }} { + t.Run(c.desc, func(t *testing.T) { + if script := makeWorkingDirScript(c.workingDirs); script != c.want { + t.Errorf("Expected `%v`, got `%v`", c.want, script) + } + }) + } +}