Skip to content

Commit c161553

Browse files
authored
fix: Use '/exec/(id)/start' param type and the correct chunk size in the HTTP request (#18)
1 parent 80c8747 commit c161553

File tree

10 files changed

+49
-186
lines changed

10 files changed

+49
-186
lines changed

src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,24 @@ namespace Docker.DotNet.BasicAuth;
33
internal class BasicAuthHandler : DelegatingHandler
44
{
55
private readonly MaybeSecureString _username;
6+
67
private readonly MaybeSecureString _password;
78

8-
public BasicAuthHandler(MaybeSecureString username, MaybeSecureString password, HttpMessageHandler innerHandler) : base(innerHandler)
9+
public BasicAuthHandler(MaybeSecureString username, MaybeSecureString password, HttpMessageHandler httpMessageHandler)
10+
: base(httpMessageHandler)
911
{
1012
_username = username.Copy();
1113
_password = password.Copy();
1214
}
1315

14-
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
15-
{
16-
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", BuildParameters());
17-
18-
return base.SendAsync(request, cancellationToken);
19-
}
20-
21-
private string BuildParameters()
22-
{
23-
var authInfo = $"{_username}:{_password}";
24-
return Convert.ToBase64String(Encoding.UTF8.GetBytes(authInfo));
25-
}
26-
2716
protected override void Dispose(bool disposing)
2817
{
2918
if (disposing)
3019
{
3120
_username.Dispose();
3221
_password.Dispose();
3322
}
23+
24+
base.Dispose(disposing);
3425
}
3526
}

src/Docker.DotNet/Endpoints/ExecOperations.cs

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace Docker.DotNet;
22

33
internal class ExecOperations : IExecOperations
44
{
5-
internal static readonly ApiResponseErrorHandlingDelegate NoSuchContainerHandler = (statusCode, responseBody) =>
5+
private static readonly ApiResponseErrorHandlingDelegate NoSuchContainerHandler = (statusCode, responseBody) =>
66
{
77
if (statusCode == HttpStatusCode.NotFound)
88
{
@@ -17,33 +17,18 @@ internal ExecOperations(DockerClient client)
1717
_client = client;
1818
}
1919

20-
public async Task<ContainerExecCreateResponse> ExecCreateContainerAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
20+
public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default)
2121
{
2222
if (string.IsNullOrEmpty(id))
2323
{
2424
throw new ArgumentNullException(nameof(id));
2525
}
2626

27-
if (parameters == null)
28-
{
29-
throw new ArgumentNullException(nameof(parameters));
30-
}
31-
32-
var data = new JsonRequestContent<ContainerExecCreateParameters>(parameters, DockerClient.JsonSerializer);
33-
return await _client.MakeRequestAsync<ContainerExecCreateResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken).ConfigureAwait(false);
34-
}
35-
36-
public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken)
37-
{
38-
if (string.IsNullOrEmpty(id))
39-
{
40-
throw new ArgumentNullException(nameof(id));
41-
}
42-
43-
return await _client.MakeRequestAsync<ContainerExecInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"exec/{id}/json", null, cancellationToken).ConfigureAwait(false);
27+
return await _client.MakeRequestAsync<ContainerExecInspectResponse>([NoSuchContainerHandler], HttpMethod.Get, $"exec/{id}/json", null, cancellationToken)
28+
.ConfigureAwait(false);
4429
}
4530

46-
public Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken)
31+
public async Task<ContainerExecCreateResponse> CreateContainerExecAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default)
4732
{
4833
if (string.IsNullOrEmpty(id))
4934
{
@@ -55,43 +40,24 @@ public Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters par
5540
throw new ArgumentNullException(nameof(parameters));
5641
}
5742

58-
var queryParameters = new QueryString<ContainerResizeParameters>(parameters);
59-
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/resize", queryParameters, cancellationToken);
43+
var data = new JsonRequestContent<ContainerExecCreateParameters>(parameters, DockerClient.JsonSerializer);
44+
45+
return await _client.MakeRequestAsync<ContainerExecCreateResponse>([NoSuchContainerHandler], HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken)
46+
.ConfigureAwait(false);
6047
}
6148

62-
// StartContainerExecAsync will start the process specified by id in detach mode with no connected
63-
// stdin, stdout, or stderr pipes.
64-
public Task StartContainerExecAsync(string id, CancellationToken cancellationToken)
49+
public async Task<MultiplexedStream> StartContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken = default)
6550
{
6651
if (string.IsNullOrEmpty(id))
6752
{
6853
throw new ArgumentNullException(nameof(id));
6954
}
7055

71-
var parameters = new ContainerExecStartParameters
72-
{
73-
Detach = true,
74-
};
7556
var data = new JsonRequestContent<ContainerExecStartParameters>(parameters, DockerClient.JsonSerializer);
76-
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, cancellationToken);
77-
}
7857

