From 75801db7dfbdefbb2c53657f513c008529627628 Mon Sep 17 00:00:00 2001 From: nati737 Date: Fri, 26 Jun 2026 20:14:59 +0300 Subject: [PATCH] Kubernetes: allow custom image when precreating workingDir as nonroot (#6771) Co-authored-by: 6543 <6543@obermui.de> --- .../11-backends/20-kubernetes.md | 9 +++++++++ pipeline/backend/kubernetes/flags.go | 6 ++++++ pipeline/backend/kubernetes/kubernetes.go | 2 ++ pipeline/backend/kubernetes/pod.go | 17 ++++++++--------- pipeline/backend/kubernetes/pod_test.go | 3 ++- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md b/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md index e296a01b8..7253974e1 100644 --- a/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md +++ b/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md @@ -625,3 +625,12 @@ Secret names to pull images from private repositories. See, how to [Pull an Imag - Default: none, which will use the default priority class configured in Kubernetes Which [Kubernetes PriorityClass](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/priority-class-v1/) to assign to created job pods. + +--- + +### BACKEND_K8S_PERMISSION_INIT_IMAGE + +- Name: `WOODPECKER_BACKEND_K8S_PERMISSION_INIT_IMAGE` +- Default: 'busybox:stable-musl' + +Container image used for the workspace permission init container, which is used to create the workspace directory and ensure correct permissions when running steps as non-root users. diff --git a/pipeline/backend/kubernetes/flags.go b/pipeline/backend/kubernetes/flags.go index f34cb6538..8e53e5865 100644 --- a/pipeline/backend/kubernetes/flags.go +++ b/pipeline/backend/kubernetes/flags.go @@ -134,4 +134,10 @@ var Flags = []cli.Flag{ Usage: "seconds Woodpecker waits for pods to stop gracefully before forcefully killing them", Value: 20, }, + &cli.StringFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_PERMISSION_INIT_IMAGE"), + Name: "backend-k8s-permission-init-image", + Usage: "image used by the workspace permission init container", + Value: "busybox:stable-musl", + }, } diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 201c7c4c8..3c6693baf 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -78,6 +78,7 @@ type config struct { NativeSecretsAllowFromStep bool PriorityClassName string StopTimeout int64 + PermissionInitImage string } func (c *config) GetNamespace(orgID int64) string { @@ -125,6 +126,7 @@ func configFromCliContext(ctx context.Context) (*config, error) { }, NativeSecretsAllowFromStep: c.Bool("backend-k8s-allow-native-secrets"), StopTimeout: c.Int64("backend-k8s-stop-timeout"), + PermissionInitImage: c.String("backend-k8s-permission-init-image"), } // Unmarshal label and annotation settings here to ensure they're valid on startup if labels := c.String("backend-k8s-pod-labels"); labels != "" { diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 856a9a939..63afd1b95 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -34,12 +34,11 @@ import ( const ( // StepLabelLegacy is the legacy label name from before the introduction of the woodpecker-ci.org namespace. // This will be removed in the future. - StepLabelLegacy = "step" - StepLabel = "woodpecker-ci.org/step" - TaskUUIDLabel = "woodpecker-ci.org/task-uuid" - podPrefix = "wp-" - defaultFSGroup int64 = 1000 - initContainerImage = "busybox:stable-musl" + StepLabelLegacy = "step" + StepLabel = "woodpecker-ci.org/step" + TaskUUIDLabel = "woodpecker-ci.org/task-uuid" + podPrefix = "wp-" + defaultFSGroup int64 = 1000 ) func mkPod(step *types.Step, config *config, podName, goos string, options BackendOptions, taskUUID string) (*kube_core_v1.Pod, error) { @@ -67,7 +66,7 @@ func mkPod(step *types.Step, config *config, podName, goos string, options Backe } spec.Containers = append(spec.Containers, container) - initContainer := podInitContainer(&spec, &container) + initContainer := podInitContainer(config, &spec, &container) if initContainer != nil { spec.InitContainers = append(spec.InitContainers, *initContainer) } @@ -294,7 +293,7 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions // podInitContainer determines whether an init container is required to prepare the // main step container's working directory with the correct permissions. // If it is required, it returns the init container spec, otherwise it returns an empty container spec. -func podInitContainer(podSpec *kube_core_v1.PodSpec, container *kube_core_v1.Container) *kube_core_v1.Container { +func podInitContainer(config *config, podSpec *kube_core_v1.PodSpec, container *kube_core_v1.Container) *kube_core_v1.Container { // if pod is running as root, we don't need an init container to precreate the workingDir // since kubelet already precreates it (as root:root) if podSpec.SecurityContext == nil || @@ -320,7 +319,7 @@ func podInitContainer(podSpec *kube_core_v1.PodSpec, container *kube_core_v1.Con return &kube_core_v1.Container{ Name: "init-" + container.Name, - Image: initContainerImage, + Image: config.PermissionInitImage, ImagePullPolicy: kube_core_v1.PullAlways, Args: []string{"mkdir", "-p", container.WorkingDir}, SecurityContext: &kube_core_v1.SecurityContext{ diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index 527ceeb55..a38a69813 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -1405,7 +1405,8 @@ func TestInitContainer(t *testing.T) { WorkingDir: "/woodpecker/src/github.com/woodpecker-ci/woodpecker", Volumes: []string{"workspace:/woodpecker/src", "other:/other"}, }, &config{ - Namespace: "woodpecker", + Namespace: "woodpecker", + PermissionInitImage: "busybox:stable-musl", }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{ SecurityContext: &SecurityContext{ RunAsNonRoot: newBool(true),