Browse code

client: move ExecOptions to client

- move api/types/container.ExecOptions to the client
- rename api/types/container.ExecOptions to ExecCreateRequest

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/09/15 19:30:18
Showing 20 changed files
... ...
@@ -8,22 +8,6 @@ import "github.com/moby/moby/api/types/common"
8 8
 // TODO(thaJeztah): make this a distinct type.
9 9
 type ExecCreateResponse = common.IDResponse
10 10
 
11
-// ExecOptions is a small subset of the Config struct that holds the configuration
12
-// for the exec feature of docker.
13
-type ExecOptions struct {
14
-	User         string   // User that will run the command
15
-	Privileged   bool     // Is the container in privileged mode
16
-	Tty          bool     // Attach standard streams to a tty.
17
-	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
18
-	AttachStdin  bool     // Attach the standard input, makes possible user interaction
19
-	AttachStderr bool     // Attach the standard error
20
-	AttachStdout bool     // Attach the standard output
21
-	DetachKeys   string   // Escape keys for detach
22
-	Env          []string // Environment variables
23
-	WorkingDir   string   // Working directory
24
-	Cmd          []string // Execution commands and args
25
-}
26
-
27 11
 // ExecInspect holds information returned by exec inspect.
28 12
 //
29 13
 // It is used by the client to unmarshal a [ExecInspectResponse],
30 14
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package container
1
+
2
+// ExecCreateRequest is a small subset of the Config struct that holds the configuration
3
+// for the exec feature of docker.
4
+type ExecCreateRequest struct {
5
+	User         string   // User that will run the command
6
+	Privileged   bool     // Is the container in privileged mode
7
+	Tty          bool     // Attach standard streams to a tty.
8
+	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
9
+	AttachStdin  bool     // Attach the standard input, makes possible user interaction
10
+	AttachStderr bool     // Attach the standard error
11
+	AttachStdout bool     // Attach the standard output
12
+	DetachKeys   string   // Escape keys for detach
13
+	Env          []string // Environment variables
14
+	WorkingDir   string   // Working directory
15
+	Cmd          []string // Execution commands and args
16
+}
... ...
@@ -70,7 +70,7 @@ type ContainerAPIClient interface {
70 70
 	ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
71 71
 	ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
72 72
 	ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error)
73
-	ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error)
73
+	ContainerExecCreate(ctx context.Context, container string, options ExecCreateOptions) (container.ExecCreateResponse, error)
74 74
 	ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
75 75
 	ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error
76 76
 	ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error
... ...
@@ -9,8 +9,24 @@ import (
9 9
 	"github.com/moby/moby/api/types/versions"
10 10
 )
11 11
 
12
+// ExecCreateOptions is a small subset of the Config struct that holds the configuration
13
+// for the exec feature of docker.
14
+type ExecCreateOptions struct {
15
+	User         string   // User that will run the command
16
+	Privileged   bool     // Is the container in privileged mode
17
+	Tty          bool     // Attach standard streams to a tty.
18
+	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
19
+	AttachStdin  bool     // Attach the standard input, makes possible user interaction
20
+	AttachStderr bool     // Attach the standard error
21
+	AttachStdout bool     // Attach the standard output
22
+	DetachKeys   string   // Escape keys for detach
23
+	Env          []string // Environment variables
24
+	WorkingDir   string   // Working directory
25
+	Cmd          []string // Execution commands and args
26
+}
27
+
12 28
 // ContainerExecCreate creates a new exec configuration to run an exec process.
13
-func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) {
29
+func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (container.ExecCreateResponse, error) {
14 30
 	containerID, err := trimID("container", containerID)
15 31
 	if err != nil {
16 32
 		return container.ExecCreateResponse{}, err
... ...
@@ -32,7 +48,21 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string,
32 32
 		options.ConsoleSize = nil
33 33
 	}
34 34
 
35
-	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil)
35
+	req := container.ExecCreateRequest{
36
+		User:         options.User,
37
+		Privileged:   options.Privileged,
38
+		Tty:          options.Tty,
39
+		ConsoleSize:  options.ConsoleSize,
40
+		AttachStdin:  options.AttachStdin,
41
+		AttachStderr: options.AttachStderr,
42
+		AttachStdout: options.AttachStdout,
43
+		DetachKeys:   options.DetachKeys,
44
+		Env:          options.Env,
45
+		WorkingDir:   options.WorkingDir,
46
+		Cmd:          options.Cmd,
47
+	}
48
+
49
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil)
36 50
 	defer ensureReaderClosed(resp)