79-
// StartAndAttachContainerExecAsync will start the process specified by id with stdin, stdout, stderr
80-
// connected, and optionally using terminal emulation if tty is true.
81-
public async Task<MultiplexedStream> StartAndAttachContainerExecAsync(string id, bool tty, CancellationToken cancellationToken)
82-
{
83-
return await StartWithConfigContainerExecAsync(id, new ContainerExecStartParameters() { AttachStdin = true, AttachStderr = true, AttachStdout = true, Tty = tty }, cancellationToken);
84-
}
58+
var result = await _client.MakeRequestForStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
59+
.ConfigureAwait(false);
8560

86-
public async Task<MultiplexedStream> StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken)
87-
{
88-
if (string.IsNullOrEmpty(id))
89-
{
90-
throw new ArgumentNullException(nameof(id));
91-
}
92-
93-
var data = new JsonRequestContent<ContainerExecStartParameters>(parameters, DockerClient.JsonSerializer);
94-
var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken).ConfigureAwait(false);
9561
return new MultiplexedStream(result, !parameters.Tty);
9662
}
9763
}

src/Docker.DotNet/Endpoints/IExecOperations.cs

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,9 @@ namespace Docker.DotNet;
22

33
public interface IExecOperations
44
{
5-
/// <summary>
6-
/// Create an exec instance.
7-
///
8-
/// Runs a command inside a running container.
9-
/// </summary>
10-
/// <remarks>
11-
/// docker exec
12-
/// docker container exec
13-
///
14-
/// 201 - No error.
15-
/// 404 - No such container.
16-
/// 409 - Container is paused.
17-
/// 500 - Server error.
18-
/// </remarks>
19-
/// <param name="id">ID or name of the container.</param>
20-
Task<ContainerExecCreateResponse> ExecCreateContainerAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
5+
Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default);
216

22-
/// <summary>
23-
/// Start an exec instance.
24-
///
25-
/// Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting
26-
/// the command. Otherwise, it sets up an interactive session with the command.
27-
/// </summary>
28-
/// <remarks>
29-
/// 204 - No error.
30-
/// 404 - No such exec instance.
31-
/// 500 - Server error.
32-
/// </remarks>
33-
/// <param name="id">Exec instance ID.</param>
34-
Task StartContainerExecAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
7+
Task<ContainerExecCreateResponse> CreateContainerExecAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default);
358

36-
Task<MultiplexedStream> StartAndAttachContainerExecAsync(string id, bool tty, CancellationToken cancellationToken = default(CancellationToken));
37-
38-
Task<MultiplexedStream> StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters eConfig, CancellationToken cancellationToken = default(CancellationToken));
39-
40-
/// <summary>
41-
/// Resize an exec instance.
42-
///
43-
/// Resize the TTY session used by an exec instance. This endpoint only works if {tty} was specified as part of
44-
/// creating and starting the exec instance.
45-
/// </summary>
46-
/// <remarks>
47-
/// 201 - No error.
48-
/// 404 - No such exec instance.
49-
/// </remarks>
50-
/// <param name="id">Exec instance ID.</param>
51-
Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
52-
53-
/// <summary>
54-
/// Inspect an exec instance.
55-
///
56-
/// Return low-level information about an exec instance.
57-
/// </summary>
58-
/// <remarks>
59-
/// docker inspect
60-
///
61-
/// 200 - No error.
62-
/// 404 - No such exec instance.
63-
/// 500 - Server error.
64-
/// </remarks>
65-
/// <param name="id">Exec instance ID.</param>
66-
Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
9+
Task<MultiplexedStream> StartContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken = default);
6710
}

src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,17 @@ public override long Position
5959

6060
protected override void Dispose(bool disposing)
6161
{
62-
// TODO: Why does disposing break the implementation, see the other chunked streams too.
63-
// base.Dispose(disposing);
64-
6562
if (disposing)
6663
{
67-
_inner.Dispose();
68-
6964
if (Interlocked.Decrement(ref _bufferRefCount) == 0)
7065
{
7166
ArrayPool<byte>.Shared.Return(_buffer);
7267
}
68+
69+
_inner.Dispose();
7370
}
71+
72+
base.Dispose(disposing);
7473
}
7574

7675
public override long Seek(long offset, SeekOrigin origin)

src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,6 @@ public override int WriteTimeout
6666
}
6767
}
6868

