diff --git a/docs/docs/20-usage/20-workflow-syntax.md b/docs/docs/20-usage/20-workflow-syntax.md index a3806b9c7..69ba362ca 100644 --- a/docs/docs/20-usage/20-workflow-syntax.md +++ b/docs/docs/20-usage/20-workflow-syntax.md @@ -161,6 +161,9 @@ Only build steps can define commands. You cannot use commands with plugins or se Allows you to specify the entrypoint for containers. Note that this must be a list of the command and its arguments (e.g. `["/bin/sh", "-c"]`). +If you define [`commands`](#commands), the default entrypoint will be `["/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"]`. +You can also use a custom shell with `CI_SCRIPT` (Base64-encoded) if you set `commands`. + ### `environment` Woodpecker provides the ability to pass environment variables to individual steps. diff --git a/pipeline/backend/common/script.go b/pipeline/backend/common/script.go index 9da62b92d..e335983d9 100644 --- a/pipeline/backend/common/script.go +++ b/pipeline/backend/common/script.go @@ -18,21 +18,19 @@ import ( "encoding/base64" ) -func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string, cmd string) { +func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string) { env = make(map[string]string) if goos == "windows" { env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands))) env["HOME"] = "c:\\root" env["SHELL"] = "powershell.exe" - entry = []string{"powershell", "-noprofile", "-noninteractive", "-command"} - cmd = "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex" + entry = []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"} } else { env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands))) env["HOME"] = "/root" env["SHELL"] = "/bin/sh" - entry = []string{"/bin/sh", "-c"} - cmd = "echo $CI_SCRIPT | base64 -d | /bin/sh -e" + entry = []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"} } - return env, entry, cmd + return env, entry } diff --git a/pipeline/backend/common/script_test.go b/pipeline/backend/common/script_test.go index 3114195a9..37911713e 100644 --- a/pipeline/backend/common/script_test.go +++ b/pipeline/backend/common/script_test.go @@ -12,16 +12,14 @@ const ( ) func TestGenerateContainerConf(t *testing.T) { - gotEnv, gotEntry, gotCmd := GenerateContainerConf([]string{"echo hello world"}, "windows") + gotEnv, gotEntry := GenerateContainerConf([]string{"echo hello world"}, "windows") assert.Equal(t, windowsScriptBase64, gotEnv["CI_SCRIPT"]) assert.Equal(t, "c:\\root", gotEnv["HOME"]) assert.Equal(t, "powershell.exe", gotEnv["SHELL"]) - assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command"}, gotEntry) - assert.Equal(t, "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex", gotCmd) - gotEnv, gotEntry, gotCmd = GenerateContainerConf([]string{"echo hello world"}, "linux") + assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}, gotEntry) + gotEnv, gotEntry = GenerateContainerConf([]string{"echo hello world"}, "linux") assert.Equal(t, posixScriptBase64, gotEnv["CI_SCRIPT"]) assert.Equal(t, "/root", gotEnv["HOME"]) assert.Equal(t, "/bin/sh", gotEnv["SHELL"]) - assert.Equal(t, []string{"/bin/sh", "-c"}, gotEntry) - assert.Equal(t, "echo $CI_SCRIPT | base64 -d | /bin/sh -e", gotCmd) + assert.Equal(t, []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, gotEntry) } diff --git a/pipeline/backend/docker/convert.go b/pipeline/backend/docker/convert.go index 1e4ad589b..4ebbd360e 100644 --- a/pipeline/backend/docker/convert.go +++ b/pipeline/backend/docker/convert.go @@ -45,16 +45,15 @@ func (e *docker) toConfig(step *types.Step) *container.Config { configEnv := make(map[string]string) maps.Copy(configEnv, step.Environment) - if len(step.Commands) != 0 { - env, entry, cmd := common.GenerateContainerConf(step.Commands, e.info.OSType) + if len(step.Commands) > 0 { + env, entry := common.GenerateContainerConf(step.Commands, e.info.OSType) for k, v := range env { configEnv[k] = v } - if len(step.Entrypoint) > 0 { - entry = step.Entrypoint - } config.Entrypoint = entry - config.Cmd = []string{cmd} + } + if len(step.Entrypoint) > 0 { + config.Entrypoint = step.Entrypoint } if len(configEnv) != 0 { diff --git a/pipeline/backend/docker/convert_test.go b/pipeline/backend/docker/convert_test.go index 435d669b4..b114398f6 100644 --- a/pipeline/backend/docker/convert_test.go +++ b/pipeline/backend/docker/convert_test.go @@ -105,8 +105,7 @@ func TestToConfigSmall(t *testing.T) { assert.EqualValues(t, &container.Config{ AttachStdout: true, AttachStderr: true, - Cmd: []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, - Entrypoint: []string{"/bin/sh", "-c"}, + Entrypoint: []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, Labels: map[string]string{ "wp_step": "test", "wp_uuid": "09238932", @@ -163,8 +162,7 @@ func TestToConfigFull(t *testing.T) { WorkingDir: "/src/abc", AttachStdout: true, AttachStderr: true, - Cmd: []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, - Entrypoint: []string{"/bin/sh", "-c"}, + Entrypoint: []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, Labels: map[string]string{ "wp_step": "test", "wp_uuid": "09238932", diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 331efae92..20255ca88 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -147,15 +147,14 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions container.ImagePullPolicy = v1.PullAlways } - if len(step.Commands) != 0 { - scriptEnv, command, args := common.GenerateContainerConf(step.Commands, goos) - if len(step.Entrypoint) > 0 { - command = step.Entrypoint - } + if len(step.Commands) > 0 { + scriptEnv, command := common.GenerateContainerConf(step.Commands, goos) container.Command = command - container.Args = []string{args} maps.Copy(step.Environment, scriptEnv) } + if len(step.Entrypoint) > 0 { + container.Command = step.Entrypoint + } container.Env = mapToEnvVars(step.Environment) diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index bf4f35a41..1e7fbedc5 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -90,9 +90,7 @@ func TestTinyPod(t *testing.T) { "image": "gradle:8.4.0-jdk21", "command": [ "/bin/sh", - "-c" - ], - "args": [ + "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e" ], "workingDir": "/woodpecker/src", @@ -183,9 +181,6 @@ func TestFullPod(t *testing.T) { "/bin/sh", "-c" ], - "args": [ - "echo $CI_SCRIPT | base64 -d | /bin/sh -e" - ], "workingDir": "/woodpecker/src", "ports": [ { @@ -415,3 +410,49 @@ func TestPodPrivilege(t *testing.T) { assert.NoError(t, err) assert.Equal(t, true, *pod.Spec.SecurityContext.RunAsNonRoot) } + +func TestScratchPod(t *testing.T) { + expected := ` + { + "metadata": { + "name": "wp-01he8bebctabr3kgk0qj36d2me-0", + "namespace": "woodpecker", + "creationTimestamp": null, + "labels": { + "step": "curl-google" + } + }, + "spec": { + "containers": [ + { + "name": "wp-01he8bebctabr3kgk0qj36d2me-0", + "image": "quay.io/curl/curl", + "command": [ + "/usr/bin/curl", + "-v", + "google.com" + ], + "resources": {} + } + ], + "restartPolicy": "Never" + }, + "status": {} + }` + + pod, err := mkPod(&types.Step{ + Name: "curl-google", + Image: "quay.io/curl/curl", + Entrypoint: []string{"/usr/bin/curl", "-v", "google.com"}, + }, &config{ + Namespace: "woodpecker", + }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{}) + assert.NoError(t, err) + + podJSON, err := json.Marshal(pod) + assert.NoError(t, err) + + ja := jsonassert.New(t) + t.Log(string(podJSON)) + ja.Assertf(string(podJSON), expected) +} diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml index 1a8f33911..fc63e8d6e 100644 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml @@ -14,6 +14,14 @@ steps: image: golang commands: go test + entrypoint: + image: alpine + entrypoint: ['some_entry', '--some-flag'] + + singla-entrypoint: + image: alpine + entrypoint: some_entry + commands: privileged: true image: golang diff --git a/pipeline/frontend/yaml/linter/schema/schema.json b/pipeline/frontend/yaml/linter/schema/schema.json index 13d7af690..289b524e2 100644 --- a/pipeline/frontend/yaml/linter/schema/schema.json +++ b/pipeline/frontend/yaml/linter/schema/schema.json @@ -371,6 +371,21 @@ }, "backend_options": { "$ref": "#/definitions/step_backend_options" + }, + "entrypoint": { + "description": "Defines container entrypoint.", + "oneOf": [ + { + "type": "array", + "minLength": 1, + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] } } },