Skip to content

Commit

Permalink
Support workingDir init on Windows
Browse files Browse the repository at this point in the history
Previously, we used --shell-image (typically distroless/base) to invoke
`mkdir -p <dirs>` to initialize any working dirs that were needed by
steps in a TaskRun.

This isn't portable to Windows, since distroless/base doesn't provide
Windows images, and since invoking `mkdir -p` won't work on Windows.

Instead, with this change, we init working dirs using a very simple Go
binary that can be cross-compiled to run on both OSes.
  • Loading branch information
imjasonh committed Jan 12, 2022
1 parent fcca727 commit 2281e25
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 15 deletions.
1 change: 1 addition & 0 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func main() {
flag.StringVar(&opts.Images.GsutilImage, "gsutil-image", "", "The container image containing gsutil")
flag.StringVar(&opts.Images.PRImage, "pr-image", "", "The container image containing our PR binary.")
flag.StringVar(&opts.Images.ImageDigestExporterImage, "imagedigest-exporter-image", "", "The container image containing our image digest exporter binary.")
flag.StringVar(&opts.Images.WorkingDirInitImage, "workingdirinit-image", "", "The container image containing our working dir init binary.")

// This parses flags.
cfg := injection.ParseAndGetRESTConfigOrDie()
Expand Down
35 changes: 35 additions & 0 deletions cmd/workingdirinit/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2022 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"log"
"os"
"path/filepath"
"strings"
)

func main() {
for _, d := range os.Args {
p := filepath.Clean(d)
if !filepath.IsAbs(p) || strings.HasPrefix(p, "/workspace/") {
if err := os.MkdirAll(p, 0755); err != nil {
log.Fatalf("Failed to mkdir %q: %v", p, err)
}
}
}
}
1 change: 1 addition & 0 deletions config/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ spec:
"-nop-image", "ko://github.com/tektoncd/pipeline/cmd/nop",
"-imagedigest-exporter-image", "ko://github.com/tektoncd/pipeline/cmd/imagedigestexporter",
"-pr-image", "ko://github.com/tektoncd/pipeline/cmd/pullrequest-init",
"-workingdirinit-image", "ko://github.com/tektoncd/pipeline/cmd/workingdirinit",

