Support custom steps entrypoint (#2985)

Closes https://github.com/woodpecker-ci/woodpecker/issues/278

---------

Co-authored-by: Anbraten <anton@ju60.de>
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
qwerty287 2024-01-19 05:34:02 +01:00 committed by GitHub
parent 9f215ab932
commit d1d2e9723d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 32 additions and 10 deletions

View file

@ -156,6 +156,10 @@ docker run --entrypoint=build.sh golang
Only build steps can define commands. You cannot use commands with plugins or services. Only build steps can define commands. You cannot use commands with plugins or services.
::: :::
### `entrypoint`
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"]`).
### `environment` ### `environment`
Woodpecker provides the ability to pass environment variables to individual steps. Woodpecker provides the ability to pass environment variables to individual steps.

View file

@ -18,20 +18,20 @@ import (
"encoding/base64" "encoding/base64"
) )
func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry, cmd []string) { func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string, cmd string) {
env = make(map[string]string) env = make(map[string]string)
if goos == "windows" { if goos == "windows" {
env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands))) env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands)))
env["HOME"] = "c:\\root" env["HOME"] = "c:\\root"
env["SHELL"] = "powershell.exe" env["SHELL"] = "powershell.exe"
entry = []string{"powershell", "-noprofile", "-noninteractive", "-command"} entry = []string{"powershell", "-noprofile", "-noninteractive", "-command"}
cmd = []string{"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"} cmd = "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"
} else { } else {
env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands))) env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands)))
env["HOME"] = "/root" env["HOME"] = "/root"
env["SHELL"] = "/bin/sh" env["SHELL"] = "/bin/sh"
entry = []string{"/bin/sh", "-c"} entry = []string{"/bin/sh", "-c"}
cmd = []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"} cmd = "echo $CI_SCRIPT | base64 -d | /bin/sh -e"
} }
return env, entry, cmd return env, entry, cmd

View file

@ -17,11 +17,11 @@ func TestGenerateContainerConf(t *testing.T) {
assert.Equal(t, "c:\\root", gotEnv["HOME"]) assert.Equal(t, "c:\\root", gotEnv["HOME"])
assert.Equal(t, "powershell.exe", gotEnv["SHELL"]) assert.Equal(t, "powershell.exe", gotEnv["SHELL"])
assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command"}, gotEntry) assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command"}, gotEntry)
assert.Equal(t, []string{"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}, gotCmd) 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") gotEnv, gotEntry, gotCmd = GenerateContainerConf([]string{"echo hello world"}, "linux")
assert.Equal(t, posixScriptBase64, gotEnv["CI_SCRIPT"]) assert.Equal(t, posixScriptBase64, gotEnv["CI_SCRIPT"])
assert.Equal(t, "/root", gotEnv["HOME"]) assert.Equal(t, "/root", gotEnv["HOME"])
assert.Equal(t, "/bin/sh", gotEnv["SHELL"]) assert.Equal(t, "/bin/sh", gotEnv["SHELL"])
assert.Equal(t, []string{"/bin/sh", "-c"}, gotEntry) assert.Equal(t, []string{"/bin/sh", "-c"}, gotEntry)
assert.Equal(t, []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, gotCmd) assert.Equal(t, "echo $CI_SCRIPT | base64 -d | /bin/sh -e", gotCmd)
} }

View file