37 51
 	if err != nil {
38 52
 		return container.ExecCreateResponse{}, err
... ...
@@ -22,14 +22,14 @@ func TestContainerExecCreateError(t *testing.T) {
22 22
 	)
23 23
 	assert.NilError(t, err)
24 24
 
25
-	_, err = client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{})
25
+	_, err = client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{})
26 26
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
27 27
 
28
-	_, err = client.ContainerExecCreate(context.Background(), "", container.ExecOptions{})
28
+	_, err = client.ContainerExecCreate(context.Background(), "", ExecCreateOptions{})
29 29
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
30 30
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
31 31
 
32
-	_, err = client.ContainerExecCreate(context.Background(), "    ", container.ExecOptions{})
32
+	_, err = client.ContainerExecCreate(context.Background(), "    ", ExecCreateOptions{})
33 33
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
34 34
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
35 35
 }
... ...
@@ -42,7 +42,7 @@ func TestContainerExecCreateConnectionError(t *testing.T) {
42 42
 	client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
43 43
 	assert.NilError(t, err)
44 44
 
45
-	_, err = client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{})
45
+	_, err = client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{})
46 46
 	assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
47 47
 }
48 48
 
... ...
@@ -60,7 +60,7 @@ func TestContainerExecCreate(t *testing.T) {
60 60
 			if err := req.ParseForm(); err != nil {
61 61
 				return nil, err
62 62
 			}
63
-			execConfig := &container.ExecOptions{}
63
+			execConfig := &container.ExecCreateRequest{}
64 64
 			if err := json.NewDecoder(req.Body).Decode(execConfig); err != nil {
65 65
 				return nil, err
66 66
 			}
... ...
@@ -81,7 +81,7 @@ func TestContainerExecCreate(t *testing.T) {
81 81
 	)
82 82
 	assert.NilError(t, err)
83 83
 
84
-	r, err := client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{
84
+	r, err := client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{
85 85
 		User: "user",
86 86
 	})
87 87
 	assert.NilError(t, err)
... ...
@@ -93,7 +93,7 @@ func (daemon *Daemon) getActiveContainer(name string) (*container.Container, err
93 93
 }
94 94
 
95 95
 // ContainerExecCreate sets up an exec in a running container.
96
-func (daemon *Daemon) ContainerExecCreate(name string, options *containertypes.ExecOptions) (string, error) {
96
+func (daemon *Daemon) ContainerExecCreate(name string, options *containertypes.ExecCreateRequest) (string, error) {
97 97
 	cntr, err := daemon.getActiveContainer(name)
98 98
 	if err != nil {
99 99
 		return "", err
... ...
@@ -14,7 +14,7 @@ import (
14 14
 
15 15
 // execBackend includes functions to implement to provide exec functionality.
16 16
 type execBackend interface {
17
-	ContainerExecCreate(name string, options *container.ExecOptions) (string, error)
17
+	ContainerExecCreate(name string, options *container.ExecCreateRequest) (string, error)
18 18
 	ContainerExecInspect(id string) (*backend.ExecInspect, error)
19 19
 	ContainerExecResize(ctx context.Context, name string, height, width uint32) error
20 20
 	ContainerExecStart(ctx context.Context, name string, options backend.ExecStartConfig) error
... ...
@@ -39,7 +39,7 @@ func (c *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re
39 39
 		return err
40 40
 	}
41 41
 
42
-	execConfig := &container.ExecOptions{}
42
+	execConfig := &container.ExecCreateRequest{}
43 43
 	if err := httputils.ReadJSON(r, execConfig); err != nil {
44 44
 		return err
45 45
 	}
... ...
@@ -12,7 +12,6 @@ import (
12 12
 	"testing"
13 13
 	"time"
14 14
 
15
-	"github.com/moby/moby/api/types/container"
16 15
 	"github.com/moby/moby/client"
17 16
 	"github.com/moby/moby/v2/integration-cli/checker"
18 17
 	"github.com/moby/moby/v2/integration-cli/cli"
... ...
@@ -65,7 +64,7 @@ func (s *DockerAPISuite) TestExecAPICreateContainerPaused(c *testing.T) {
65 65
 	assert.NilError(c, err)
66 66
 	defer apiClient.Close()
67 67
 
68
-	_, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, container.ExecOptions{
68
+	_, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, client.ExecCreateOptions{
69 69
 		Cmd: []string{"true"},
70 70
 	})
71 71
 	assert.ErrorContains(c, err, "Container "+name+" is paused, unpause the container before exec", "Expected message when creating exec command with Container %s is paused", name)
... ...
@@ -129,7 +128,7 @@ func (s *DockerAPISuite) TestExecAPIStartWithDetach(c *testing.T) {
129 129
 	assert.NilError(c, err)
130 130
 	defer apiClient.Close()
131 131
 
132
-	createResp, err := apiClient.ContainerExecCreate(ctx, name, container.ExecOptions{
132
+	createResp, err := apiClient.ContainerExecCreate(ctx, name, client.ExecCreateOptions{
133 133
 		Cmd:          []string{"true"},
134 134
 		AttachStderr: true,
135 135
 	})
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/pkg/stdcopy"
13
-	"github.com/moby/moby/api/types/container"
14 13
 	"github.com/moby/moby/api/types/filters"
15 14
 	swarmtypes "github.com/moby/moby/api/types/swarm"
16 15
 	"github.com/moby/moby/client"
... ...
@@ -312,7 +311,7 @@ func TestTemplatedConfig(t *testing.T) {
312 312
 	tasks := swarm.GetRunningTasks(ctx, t, c, serviceID)
313 313
 	assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID)
314 314
 
315
-	resp := swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{
315
+	resp := swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{
316 316
 		Cmd:          []string{"/bin/cat", "/templated_config"},
317 317
 		AttachStdout: true,
318 318
 		AttachStderr: true,
... ...
@@ -327,7 +326,7 @@ func TestTemplatedConfig(t *testing.T) {
327 327
 
328 328
 	outBuf.Reset()
329 329
 	errBuf.Reset()
330
-	resp = swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{
330
+	resp = swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{
331 331
 		Cmd:          []string{"mount"},
332 332
 		AttachStdout: true,
333 333
 		AttachStderr: true,
... ...
@@ -4,8 +4,8 @@ import (
4 4
 	"strings"
5 5
 	"testing"
6 6
 
7
-	containertypes "github.com/moby/moby/api/types/container"
8 7
 	"github.com/moby/moby/api/types/versions"
8
+	"github.com/moby/moby/client"
9 9
 	"github.com/moby/moby/v2/integration/internal/container"
10 10
 	"gotest.tools/v3/assert"
11 11
 	"gotest.tools/v3/skip"
... ...
@@ -21,7 +21,7 @@ func TestExecConsoleSize(t *testing.T) {
21 21
 	cID := container.Run(ctx, t, apiClient, container.WithImage("busybox"))
22 22
 
23 23
 	result, err := container.Exec(ctx, apiClient, cID, []string{"stty", "size"},
24
-		func(ec *containertypes.ExecOptions) {
24
+		func(ec *client.ExecCreateOptions) {
25 25
 			ec.Tty = true
26 26
 			ec.ConsoleSize = &[2]uint{57, 123}
27 27
 		},
... ...
@@ -12,7 +12,6 @@ import (
12 12
 
13 13
 	cerrdefs "github.com/containerd/errdefs"
14 14
 	"github.com/moby/moby/api/types/common"
15
-	containertypes "github.com/moby/moby/api/types/container"
16 15
 	"github.com/moby/moby/client"
17 16
 	"github.com/moby/moby/v2/integration/internal/build"
18 17
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -34,7 +33,7 @@ func TestExecWithCloseStdin(t *testing.T) {
34 34
 	cID := container.Run(ctx, t, apiClient)
35 35
 
36 36
 	const expected = "closeIO"
37
-	execResp, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{
37
+	execResp, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{
38 38
 		AttachStdin:  true,
39 39
 		AttachStdout: true,
40 40
 		Cmd:          []string{"sh", "-c", "cat && echo " + expected},
... ...
@@ -89,7 +88,7 @@ func TestExec(t *testing.T) {
89 89
 
90 90
 	cID := container.Run(ctx, t, apiClient, container.WithTty(true), container.WithWorkingDir("/root"))
91 91
 
92
-	id, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{
92
+	id, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{
93 93
 		WorkingDir:   "/tmp",
94 94
 		Env:          []string{"FOO=BAR"},
95 95
 		AttachStdout: true,
... ...
@@ -127,7 +126,7 @@ func TestExecResize(t *testing.T) {
127 127
 	if runtime.GOOS == "windows" {
128 128
 		cmd = []string{"sleep", "240"}
129 129
 	}
130
-	resp, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{
130
+	resp, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{
131 131
 		Tty: true, // Windows requires a TTY for the resize to work, otherwise fails with "is not a tty: failed precondition", see https://github.com/moby/moby/pull/48665#issuecomment-2412530345
132 132
 		Cmd: cmd,
133 133
 	})
... ...
@@ -296,8 +295,8 @@ func TestExecUser(t *testing.T) {
296 296
 	withoutEtcGroups := container.WithImage(build.Do(ctx, t, apiClient, fakecontext.New(t, "", fakecontext.WithDockerfile("FROM busybox\nRUN rm /etc/group"))))
297 297
 	withoutEtcPasswd := container.WithImage(build.Do(ctx, t, apiClient, fakecontext.New(t, "", fakecontext.WithDockerfile("FROM busybox\nRUN rm /etc/passwd"))))
298 298
 
299
-	withUser := func(user string) func(options *containertypes.ExecOptions) {
300
-		return func(options *containertypes.ExecOptions) { options.User = user }
299
+	withUser := func(user string) func(options *client.ExecCreateOptions) {
300
+		return func(options *client.ExecCreateOptions) { options.User = user }
301 301
 	}
302 302
 
303 303
 	tests := []struct {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"context"
6 6
 	"testing"
7 7
 
8
-	"github.com/moby/moby/api/types/container"
9 8
 	"github.com/moby/moby/client"
10 9
 )
11 10
 
... ...
@@ -47,9 +46,9 @@ func (res ExecResult) AssertSuccess(t testing.TB) {
47 47
 // containing stdout, stderr, and exit code. Note:
48 48
 //   - this is a synchronous operation;
49 49
 //   - cmd stdin is closed.
50
-func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*container.ExecOptions)) (ExecResult, error) {
50
+func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*client.ExecCreateOptions)) (ExecResult, error) {
51 51
 	// prepare exec
52
-	execOptions := container.ExecOptions{
52
+	execOptions := client.ExecCreateOptions{
53 53
 		AttachStdout: true,
54 54
 		AttachStderr: true,
55 55
 		Cmd:          cmd,
... ...
@@ -87,7 +86,7 @@ func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []stri
87 87
 }
88 88
 
89 89
 // ExecT calls Exec() and aborts the test if an error occurs.
90
-func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*container.ExecOptions)) ExecResult {
90
+func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*client.ExecCreateOptions)) ExecResult {
91 91
 	t.Helper()
92 92
 	res, err := Exec(ctx, apiClient, id, cmd, ops...)
93 93
 	if err != nil {
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
-	"github.com/moby/moby/api/types/container"
10 9
 	"github.com/moby/moby/api/types/filters"
11 10
 	swarmtypes "github.com/moby/moby/api/types/swarm"
12 11
 	"github.com/moby/moby/client"
... ...
@@ -211,7 +210,7 @@ func GetRunningTasks(ctx context.Context, t *testing.T, c client.ServiceAPIClien
211 211
 }
212 212
 
213 213
 // ExecTask runs the passed in exec config on the given task
214
-func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtypes.Task, options container.ExecOptions) client.HijackedResponse {
214
+func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtypes.Task, options client.ExecCreateOptions) client.HijackedResponse {
215 215
 	t.Helper()
216 216
 	apiClient := d.NewClientT(t)
217 217
 	defer apiClient.Close()
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/pkg/stdcopy"
13
-	"github.com/moby/moby/api/types/container"
14 13
 	"github.com/moby/moby/api/types/filters"
15 14
 	swarmtypes "github.com/moby/moby/api/types/swarm"
16 15
 	"github.com/moby/moby/client"
... ...
@@ -313,7 +312,7 @@ func TestTemplatedSecret(t *testing.T) {
313 313
 	tasks := swarm.GetRunningTasks(ctx, t, c, serviceID)
314 314
 	assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID)
315 315
 
316
-	resp := swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{
316
+	resp := swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{
317 317
 		Cmd:          []string{"/bin/cat", "/run/secrets/templated_secret"},
318 318
 		AttachStdout: true,
319 319
 		AttachStderr: true,
... ...
@@ -328,7 +327,7 @@ func TestTemplatedSecret(t *testing.T) {
328 328
 
329 329
 	outBuf.Reset()
330 330
 	errBuf.Reset()
331
-	resp = swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{
331
+	resp = swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{
332 332
 		Cmd:          []string{"mount"},
333 333
 		AttachStdout: true,
334 334
 		AttachStderr: true,
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 	"time"
10 10
 
11
-	containertypes "github.com/moby/moby/api/types/container"
12 11
 	"github.com/moby/moby/api/types/events"
13 12
 	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/mount"
... ...
@@ -28,7 +27,7 @@ func TestEventsExecDie(t *testing.T) {
28 28
 
29 29
 	cID := container.Run(ctx, t, apiClient)
30 30
 
31
-	id, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{
31
+	id, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{
32 32
 		Cmd: []string{"echo", "hello"},
33 33
 	})
34 34
 	assert.NilError(t, err)
... ...
@@ -8,22 +8,6 @@ import "github.com/moby/moby/api/types/common"
8 8
 // TODO(thaJeztah): make this a distinct type.
9 9
 type ExecCreateResponse = common.IDResponse
10 10
 
11
-// ExecOptions is a small subset of the Config struct that holds the configuration
12
-// for the exec feature of docker.
13
-type ExecOptions struct {
14
-	User         string   // User that will run the command
15
-	Privileged   bool     // Is the container in privileged mode
16
-	Tty          bool     // Attach standard streams to a tty.
17
-	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
18
-	AttachStdin  bool     // Attach the standard input, makes possible user interaction
19
-	AttachStderr bool     // Attach the standard error
20
-	AttachStdout bool     // Attach the standard output
21
-	DetachKeys   string   // Escape keys for detach
22
-	Env          []string // Environment variables
23
-	WorkingDir   string   // Working directory
24
-	Cmd          []string // Execution commands and args
25
-}
26
-
27 11
 // ExecInspect holds information returned by exec inspect.
28 12
 //
29 13
 // It is used by the client to unmarshal a [ExecInspectResponse],
30 14
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package container
1
+
2
+// ExecCreateRequest is a small subset of the Config struct that holds the configuration
3
+// for the exec feature of docker.
4
+type ExecCreateRequest struct {
5
+	User         string   // User that will run the command
6
+	Privileged   bool     // Is the container in privileged mode
7
+	Tty          bool     // Attach standard streams to a tty.
8
+	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
9
+	AttachStdin  bool     // Attach the standard input, makes possible user interaction
10
+	AttachStderr bool     // Attach the standard error
11
+	AttachStdout bool     // Attach the standard output
12
+	DetachKeys   string   // Escape keys for detach
13
+	Env          []string // Environment variables
14
+	WorkingDir   string   // Working directory
15
+	Cmd          []string // Execution commands and args
16
+}
... ...
@@ -70,7 +70,7 @@ type ContainerAPIClient interface {
70 70
 	ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
71 71
 	ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
72 72
 	ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error)
73
-	ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error)
73
+	ContainerExecCreate(ctx context.Context, container string, options ExecCreateOptions) (container.ExecCreateResponse, error)
74 74
 	ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
75 75
 	ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error
76 76
 	ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error
... ...
@@ -9,8 +9,24 @@ import (
9 9
 	"github.com/moby/moby/api/types/versions"
10 10
 )
11 11
 
12
+// ExecCreateOptions is a small subset of the Config struct that holds the configuration
13
+// for the exec feature of docker.
14
+type ExecCreateOptions struct {
15
+	User         string   // User that will run the command
16
+	Privileged   bool     // Is the container in privileged mode
17
+	Tty          bool     // Attach standard streams to a tty.
18
+	ConsoleSize  *[2]uint `json:",omitempty"` // Initial console size [height, width]
19
+	AttachStdin  bool     // Attach the standard input, makes possible user interaction
20
+	AttachStderr bool     // Attach the standard error
21
+	AttachStdout bool     // Attach the standard output
22
+	DetachKeys   string   // Escape keys for detach
23
+	Env          []string // Environment variables
24
+	WorkingDir   string   // Working directory
25
+	Cmd          []string // Execution commands and args
26
+}
27
+
12 28
 // ContainerExecCreate creates a new exec configuration to run an exec process.
13
-func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) {
29
+func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (container.ExecCreateResponse, error) {
14 30
 	containerID, err := trimID("container", containerID)
15 31
 	if err != nil {
16 32
 		return container.ExecCreateResponse{}, err
... ...
@@ -32,7 +48,21 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string,
32 32
 		options.ConsoleSize = nil
33 33
 	}
34 34
 
35
-	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil)
35
+	req := container.ExecCreateRequest{
36
+		User:         options.User,
37
+		Privileged:   options.Privileged,
38
+		Tty:          options.Tty,
39
+		ConsoleSize:  options.ConsoleSize,
40
+		AttachStdin:  options.AttachStdin,
41
+		AttachStderr: options.AttachStderr,
42
+		AttachStdout: options.AttachStdout,
43
+		DetachKeys:   options.DetachKeys,
44
+		Env:          options.Env,
45
+		WorkingDir:   options.WorkingDir,
46
+		Cmd:          options.Cmd,
47
+	}
48
+
49
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil)
36 50
 	defer ensureReaderClosed(resp)
37 51
 	if err != nil {
38 52
 		return container.ExecCreateResponse{}, err