diff --git a/src/Docker.DotNet/Docker.DotNet.csproj b/src/Docker.DotNet/Docker.DotNet.csproj
index e58096c1..97a59fa4 100644
--- a/src/Docker.DotNet/Docker.DotNet.csproj
+++ b/src/Docker.DotNet/Docker.DotNet.csproj
@@ -11,7 +11,6 @@
-
diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs
index f0e67b6c..82e67343 100644
--- a/src/Docker.DotNet/DockerClient.cs
+++ b/src/Docker.DotNet/DockerClient.cs
@@ -380,6 +380,20 @@ internal async Task MakeRequestForHijackedStreamAsync(
TimeSpan timeout,
CancellationToken cancellationToken)
{
+ // The Docker Engine API docs sounds like these headers are optional, but if they
+ // aren't include in the request, the daemon doesn't set up the raw stream
+ // correctly. Either the headers are always required, or they're necessary
+ // specifically in Docker Desktop environments because of some internal communication
+ // (using a proxy).
+
+ if (headers == null)
+ {
+ headers = new Dictionary();
+ }
+
+ headers.Add("Connection", "tcp");
+ headers.Add("Upgrade", "Upgrade");
+
var response = await PrivateMakeRequestAsync(timeout, HttpCompletionOption.ResponseHeadersRead, method, path, queryString, headers, body, cancellationToken)
.ConfigureAwait(false);
@@ -455,7 +469,7 @@ private HttpRequestMessage PrepareRequest(HttpMethod method, string path, IQuery
private async Task HandleIfErrorResponseAsync(HttpStatusCode statusCode, HttpResponseMessage response, IEnumerable handlers)
{
- var isErrorResponse = statusCode < HttpStatusCode.OK || statusCode >= HttpStatusCode.BadRequest;
+ var isErrorResponse = (statusCode < HttpStatusCode.OK || statusCode >= HttpStatusCode.BadRequest) && statusCode != HttpStatusCode.SwitchingProtocols;
string responseBody = null;
diff --git a/src/Docker.DotNet/DockerPipeStream.cs b/src/Docker.DotNet/DockerPipeStream.cs
index 83398598..0221849d 100644
--- a/src/Docker.DotNet/DockerPipeStream.cs
+++ b/src/Docker.DotNet/DockerPipeStream.cs
@@ -2,8 +2,8 @@ namespace Docker.DotNet;
internal class DockerPipeStream : WriteClosableStream, IPeekableStream
{
- private readonly PipeStream _stream;
private readonly EventWaitHandle _event = new EventWaitHandle(false, EventResetMode.AutoReset);
+ private readonly PipeStream _stream;
public DockerPipeStream(PipeStream stream)
{
@@ -26,7 +26,6 @@ public override long Length
public override long Position
{
get { throw new NotImplementedException(); }
-
set { throw new NotImplementedException(); }
}
@@ -37,7 +36,7 @@ public override long Position
private static extern int GetOverlappedResult(SafeHandle handle, ref NativeOverlapped overlapped, out int numBytesWritten, int wait);
[DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool PeekNamedPipe(SafeHandle handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, ref uint bytesAvail, ref uint BytesLeftThisMessage);
+ private static extern bool PeekNamedPipe(SafeHandle handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, ref uint bytesAvail, ref uint bytesLeftThisMessage);
public override void CloseWrite()
{
@@ -45,11 +44,7 @@ public override void CloseWrite()
// calls to achieve this since CoreCLR ignores a zero-byte write.
var overlapped = new NativeOverlapped();
-#if NET45
- var handle = _event.SafeWaitHandle;
-#else
var handle = _event.GetSafeWaitHandle();
-#endif
// Set the low bit to tell Windows not to send the result of this IO to the
// completion port.
diff --git a/src/Docker.DotNet/Endpoints/ContainerOperations.cs b/src/Docker.DotNet/Endpoints/ContainerOperations.cs
index 3239a5c3..735ab0e1 100644
--- a/src/Docker.DotNet/Endpoints/ContainerOperations.cs
+++ b/src/Docker.DotNet/Endpoints/ContainerOperations.cs
@@ -25,7 +25,7 @@ internal ContainerOperations(DockerClient client)
_client = client;
}
- public async Task> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default)
{
if (parameters == null)
{
@@ -36,7 +36,7 @@ internal ContainerOperations(DockerClient client)
return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Get, "containers/json", queryParameters, cancellationToken).ConfigureAwait(false);
}
- public async Task CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default)
{
IQueryString qs = null;
@@ -54,7 +54,7 @@ internal ContainerOperations(DockerClient client)
return await _client.MakeRequestAsync(new[] { NoSuchImageHandler }, HttpMethod.Post, "containers/create", qs, data, cancellationToken).ConfigureAwait(false);
}
- public async Task InspectContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task InspectContainerAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -64,7 +64,7 @@ internal ContainerOperations(DockerClient client)
return await _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", cancellationToken).ConfigureAwait(false);
}
- public async Task InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -80,7 +80,7 @@ internal ContainerOperations(DockerClient client)
return await _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", queryString, cancellationToken).ConfigureAwait(false);
}
- public async Task ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -96,32 +96,16 @@ internal ContainerOperations(DockerClient client)
return await _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/top", queryParameters, cancellationToken).ConfigureAwait(false);
}
- public Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, IProgress progress, CancellationToken cancellationToken = default)
{
- if (string.IsNullOrEmpty(id))
- {
- throw new ArgumentNullException(nameof(id));
- }
-
- if (parameters == null)
- {
- throw new ArgumentNullException(nameof(parameters));
- }
+ using var multiplexedStream = await GetContainerLogsAsync(id, parameters, cancellationToken)
+ .ConfigureAwait(false);
- IQueryString queryParameters = new QueryString(parameters);
- return _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, cancellationToken);
+ await StreamUtil.MonitorStreamAsync(multiplexedStream, progress, cancellationToken)
+ .ConfigureAwait(false);
}
- public Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken, IProgress progress)
- {
- return StreamUtil.MonitorStreamAsync(
- GetContainerLogsAsync(id, parameters, cancellationToken),
- _client,
- cancellationToken,
- progress);
- }
-
- public async Task GetContainerLogsAsync(string id, bool tty, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -133,14 +117,18 @@ public Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters,
throw new ArgumentNullException(nameof(parameters));
}
- IQueryString queryParameters = new QueryString(parameters);
+ var queryParameters = new QueryString(parameters);
- Stream result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, cancellationToken).ConfigureAwait(false);
+ var containerInspectResponse = await InspectContainerAsync(id, cancellationToken)
+ .ConfigureAwait(false);
- return new MultiplexedStream(result, !tty);
+ var stream = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, null, null, cancellationToken)
+ .ConfigureAwait(false);
+
+ return new MultiplexedStream(stream, !containerInspectResponse.Config.Tty);
}
- public async Task> InspectChangesAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task> InspectChangesAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -176,7 +164,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
return _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/stats", queryParameters, null, null, cancellationToken);
}
- public Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress progress, CancellationToken cancellationToken = default(CancellationToken))
+ public Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress progress, CancellationToken cancellationToken = default)
{
return StreamUtil.MonitorStreamForMessagesAsync(
GetContainerStatsAsync(id, parameters, cancellationToken),
@@ -185,7 +173,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
progress);
}
- public Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -201,7 +189,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/resize", queryParameters, cancellationToken);
}
- public async Task StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -214,7 +202,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
return result ?? throw new InvalidOperationException();
}
- public async Task StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -234,7 +222,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
return result ?? throw new InvalidOperationException();
}
- public Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -253,7 +241,7 @@ public Task GetContainerStatsAsync(string id, ContainerStatsParameters p
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/restart", queryParameters, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken);
}
- public Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -280,7 +268,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/rename", queryParameters, cancellationToken);
}
- public Task PauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
+ public Task PauseContainerAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -290,7 +278,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/pause", cancellationToken);
}
- public Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
+ public Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -300,7 +288,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/unpause", cancellationToken);
}
- public async Task AttachContainerAsync(string id, bool tty, ContainerAttachParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task AttachContainerAsync(string id, ContainerAttachParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -313,11 +301,17 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
}
var queryParameters = new QueryString(parameters);
- var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken).ConfigureAwait(false);
- return new MultiplexedStream(result, !tty);
+
+ var containerInspectResponse = await InspectContainerAsync(id, cancellationToken)
+ .ConfigureAwait(false);
+
+ var stream = await _client.MakeRequestForHijackedStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken)
+ .ConfigureAwait(false);
+
+ return new MultiplexedStream(stream, !containerInspectResponse.Config.Tty);
}
- public async Task WaitContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task WaitContainerAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -327,7 +321,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
return await _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/wait", null, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
}
- public Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -343,7 +337,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Delete, $"containers/{id}", queryParameters, cancellationToken);
}
- public async Task GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -372,7 +366,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
};
}
- public Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
+ public Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
@@ -396,7 +390,7 @@ public async Task PruneContainersAsync(ContainersPruneP
return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Post, "containers/prune", queryParameters, cancellationToken).ConfigureAwait(false);
}
- public async Task UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
diff --git a/src/Docker.DotNet/Endpoints/ExecOperations.cs b/src/Docker.DotNet/Endpoints/ExecOperations.cs
index 7fd70461..db961bc3 100644
--- a/src/Docker.DotNet/Endpoints/ExecOperations.cs
+++ b/src/Docker.DotNet/Endpoints/ExecOperations.cs
@@ -55,9 +55,9 @@ public async Task StartContainerExecAsync(string id, Containe
var data = new JsonRequestContent(parameters, DockerClient.JsonSerializer);
- var result = await _client.MakeRequestForStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
+ var stream = await _client.MakeRequestForHijackedStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
.ConfigureAwait(false);
- return new MultiplexedStream(result, !parameters.Tty);
+ return new MultiplexedStream(stream, !parameters.Tty);
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Endpoints/IContainerOperations.cs b/src/Docker.DotNet/Endpoints/IContainerOperations.cs
index cb21bc3f..90e4f633 100644
--- a/src/Docker.DotNet/Endpoints/IContainerOperations.cs
+++ b/src/Docker.DotNet/Endpoints/IContainerOperations.cs
@@ -15,7 +15,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default);
///
/// Creates a new container from an image.
@@ -28,7 +28,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid, there was a conflict with another container, or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default);
///
/// Retrieves low-level information about a container.
@@ -41,7 +41,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task InspectContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task InspectContainerAsync(string id, CancellationToken cancellationToken = default);
///
/// Retrieves low-level information about a container with additional options.
@@ -55,7 +55,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default);
///
/// Retrieves a list of processes running within the container.
@@ -70,57 +70,44 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default);
///
- /// Gets stdout and stderr logs from a container created with a TTY.
+ /// Gets stdout and stderr logs from a container.
///
+ ///
+ /// The corresponding commands in the Docker CLI are docker inspect and docker container inspect.
+ ///
/// The ID or name of the container.
/// Specifics of how to perform the operation.
+ /// Provides a callback for reporting log entries as they're read. Every reported string represents one log line, with its terminating newline removed.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a , which provides the log information.
- /// This method works only for containers with the json-file or journald logging driver.
- ///
The corresponding commands in the Docker CLI are docker logs and docker container logs.
- /// No such container was found.
- /// One or more of the inputs was .
+ ///
+ /// A that will complete once all log lines have been read, or once the container has exited if Follow is set to .
+ ///
+ /// One or more of the inputs were .
+ /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.
/// The input is invalid or the daemon experienced an error.
- /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- [Obsolete("The stream returned by this method won't be demultiplexed properly if the container was created without a TTY. Use GetContainerLogsAsync(string, bool, ContainerLogsParameters, CancellationToken) instead")]
- Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
-
- ///
- /// Gets stdout and stderr logs from a container that was created with a TTY.
- ///
- /// The ID or name of the container.
- /// Specifics of how to perform the operation.
- /// When triggered, the operation will stop at the next available time, if possible.
- /// Provides a callback for reporting log entries as they're read. Every reported string represents one log line, with its terminating newline removed.
- /// A that will complete once all log lines have been read, or once the container has exited if Follow is set to .
- /// This method is only suited for containers created with a TTY. For containers created without a TTY, use
- /// instead.
- ///
- /// The corresponding commands in the Docker CLI are docker inspect and docker container inspect.
/// No such container was found.
- /// One or more of the inputs was .
- /// The input is invalid or the daemon experienced an error.
- /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken, IProgress progress);
+ Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, IProgress progress, CancellationToken cancellationToken = default);
///
/// Gets stdout and stderr logs from a container.
///
+ ///
+ /// The corresponding commands in the Docker CLI are docker inspect and docker container inspect.
+ ///
/// The ID or name of the container.
- /// Indicates whether the container was created with a TTY. If , the returned stream is multiplexed.
/// Specifics of how to perform the operation.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a , which provides the log information.
- /// If the container wasn't created with a TTY, this stream is multiplexed.
- /// The corresponding commands in the Docker CLI are docker inspect and docker container inspect.
- /// No such container was found.
- /// One or more of the inputs was .
+ ///
+ /// A that resolves to a , which provides the log information.
+ ///
+ /// One or more of the inputs were .
+ /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.
/// The input is invalid or the daemon experienced an error.
- /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task GetContainerLogsAsync(string id, bool tty, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ /// No such container was found.
+ Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default);
///
/// Reports which files in a container's filesystem have been added, deleted, or modified.
@@ -132,7 +119,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task> InspectChangesAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task> InspectChangesAsync(string id, CancellationToken cancellationToken = default);
///
/// Exports the contents of a container as a tarball.
@@ -145,7 +132,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task ExportContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task ExportContainerAsync(string id, CancellationToken cancellationToken = default);
///
/// Retrieves a live, raw stream of the container's resource usage statistics.
@@ -153,7 +140,7 @@ public interface IContainerOperations
/// The ID or name of the container.
/// Specifics of how to perform the operation.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a , which can be used to read the frames of statistics. For details
+ /// A that resolves to a , which can be used to read the frames of statistics. For details
/// on the format, refer to the Docker Engine API documentation.
///
/// The corresponding commands in the Docker CLI are docker stats and docker container stats.
@@ -177,7 +164,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress progress, CancellationToken cancellationToken = default(CancellationToken));
+ Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress progress, CancellationToken cancellationToken = default);
///
/// Resizes a container's TTY.
@@ -192,7 +179,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default);
///
/// Starts a container.
@@ -209,7 +196,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default);
///
/// Stops a container.
@@ -227,7 +214,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default);
///
/// Stops and then restarts a container.
@@ -241,7 +228,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default);
///
/// Sends a POSIX signal to a container--typically to kill it.
@@ -257,7 +244,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The container was not running, the input is invalid, or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default);
///
/// Changes the name of a container.
@@ -279,7 +266,7 @@ public interface IContainerOperations
/// When triggered, the operation will stop at the next available time, if possible.
/// A that resolves when the operation is complete.
///
- /// This uses the freeze cgroup to suspend all processes in the container. The processes are unaware that they are being
+ /// This uses the freeze cgroup to suspend all processes in the container. The processes are unaware that they are being
/// suspended (e.g., they cannot capture a SIGSTOP signal).
///
/// The corresponding commands in the Docker CLI are docker pause and docker container pause.
@@ -288,7 +275,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task PauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task PauseContainerAsync(string id, CancellationToken cancellationToken = default);
///
/// Resumes a container that was suspended.
@@ -302,44 +289,40 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default);
///
/// Attaches to a container to read its output and send it input.
///
+ ///
+ /// The corresponding commands in the Docker CLI are docker attach and docker container attach.
+ ///
/// The ID or name of the container.
- /// Indicates whether the stream is a TTY stream. When , stdout and stderr are
- /// combined into a single, undifferentiated stream. When , the stream is multiplexed.
/// Specifics of how to perform the operation.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a , which contains the
- /// container's stdout and stderr content and which can be used to write to the container's stdin.
- /// The format of the stream various, in part by the parameter's value. See the
- /// Docker Engine API reference for details on
- /// the format.
- /// The corresponding commands in the Docker CLI are docker attach and docker container attach.
- /// No such container was found.
- /// One or more of the inputs was .
+ ///
+ /// A that resolves to a , which contains the container's
+ /// stdout and stderr content and which can be used to write to the container's stdin.
+ ///
+ /// One or more of the inputs were .
+ /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout.
/// The input is invalid or the daemon experienced an error.
- /// The transport is unsuitable for the operation.
- /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task AttachContainerAsync(string id, bool tty, ContainerAttachParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
-
- // TODO: Attach Web Socket
+ /// No such container was found.
+ Task AttachContainerAsync(string id, ContainerAttachParameters parameters, CancellationToken cancellationToken = default);
///
/// Waits for a container to stop.
///
/// The ID or name of the container.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a when the container has
+ /// A that resolves to a when the container has
/// stopped.
/// The corresponding commands in the Docker CLI are docker wait and docker container wait.
/// No such container was found.
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task WaitContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
+ Task WaitContainerAsync(string id, CancellationToken cancellationToken = default);
///
/// Deletes a container.
@@ -353,7 +336,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// There is a conflict, the input is invalid, or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default);
///
/// Gets information about the filesystem in a container. This may be either a listing of files or a complete
@@ -361,7 +344,7 @@ public interface IContainerOperations
///
/// The ID or name of the container.
/// Specifics of how to perform the operation.
- /// If , the method will only return file information; otherwise, it will return a
+ /// If , the method will only return file information; otherwise, it will return a
/// stream of the filesystem as a tarball.
/// When triggered, the operation will stop at the next available time, if possible.
/// A that resolves to a , which holds
@@ -370,7 +353,7 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default(CancellationToken));
+ Task GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default);
///
/// Extracts a tar archive into a container's filesystem.
@@ -382,23 +365,23 @@ public interface IContainerOperations
/// A that resolves when the operation completes.
/// No such container was found, or the path does not exist inside the container.
/// One or more of the inputs was .
- /// Permission is denied (the volume or container rootfs is marked read-only),
+ /// Permission is denied (the volume or container rootfs is marked read-only),
/// the input is invalid, or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default(CancellationToken));
+ Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default);
///
/// Deletes stopped containers.
///
/// Specifics of how to perform the operation.
/// When triggered, the operation will stop at the next available time, if possible.
- /// A that resolves to a , which details which containers
+ /// A that resolves to a , which details which containers
/// were removed.
/// The corresponding command in the Docker CLI is docker container prune.
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task PruneContainersAsync(ContainersPruneParameters parameters = null, CancellationToken cancellationToken = default(CancellationToken));
+ Task PruneContainersAsync(ContainersPruneParameters parameters = null, CancellationToken cancellationToken = default);
///
/// Changes configuration options of a container without recreating it.
@@ -413,5 +396,5 @@ public interface IContainerOperations
/// One or more of the inputs was .
/// The input is invalid or the daemon experienced an error.
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
- Task UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
+ Task UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Endpoints/StreamUtil.cs b/src/Docker.DotNet/Endpoints/StreamUtil.cs
index 8efe8799..3e6feab0 100644
--- a/src/Docker.DotNet/Endpoints/StreamUtil.cs
+++ b/src/Docker.DotNet/Endpoints/StreamUtil.cs
@@ -2,19 +2,65 @@ namespace Docker.DotNet.Models;
internal static class StreamUtil
{
- internal static async Task MonitorStreamAsync(Task streamTask, DockerClient client, CancellationToken cancellationToken, IProgress progress)
+ internal static async Task MonitorStreamAsync(MultiplexedStream multiplexedStream, IProgress progress, CancellationToken cancellationToken = default)
{
- var tcs = new TaskCompletionSource();
+ var buffer = new byte[8192];
- using (var stream = await streamTask)
- using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
- using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
+ using var memoryStream = new MemoryStream();
+
+ using var streamReader = new StreamReader(memoryStream, new UTF8Encoding(false), false, buffer.Length, true);
+
+ while (true)
{
- string line;
- while ((line = await await Task.WhenAny(reader.ReadLineAsync(), tcs.Task)) != null)
+ // TODO:
+ // Make sure that split multi-byte characters across read operations don't
+ // cause exceptions or get misinterpreted.
+
+ var bytesRead = await multiplexedStream.ReadOutputAsync(buffer, 0, buffer.Length, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (bytesRead.Count == 0)
+ {
+ break;
+ }
+
+ await memoryStream.WriteAsync(buffer, 0, bytesRead.Count, cancellationToken)
+ .ConfigureAwait(false);
+
+ // Read each line from the stream. If no complete line is available, it may be because
+ // the line is incomplete and the remaining bytes will be read in the next operation.
+ // We need to retain any leftover bytes to process them in the next iteration.
+
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ long lastReadPosition;
+
+ while (true)
{
- progress.Report(line);
+ var line = await streamReader.ReadLineAsync()
+ .ConfigureAwait(false);
+
+ if (line == null)
+ {
+ lastReadPosition = memoryStream.Position;
+ break;
+ }
+ else
+ {
+ progress.Report(line);
+ }
}
+
+ var remainingBytes = memoryStream.Length - lastReadPosition;
+
+ if (remainingBytes > 0)
+ {
+ var internalBuffer = memoryStream.GetBuffer();
+ Array.Copy(internalBuffer, lastReadPosition, internalBuffer, 0, remainingBytes);
+ }
+
+ memoryStream.Position = remainingBytes;
+ memoryStream.SetLength(remainingBytes);
}
}
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
index 56ecc36a..0c766b42 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
@@ -61,7 +61,7 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
- if (Interlocked.Decrement(ref _bufferRefCount) == 0)
+ if (Interlocked.Exchange(ref _bufferRefCount, 0) == 1)
{
ArrayPool.Shared.Return(_buffer);
}
@@ -159,91 +159,45 @@ public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available
public async Task ReadLineAsync(CancellationToken cancellationToken)
{
- const char nullChar = '\0';
+ var line = new StringBuilder(_buffer.Length);
- const char cr = '\r';
+ var crIndex = -1;
- const char lf = '\n';
+ var lfIndex = -1;
- if (_bufferCount == 0)
- {
- var bufferInUse = Interlocked.Increment(ref _bufferRefCount) > 1;
-
- try
- {
- if (bufferInUse)
- {
- _bufferOffset = 0;
+ bool crlfFound;
- _bufferCount = await _inner.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken)
- .ConfigureAwait(false);
- }
- }
- catch (Exception e)
- {
- _logger.LogCritical(e, "Failed to read from buffer.");
- throw;
- }
- finally
+ do
+ {
+ if (_bufferCount == 0)
{
- var bufferReleased = Interlocked.Decrement(ref _bufferRefCount) == 0;
+ _bufferOffset = 0;
- if (bufferInUse && bufferReleased)
- {
- ArrayPool.Shared.Return(_buffer);
- }
+ _bufferCount = await _inner.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken)
+ .ConfigureAwait(false);
}
- }
-
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- var content = Encoding.ASCII.GetString(_buffer.TakeWhile(value => value != nullChar).ToArray());
- content = content.Replace("\r", "");
- content = content.Replace("\n", "");
- _logger.LogDebug("Raw buffer content: '{Content}'.", content);
- }
-
- var start = _bufferOffset;
- var end = -1;
+ var c = (char)_buffer[_bufferOffset];
+ line.Append(c);
- for (var i = _bufferOffset; i < _buffer.Length; i++)
- {
- // If a null terminator is found, skip the rest of the buffer.
- if (_buffer[i] == nullChar)
- {
- _logger.LogDebug("Null terminator found at position: {Position}.", i);
- end = i;
- break;
- }
+ _bufferOffset++;
+ _bufferCount--;
- // Check if current byte is CR and the next byte is LF.
- if (_buffer[i] == cr && i + 1 < _buffer.Length && _buffer[i + 1] == lf)
+ switch (c)
{
- _logger.LogDebug("CRLF found at positions {CR} and {LF}.", i, i + 1);
- end = i;
- break;
+ case '\r':
+ crIndex = line.Length;
+ break;
+ case '\n':
+ lfIndex = line.Length;
+ break;
}
- }
- // No CRLF found, process the entire remaining buffer.
- if (end == -1)
- {
- end = _buffer.Length;
- _logger.LogDebug("No CRLF found. Setting end position to buffer length: {End}.", end);
+ crlfFound = crIndex + 1 == lfIndex;
}
- else
- {
- _bufferCount -= end - start + 2;
- _bufferOffset = end + 2;
- _logger.LogDebug("CRLF found. Consumed {Consumed} bytes. New offset: {Offset}, Remaining count: {RemainingBytes}.", end - start + 2, _bufferOffset, _bufferCount);
- }
-
- var length = end - start;
- var line = Encoding.ASCII.GetString(_buffer, start, length);
+ while (!crlfFound);
- _logger.LogDebug("String from positions {Start} to {End} (length {Length}): '{Line}'.", start, end, length, line);
- return line;
+ return line.ToString(0, line.Length - 2);
}
private int ReadBuffer(byte[] buffer, int offset, int count)
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
index aba3d8fc..37dc69b2 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
@@ -2,7 +2,7 @@ namespace Microsoft.Net.Http.Client;
internal sealed class HttpConnection : IDisposable
{
- // private static readonly ISet DockerStreamHeaders = new HashSet{ "application/vnd.docker.raw-stream", "application/vnd.docker.multiplexed-stream" };
+ private static readonly ISet DockerStreamHeaders = new HashSet{ "application/vnd.docker.raw-stream", "application/vnd.docker.multiplexed-stream" };
public HttpConnection(BufferedReadStream transport)
{
@@ -147,10 +147,29 @@ private HttpResponseMessage CreateResponseMessage(List responseLines)
}
}
- // var isStream = content.Headers.TryGetValues("Content-Type", out var headerValues)
- // && headerValues.Any(header => DockerStreamHeaders.Contains(header));
+ // TODO: We'll need to refactor this in the future.
+ //
+ // Depending on the request and response (headers), we need to handle the response
+ // differently. We need to distinguish between four types of responses:
+ //
+ // 1. Chunked transfer encoding
+ // 2. HTTP with a `Content-Length` header
+ // 3. Hijacked TCP connections (using the connection upgrade headers)
+ // - `/containers/{id}/attach`
+ // - `/exec/{id}/start`
+ // 4. Streams without the connection upgrade headers
+ // - `/containers/{id}/logs`
+
+ var isConnectionUpgrade = response.Headers.TryGetValues("Upgrade", out var responseHeaderValues)
+ && responseHeaderValues.Any(header => "tcp".Equals(header));
+
+ var isStream = content.Headers.TryGetValues("Content-Type", out var contentHeaderValues)
+ && contentHeaderValues.Any(header => DockerStreamHeaders.Contains(header));
+
+ var isChunkedTransferEncoding = (response.Headers.TransferEncodingChunked.GetValueOrDefault() && !isStream) || (isStream && !isConnectionUpgrade);
+
+ content.ResolveResponseStream(chunked: isChunkedTransferEncoding);
- content.ResolveResponseStream(chunked: response.Headers.TransferEncodingChunked.HasValue && response.Headers.TransferEncodingChunked.Value);
return response;
}
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs
index a37b9b29..2522ac87 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs
@@ -26,7 +26,6 @@ internal void ResolveResponseStream(bool chunked)
}
else
{
- // Raw, read until end and close
_responseStream = _connection.Transport;
}
}
diff --git a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
index 0532f916..164a2f9b 100644
--- a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
@@ -60,9 +60,8 @@ await _testFixture.DockerClient.Containers.StartContainerAsync(
Timestamps = true,
Follow = true
},
- containerLogsCts.Token,
- new Progress(m => _testOutputHelper.WriteLine(m))
- );
+ new Progress(m => _testOutputHelper.WriteLine(m)),
+ containerLogsCts.Token);
await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
@@ -106,8 +105,8 @@ await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
Timestamps = true,
Follow = false
},
- _testFixture.Cts.Token,
- new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); }),
+ _testFixture.Cts.Token
);
await _testFixture.DockerClient.Containers.StopContainerAsync(
@@ -153,8 +152,8 @@ await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
Timestamps = true,
Follow = false
},
- _testFixture.Cts.Token,
- new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); }),
+ _testFixture.Cts.Token
);
await _testFixture.DockerClient.Containers.StopContainerAsync(
@@ -191,7 +190,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync(
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5));
- await Assert.ThrowsAsync(() => _testFixture.DockerClient.Containers.GetContainerLogsAsync(
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -200,8 +199,8 @@ await Assert.ThrowsAsync(() => _testFixture.DockerClient.
Timestamps = true,
Follow = true
},
- containerLogsCts.Token,
- new Progress(m => _testOutputHelper.WriteLine(m))
+ new Progress(m => _testOutputHelper.WriteLine(m)),
+ containerLogsCts.Token
));
}
@@ -237,11 +236,11 @@ await _testFixture.DockerClient.Containers.StartContainerAsync(
Timestamps = true,
Follow = true
},
- containerLogsCts.Token,
- new Progress(m => _testOutputHelper.WriteLine(m))
+ new Progress(m => _testOutputHelper.WriteLine(m)),
+ containerLogsCts.Token
);
- await Assert.ThrowsAsync(() => containerLogsTask);
+ await Assert.ThrowsAsync(() => containerLogsTask);
}
[Fact]
@@ -277,8 +276,8 @@ await _testFixture.DockerClient.Containers.StartContainerAsync(
Timestamps = true,
Follow = true
},
- containerLogsCts.Token,
- new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); }),
+ containerLogsCts.Token
);
await Task.Delay(TimeSpan.FromSeconds(5));
@@ -289,7 +288,7 @@ await _testFixture.DockerClient.Containers.StopContainerAsync(
_testFixture.Cts.Token
);
- await Assert.ThrowsAsync(() => containerLogsTask);
+ await Assert.ThrowsAsync(() => containerLogsTask);
_testOutputHelper.WriteLine($"Line count: {logList.Count}");
Assert.NotEmpty(logList);
@@ -708,10 +707,49 @@ public async Task CreateImageAsync_NonExistingImage_ThrowsDockerImageNotFoundExc
await Assert.ThrowsAsync(op);
}
- [Fact(Skip = "Refactor IExecOperations operations and writing/reading to/from stdin and stdout. It does not work reliably.")]
- public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException()
+ [Fact]
+ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToPid1Stdin_CompletesPid1Process()
+ {
+ // Given
+ var linefeedByte = new byte[] { 10 };
+
+ var createContainerParameters = new CreateContainerParameters();
+ createContainerParameters.Image = _testFixture.Image.ID;
+ createContainerParameters.Entrypoint = new[] { "/bin/sh", "-c" };
+ createContainerParameters.Cmd = new[] { "read line; echo Done" };
+ createContainerParameters.OpenStdin = true;
+
+ var containerAttachParameters = new ContainerAttachParameters();
+ containerAttachParameters.Stdin = true;
+ containerAttachParameters.Stdout = true;
+ containerAttachParameters.Stderr = true;
+ containerAttachParameters.Logs = true;
+ containerAttachParameters.Stream = true;
+
+ // When
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters);
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters());
+
+ using var stream = await _testFixture.DockerClient.Containers.AttachContainerAsync(createContainerResponse.ID, containerAttachParameters);
+
+ await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token);
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ var (stdout, _) = await stream.ReadOutputToEndAsync(cts.Token);
+
+ var containerInspectResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync(createContainerResponse.ID, _testFixture.Cts.Token);
+
+ // Then
+ Assert.Equal(0, containerInspectResponse.State.ExitCode);
+ Assert.Equal("Done\n", stdout);
+ }
+
+ [Fact]
+ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToExecStdin_CompletesExecProcess()
{
// Given
+ var linefeedByte = new byte[] { 10 };
+
var createContainerParameters = new CreateContainerParameters();
createContainerParameters.Image = _testFixture.Image.ID;
createContainerParameters.Entrypoint = CommonCommands.SleepInfinity;
@@ -731,10 +769,15 @@ public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException()
var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters);
using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);
- var buffer = new byte[] { 10 };
- var exception = await Record.ExceptionAsync(() => stream.WriteAsync(buffer, 0, buffer.Length, _testFixture.Cts.Token));
+ await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token);
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ var (stdout, _) = await stream.ReadOutputToEndAsync(cts.Token);
+
+ var containerExecInspectResponse = await _testFixture.DockerClient.Exec.InspectContainerExecAsync(containerExecCreateResponse.ID, _testFixture.Cts.Token);
// Then
- Assert.Null(exception);
+ Assert.Equal(0, containerExecInspectResponse.ExitCode);
+ Assert.Equal("Done\n", stdout);
}
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/TestFixture.cs b/test/Docker.DotNet.Tests/TestFixture.cs
index 8ae4634b..b32ecd6e 100644
--- a/test/Docker.DotNet.Tests/TestFixture.cs
+++ b/test/Docker.DotNet.Tests/TestFixture.cs
@@ -101,7 +101,7 @@ await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImag
}
catch
{
- this.LogDebug("Couldn't init a new swarm, the node should take part of an existing one.");
+ this.LogInformation("Couldn't init a new swarm, the node should take part of an existing one.");
_hasInitializedSwarm = false;
}
@@ -178,7 +178,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
///
public bool IsEnabled(LogLevel logLevel)
{
- return logLevel >= MinLogLevel;
+ return logLevel == MinLogLevel;
}
///
@@ -191,7 +191,7 @@ public IDisposable BeginScope(TState state) where TState : notnull
protected override void OnReport(JSONMessage value)
{
var message = JsonSerializer.Instance.Serialize(value);
- this.LogDebug("Progress: '{Progress}'.", message);
+ this.LogInformation("Progress: '{Progress}'.", message);
}
private sealed class Disposable : IDisposable
diff --git a/test/Docker.DotNet.Tests/xunit.runner.json b/test/Docker.DotNet.Tests/xunit.runner.json
index 0e2fef59..262e03ef 100644
--- a/test/Docker.DotNet.Tests/xunit.runner.json
+++ b/test/Docker.DotNet.Tests/xunit.runner.json
@@ -1,4 +1,4 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
- "diagnosticMessages" : true
+ "diagnosticMessages" : false
}
\ No newline at end of file
diff --git a/version.json b/version.json
index 423da8f4..a7718d1c 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "3.126.1",
+ "version": "3.127.0",
"nugetPackageVersion": {
"semVer": 2
},