From 871c87fe9b5e8036f828e7baa0c528b552bfb3bb Mon Sep 17 00:00:00 2001 From: Alex DiCarlo Date: Tue, 23 Apr 2019 00:28:43 -0700 Subject: [PATCH] Initialize working dirs inside workspace dir In order to allow steps to define workingDirs that are subdirectories of the workspace directory we need to make sure they have been created first since they will not exist on startup. Fixes #725 --- .../v1alpha1/taskrun/resources/pod.go | 48 ++++++++++++ .../v1alpha1/taskrun/resources/pod_test.go | 73 +++++++++++++++++++ 2 files changed, 121 insertions(+) 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) + } + }) + } +}