From e2ac8a424578def7d593b552d67b8c81dc8131e2 Mon Sep 17 00:00:00 2001 From: j4587698 Date: Sat, 28 Feb 2026 01:18:01 +0000 Subject: [PATCH 1/2] feat: Add ResizeExecTtyAsync to IExecOperations Add the missing exec TTY resize API (POST /exec/{id}/resize) to IExecOperations and ExecOperations, mirroring the existing ResizeContainerTtyAsync in ContainerOperations. Docker Engine API supports resizing exec instances via POST /exec/{id}/resize?h={height}&w={width}, but this endpoint was not exposed in the SDK. This makes it impossible to properly resize terminal sessions created via exec without resorting to raw HTTP requests or workarounds like stty commands. --- src/Docker.DotNet/Endpoints/ExecOperations.cs | 16 ++++++++++++++++ src/Docker.DotNet/Endpoints/IExecOperations.cs | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/Docker.DotNet/Endpoints/ExecOperations.cs b/src/Docker.DotNet/Endpoints/ExecOperations.cs index 221ff6e2..55d5e854 100644 --- a/src/Docker.DotNet/Endpoints/ExecOperations.cs +++ b/src/Docker.DotNet/Endpoints/ExecOperations.cs @@ -60,4 +60,20 @@ public async Task StartContainerExecAsync(string id, Containe return new MultiplexedStream(response, !parameters.TTY); } + + public Task ResizeExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + var queryParameters = new QueryString(parameters); + return _client.MakeRequestAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/resize", queryParameters, cancellationToken); + } } \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/IExecOperations.cs b/src/Docker.DotNet/Endpoints/IExecOperations.cs index db9824a6..dc747757 100644 --- a/src/Docker.DotNet/Endpoints/IExecOperations.cs +++ b/src/Docker.DotNet/Endpoints/IExecOperations.cs @@ -7,4 +7,6 @@ public interface IExecOperations Task CreateContainerExecAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default); Task StartContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken = default); + + Task ResizeExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default); } \ No newline at end of file From e960ce7adcee436cda7eb25c6c2187d3492c643f Mon Sep 17 00:00:00 2001 From: j4587698 Date: Sat, 28 Feb 2026 01:33:22 +0000 Subject: [PATCH 2/2] test: Add ResizeExecTtyAsync integration tests --- .../IExecOperationsTests.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/Docker.DotNet.Tests/IExecOperationsTests.cs diff --git a/test/Docker.DotNet.Tests/IExecOperationsTests.cs b/test/Docker.DotNet.Tests/IExecOperationsTests.cs new file mode 100644 index 00000000..f2da1f12 --- /dev/null +++ b/test/Docker.DotNet.Tests/IExecOperationsTests.cs @@ -0,0 +1,93 @@ +namespace Docker.DotNet.Tests; + +[Collection(nameof(TestCollection))] +public class IExecOperationsTests +{ + private readonly TestFixture _testFixture; + private readonly ITestOutputHelper _testOutputHelper; + + public IExecOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper) + { + _testFixture = testFixture; + _testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task ResizeExecTtyAsync_RunningExec_Succeeds() + { + // Given: a running container with a TTY exec session + var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( + new CreateContainerParameters + { + Image = _testFixture.Image.ID, + Entrypoint = CommonCommands.SleepInfinity + }, + _testFixture.Cts.Token + ); + + await _testFixture.DockerClient.Containers.StartContainerAsync( + createContainerResponse.ID, + new ContainerStartParameters(), + _testFixture.Cts.Token + ); + + var execCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync( + createContainerResponse.ID, + new ContainerExecCreateParameters + { + AttachStdout = true, + AttachStderr = true, + AttachStdin = true, + TTY = true, + Cmd = ["/bin/sh"] + }, + _testFixture.Cts.Token + ); + + // Start the exec to make it running (resize only works on a running exec) + using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync( + execCreateResponse.ID, + new ContainerExecStartParameters { TTY = true }, + _testFixture.Cts.Token + ); + + // When: resize the exec TTY + await _testFixture.DockerClient.Exec.ResizeExecTtyAsync( + execCreateResponse.ID, + new ContainerResizeParameters + { + Height = 40, + Width = 120 + }, + _testFixture.Cts.Token + ); + + // Then: no exception means success + _testOutputHelper.WriteLine("ResizeExecTtyAsync succeeded for running exec session."); + + // Verify exec is still running + var execInspect = await _testFixture.DockerClient.Exec.InspectContainerExecAsync( + execCreateResponse.ID, + _testFixture.Cts.Token + ); + + Assert.True(execInspect.Running); + } + + [Fact] + public async Task ResizeExecTtyAsync_NonExistentExecId_ThrowsException() + { + // When/Then: resizing a non-existent exec ID should throw + await Assert.ThrowsAsync( + () => _testFixture.DockerClient.Exec.ResizeExecTtyAsync( + Guid.NewGuid().ToString("N"), + new ContainerResizeParameters + { + Height = 24, + Width = 80 + }, + _testFixture.Cts.Token + ) + ); + } +}