diff --git a/api/swagger.yaml b/api/swagger.yaml index 96386e0599..b7ed3b8731 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -7259,6 +7259,9 @@ paths: User: type: "string" description: "The user, and optionally, group to run the exec process inside the container. Format is one of: `user`, `user:group`, `uid`, or `uid:gid`." + WorkingDir: + type: "string" + description: "The working directory for the exec process inside the container." example: AttachStdin: false AttachStdout: true diff --git a/api/types/configs.go b/api/types/configs.go index 20c19f2132..54d3e39fb9 100644 --- a/api/types/configs.go +++ b/api/types/configs.go @@ -50,6 +50,7 @@ type ExecConfig struct { Detach bool // Execute in detach mode DetachKeys string // Escape keys for detach Env []string // Environment variables + WorkingDir string // Working directory Cmd []string // Execution commands and args } diff --git a/daemon/exec.go b/daemon/exec.go index 01670faa5d..83b7de2255 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -122,6 +122,7 @@ func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (str execConfig.Tty = config.Tty execConfig.Privileged = config.Privileged execConfig.User = config.User + execConfig.WorkingDir = config.WorkingDir linkedEnv, err := d.setupLinkedContainers(cntr) if err != nil { @@ -131,6 +132,9 @@ func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (str if len(execConfig.User) == 0 { execConfig.User = cntr.Config.User } + if len(execConfig.WorkingDir) == 0 { + execConfig.WorkingDir = cntr.Config.WorkingDir + } d.registerExecCommand(cntr, execConfig) @@ -211,7 +215,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R Args: append([]string{ec.Entrypoint}, ec.Args...), Env: ec.Env, Terminal: ec.Tty, - Cwd: c.Config.WorkingDir, + Cwd: ec.WorkingDir, } if p.Cwd == "" { p.Cwd = "/" diff --git a/daemon/exec/exec.go b/daemon/exec/exec.go index 193d32f022..370b4032c7 100644 --- a/daemon/exec/exec.go +++ b/daemon/exec/exec.go @@ -31,6 +31,7 @@ type Config struct { Tty bool Privileged bool User string + WorkingDir string Env []string Pid int } diff --git a/integration/container/exec_test.go b/integration/container/exec_test.go new file mode 100644 index 0000000000..22d7ec01cc --- /dev/null +++ b/integration/container/exec_test.go @@ -0,0 +1,60 @@ +package container + +import ( + "context" + "io/ioutil" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/integration/util/request" + "github.com/stretchr/testify/require" +) + +func TestExec(t *testing.T) { + defer setupTest(t)() + ctx := context.Background() + client := request.NewAPIClient(t) + + container, err := client.ContainerCreate(ctx, + &container.Config{ + Image: "busybox", + Tty: true, + WorkingDir: "/root", + Cmd: strslice.StrSlice([]string{"top"}), + }, + &container.HostConfig{}, + &network.NetworkingConfig{}, + "foo", + ) + require.NoError(t, err) + err = client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) + require.NoError(t, err) + + id, err := client.ContainerExecCreate(ctx, container.ID, + types.ExecConfig{ + WorkingDir: "/tmp", + Env: strslice.StrSlice([]string{"FOO=BAR"}), + AttachStdout: true, + Cmd: strslice.StrSlice([]string{"sh", "-c", "env"}), + }, + ) + require.NoError(t, err) + + resp, err := client.ContainerExecAttach(ctx, id.ID, + types.ExecStartCheck{ + Detach: false, + Tty: false, + }, + ) + require.NoError(t, err) + defer resp.Close() + r, err := ioutil.ReadAll(resp.Reader) + require.NoError(t, err) + out := string(r) + require.NoError(t, err) + require.Contains(t, out, "PWD=/tmp", "exec command not running in expected /tmp working directory") + require.Contains(t, out, "FOO=BAR", "exec command not running with expected environment variable FOO") +}