69-
protected override void Dispose(bool disposing)
70-
{
71-
// base.Dispose(disposing);
72-
73-
if (disposing)
74-
{
75-
// _inner.Dispose();
76-
}
77-
}
78-
7969
public override int Read(byte[] buffer, int offset, int count)
8070
{
8171
throw new NotSupportedException();

src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace Microsoft.Net.Http.Client;
22

33
internal sealed class ChunkedWriteStream : Stream
44
{
5-
private static readonly byte[] s_EndContentBytes = Encoding.ASCII.GetBytes("0\r\n\r\n");
5+
private static readonly byte[] EndOfContentBytes = Encoding.ASCII.GetBytes("0\r\n\r\n");
66

77
private readonly Stream _inner;
88

@@ -28,16 +28,6 @@ public override long Position
2828
set { throw new NotImplementedException(); }
2929
}
3030

31-
protected override void Dispose(bool disposing)
32-
{
33-
// base.Dispose(disposing);
34-
35-
if (disposing)
36-
{
37-
// _inner.Dispose();
38-
}
39-
}
40-
4131
public override void Flush()
4232
{
4333
_inner.Flush();
@@ -75,14 +65,26 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
7565
return;
7666
}
7767

78-
var chunkSize = Encoding.ASCII.GetBytes(count.ToString("x") + "\r\n");
79-
await _inner.WriteAsync(chunkSize, 0, chunkSize.Length, cancellationToken).ConfigureAwait(false);
80-
await _inner.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
81-
await _inner.WriteAsync(chunkSize, chunkSize.Length - 2, 2, cancellationToken).ConfigureAwait(false);
68+
const string crlf = "\r\n";
69+
70+
var chunkHeader = count.ToString("X") + crlf;
71+
var headerBytes = Encoding.ASCII.GetBytes(chunkHeader);
72+
73+
// Write the chunk header
74+
await _inner.WriteAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
75+
.ConfigureAwait(false);
76+
77+
// Write the chunk data
78+
await _inner.WriteAsync(buffer, offset, count, cancellationToken)
79+
.ConfigureAwait(false);
80+
81+
// Write the chunk footer (CRLF)
82+
await _inner.WriteAsync(headerBytes, headerBytes.Length - 2, 2, cancellationToken)
83+
.ConfigureAwait(false);
8284
}
8385

8486
public Task EndContentAsync(CancellationToken cancellationToken)
8587
{
86-
return _inner.WriteAsync(s_EndContentBytes, 0, s_EndContentBytes.Length, cancellationToken);
88+
return _inner.WriteAsync(EndOfContentBytes, 0, EndOfContentBytes.Length, cancellationToken);
8789
}
8890
}
Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,16 @@
1-
using System.Collections.Generic;
21
using System.Text.Json.Serialization;
32

43
namespace Docker.DotNet.Models
54
{
65
public class ContainerExecStartParameters // (main.ContainerExecStartParameters)
76
{
8-
[JsonPropertyName("User")]
9-
public string User { get; set; }
10-
11-
[JsonPropertyName("Privileged")]
12-
public bool Privileged { get; set; }
7+
[JsonPropertyName("Detach")]
8+
public bool Detach { get; set; }
139

1410
[JsonPropertyName("Tty")]
1511
public bool Tty { get; set; }
1612

1713
[JsonPropertyName("ConsoleSize")]
1814
public ulong[] ConsoleSize { get; set; }
19-
20-
[JsonPropertyName("AttachStdin")]
21-
public bool AttachStdin { get; set; }
22-
23-
[JsonPropertyName("AttachStderr")]
24-
public bool AttachStderr { get; set; }
25-
26-
[JsonPropertyName("AttachStdout")]
27-
public bool AttachStdout { get; set; }
28-
29-
[JsonPropertyName("Detach")]
30-
public bool Detach { get; set; }
31-
32-
[JsonPropertyName("DetachKeys")]
33-
public string DetachKeys { get; set; }
34-
35-
[JsonPropertyName("Env")]
36-
public IList<string> Env { get; set; }
37-
38-
[JsonPropertyName("WorkingDir")]
39-
public string WorkingDir { get; set; }
40-
41-
[JsonPropertyName("Cmd")]
42-
public IList<string> Cmd { get; set; }
4315
}
4416
}

test/Docker.DotNet.Tests/IContainerOperationsTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,8 @@ public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException()
728728
_ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters());
729729

730730
// When
731-
var containerExecCreateResponse = await _testFixture.DockerClient.Exec.ExecCreateContainerAsync(createContainerResponse.ID, containerExecCreateParameters);
732-
using var stream = await _testFixture.DockerClient.Exec.StartWithConfigContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);
731+
var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters);
732+
using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);
733733

734734
var buffer = new byte[] { 10 };
735735
var exception = await Record.ExceptionAsync(() => stream.WriteAsync(buffer, 0, buffer.Length, _testFixture.Cts.Token));

test/Docker.DotNet.Tests/ISystemOperations.Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ await _testFixture.DockerClient.Images.DeleteImageAsync(
9494

9595
await cts.CancelAsync();
9696

97-
await Assert.ThrowsAsync<OperationCanceledException>(() => task);
97+
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => task);
9898

9999
Assert.True(wasProgressCalled);
100100
}
@@ -239,7 +239,7 @@ await _testFixture.DockerClient.Images.TagImageAsync(
239239
await Task.Delay(TimeSpan.FromSeconds(1));
240240
await cts.CancelAsync();
241241

242-
await Assert.ThrowsAsync<OperationCanceledException>(() => task);
242+
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => task);
243243

244244
Assert.Equal(2, progressCalledCounter);
245245
Assert.True(task.IsCanceled);

tools/specgen/modeldefs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ type ContainerExecCreateParameters container.ExecOptions
188188
type ContainerExecCreateResponse types.IDResponse
189189

190190
// ContainerExecStartParameters for POST /exec/(id)/start
191-
type ContainerExecStartParameters container.ExecOptions
191+
type ContainerExecStartParameters container.ExecStartOptions
192192

193193
// ImagesCreateParameters for POST /images/create
194194
type ImagesCreateParameters struct {

0 commit comments

Comments
 (0)