From 19f2749d3906707717aeec9da27c499bcdc07da8 Mon Sep 17 00:00:00 2001
From: Nicolas De Loof <nicolas.deloof@gmail.com>
Date: Fri, 1 Dec 2017 09:06:07 +0100
Subject: [PATCH 1/2] introduce `workingdir` option for docker exec

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
---
 api/swagger.yaml     | 3 +++
 api/types/configs.go | 1 +
 daemon/exec.go       | 6 +++++-
 daemon/exec/exec.go  | 1 +
 4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/api/swagger.yaml b/api/swagger.yaml
index 07ee0674cd..a01926df6e 100644
--- a/api/swagger.yaml
+++ b/api/swagger.yaml
@@ -7269,6 +7269,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
 }

From 39d5ace6e1c8f3f7a008fcdf328592bfa799a243 Mon Sep 17 00:00:00 2001
From: Nicolas De Loof <nicolas.deloof@gmail.com>
Date: Sun, 3 Dec 2017 10:56:50 +0100
Subject: [PATCH 2/2] =?UTF-8?q?test=20case=20to=20check=20=C2=AB=C2=A0exec?=
 =?UTF-8?q?=C2=A0=C2=BB=20works=20as=20expected?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
---
 integration/container/exec_test.go | 60 ++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 integration/container/exec_test.go

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")
+}