From 6a642300f032acc47a377a668fa4c6067b24ece1 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Sep 2025 09:57:42 +0200 Subject: [PATCH 1/2] client: move ExecStartOptions, ExecAttachOptions to client - move api/types/container.ExecStartOptions to the client - move api/types/container.ExecAttachOptions to the client - rename api/types/container.ExecStartOptions to ExecStartRequest Signed-off-by: Sebastiaan van Stijn --- api/types/container/exec.go | 16 --------- api/types/container/exec_start_request.go | 12 +++++++ client/client_interfaces.go | 4 +-- client/container_exec.go | 35 ++++++++++++++++--- client/container_exec_test.go | 12 +++---- daemon/server/router/container/exec.go | 2 +- integration/container/exec_test.go | 6 ++-- integration/internal/container/exec.go | 2 +- integration/internal/swarm/service.go | 2 +- integration/system/event_test.go | 2 +- .../moby/moby/api/types/container/exec.go | 16 --------- .../api/types/container/exec_start_request.go | 12 +++++++ .../moby/moby/client/client_interfaces.go | 4 +-- .../moby/moby/client/container_exec.go | 35 ++++++++++++++++--- 14 files changed, 103 insertions(+), 57 deletions(-) create mode 100644 api/types/container/exec_start_request.go create mode 100644 vendor/github.com/moby/moby/api/types/container/exec_start_request.go diff --git a/api/types/container/exec.go b/api/types/container/exec.go index 4dfb56084b..970eeece71 100644 --- a/api/types/container/exec.go +++ b/api/types/container/exec.go @@ -24,22 +24,6 @@ type ExecOptions struct { Cmd []string // Execution commands and args } -// ExecStartOptions is a temp struct used by execStart -// Config fields is part of ExecConfig in runconfig package -type ExecStartOptions struct { - // ExecStart will first check if it's detached - Detach bool - // Check if there's a tty - Tty bool - // Terminal size [height, width], unused if Tty == false - ConsoleSize *[2]uint `json:",omitempty"` -} - -// ExecAttachOptions is a temp struct used by execAttach. -// -// TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached. -type ExecAttachOptions = ExecStartOptions - // ExecInspect holds information returned by exec inspect. // // It is used by the client to unmarshal a [ExecInspectResponse], diff --git a/api/types/container/exec_start_request.go b/api/types/container/exec_start_request.go new file mode 100644 index 0000000000..4c2ba0a77c --- /dev/null +++ b/api/types/container/exec_start_request.go @@ -0,0 +1,12 @@ +package container + +// ExecStartRequest is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartRequest struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool + // Terminal size [height, width], unused if Tty == false + ConsoleSize *[2]uint `json:",omitempty"` +} diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 448ecd9f71..8107a7edde 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -69,11 +69,11 @@ type ContainerAPIClient interface { ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) - ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (HijackedResponse, error) + ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error) ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error - ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error + ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (container.InspectResponse, []byte, error) diff --git a/client/container_exec.go b/client/container_exec.go index 8739c2967b..6ddb3c1246 100644 --- a/client/container_exec.go +++ b/client/container_exec.go @@ -43,8 +43,19 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, return response, err } +// ExecStartOptions is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartOptions struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool + // Terminal size [height, width], unused if Tty == false + ConsoleSize *[2]uint `json:",omitempty"` +} + // ContainerExecStart starts an exec process already created in the docker host. -func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error { +func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config ExecStartOptions) error { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // @@ -57,11 +68,22 @@ func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } - resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) + + req := container.ExecStartRequest{ + Detach: config.Detach, + Tty: config.Tty, + ConsoleSize: config.ConsoleSize, + } + resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil) defer ensureReaderClosed(resp) return err } +// ExecAttachOptions is a temp struct used by execAttach. +// +// TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached. +type ExecAttachOptions = ExecStartOptions + // ContainerExecAttach attaches a connection to an exec process in the server. // // It returns a [HijackedResponse] with the hijacked connection @@ -80,11 +102,16 @@ func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config // [Client.ContainerAttach] for details about the multiplexed stream. // // [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy -func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (HijackedResponse, error) { +func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config ExecAttachOptions) (HijackedResponse, error) { if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } - return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, http.Header{ + req := container.ExecStartRequest{ + Detach: config.Detach, + Tty: config.Tty, + ConsoleSize: config.ConsoleSize, + } + return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, req, http.Header{ "Content-Type": {"application/json"}, }) } diff --git a/client/container_exec_test.go b/client/container_exec_test.go index 4b23804e7f..1dafba9a0b 100644 --- a/client/container_exec_test.go +++ b/client/container_exec_test.go @@ -94,7 +94,7 @@ func TestContainerExecStartError(t *testing.T) { ) assert.NilError(t, err) - err = client.ContainerExecStart(context.Background(), "nothing", container.ExecStartOptions{}) + err = client.ContainerExecStart(context.Background(), "nothing", ExecStartOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) } @@ -108,12 +108,12 @@ func TestContainerExecStart(t *testing.T) { if err := req.ParseForm(); err != nil { return nil, err } - options := &container.ExecStartOptions{} - if err := json.NewDecoder(req.Body).Decode(options); err != nil { + request := &container.ExecStartRequest{} + if err := json.NewDecoder(req.Body).Decode(request); err != nil { return nil, err } - if options.Tty || !options.Detach { - return nil, fmt.Errorf("expected ExecStartOptions{Detach:true,Tty:false}, got %v", options) + if request.Tty || !request.Detach { + return nil, fmt.Errorf("expected ExecStartOptions{Detach:true,Tty:false}, got %v", request) } return &http.Response{ @@ -124,7 +124,7 @@ func TestContainerExecStart(t *testing.T) { ) assert.NilError(t, err) - err = client.ContainerExecStart(context.Background(), "exec_id", container.ExecStartOptions{ + err = client.ContainerExecStart(context.Background(), "exec_id", ExecStartOptions{ Detach: true, Tty: false, }) diff --git a/daemon/server/router/container/exec.go b/daemon/server/router/container/exec.go index 328796e09a..012397675d 100644 --- a/daemon/server/router/container/exec.go +++ b/daemon/server/router/container/exec.go @@ -78,7 +78,7 @@ func (c *containerRouter) postContainerExecStart(ctx context.Context, w http.Res stdout, stderr, outStream io.Writer ) - options := &container.ExecStartOptions{} + options := &container.ExecStartRequest{} if err := httputils.ReadJSON(r, options); err != nil { return err } diff --git a/integration/container/exec_test.go b/integration/container/exec_test.go index 90e7b40caf..e73a1c99e1 100644 --- a/integration/container/exec_test.go +++ b/integration/container/exec_test.go @@ -41,7 +41,7 @@ func TestExecWithCloseStdin(t *testing.T) { }) assert.NilError(t, err) - resp, err := apiClient.ContainerExecAttach(ctx, execResp.ID, containertypes.ExecAttachOptions{}) + resp, err := apiClient.ContainerExecAttach(ctx, execResp.ID, client.ExecAttachOptions{}) assert.NilError(t, err) defer resp.Close() @@ -101,7 +101,7 @@ func TestExec(t *testing.T) { assert.NilError(t, err) assert.Check(t, is.Equal(inspect.ExecID, id.ID)) - resp, err := apiClient.ContainerExecAttach(ctx, id.ID, containertypes.ExecAttachOptions{}) + resp, err := apiClient.ContainerExecAttach(ctx, id.ID, client.ExecAttachOptions{}) assert.NilError(t, err) defer resp.Close() r, err := io.ReadAll(resp.Reader) @@ -134,7 +134,7 @@ func TestExecResize(t *testing.T) { assert.NilError(t, err) execID := resp.ID assert.NilError(t, err) - err = apiClient.ContainerExecStart(ctx, execID, containertypes.ExecStartOptions{Detach: true}) + err = apiClient.ContainerExecStart(ctx, execID, client.ExecStartOptions{Detach: true}) assert.NilError(t, err) t.Run("success", func(t *testing.T) { diff --git a/integration/internal/container/exec.go b/integration/internal/container/exec.go index af9ebe0a61..b7438d9105 100644 --- a/integration/internal/container/exec.go +++ b/integration/internal/container/exec.go @@ -66,7 +66,7 @@ func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []stri execID := cresp.ID // run it, with stdout/stderr attached - aresp, err := apiClient.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{}) + aresp, err := apiClient.ContainerExecAttach(ctx, execID, client.ExecAttachOptions{}) if err != nil { return ExecResult{}, err } diff --git a/integration/internal/swarm/service.go b/integration/internal/swarm/service.go index 470aa15381..d6470ecd20 100644 --- a/integration/internal/swarm/service.go +++ b/integration/internal/swarm/service.go @@ -219,7 +219,7 @@ func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtyp resp, err := apiClient.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, options) assert.NilError(t, err, "error creating exec") - attach, err := apiClient.ContainerExecAttach(ctx, resp.ID, container.ExecAttachOptions{}) + attach, err := apiClient.ContainerExecAttach(ctx, resp.ID, client.ExecAttachOptions{}) assert.NilError(t, err, "error attaching to exec") return attach } diff --git a/integration/system/event_test.go b/integration/system/event_test.go index a54a9168e6..34b5f1a534 100644 --- a/integration/system/event_test.go +++ b/integration/system/event_test.go @@ -40,7 +40,7 @@ func TestEventsExecDie(t *testing.T) { ), }) - err = apiClient.ContainerExecStart(ctx, id.ID, containertypes.ExecStartOptions{ + err = apiClient.ContainerExecStart(ctx, id.ID, client.ExecStartOptions{ Detach: true, Tty: false, }) diff --git a/vendor/github.com/moby/moby/api/types/container/exec.go b/vendor/github.com/moby/moby/api/types/container/exec.go index 4dfb56084b..970eeece71 100644 --- a/vendor/github.com/moby/moby/api/types/container/exec.go +++ b/vendor/github.com/moby/moby/api/types/container/exec.go @@ -24,22 +24,6 @@ type ExecOptions struct { Cmd []string // Execution commands and args } -// ExecStartOptions is a temp struct used by execStart -// Config fields is part of ExecConfig in runconfig package -type ExecStartOptions struct { - // ExecStart will first check if it's detached - Detach bool - // Check if there's a tty - Tty bool - // Terminal size [height, width], unused if Tty == false - ConsoleSize *[2]uint `json:",omitempty"` -} - -// ExecAttachOptions is a temp struct used by execAttach. -// -// TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached. -type ExecAttachOptions = ExecStartOptions - // ExecInspect holds information returned by exec inspect. // // It is used by the client to unmarshal a [ExecInspectResponse], diff --git a/vendor/github.com/moby/moby/api/types/container/exec_start_request.go b/vendor/github.com/moby/moby/api/types/container/exec_start_request.go new file mode 100644 index 0000000000..4c2ba0a77c --- /dev/null +++ b/vendor/github.com/moby/moby/api/types/container/exec_start_request.go @@ -0,0 +1,12 @@ +package container + +// ExecStartRequest is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartRequest struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool + // Terminal size [height, width], unused if Tty == false + ConsoleSize *[2]uint `json:",omitempty"` +} diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 448ecd9f71..8107a7edde 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -69,11 +69,11 @@ type ContainerAPIClient interface { ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) - ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (HijackedResponse, error) + ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error) ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error - ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error + ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (container.InspectResponse, []byte, error) diff --git a/vendor/github.com/moby/moby/client/container_exec.go b/vendor/github.com/moby/moby/client/container_exec.go index 8739c2967b..6ddb3c1246 100644 --- a/vendor/github.com/moby/moby/client/container_exec.go +++ b/vendor/github.com/moby/moby/client/container_exec.go @@ -43,8 +43,19 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, return response, err } +// ExecStartOptions is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartOptions struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool + // Terminal size [height, width], unused if Tty == false + ConsoleSize *[2]uint `json:",omitempty"` +} + // ContainerExecStart starts an exec process already created in the docker host. -func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error { +func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config ExecStartOptions) error { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // @@ -57,11 +68,22 @@ func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } - resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) + + req := container.ExecStartRequest{ + Detach: config.Detach, + Tty: config.Tty, + ConsoleSize: config.ConsoleSize, + } + resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil) defer ensureReaderClosed(resp) return err } +// ExecAttachOptions is a temp struct used by execAttach. +// +// TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached. +type ExecAttachOptions = ExecStartOptions + // ContainerExecAttach attaches a connection to an exec process in the server. // // It returns a [HijackedResponse] with the hijacked connection @@ -80,11 +102,16 @@ func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config // [Client.ContainerAttach] for details about the multiplexed stream. // // [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy -func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (HijackedResponse, error) { +func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config ExecAttachOptions) (HijackedResponse, error) { if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } - return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, http.Header{ + req := container.ExecStartRequest{ + Detach: config.Detach, + Tty: config.Tty, + ConsoleSize: config.ConsoleSize, + } + return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, req, http.Header{ "Content-Type": {"application/json"}, }) } From 082b4e8d77cba4d0a9a82c05ebe8200d857a6ee3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Sep 2025 12:30:18 +0200 Subject: [PATCH 2/2] 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 --- api/types/container/exec.go | 16 --------- api/types/container/exec_create_request.go | 17 ++++++++++ client/client_interfaces.go | 2 +- client/container_exec.go | 34 +++++++++++++++++-- client/container_exec_test.go | 12 +++---- daemon/exec.go | 2 +- daemon/server/router/container/backend.go | 2 +- daemon/server/router/container/exec.go | 2 +- integration-cli/docker_api_exec_test.go | 5 ++- integration/config/config_test.go | 5 ++- integration/container/exec_linux_test.go | 4 +-- integration/container/exec_test.go | 11 +++--- integration/internal/container/exec.go | 7 ++-- integration/internal/swarm/service.go | 3 +- integration/secret/secret_test.go | 5 ++- integration/system/event_test.go | 3 +- .../moby/moby/api/types/container/exec.go | 16 --------- .../types/container/exec_create_request.go | 17 ++++++++++ .../moby/moby/client/client_interfaces.go | 2 +- .../moby/moby/client/container_exec.go | 34 +++++++++++++++++-- 20 files changed, 127 insertions(+), 72 deletions(-) create mode 100644 api/types/container/exec_create_request.go create mode 100644 vendor/github.com/moby/moby/api/types/container/exec_create_request.go diff --git a/api/types/container/exec.go b/api/types/container/exec.go index 970eeece71..29c7b96268 100644 --- a/api/types/container/exec.go +++ b/api/types/container/exec.go @@ -8,22 +8,6 @@ import "github.com/moby/moby/api/types/common" // TODO(thaJeztah): make this a distinct type. type ExecCreateResponse = common.IDResponse -// ExecOptions is a small subset of the Config struct that holds the configuration -// for the exec feature of docker. -type ExecOptions struct { - User string // User that will run the command - Privileged bool // Is the container in privileged mode - Tty bool // Attach standard streams to a tty. - ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] - AttachStdin bool // Attach the standard input, makes possible user interaction - AttachStderr bool // Attach the standard error - AttachStdout bool // Attach the standard output - DetachKeys string // Escape keys for detach - Env []string // Environment variables - WorkingDir string // Working directory - Cmd []string // Execution commands and args -} - // ExecInspect holds information returned by exec inspect. // // It is used by the client to unmarshal a [ExecInspectResponse], diff --git a/api/types/container/exec_create_request.go b/api/types/container/exec_create_request.go new file mode 100644 index 0000000000..dd7437cd2f --- /dev/null +++ b/api/types/container/exec_create_request.go @@ -0,0 +1,17 @@ +package container + +// ExecCreateRequest is a small subset of the Config struct that holds the configuration +// for the exec feature of docker. +type ExecCreateRequest struct { + User string // User that will run the command + Privileged bool // Is the container in privileged mode + Tty bool // Attach standard streams to a tty. + ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStderr bool // Attach the standard error + AttachStdout bool // Attach the standard output + DetachKeys string // Escape keys for detach + Env []string // Environment variables + WorkingDir string // Working directory + Cmd []string // Execution commands and args +} diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 8107a7edde..a1059e68d3 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -70,7 +70,7 @@ type ContainerAPIClient interface { ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error) - ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error) + ContainerExecCreate(ctx context.Context, container string, options ExecCreateOptions) (container.ExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error diff --git a/client/container_exec.go b/client/container_exec.go index 6ddb3c1246..d2fb4cba1b 100644 --- a/client/container_exec.go +++ b/client/container_exec.go @@ -9,8 +9,24 @@ import ( "github.com/moby/moby/api/types/versions" ) +// ExecCreateOptions is a small subset of the Config struct that holds the configuration +// for the exec feature of docker. +type ExecCreateOptions struct { + User string // User that will run the command + Privileged bool // Is the container in privileged mode + Tty bool // Attach standard streams to a tty. + ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStderr bool // Attach the standard error + AttachStdout bool // Attach the standard output + DetachKeys string // Escape keys for detach + Env []string // Environment variables + WorkingDir string // Working directory + Cmd []string // Execution commands and args +} + // ContainerExecCreate creates a new exec configuration to run an exec process. -func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) { +func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (container.ExecCreateResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.ExecCreateResponse{}, err @@ -32,7 +48,21 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options.ConsoleSize = nil } - resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil) + req := container.ExecCreateRequest{ + User: options.User, + Privileged: options.Privileged, + Tty: options.Tty, + ConsoleSize: options.ConsoleSize, + AttachStdin: options.AttachStdin, + AttachStderr: options.AttachStderr, + AttachStdout: options.AttachStdout, + DetachKeys: options.DetachKeys, + Env: options.Env, + WorkingDir: options.WorkingDir, + Cmd: options.Cmd, + } + + resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil) defer ensureReaderClosed(resp) if err != nil { return container.ExecCreateResponse{}, err diff --git a/client/container_exec_test.go b/client/container_exec_test.go index 1dafba9a0b..337d43c163 100644 --- a/client/container_exec_test.go +++ b/client/container_exec_test.go @@ -22,14 +22,14 @@ func TestContainerExecCreateError(t *testing.T) { ) assert.NilError(t, err) - _, err = client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{}) + _, err = client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - _, err = client.ContainerExecCreate(context.Background(), "", container.ExecOptions{}) + _, err = client.ContainerExecCreate(context.Background(), "", ExecCreateOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - _, err = client.ContainerExecCreate(context.Background(), " ", container.ExecOptions{}) + _, err = client.ContainerExecCreate(context.Background(), " ", ExecCreateOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,7 +42,7 @@ func TestContainerExecCreateConnectionError(t *testing.T) { client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid")) assert.NilError(t, err) - _, err = client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{}) + _, err = client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{}) assert.Check(t, is.ErrorType(err, IsErrConnectionFailed)) } @@ -60,7 +60,7 @@ func TestContainerExecCreate(t *testing.T) { if err := req.ParseForm(); err != nil { return nil, err } - execConfig := &container.ExecOptions{} + execConfig := &container.ExecCreateRequest{} if err := json.NewDecoder(req.Body).Decode(execConfig); err != nil { return nil, err } @@ -81,7 +81,7 @@ func TestContainerExecCreate(t *testing.T) { ) assert.NilError(t, err) - r, err := client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{ + r, err := client.ContainerExecCreate(context.Background(), "container_id", ExecCreateOptions{ User: "user", }) assert.NilError(t, err) diff --git a/daemon/exec.go b/daemon/exec.go index 914cebf22b..09f0be6e6c 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -93,7 +93,7 @@ func (daemon *Daemon) getActiveContainer(name string) (*container.Container, err } // ContainerExecCreate sets up an exec in a running container. -func (daemon *Daemon) ContainerExecCreate(name string, options *containertypes.ExecOptions) (string, error) { +func (daemon *Daemon) ContainerExecCreate(name string, options *containertypes.ExecCreateRequest) (string, error) { cntr, err := daemon.getActiveContainer(name) if err != nil { return "", err diff --git a/daemon/server/router/container/backend.go b/daemon/server/router/container/backend.go index 4cb4b956fd..8318094f64 100644 --- a/daemon/server/router/container/backend.go +++ b/daemon/server/router/container/backend.go @@ -14,7 +14,7 @@ import ( // execBackend includes functions to implement to provide exec functionality. type execBackend interface { - ContainerExecCreate(name string, options *container.ExecOptions) (string, error) + ContainerExecCreate(name string, options *container.ExecCreateRequest) (string, error) ContainerExecInspect(id string) (*backend.ExecInspect, error) ContainerExecResize(ctx context.Context, name string, height, width uint32) error ContainerExecStart(ctx context.Context, name string, options backend.ExecStartConfig) error diff --git a/daemon/server/router/container/exec.go b/daemon/server/router/container/exec.go index 012397675d..b106c28910 100644 --- a/daemon/server/router/container/exec.go +++ b/daemon/server/router/container/exec.go @@ -39,7 +39,7 @@ func (c *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re return err } - execConfig := &container.ExecOptions{} + execConfig := &container.ExecCreateRequest{} if err := httputils.ReadJSON(r, execConfig); err != nil { return err } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index d691f2c13a..7af0cf804d 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -12,7 +12,6 @@ import ( "testing" "time" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" "github.com/moby/moby/v2/integration-cli/checker" "github.com/moby/moby/v2/integration-cli/cli" @@ -65,7 +64,7 @@ func (s *DockerAPISuite) TestExecAPICreateContainerPaused(c *testing.T) { assert.NilError(c, err) defer apiClient.Close() - _, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, container.ExecOptions{ + _, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, client.ExecCreateOptions{ Cmd: []string{"true"}, }) 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) { assert.NilError(c, err) defer apiClient.Close() - createResp, err := apiClient.ContainerExecCreate(ctx, name, container.ExecOptions{ + createResp, err := apiClient.ContainerExecCreate(ctx, name, client.ExecCreateOptions{ Cmd: []string{"true"}, AttachStderr: true, }) diff --git a/integration/config/config_test.go b/integration/config/config_test.go index 412f148732..98e8a74648 100644 --- a/integration/config/config_test.go +++ b/integration/config/config_test.go @@ -10,7 +10,6 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/pkg/stdcopy" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/filters" swarmtypes "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" @@ -312,7 +311,7 @@ func TestTemplatedConfig(t *testing.T) { tasks := swarm.GetRunningTasks(ctx, t, c, serviceID) assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID) - resp := swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{ + resp := swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ Cmd: []string{"/bin/cat", "/templated_config"}, AttachStdout: true, AttachStderr: true, @@ -327,7 +326,7 @@ func TestTemplatedConfig(t *testing.T) { outBuf.Reset() errBuf.Reset() - resp = swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{ + resp = swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ Cmd: []string{"mount"}, AttachStdout: true, AttachStderr: true, diff --git a/integration/container/exec_linux_test.go b/integration/container/exec_linux_test.go index b3264cca8b..2eb9c18298 100644 --- a/integration/container/exec_linux_test.go +++ b/integration/container/exec_linux_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - containertypes "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/versions" + "github.com/moby/moby/client" "github.com/moby/moby/v2/integration/internal/container" "gotest.tools/v3/assert" "gotest.tools/v3/skip" @@ -21,7 +21,7 @@ func TestExecConsoleSize(t *testing.T) { cID := container.Run(ctx, t, apiClient, container.WithImage("busybox")) result, err := container.Exec(ctx, apiClient, cID, []string{"stty", "size"}, - func(ec *containertypes.ExecOptions) { + func(ec *client.ExecCreateOptions) { ec.Tty = true ec.ConsoleSize = &[2]uint{57, 123} }, diff --git a/integration/container/exec_test.go b/integration/container/exec_test.go index e73a1c99e1..535c9cc822 100644 --- a/integration/container/exec_test.go +++ b/integration/container/exec_test.go @@ -12,7 +12,6 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/types/common" - containertypes "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" "github.com/moby/moby/v2/integration/internal/build" "github.com/moby/moby/v2/integration/internal/container" @@ -34,7 +33,7 @@ func TestExecWithCloseStdin(t *testing.T) { cID := container.Run(ctx, t, apiClient) const expected = "closeIO" - execResp, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{ + execResp, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{ AttachStdin: true, AttachStdout: true, Cmd: []string{"sh", "-c", "cat && echo " + expected}, @@ -89,7 +88,7 @@ func TestExec(t *testing.T) { cID := container.Run(ctx, t, apiClient, container.WithTty(true), container.WithWorkingDir("/root")) - id, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{ + id, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{ WorkingDir: "/tmp", Env: []string{"FOO=BAR"}, AttachStdout: true, @@ -127,7 +126,7 @@ func TestExecResize(t *testing.T) { if runtime.GOOS == "windows" { cmd = []string{"sleep", "240"} } - resp, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{ + resp, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{ 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 Cmd: cmd, }) @@ -296,8 +295,8 @@ func TestExecUser(t *testing.T) { withoutEtcGroups := container.WithImage(build.Do(ctx, t, apiClient, fakecontext.New(t, "", fakecontext.WithDockerfile("FROM busybox\nRUN rm /etc/group")))) withoutEtcPasswd := container.WithImage(build.Do(ctx, t, apiClient, fakecontext.New(t, "", fakecontext.WithDockerfile("FROM busybox\nRUN rm /etc/passwd")))) - withUser := func(user string) func(options *containertypes.ExecOptions) { - return func(options *containertypes.ExecOptions) { options.User = user } + withUser := func(user string) func(options *client.ExecCreateOptions) { + return func(options *client.ExecCreateOptions) { options.User = user } } tests := []struct { diff --git a/integration/internal/container/exec.go b/integration/internal/container/exec.go index b7438d9105..8377b7f8fa 100644 --- a/integration/internal/container/exec.go +++ b/integration/internal/container/exec.go @@ -5,7 +5,6 @@ import ( "context" "testing" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" ) @@ -47,9 +46,9 @@ func (res ExecResult) AssertSuccess(t testing.TB) { // containing stdout, stderr, and exit code. Note: // - this is a synchronous operation; // - cmd stdin is closed. -func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*container.ExecOptions)) (ExecResult, error) { +func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*client.ExecCreateOptions)) (ExecResult, error) { // prepare exec - execOptions := container.ExecOptions{ + execOptions := client.ExecCreateOptions{ AttachStdout: true, AttachStderr: true, Cmd: cmd, @@ -87,7 +86,7 @@ func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []stri } // ExecT calls Exec() and aborts the test if an error occurs. -func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*container.ExecOptions)) ExecResult { +func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*client.ExecCreateOptions)) ExecResult { t.Helper() res, err := Exec(ctx, apiClient, id, cmd, ops...) if err != nil { diff --git a/integration/internal/swarm/service.go b/integration/internal/swarm/service.go index d6470ecd20..9d7ad74fc7 100644 --- a/integration/internal/swarm/service.go +++ b/integration/internal/swarm/service.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/filters" swarmtypes "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" @@ -211,7 +210,7 @@ func GetRunningTasks(ctx context.Context, t *testing.T, c client.ServiceAPIClien } // ExecTask runs the passed in exec config on the given task -func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtypes.Task, options container.ExecOptions) client.HijackedResponse { +func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtypes.Task, options client.ExecCreateOptions) client.HijackedResponse { t.Helper() apiClient := d.NewClientT(t) defer apiClient.Close() diff --git a/integration/secret/secret_test.go b/integration/secret/secret_test.go index 6372af25eb..463767dcd0 100644 --- a/integration/secret/secret_test.go +++ b/integration/secret/secret_test.go @@ -10,7 +10,6 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/pkg/stdcopy" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/filters" swarmtypes "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" @@ -313,7 +312,7 @@ func TestTemplatedSecret(t *testing.T) { tasks := swarm.GetRunningTasks(ctx, t, c, serviceID) assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID) - resp := swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{ + resp := swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ Cmd: []string{"/bin/cat", "/run/secrets/templated_secret"}, AttachStdout: true, AttachStderr: true, @@ -328,7 +327,7 @@ func TestTemplatedSecret(t *testing.T) { outBuf.Reset() errBuf.Reset() - resp = swarm.ExecTask(ctx, t, d, tasks[0], container.ExecOptions{ + resp = swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ Cmd: []string{"mount"}, AttachStdout: true, AttachStderr: true, diff --git a/integration/system/event_test.go b/integration/system/event_test.go index 34b5f1a534..19cb6802d9 100644 --- a/integration/system/event_test.go +++ b/integration/system/event_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - containertypes "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/mount" @@ -28,7 +27,7 @@ func TestEventsExecDie(t *testing.T) { cID := container.Run(ctx, t, apiClient) - id, err := apiClient.ContainerExecCreate(ctx, cID, containertypes.ExecOptions{ + id, err := apiClient.ContainerExecCreate(ctx, cID, client.ExecCreateOptions{ Cmd: []string{"echo", "hello"}, }) assert.NilError(t, err) diff --git a/vendor/github.com/moby/moby/api/types/container/exec.go b/vendor/github.com/moby/moby/api/types/container/exec.go index 970eeece71..29c7b96268 100644 --- a/vendor/github.com/moby/moby/api/types/container/exec.go +++ b/vendor/github.com/moby/moby/api/types/container/exec.go @@ -8,22 +8,6 @@ import "github.com/moby/moby/api/types/common" // TODO(thaJeztah): make this a distinct type. type ExecCreateResponse = common.IDResponse -// ExecOptions is a small subset of the Config struct that holds the configuration -// for the exec feature of docker. -type ExecOptions struct { - User string // User that will run the command - Privileged bool // Is the container in privileged mode - Tty bool // Attach standard streams to a tty. - ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] - AttachStdin bool // Attach the standard input, makes possible user interaction - AttachStderr bool // Attach the standard error - AttachStdout bool // Attach the standard output - DetachKeys string // Escape keys for detach - Env []string // Environment variables - WorkingDir string // Working directory - Cmd []string // Execution commands and args -} - // ExecInspect holds information returned by exec inspect. // // It is used by the client to unmarshal a [ExecInspectResponse], diff --git a/vendor/github.com/moby/moby/api/types/container/exec_create_request.go b/vendor/github.com/moby/moby/api/types/container/exec_create_request.go new file mode 100644 index 0000000000..dd7437cd2f --- /dev/null +++ b/vendor/github.com/moby/moby/api/types/container/exec_create_request.go @@ -0,0 +1,17 @@ +package container + +// ExecCreateRequest is a small subset of the Config struct that holds the configuration +// for the exec feature of docker. +type ExecCreateRequest struct { + User string // User that will run the command + Privileged bool // Is the container in privileged mode + Tty bool // Attach standard streams to a tty. + ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStderr bool // Attach the standard error + AttachStdout bool // Attach the standard output + DetachKeys string // Escape keys for detach + Env []string // Environment variables + WorkingDir string // Working directory + Cmd []string // Execution commands and args +} diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 8107a7edde..a1059e68d3 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -70,7 +70,7 @@ type ContainerAPIClient interface { ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) ContainerExecAttach(ctx context.Context, execID string, options ExecAttachOptions) (HijackedResponse, error) - ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error) + ContainerExecCreate(ctx context.Context, container string, options ExecCreateOptions) (container.ExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecResize(ctx context.Context, execID string, options ContainerResizeOptions) error ContainerExecStart(ctx context.Context, execID string, options ExecStartOptions) error diff --git a/vendor/github.com/moby/moby/client/container_exec.go b/vendor/github.com/moby/moby/client/container_exec.go index 6ddb3c1246..d2fb4cba1b 100644 --- a/vendor/github.com/moby/moby/client/container_exec.go +++ b/vendor/github.com/moby/moby/client/container_exec.go @@ -9,8 +9,24 @@ import ( "github.com/moby/moby/api/types/versions" ) +// ExecCreateOptions is a small subset of the Config struct that holds the configuration +// for the exec feature of docker. +type ExecCreateOptions struct { + User string // User that will run the command + Privileged bool // Is the container in privileged mode + Tty bool // Attach standard streams to a tty. + ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStderr bool // Attach the standard error + AttachStdout bool // Attach the standard output + DetachKeys string // Escape keys for detach + Env []string // Environment variables + WorkingDir string // Working directory + Cmd []string // Execution commands and args +} + // ContainerExecCreate creates a new exec configuration to run an exec process. -func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) { +func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options ExecCreateOptions) (container.ExecCreateResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.ExecCreateResponse{}, err @@ -32,7 +48,21 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options.ConsoleSize = nil } - resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil) + req := container.ExecCreateRequest{ + User: options.User, + Privileged: options.Privileged, + Tty: options.Tty, + ConsoleSize: options.ConsoleSize, + AttachStdin: options.AttachStdin, + AttachStderr: options.AttachStderr, + AttachStdout: options.AttachStdout, + DetachKeys: options.DetachKeys, + Env: options.Env, + WorkingDir: options.WorkingDir, + Cmd: options.Cmd, + } + + resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil) defer ensureReaderClosed(resp) if err != nil { return container.ExecCreateResponse{}, err