@ -47,8 +47,11 @@ func (e *docker) toConfig(step *types.Step) *container.Config {
for k, v := range env { for k, v := range env {
configEnv[k] = v configEnv[k] = v
} }
if len(step.Entrypoint) > 0 {
entry = step.Entrypoint
}
config.Entrypoint = entry config.Entrypoint = entry
config.Cmd = cmd config.Cmd = []string{cmd}
} }
if len(configEnv) != 0 { if len(configEnv) != 0 {

View file

@ -129,8 +129,11 @@ func podContainer(step *types.Step, podName, goos string) (v1.Container, error)
if len(step.Commands) != 0 { if len(step.Commands) != 0 {
scriptEnv, command, args := common.GenerateContainerConf(step.Commands, goos) scriptEnv, command, args := common.GenerateContainerConf(step.Commands, goos)
if len(step.Entrypoint) > 0 {
command = step.Entrypoint
}
container.Command = command container.Command = command
container.Args = args container.Args = []string{args}
maps.Copy(step.Environment, scriptEnv) maps.Copy(step.Environment, scriptEnv)
} }

View file

@ -310,6 +310,7 @@ func TestFullPod(t *testing.T) {
Pull: true, Pull: true,
Privileged: true, Privileged: true,
Commands: []string{"go get", "go test"}, Commands: []string{"go get", "go test"},
Entrypoint: []string{"/bin/sh", "-c"},
Volumes: []string{"woodpecker-cache:/woodpecker/src/cache"}, Volumes: []string{"woodpecker-cache:/woodpecker/src/cache"},
Environment: map[string]string{"CGO": "0"}, Environment: map[string]string{"CGO": "0"},
ExtraHosts: hostAliases, ExtraHosts: hostAliases,

View file

@ -146,6 +146,7 @@ func (e *local) StartStep(ctx context.Context, step *types.Step, taskUUID string
// execCommands use step.Image as shell and run the commands in it // execCommands use step.Image as shell and run the commands in it
func (e *local) execCommands(ctx context.Context, step *types.Step, state *workflowState, env []string) error { func (e *local) execCommands(ctx context.Context, step *types.Step, state *workflowState, env []string) error {
// Prepare commands // Prepare commands
// TODO support `entrypoint` from pipeline config
args, err := e.genCmdByShell(step.Image, step.Commands) args, err := e.genCmdByShell(step.Image, step.Commands)
if err != nil { if err != nil {
return fmt.Errorf("could not convert commands into args: %w", err) return fmt.Errorf("could not convert commands into args: %w", err)

View file

@ -14,7 +14,7 @@
package metadata package metadata
// Event types corresponding to scm hooks. // Event types corresponding to forge hooks.
const ( const (
EventPush = "push" EventPush = "push"
EventPull = "pull_request" EventPull = "pull_request"

View file

@ -185,6 +185,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
WorkingDir: workingdir, WorkingDir: workingdir,
Environment: environment, Environment: environment,
Commands: container.Commands, Commands: container.Commands,
Entrypoint: container.Entrypoint,
ExtraHosts: extraHosts, ExtraHosts: extraHosts,
Volumes: volumes, Volumes: volumes,
Tmpfs: container.Tmpfs, Tmpfs: container.Tmpfs,

View file

@ -35,6 +35,7 @@ type (
Container struct { Container struct {
BackendOptions BackendOptions `yaml:"backend_options,omitempty"` BackendOptions BackendOptions `yaml:"backend_options,omitempty"`
Commands base.StringOrSlice `yaml:"commands,omitempty"` Commands base.StringOrSlice `yaml:"commands,omitempty"`
Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"`
Detached bool `yaml:"detach,omitempty"` Detached bool `yaml:"detach,omitempty"`
Directory string `yaml:"directory,omitempty"` Directory string `yaml:"directory,omitempty"`
Environment base.SliceOrMap `yaml:"environment,omitempty"` Environment base.SliceOrMap `yaml:"environment,omitempty"`
@ -50,7 +51,7 @@ type (
Ports []string `yaml:"ports,omitempty"` Ports []string `yaml:"ports,omitempty"`
DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"` DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"`
// Docker Specific // Docker and Kubernetes Specific
Privileged bool `yaml:"privileged,omitempty"` Privileged bool `yaml:"privileged,omitempty"`
// Undocumented // Undocumented
@ -119,7 +120,7 @@ func (c *ContainerList) UnmarshalYAML(value *yaml.Node) error {
} }
func (c *Container) IsPlugin() bool { func (c *Container) IsPlugin() bool {
return len(c.Commands) == 0 return len(c.Commands) == 0 && len(c.Entrypoint) == 0
} }
func (c *Container) IsTrustedCloneImage() bool { func (c *Container) IsTrustedCloneImage() bool {

View file

@ -39,6 +39,7 @@ devices:
directory: example/ directory: example/
dns: 8.8.8.8 dns: 8.8.8.8
dns_search: example.com dns_search: example.com
entrypoint: [/bin/sh, -c]
environment: environment:
- RACK_ENV=development - RACK_ENV=development
- SHOW=true - SHOW=true
@ -86,6 +87,7 @@ func TestUnmarshalContainer(t *testing.T) {
Directory: "example/", Directory: "example/",
DNS: base.StringOrSlice{"8.8.8.8"}, DNS: base.StringOrSlice{"8.8.8.8"},
DNSSearch: base.StringOrSlice{"example.com"}, DNSSearch: base.StringOrSlice{"example.com"},
Entrypoint: []string{"/bin/sh", "-c"},
Environment: base.SliceOrMap{"RACK_ENV": "development", "SHOW": "true"}, Environment: base.SliceOrMap{"RACK_ENV": "development", "SHOW": "true"},
ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"}, ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"},
Image: "golang:latest", Image: "golang:latest",
@ -308,4 +310,10 @@ func TestIsPlugin(t *testing.T) {
assert.False(t, (&Container{ assert.False(t, (&Container{
Commands: base.StringOrSlice(strslice.StrSlice{"echo 'this is not a plugin'"}), Commands: base.StringOrSlice(strslice.StrSlice{"echo 'this is not a plugin'"}),
}).IsPlugin()) }).IsPlugin())
assert.True(t, (&Container{
Entrypoint: base.StringOrSlice(strslice.StrSlice{}),
}).IsPlugin())
assert.False(t, (&Container{
Entrypoint: base.StringOrSlice(strslice.StrSlice{"echo 'this is not a plugin'"}),
}).IsPlugin())
} }