# This is gcr.io/google.com/cloudsdktool/cloud-sdk:302.0.0-slim
"-gsutil-image", "gcr.io/google.com/cloudsdktool/cloud-sdk@sha256:27b2c22bf259d9bc1a291e99c63791ba0c27a04d2db0a43241ba0f1f20f4067f",
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/pipeline/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type Images struct {
PRImage string
// ImageDigestExporterImage is the container image containing our image digest exporter binary.
ImageDigestExporterImage string
// WorkingDirInitImage is the container image containing our working dir init binary.
WorkingDirInitImage string

// NOTE: Make sure to add any new images to Validate below!
}
Expand All @@ -61,6 +63,7 @@ func (i Images) Validate() error {
{i.GsutilImage, "gsutil-image"},
{i.PRImage, "pr-image"},
{i.ImageDigestExporterImage, "imagedigest-exporter-image"},
{i.WorkingDirInitImage, "workingdirinit-image"},
} {
if f.v == "" {
unset = append(unset, f.name)
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/pipeline/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestValidate(t *testing.T) {
GsutilImage: "set",
PRImage: "set",
ImageDigestExporterImage: "set",
WorkingDirInitImage: "set",
}
if err := valid.Validate(); err != nil {
t.Errorf("valid Images returned error: %v", err)
Expand All @@ -33,7 +34,7 @@ func TestValidate(t *testing.T) {
PRImage: "", // unset!
ImageDigestExporterImage: "set",
}
wantErr := "found unset image flags: [git-image pr-image shell-image]"
wantErr := "found unset image flags: [git-image pr-image shell-image workingdirinit-image]"
if err := invalid.Validate(); err == nil {
t.Error("invalid Images expected error, got nil")
} else if err.Error() != wantErr {
Expand Down
2 changes: 1 addition & 1 deletion pkg/pod/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
}

// Initialize any workingDirs under /workspace.
if workingDirInit := workingDirInit(b.Images.ShellImage, stepContainers); workingDirInit != nil {
if workingDirInit := workingDirInit(b.Images.WorkingDirInitImage, stepContainers); workingDirInit != nil {
initContainers = append(initContainers, *workingDirInit)
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/pod/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,9 @@ func TestPodBuild(t *testing.T) {
tektonDirInit(images.EntrypointImage, []v1beta1.Step{{Container: corev1.Container{Name: "name"}}}),
{
Name: "working-dir-initializer",
Image: images.ShellImage,
Command: []string{"sh"},
Args: []string{"-c", fmt.Sprintf("mkdir -p %s", filepath.Join(pipeline.WorkspaceDir, "test"))},
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{filepath.Join(pipeline.WorkspaceDir, "test")},
WorkingDir: pipeline.WorkspaceDir,
VolumeMounts: implicitVolumeMounts,
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/pod/workingdir_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
//
// If no such directories need to be created (i.e., no relative workingDirs
// are specified), this method returns nil, as no init container is necessary.
func workingDirInit(shellImage string, stepContainers []corev1.Container) *corev1.Container {
func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container) *corev1.Container {
// Gather all unique workingDirs.
workingDirs := sets.NewString()
for _, step := range stepContainers {
Expand Down Expand Up @@ -59,9 +59,9 @@ func workingDirInit(shellImage string, stepContainers []corev1.Container) *corev

return &corev1.Container{
Name: "working-dir-initializer",
Image: shellImage,
Command: []string{"sh"},
Args: []string{"-c", "mkdir -p " + strings.Join(relativeDirs, " ")},
Image: workingdirinitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: relativeDirs,
WorkingDir: pipeline.WorkspaceDir,
VolumeMounts: implicitVolumeMounts,
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/pod/workingdir_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ func TestWorkingDirInit(t *testing.T) {
}},
want: &corev1.Container{
Name: "working-dir-initializer",
Image: images.ShellImage,
Command: []string{"sh"},
Args: []string{"-c", "mkdir -p /workspace/bbb aaa zzz"},
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{"/workspace/bbb", "aaa", "zzz"},
WorkingDir: pipeline.WorkspaceDir,
VolumeMounts: implicitVolumeMounts,
},
}} {
t.Run(c.desc, func(t *testing.T) {
got := workingDirInit(images.ShellImage, c.stepContainers)
got := workingDirInit(images.WorkingDirInitImage, c.stepContainers)
if d := cmp.Diff(c.want, got); d != "" {
t.Fatalf("Diff %s", diff.PrintWantGot(d))
}
Expand Down
5 changes: 3 additions & 2 deletions tekton/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ spec:
default: github.com/tektoncd/pipeline
- name: images
description: List of cmd/* paths to be published as images
default: "controller webhook entrypoint nop kubeconfigwriter git-init imagedigestexporter pullrequest-init"
default: "controller webhook entrypoint nop kubeconfigwriter git-init imagedigestexporter pullrequest-init workingdirinit"
- name: versionTag
description: The vX.Y.Z version that the artifacts should be tagged with (including `v`)
- name: imageRegistry
Expand Down Expand Up @@ -103,8 +103,9 @@ spec:
# This matches the value configured in .ko.yaml
defaultBaseImage: gcr.io/distroless/static:nonroot
baseImageOverrides:
# Use the combined base image for entrypoint.
# Use the combined base image for images that should include Windows support.
$(params.package)/cmd/entrypoint: ${COMBINED_BASE_IMAGE}
$(params.package)/cmd/workingdirinit: ${COMBINED_BASE_IMAGE}
$(params.package)/cmd/git-init: ${CONTAINER_REGISTRY}/$(params.package)/git-init-build-base:latest
# These match values configured in .ko.yaml
Expand Down

0 comments on commit 2281e25

Please sign in to comment.