diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 301c59f0..9f613c4a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,4 +24,4 @@ jobs:
- name: Build
run: dotnet build -c Release --framework ${{ matrix.framework }}
- name: Test
- run: dotnet test -c Release --framework ${{ matrix.framework }} --no-build
+ run: dotnet test -c Release --framework ${{ matrix.framework }} --no-build --logger console
diff --git a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs
index f1ba77f4..b51bfc85 100644
--- a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs
+++ b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs
@@ -5,12 +5,10 @@ public class BasicAuthCredentials : Credentials
private readonly bool _isTls;
private readonly MaybeSecureString _username;
+
private readonly MaybeSecureString _password;
- public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler)
- {
- return new BasicAuthHandler(_username, _password, innerHandler);
- }
+ private bool _disposed;
public BasicAuthCredentials(SecureString username, SecureString password, bool isTls = false)
: this(new MaybeSecureString(username), new MaybeSecureString(password), isTls)
@@ -29,14 +27,35 @@ private BasicAuthCredentials(MaybeSecureString username, MaybeSecureString passw
_password = password;
}
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
public override bool IsTlsCredentials()
{
return _isTls;
}
- public override void Dispose()
+ public override HttpMessageHandler GetHandler(HttpMessageHandler handler)
+ {
+ return new BasicAuthHandler(_username, _password, handler);
+ }
+
+ protected virtual void Dispose(bool disposing)
{
- _username.Dispose();
- _password.Dispose();
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _username.Dispose();
+ _password.Dispose();
+ }
+
+ _disposed = true;
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet.X509/CertificateCredentials.cs b/src/Docker.DotNet.X509/CertificateCredentials.cs
index e7e56ce0..3404616b 100644
--- a/src/Docker.DotNet.X509/CertificateCredentials.cs
+++ b/src/Docker.DotNet.X509/CertificateCredentials.cs
@@ -4,37 +4,47 @@ public class CertificateCredentials : Credentials
{
private readonly X509Certificate2 _certificate;
- public CertificateCredentials(X509Certificate2 clientCertificate)
+ private bool _disposed;
+
+ public CertificateCredentials(X509Certificate2 certificate)
{
- _certificate = clientCertificate;
+ _certificate = certificate;
}
- public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; }
-
- public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler)
+ public override void Dispose()
{
- var handler = (ManagedHandler)innerHandler;
- handler.ClientCertificates = new X509CertificateCollection
- {
- _certificate
- };
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- handler.ServerCertificateValidationCallback = ServerCertificateValidationCallback;
+ public override bool IsTlsCredentials()
+ {
+ return true;
+ }
- if (handler.ServerCertificateValidationCallback == null)
+ public override HttpMessageHandler GetHandler(HttpMessageHandler handler)
+ {
+ if (handler is HttpClientHandler httpClientHandler)
{
- handler.ServerCertificateValidationCallback = ServicePointManager.ServerCertificateValidationCallback;
+ httpClientHandler.ClientCertificates.Add(_certificate);
+ httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}
return handler;
}
- public override bool IsTlsCredentials()
+ protected virtual void Dispose(bool disposing)
{
- return true;
- }
+ if (_disposed)
+ {
+ return;
+ }
- public override void Dispose()
- {
+ if (disposing)
+ {
+ _certificate.Dispose();
+ }
+
+ _disposed = true;
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj b/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj
index 017ee021..8c2dc7c0 100644
--- a/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj
+++ b/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj
@@ -4,6 +4,9 @@
Docker.DotNet.Enhanced.X509
Docker.DotNet.X509 is a library that allows you to use certificate authentication with a remote Docker engine programmatically in your .NET applications.
+
+
+
diff --git a/src/Docker.DotNet.X509/RSAUtil.cs b/src/Docker.DotNet.X509/RSAUtil.cs
index 274e3ef7..9f9ceda9 100644
--- a/src/Docker.DotNet.X509/RSAUtil.cs
+++ b/src/Docker.DotNet.X509/RSAUtil.cs
@@ -2,179 +2,25 @@ namespace Docker.DotNet.X509;
public static class RSAUtil
{
- private const byte Padding = 0x00;
-
public static X509Certificate2 GetCertFromPFX(string pfxFilePath, string password)
{
+#if NET9_0_OR_GREATER
+ return X509CertificateLoader.LoadPkcs12FromFile(pfxFilePath, password);
+#else
return new X509Certificate2(pfxFilePath, password);
+#endif
}
- public static X509Certificate2 GetCertFromPFXSecure(string pfxFilePath, SecureString password)
- {
- return new X509Certificate2(pfxFilePath, password);
- }
-
- public static X509Certificate2 GetCertFromPEMFiles(string certFilePath, string keyFilePath)
- {
- var cert = new X509Certificate2(certFilePath);
- cert.PrivateKey = ReadFromPemFile(keyFilePath);
- return cert;
- }
-
- private static RSACryptoServiceProvider ReadFromPemFile(string pemFilePath)
- {
- var allBytes = File.ReadAllBytes(pemFilePath);
- var mem = new MemoryStream(allBytes);
- var startIndex = 0;
- var endIndex = 0;
-
- using (var rdr = new BinaryReader(mem))
- {
- if (!TryReadUntil(rdr, "-----BEGIN RSA PRIVATE KEY-----"))
- {
- throw new Exception("Invalid file format expected. No begin tag.");
- }
-
- startIndex = (int)(mem.Position);
-
- const string endTag = "-----END RSA PRIVATE KEY-----";
- if (!TryReadUntil(rdr, endTag))
- {
- throw new Exception("Invalid file format expected. No end tag.");
- }
-
- endIndex = (int)(mem.Position - endTag.Length - 2);
- }
-
- // Convert the bytes from base64;
- var convertedBytes = Convert.FromBase64String(Encoding.UTF8.GetString(allBytes, startIndex, endIndex - startIndex));
- mem = new MemoryStream(convertedBytes);
- using (var rdr = new BinaryReader(mem))
- {
- var val = rdr.ReadUInt16();
- if (val != 0x8230)
- {
- throw new Exception("Invalid byte ordering.");
- }
-
- // Discard the next bits of the version.
- rdr.ReadUInt32();
- if (rdr.ReadByte() != Padding)
- {
- throw new InvalidDataException("Invalid ASN.1 format.");
- }
-
- var rsa = new RSAParameters()
- {
- Modulus = rdr.ReadBytes(ReadIntegerCount(rdr)),
- Exponent = rdr.ReadBytes(ReadIntegerCount(rdr)),
- D = rdr.ReadBytes(ReadIntegerCount(rdr)),
- P = rdr.ReadBytes(ReadIntegerCount(rdr)),
- Q = rdr.ReadBytes(ReadIntegerCount(rdr)),
- DP = rdr.ReadBytes(ReadIntegerCount(rdr)),
- DQ = rdr.ReadBytes(ReadIntegerCount(rdr)),
- InverseQ = rdr.ReadBytes(ReadIntegerCount(rdr))
- };
-
- // Use "1" to indicate RSA.
- var csp = new CspParameters(1)
- {
-
- // Set the KeyContainerName so that native code that looks up the private key
- // can find it. This produces a keyset file on disk as a side effect.
- KeyContainerName = pemFilePath
- };
- var rsaProvider = new RSACryptoServiceProvider(csp)
- {
-
- // Setting to false makes sure the keystore file will be cleaned up
- // when the current process exits.
- PersistKeyInCsp = false
- };
-
- // Import the private key into the keyset.
- rsaProvider.ImportParameters(rsa);
-
- return rsaProvider;
- }
- }
-
- ///
- /// Reads an integer count encoding in DER ASN.1 format.
- ///
- private static int ReadIntegerCount(BinaryReader rdr)
+ public static X509Certificate2 GetCertFromPEM(string certFilePath, string keyFilePath)
{
- const byte highBitOctet = 0x80;
- const byte ASN1_INTEGER = 0x02;
-
- if (rdr.ReadByte() != ASN1_INTEGER)
- {
- throw new Exception("Integer tag expected.");
- }
-
- int count = 0;
- var val = rdr.ReadByte();
- if ((val & highBitOctet) == highBitOctet)
- {
- byte numOfOctets = (byte)(val - highBitOctet);
- if (numOfOctets > 4)
- {
- throw new InvalidDataException("Too many octets.");
- }
-
- for (var i = 0; i < numOfOctets; i++)
- {
- count <<= 8;
- count += rdr.ReadByte();
- }
- }
- else
- {
- count = val;
- }
-
- while (rdr.ReadByte() == Padding)
- {
- count--;
- }
-
- // The last read was a valid byte. Go back here.
- rdr.BaseStream.Seek(-1, SeekOrigin.Current);
-
- return count;
- }
-
- ///
- /// Reads until the matching PEM tag is found.
- ///
- private static bool TryReadUntil(BinaryReader rdr, string tag)
- {
- char delim = '\n';
- char c;
- char[] line = new char[64];
- int index;
-
- try
- {
- do
- {
- index = 0;
- while ((c = rdr.ReadChar()) != delim)
- {
- if(c == '\r')
- {
- continue;
- }
- line[index] = c;
- index++;
- }
- } while (new string(line, 0, index) != tag);
-
- return true;
- }
- catch (EndOfStreamException)
- {
- return false;
- }
+#if NETSTANDARD
+ return Polyfills.X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath);
+#elif NET9_0_OR_GREATER
+ var certificate = X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath);
+ return OperatingSystem.IsWindows() ? X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx), null) : certificate;
+#elif NET6_0_OR_GREATER
+ var certificate = X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath);
+ return OperatingSystem.IsWindows() ? new X509Certificate2(certificate.Export(X509ContentType.Pfx)) : certificate;
+#endif
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet.X509/X509Certificate2.cs b/src/Docker.DotNet.X509/X509Certificate2.cs
new file mode 100644
index 00000000..f3a9ff91
--- /dev/null
+++ b/src/Docker.DotNet.X509/X509Certificate2.cs
@@ -0,0 +1,64 @@
+#if NETSTANDARD
+namespace Docker.DotNet.X509.Polyfills;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Pkcs;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+public static class X509Certificate2
+{
+ private static readonly X509CertificateParser CertificateParser = new X509CertificateParser();
+
+ public static System.Security.Cryptography.X509Certificates.X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath)
+ {
+ if (!File.Exists(certPemFilePath))
+ {
+ throw new FileNotFoundException(certPemFilePath);
+ }
+
+ if (!File.Exists(keyPemFilePath))
+ {
+ throw new FileNotFoundException(keyPemFilePath);
+ }
+
+ using var keyPairStream = new StreamReader(keyPemFilePath);
+
+ using var certificateStream = new MemoryStream();
+
+ var store = new Pkcs12StoreBuilder().Build();
+
+ var certificate = CertificateParser.ReadCertificate(File.ReadAllBytes(certPemFilePath));
+
+ var password = Guid.NewGuid().ToString("D");
+
+ var keyObject = new PemReader(keyPairStream).ReadObject();
+
+ var certificateEntry = new X509CertificateEntry(certificate);
+
+ var keyParameter = ResolveKeyParameter(keyObject);
+
+ var keyEntry = new AsymmetricKeyEntry(keyParameter);
+
+ store.SetKeyEntry(certificate.SubjectDN + "_key", keyEntry, new[] { certificateEntry });
+ store.Save(certificateStream, password.ToCharArray(), new SecureRandom());
+
+ return new System.Security.Cryptography.X509Certificates.X509Certificate2(Pkcs12Utilities.ConvertToDefiniteLength(certificateStream.ToArray()), password);
+ }
+
+ private static AsymmetricKeyParameter ResolveKeyParameter(object keyObject)
+ {
+ switch (keyObject)
+ {
+ case AsymmetricCipherKeyPair ackp:
+ return ackp.Private;
+ case RsaPrivateCrtKeyParameters rpckp:
+ return rpckp;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(keyObject), $"Unsupported asymmetric key entry encountered while trying to resolve key from input object '{keyObject.GetType()}'.");
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Docker.DotNet/AnonymousCredentials.cs b/src/Docker.DotNet/AnonymousCredentials.cs
index 6844c5f4..0375ca01 100644
--- a/src/Docker.DotNet/AnonymousCredentials.cs
+++ b/src/Docker.DotNet/AnonymousCredentials.cs
@@ -2,17 +2,17 @@ namespace Docker.DotNet;
public class AnonymousCredentials : Credentials
{
- public override bool IsTlsCredentials()
+ public override void Dispose()
{
- return false;
}
- public override void Dispose()
+ public override bool IsTlsCredentials()
{
+ return false;
}
- public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler)
+ public override HttpMessageHandler GetHandler(HttpMessageHandler handler)
{
- return innerHandler;
+ return handler;
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Credentials.cs b/src/Docker.DotNet/Credentials.cs
index d971a21b..a20428a6 100644
--- a/src/Docker.DotNet/Credentials.cs
+++ b/src/Docker.DotNet/Credentials.cs
@@ -2,11 +2,9 @@ namespace Docker.DotNet;
public abstract class Credentials : IDisposable
{
- public abstract bool IsTlsCredentials();
+ public abstract void Dispose();
- public abstract HttpMessageHandler GetHandler(HttpMessageHandler innerHandler);
+ public abstract bool IsTlsCredentials();
- public virtual void Dispose()
- {
- }
+ public abstract HttpMessageHandler GetHandler(HttpMessageHandler handler);
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Docker.DotNet.csproj b/src/Docker.DotNet/Docker.DotNet.csproj
index ea1aa6ec..e58096c1 100644
--- a/src/Docker.DotNet/Docker.DotNet.csproj
+++ b/src/Docker.DotNet/Docker.DotNet.csproj
@@ -4,6 +4,9 @@
Docker.DotNet.Enhanced
Docker.DotNet is a library that allows you to interact with the Docker Remote API programmatically with fully asynchronous, non-blocking and object-oriented code in your .NET applications.
+
+
+
@@ -44,6 +47,8 @@
+
+
\ No newline at end of file
diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs
index 821b1369..f0e67b6c 100644
--- a/src/Docker.DotNet/DockerClient.cs
+++ b/src/Docker.DotNet/DockerClient.cs
@@ -17,9 +17,12 @@ public sealed class DockerClient : IDockerClient
private readonly Version _requestedApiVersion;
- internal DockerClient(DockerClientConfiguration configuration, Version requestedApiVersion)
+ private readonly ILogger _logger;
+
+ internal DockerClient(DockerClientConfiguration configuration, Version requestedApiVersion, ILogger logger = null)
{
_requestedApiVersion = requestedApiVersion;
+ _logger = logger ?? NullLogger.Instance;
Configuration = configuration;
DefaultTimeout = configuration.DefaultTimeout;
@@ -72,7 +75,7 @@ await stream.ConnectAsync(timeout, cancellationToken)
.ConfigureAwait(false);
return dockerStream;
- });
+ }, _logger);
break;
case "tcp":
@@ -82,11 +85,11 @@ await stream.ConnectAsync(timeout, cancellationToken)
Scheme = configuration.Credentials.IsTlsCredentials() ? "https" : "http"
};
uri = builder.Uri;
- handler = new ManagedHandler();
+ handler = new ManagedHandler(_logger);
break;
case "https":
- handler = new ManagedHandler();
+ handler = new ManagedHandler(_logger);
break;
case "unix":
@@ -99,7 +102,7 @@ await sock.ConnectAsync(new Microsoft.Net.Http.Client.UnixDomainSocketEndPoint(p
.ConfigureAwait(false);
return sock;
- });
+ }, _logger);
uri = new UriBuilder("http", uri.Segments.Last()).Uri;
break;
@@ -388,10 +391,7 @@ await HandleIfErrorResponseAsync(response.StatusCode, response, errorHandlers)
throw new NotSupportedException("message handler does not support hijacked streams");
}
- var stream = await content.ReadAsStreamAsync()
- .ConfigureAwait(false);
-
- return (WriteClosableStream)stream;
+ return content.HijackStream();
}
private async Task PrivateMakeRequestAsync(
diff --git a/src/Docker.DotNet/DockerClientConfiguration.cs b/src/Docker.DotNet/DockerClientConfiguration.cs
index d92e4ef7..046359a0 100644
--- a/src/Docker.DotNet/DockerClientConfiguration.cs
+++ b/src/Docker.DotNet/DockerClientConfiguration.cs
@@ -50,9 +50,9 @@ public DockerClientConfiguration(
public TimeSpan NamedPipeConnectTimeout { get; }
- public DockerClient CreateClient(Version requestedApiVersion = null)
+ public DockerClient CreateClient(Version requestedApiVersion = null, ILogger logger = null)
{
- return new DockerClient(this, requestedApiVersion);
+ return new DockerClient(this, requestedApiVersion, logger);
}
public void Dispose()
diff --git a/src/Docker.DotNet/Endpoints/ContainerOperations.cs b/src/Docker.DotNet/Endpoints/ContainerOperations.cs
index 9fad6cad..226f2c76 100644
--- a/src/Docker.DotNet/Endpoints/ContainerOperations.cs
+++ b/src/Docker.DotNet/Endpoints/ContainerOperations.cs
@@ -297,14 +297,8 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
}
var queryParameters = new QueryString(parameters);
- var stream = await _client.MakeRequestForHijackedStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken).ConfigureAwait(false);
- if (!stream.CanCloseWrite)
- {
- stream.Dispose();
- throw new NotSupportedException("Cannot shutdown write on this transport");
- }
-
- return new MultiplexedStream(stream, !tty);
+ var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken).ConfigureAwait(false);
+ return new MultiplexedStream(result, !tty);
}
public async Task WaitContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
diff --git a/src/Docker.DotNet/Endpoints/ExecOperations.cs b/src/Docker.DotNet/Endpoints/ExecOperations.cs
index 8b6e87b6..884be06b 100644
--- a/src/Docker.DotNet/Endpoints/ExecOperations.cs
+++ b/src/Docker.DotNet/Endpoints/ExecOperations.cs
@@ -83,21 +83,15 @@ public async Task StartAndAttachContainerExecAsync(string id,
return await StartWithConfigContainerExecAsync(id, new ContainerExecStartParameters() { AttachStdin = true, AttachStderr = true, AttachStdout = true, Tty = tty }, cancellationToken);
}
- public async Task StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters eConfig, CancellationToken cancellationToken)
+ public async Task StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
- var data = new JsonRequestContent(eConfig, DockerClient.JsonSerializer);
- var stream = await _client.MakeRequestForHijackedStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken).ConfigureAwait(false);
- if (!stream.CanCloseWrite)
- {
- stream.Dispose();
- throw new NotSupportedException("Cannot shutdown write on this transport");
- }
-
- return new MultiplexedStream(stream, !eConfig.Tty);
+ var data = new JsonRequestContent(parameters, DockerClient.JsonSerializer);
+ var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken).ConfigureAwait(false);
+ return new MultiplexedStream(result, !parameters.Tty);
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/JsonNullableDateTimeConverter.cs b/src/Docker.DotNet/JsonNullableDateTimeConverter.cs
index 52b1fffe..fa92a113 100644
--- a/src/Docker.DotNet/JsonNullableDateTimeConverter.cs
+++ b/src/Docker.DotNet/JsonNullableDateTimeConverter.cs
@@ -1,4 +1,4 @@
-namespace Docker.DotNet;
+namespace Docker.DotNet;
internal sealed class JsonNullableDateTimeConverter : JsonConverter
{
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
index 94ba4246..2a216647 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/BufferedReadStream.cs
@@ -1,39 +1,27 @@
-#if !NET45
-#endif
-
namespace Microsoft.Net.Http.Client;
-internal class BufferedReadStream : WriteClosableStream, IPeekableStream
+internal sealed class BufferedReadStream : WriteClosableStream, IPeekableStream
{
- private const char CR = '\r';
- private const char LF = '\n';
-
private readonly Stream _inner;
private readonly Socket _socket;
private readonly byte[] _buffer;
- private volatile int _bufferRefCount;
- private int _bufferOffset = 0;
- private int _bufferCount = 0;
- private bool _disposed;
+ private readonly ILogger _logger;
+ private int _bufferRefCount;
+ private int _bufferOffset;
+ private int _bufferCount;
- public BufferedReadStream(Stream inner, Socket socket)
- : this(inner, socket, 1024)
- { }
+ public BufferedReadStream(Stream inner, Socket socket, ILogger logger)
+ : this(inner, socket, 8192, logger)
+ {
+ }
- public BufferedReadStream(Stream inner, Socket socket, int bufferLength)
+ public BufferedReadStream(Stream inner, Socket socket, int bufferLength, ILogger logger)
{
- if (inner == null)
- {
- throw new ArgumentNullException(nameof(inner));
- }
- _inner = inner;
+ _inner = inner ?? throw new ArgumentNullException(nameof(inner));
_socket = socket;
-#if !NET45
- _bufferRefCount = 1;
_buffer = ArrayPool.Shared.Rent(bufferLength);
-#else
- _buffer = new byte[bufferLength];
-#endif
+ _logger = logger;
+ _bufferRefCount = 1;
}
public override bool CanRead
@@ -67,32 +55,32 @@ public override long Position
set { throw new NotSupportedException(); }
}
- public override long Seek(long offset, SeekOrigin origin)
+ public override bool CanCloseWrite => _socket != null || _inner is WriteClosableStream;
+
+ protected override void Dispose(bool disposing)
{
- throw new NotSupportedException();
+ // TODO: Why does disposing break the implementation, see the other chunked streams too.
+ // base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _inner.Dispose();
+
+ if (Interlocked.Decrement(ref _bufferRefCount) == 0)
+ {
+ ArrayPool.Shared.Return(_buffer);
+ }
+ }
}
- public override void SetLength(long value)
+ public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
- protected override void Dispose(bool disposing)
+ public override void SetLength(long value)
{
- if (!_disposed)
- {
- _disposed = true;
- if (disposing)
- {
- _inner.Dispose();
-#if !NET45
- if (Interlocked.Decrement(ref _bufferRefCount) == 0)
- {
- ArrayPool.Shared.Return(_buffer);
- }
-#endif
- }
- }
+ throw new NotSupportedException();
}
public override void Flush()
@@ -137,6 +125,23 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel
return _inner.ReadAsync(buffer, offset, count, cancellationToken);
}
+ public override void CloseWrite()
+ {
+ if (_socket != null)
+ {
+ _socket.Shutdown(SocketShutdown.Send);
+ return;
+ }
+
+ if (_inner is WriteClosableStream writeClosableStream)
+ {
+ writeClosableStream.CloseWrite();
+ return;
+ }
+
+ throw new NotSupportedException("Cannot shutdown write on this transport");
+ }
+
public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available, out uint remaining)
{
int read = PeekBuffer(buffer, toPeek, out peeked, out available, out remaining);
@@ -153,132 +158,124 @@ public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available
throw new NotSupportedException("_inner stream isn't a peekable stream");
}
- private int ReadBuffer(byte[] buffer, int offset, int count)
+ public async Task ReadLineAsync(CancellationToken cancellationToken)
{
- if (_bufferCount > 0)
- {
- int toCopy = Math.Min(_bufferCount, count);
- Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
- _bufferOffset += toCopy;
- _bufferCount -= toCopy;
- return toCopy;
- }
+ const char nullChar = '\0';
- return 0;
- }
+ const char cr = '\r';
- private int PeekBuffer(byte[] buffer, uint toPeek, out uint peeked, out uint available, out uint remaining)
- {
- if (_bufferCount > 0)
- {
- int toCopy = Math.Min(_bufferCount, (int)toPeek);
- Buffer.BlockCopy(_buffer, _bufferOffset, buffer, 0, toCopy);
- peeked = (uint) toCopy;
- available = (uint)_bufferCount;
- remaining = available - peeked;
- return toCopy;
- }
+ const char lf = '\n';
- peeked = 0;
- available = 0;
- remaining = 0;
- return 0;
- }
-
- private async Task EnsureBufferedAsync(CancellationToken cancel)
- {
if (_bufferCount == 0)
{
- _bufferOffset = 0;
-#if !NET45
- bool validBuffer = Interlocked.Increment(ref _bufferRefCount) > 1;
+ var bufferInUse = Interlocked.Increment(ref _bufferRefCount) > 1;
+
try
{
- if (validBuffer)
+ if (bufferInUse)
{
- _bufferCount = await _inner.ReadAsync(_buffer, _bufferOffset, _buffer.Length, cancel).ConfigureAwait(false);
+ _bufferOffset = 0;
+
+ _bufferCount = await _inner.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken)
+ .ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ _logger.LogCritical(e, "Failed to read from buffer.");
+ throw;
+ }
finally
{
- if ((Interlocked.Decrement(ref _bufferRefCount) == 0) && validBuffer)
+ var bufferReleased = Interlocked.Decrement(ref _bufferRefCount) == 0;
+
+ if (bufferInUse && bufferReleased)
{
ArrayPool.Shared.Return(_buffer);
}
}
-#else
- _bufferCount = await _inner.ReadAsync(_buffer, _bufferOffset, _buffer.Length, cancel).ConfigureAwait(false);
-#endif
- if (_bufferCount == 0)
- {
- throw new IOException("Unexpected end of stream");
- }
}
- }
- // TODO: Line length limits?
- public async Task ReadLineAsync(CancellationToken cancel)
- {
- ThrowIfDisposed();
- StringBuilder builder = new StringBuilder();
- bool foundCR = false, foundCRLF = false;
- do
+ if (_logger.IsEnabled(LogLevel.Debug))
{
- if (_bufferCount == 0)
- {
- await EnsureBufferedAsync(cancel).ConfigureAwait(false);
- }
+ 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;
- char ch = (char)_buffer[_bufferOffset]; // TODO: Encoding enforcement
- builder.Append(ch);
- _bufferOffset++;
- _bufferCount--;
- if (ch == CR)
+ var end = -1;
+
+ for (var i = _bufferOffset; i < _buffer.Length; i++)
+ {
+ // If a null terminator is found, skip the rest of the buffer.
+ if (_buffer[i] == nullChar)
{
- foundCR = true;
+ _logger.LogDebug("Null terminator found at position: {Position}.", i);
+ end = i;
+ break;
}
- else if (ch == LF)
+
+ // Check if current byte is CR and the next byte is LF.
+ if (_buffer[i] == cr && i + 1 < _buffer.Length && _buffer[i + 1] == lf)
{
- if (foundCR)
- {
- foundCRLF = true;
- }
- else
- {
- foundCR = false;
- }
+ _logger.LogDebug("CRLF found at positions {CR} and {LF}.", i, i + 1);
+ end = i;
+ break;
}
}
- while (!foundCRLF);
-
- return builder.ToString(0, builder.Length - 2); // Drop the CRLF
- }
- private void ThrowIfDisposed()
- {
- if (_disposed)
+ // No CRLF found, process the entire remaining buffer.
+ if (end == -1)
{
- throw new ObjectDisposedException(nameof(BufferedReadStream));
+ end = _buffer.Length;
+ _logger.LogDebug("No CRLF found. Setting end position to buffer length: {End}.", end);
+ }
+ 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);
}
- }
- public override bool CanCloseWrite => _socket != null || _inner is WriteClosableStream;
+ var length = end - start;
+ var line = Encoding.ASCII.GetString(_buffer, start, length);
- public override void CloseWrite()
+ _logger.LogDebug("String from positions {Start} to {End} (length {Length}): '{Line}'.", start, end, length, line);
+ return line;
+ }
+
+ private int ReadBuffer(byte[] buffer, int offset, int count)
{
- if (_socket != null)
+ if (_bufferCount > 0)
{
- _socket.Shutdown(SocketShutdown.Send);
- return;
+ int toCopy = Math.Min(_bufferCount, count);
+ Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
+ _bufferOffset += toCopy;
+ _bufferCount -= toCopy;
+ return toCopy;
}
- var s = _inner as WriteClosableStream;
- if (s != null)
+ return 0;
+ }
+
+ private int PeekBuffer(byte[] buffer, uint toPeek, out uint peeked, out uint available, out uint remaining)
+ {
+ if (_bufferCount > 0)
{
- s.CloseWrite();
- return;
+ int toCopy = Math.Min(_bufferCount, (int)toPeek);
+ Buffer.BlockCopy(_buffer, _bufferOffset, buffer, 0, toCopy);
+ peeked = (uint) toCopy;
+ available = (uint)_bufferCount;
+ remaining = available - peeked;
+ return toCopy;
}
- throw new NotSupportedException("Cannot shutdown write on this transport");
+ peeked = 0;
+ available = 0;
+ remaining = 0;
+ return 0;
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs
index 99496512..3bcdc582 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs
@@ -1,20 +1,19 @@
namespace Microsoft.Net.Http.Client;
-internal class ChunkedReadStream : WriteClosableStream
+internal sealed class ChunkedReadStream : Stream
{
private readonly BufferedReadStream _inner;
- private long _chunkBytesRemaining;
- private bool _disposed;
+ private int _chunkBytesRemaining;
private bool _done;
- public ChunkedReadStream(BufferedReadStream inner)
+ public ChunkedReadStream(BufferedReadStream stream)
{
- _inner = inner;
+ _inner = stream ?? throw new ArgumentNullException(nameof(stream));
}
public override bool CanRead
{
- get { return !_disposed; }
+ get { return _inner.CanRead; }
}
public override bool CanSeek
@@ -32,11 +31,6 @@ public override bool CanWrite
get { return false; }
}
- public override bool CanCloseWrite
- {
- get { return _inner.CanCloseWrite; }
- }
-
public override long Length
{
get { throw new NotSupportedException(); }
@@ -52,12 +46,10 @@ public override int ReadTimeout
{
get
{
- ThrowIfDisposed();
return _inner.ReadTimeout;
}
set
{
- ThrowIfDisposed();
_inner.ReadTimeout = value;
}
}
@@ -66,89 +58,78 @@ public override int WriteTimeout
{
get
{
- ThrowIfDisposed();
return _inner.WriteTimeout;
}
set
{
- ThrowIfDisposed();
_inner.WriteTimeout = value;
}
}
- public override int Read(byte[] buffer, int offset, int count)
+ protected override void Dispose(bool disposing)
{
- return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
+ // base.Dispose(disposing);
+
+ if (disposing)
+ {
+ // _inner.Dispose();
+ }
}
- public async override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ public override int Read(byte[] buffer, int offset, int count)
{
- // TODO: Validate buffer
- ThrowIfDisposed();
+ throw new NotSupportedException();
+ }
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
if (_done)
{
return 0;
}
- cancellationToken.ThrowIfCancellationRequested();
-
if (_chunkBytesRemaining == 0)
{
- string headerLine = await _inner.ReadLineAsync(cancellationToken).ConfigureAwait(false);
- if (!long.TryParse(headerLine, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _chunkBytesRemaining))
+ var headerLine = await _inner.ReadLineAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ if (!int.TryParse(headerLine, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _chunkBytesRemaining))
{
- throw new IOException("Invalid chunk header: " + headerLine);
+ throw new IOException($"Invalid chunk header encountered: '{headerLine}'.");
}
}
- int read = 0;
+ var readBytesCount = 0;
+
if (_chunkBytesRemaining > 0)
{
- int toRead = (int)Math.Min(count, _chunkBytesRemaining);
- read = await _inner.ReadAsync(buffer, offset, toRead, cancellationToken).ConfigureAwait(false);
- if (read == 0)
+ var remainingBytesCount = Math.Min(_chunkBytesRemaining, count);
+
+ readBytesCount = await _inner.ReadAsync(buffer, offset, remainingBytesCount, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (readBytesCount == 0)
{
throw new EndOfStreamException();
}
- _chunkBytesRemaining -= read;
+ _chunkBytesRemaining -= readBytesCount;
}
if (_chunkBytesRemaining == 0)
{
- // End of chunk, read the terminator CRLF
- var trailer = await _inner.ReadLineAsync(cancellationToken).ConfigureAwait(false);
- if (trailer.Length > 0)
- {
- throw new IOException("Invalid chunk trailer");
- }
+ var emptyLine = await _inner.ReadLineAsync(cancellationToken)
+ .ConfigureAwait(false);
- if (read == 0)
+ if (!string.IsNullOrEmpty(emptyLine))
{
- _done = true;
+ throw new IOException($"Expected an empty line, but received: '{emptyLine}'.");
}
- }
- return read;
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- // TODO: Sync drain with timeout if small number of bytes remaining? This will let us re-use the connection.
- _inner.Dispose();
+ _done = readBytesCount == 0;
}
- _disposed = true;
- }
- private void ThrowIfDisposed()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(typeof(ContentLengthReadStream).FullName);
- }
+ return readBytesCount;
}
public override void Write(byte[] buffer, int offset, int count)
@@ -175,9 +156,4 @@ public override void Flush()
{
_inner.Flush();
}
-
- public override void CloseWrite()
- {
- _inner.CloseWrite();
- }
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs
index 65e74185..e66174a3 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs
@@ -1,17 +1,14 @@
namespace Microsoft.Net.Http.Client;
-internal class ChunkedWriteStream : Stream
+internal sealed class ChunkedWriteStream : Stream
{
private static readonly byte[] s_EndContentBytes = Encoding.ASCII.GetBytes("0\r\n\r\n");
- private Stream _innerStream;
+ private readonly Stream _inner;
public ChunkedWriteStream(Stream stream)
{
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
-
- _innerStream = stream;
+ _inner = stream ?? throw new ArgumentNullException(nameof(stream));
}
public override bool CanRead => false;
@@ -31,14 +28,24 @@ public override long Position
set { throw new NotImplementedException(); }
}
+ protected override void Dispose(bool disposing)
+ {
+ // base.Dispose(disposing);
+
+ if (disposing)
+ {
+ // _inner.Dispose();
+ }
+ }
+
public override void Flush()
{
- _innerStream.Flush();
+ _inner.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
- return _innerStream.FlushAsync(cancellationToken);
+ return _inner.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
@@ -58,7 +65,7 @@ public override void SetLength(long value)
public override void Write(byte[] buffer, int offset, int count)
{
- WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
+ throw new NotSupportedException();
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -69,13 +76,13 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
}
var chunkSize = Encoding.ASCII.GetBytes(count.ToString("x") + "\r\n");
- await _innerStream.WriteAsync(chunkSize, 0, chunkSize.Length, cancellationToken).ConfigureAwait(false);
- await _innerStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
- await _innerStream.WriteAsync(chunkSize, chunkSize.Length - 2, 2, cancellationToken).ConfigureAwait(false);
+ await _inner.WriteAsync(chunkSize, 0, chunkSize.Length, cancellationToken).ConfigureAwait(false);
+ await _inner.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
+ await _inner.WriteAsync(chunkSize, chunkSize.Length - 2, 2, cancellationToken).ConfigureAwait(false);
}
public Task EndContentAsync(CancellationToken cancellationToken)
{
- return _innerStream.WriteAsync(s_EndContentBytes, 0, s_EndContentBytes.Length, cancellationToken);
+ return _inner.WriteAsync(s_EndContentBytes, 0, s_EndContentBytes.Length, cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/ContentLengthReadStream.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/ContentLengthReadStream.cs
index a46a736f..2d38d85c 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/ContentLengthReadStream.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/ContentLengthReadStream.cs
@@ -100,7 +100,7 @@ public override int Read(byte[] buffer, int offset, int count)
return read;
}
- public async override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// TODO: Validate args
if (_disposed)
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
index 666cc8f2..aba3d8fc 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs
@@ -1,13 +1,15 @@
namespace Microsoft.Net.Http.Client;
-internal class HttpConnection : IDisposable
+internal sealed class HttpConnection : IDisposable
{
+ // private static readonly ISet DockerStreamHeaders = new HashSet{ "application/vnd.docker.raw-stream", "application/vnd.docker.multiplexed-stream" };
+
public HttpConnection(BufferedReadStream transport)
{
Transport = transport;
}
- public BufferedReadStream Transport { get; private set; }
+ public BufferedReadStream Transport { get; }
public async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
@@ -37,8 +39,8 @@ public async Task SendAsync(HttpRequestMessage request, Can
// Receive headers
List responseLines = await ReadResponseLinesAsync(cancellationToken);
- // Determine response type (Chunked, Content-Length, opaque, none...)
- // Receive body
+
+ // Receive body and determine the response type (Content-Length, Transfer-Encoding, Opaque)
return CreateResponseMessage(responseLines);
}
catch (Exception ex)
@@ -83,13 +85,22 @@ private string SerializeRequest(HttpRequestMessage request)
private async Task> ReadResponseLinesAsync(CancellationToken cancellationToken)
{
- List lines = new List();
- string line = await Transport.ReadLineAsync(cancellationToken);
- while (line.Length > 0)
+ var lines = new List(12);
+
+ do
{
+ var line = await Transport.ReadLineAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ if (string.IsNullOrEmpty(line))
+ {
+ break;
+ }
+
lines.Add(line);
- line = await Transport.ReadLineAsync(cancellationToken);
}
+ while (true);
+
return lines;
}
@@ -103,8 +114,8 @@ private HttpResponseMessage CreateResponseMessage(List responseLines)
{
throw new HttpRequestException("Invalid response line: " + responseLine);
}
- int statusCode = 0;
- if (int.TryParse(responseLineParts[1], NumberStyles.None, CultureInfo.InvariantCulture, out statusCode))
+
+ if (int.TryParse(responseLineParts[1], NumberStyles.None, CultureInfo.InvariantCulture, out var statusCode))
{
// TODO: Validate range
}
@@ -135,22 +146,16 @@ private HttpResponseMessage CreateResponseMessage(List responseLines)
System.Diagnostics.Debug.Assert(success, "Failed to add response header: " + rawHeader);
}
}
- // After headers have been set
- content.ResolveResponseStream(chunked: response.Headers.TransferEncodingChunked.HasValue && response.Headers.TransferEncodingChunked.Value);
+ // var isStream = content.Headers.TryGetValues("Content-Type", out var headerValues)
+ // && headerValues.Any(header => DockerStreamHeaders.Contains(header));
+
+ content.ResolveResponseStream(chunked: response.Headers.TransferEncodingChunked.HasValue && response.Headers.TransferEncodingChunked.Value);
return response;
}
public void Dispose()
{
- Dispose(true);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- Transport.Dispose();
- }
+ Transport.Dispose();
}
}
\ No newline at end of file
diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/ManagedHandler.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/ManagedHandler.cs
index f9fcef56..a34d0ac7 100644
--- a/src/Docker.DotNet/Microsoft.Net.Http.Client/ManagedHandler.cs
+++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/ManagedHandler.cs
@@ -4,21 +4,27 @@ namespace Microsoft.Net.Http.Client;
public class ManagedHandler : HttpMessageHandler
{
+ private readonly ILogger _logger;
+
public delegate Task StreamOpener(string host, int port, CancellationToken cancellationToken);
+
public delegate Task SocketOpener(string host, int port, CancellationToken cancellationToken);
- public ManagedHandler()
+ public ManagedHandler(ILogger logger)
{
+ _logger = logger;
_socketOpener = TCPSocketOpenerAsync;
}
- public ManagedHandler(StreamOpener opener)
+ public ManagedHandler(StreamOpener opener, ILogger logger)
{
+ _logger = logger;
_streamOpener = opener ?? throw new ArgumentNullException(nameof(opener));
}
- public ManagedHandler(SocketOpener opener)
+ public ManagedHandler(SocketOpener opener, ILogger logger)
{
+ _logger = logger;
_socketOpener = opener ?? throw new ArgumentNullException(nameof(opener));
}
@@ -52,7 +58,7 @@ public IWebProxy Proxy
private SocketOpener _socketOpener;
private IWebProxy _proxy;
- protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
@@ -172,7 +178,7 @@ private async Task ProcessRequestAsync(HttpRequestMessage r
transport = sslStream;
}
- var bufferedReadStream = new BufferedReadStream(transport, socket);
+ var bufferedReadStream = new BufferedReadStream(transport, socket, _logger);
var connection = new HttpConnection(bufferedReadStream);
return await connection.SendAsync(request, cancellationToken);
}
@@ -308,36 +314,35 @@ private ProxyMode DetermineProxyModeAndAddressLine(HttpRequestMessage request)
private static async Task TCPSocketOpenerAsync(string host, int port, CancellationToken cancellationToken)
{
- var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
+ var addresses = await Dns.GetHostAddressesAsync(host)
+ .ConfigureAwait(false);
+
if (addresses.Length == 0)
{
- throw new Exception($"could not resolve address for {host}");
+ throw new Exception($"Unable to resolve any IP addresses for the host '{host}'.");
}
- Socket connectedSocket = null;
- Exception lastException = null;
+ var exceptions = new List();
+
foreach (var address in addresses)
{
- var s = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+
try
{
- await s.ConnectAsync(address, port).ConfigureAwait(false);
- connectedSocket = s;
- break;
+ await socket.ConnectAsync(address, port)
+ .ConfigureAwait(false);
+
+ return socket;
}
catch (Exception e)
{
- s.Dispose();
- lastException = e;
+ socket.Dispose();
+ exceptions.Add(e);
}
}
- if (connectedSocket == null)
- {
- throw lastException;
- }
-
- return connectedSocket;
+ throw new AggregateException(exceptions);
}
private async Task TunnelThroughProxyAsync(HttpRequestMessage request, Stream transport, CancellationToken cancellationToken)
@@ -355,7 +360,7 @@ private async Task TunnelThroughProxyAsync(HttpRequestMessage request, Stream tr
connectRequest.SetAddressLineProperty(authority);
connectRequest.Headers.Host = authority;
- HttpConnection connection = new HttpConnection(new BufferedReadStream(transport, null));
+ HttpConnection connection = new HttpConnection(new BufferedReadStream(transport, null, _logger));
HttpResponseMessage connectResponse;
try
{
diff --git a/src/Docker.DotNet/MultiplexedStream.cs b/src/Docker.DotNet/MultiplexedStream.cs
index 48d5727e..e35db4d1 100644
--- a/src/Docker.DotNet/MultiplexedStream.cs
+++ b/src/Docker.DotNet/MultiplexedStream.cs
@@ -1,51 +1,55 @@
-#if !NET45
-#endif
-
namespace Docker.DotNet;
-public class MultiplexedStream : IDisposable, IPeekableStream
+public sealed class MultiplexedStream : IDisposable, IPeekableStream
{
+ private const int BufferSize = 16384;
private readonly Stream _stream;
private TargetStream _target;
private int _remaining;
private readonly byte[] _header = new byte[8];
private readonly bool _multiplexed;
- const int BufferSize = 81920;
-
public MultiplexedStream(Stream stream, bool multiplexed)
{
_stream = stream;
_multiplexed = multiplexed;
}
- public enum TargetStream
+ public void Dispose()
+ {
+ _stream.Dispose();
+ }
+
+ public enum TargetStream : byte
{
StandardIn = 0,
StandardOut = 1,
StandardError = 2
}
- public struct ReadResult
+ public readonly struct ReadResult
{
- public int Count { get; set; }
- public TargetStream Target { get; set; }
+ public ReadResult(TargetStream target, int count)
+ {
+ Target = target;
+ Count = count;
+ }
+
+ public TargetStream Target { get; }
+
+ public int Count { get; }
+
public bool EOF => Count == 0;
}
public void CloseWrite()
{
- if (_stream is WriteClosableStream closable)
+ if (_stream is WriteClosableStream writeClosableStream)
{
- closable.CloseWrite();
+ writeClosableStream.CloseWrite();
}
}
- public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return _stream.WriteAsync(buffer, offset, count, cancellationToken);
- }
-
public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available, out uint remaining)
{
if (_stream is IPeekableStream peekableStream)
@@ -56,46 +60,50 @@ public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available
throw new NotSupportedException("_stream isn't a peekable stream");
}
+ public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return _stream.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+
public async Task ReadOutputAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
+ int readBytesCount;
+
if (!_multiplexed)
{
- return new ReadResult
- {
- Count = await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false),
- Target = TargetStream.StandardOut
- };
+ readBytesCount = await _stream.ReadAsync(buffer, offset, count, cancellationToken)
+ .ConfigureAwait(false);
+
+ return new ReadResult(TargetStream.StandardOut, readBytesCount);
}
while (_remaining == 0)
{
for (var i = 0; i < _header.Length;)
{
- var n = await _stream.ReadAsync(_header, i, _header.Length - i, cancellationToken).ConfigureAwait(false);
- if (n == 0)
+ readBytesCount = await _stream.ReadAsync(_header, i, _header.Length - i, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (readBytesCount == 0)
{
if (i == 0)
{
- // End of the stream.
return new ReadResult();
}
throw new EndOfStreamException();
}
- i += n;
+ i += readBytesCount;
}
- switch ((TargetStream)_header[0])
+ if (Enum.IsDefined(typeof(TargetStream), _header[0]))
{
- case TargetStream.StandardIn:
- case TargetStream.StandardOut:
- case TargetStream.StandardError:
- _target = (TargetStream)_header[0];
- break;
-
- default:
- throw new IOException("unknown stream type");
+ _target = (TargetStream)_header[0];
+ }
+ else
+ {
+ throw new IOException($"Unknown stream type: '{_header[0]}'.");
}
_remaining = (_header[4] << 24) |
@@ -104,87 +112,84 @@ public async Task ReadOutputAsync(byte[] buffer, int offset, int cou
_header[7];
}
- var toRead = Math.Min(count, _remaining);
- var read = await _stream.ReadAsync(buffer, offset, toRead, cancellationToken).ConfigureAwait(false);
- if (read == 0)
+ var remainingBytesCount = Math.Min(count, _remaining);
+
+ readBytesCount = await _stream.ReadAsync(buffer, offset, remainingBytesCount, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (readBytesCount == 0)
{
throw new EndOfStreamException();
}
- _remaining -= read;
- return new ReadResult
- {
- Count = read,
- Target = _target
- };
+ _remaining -= readBytesCount;
+ return new ReadResult(_target, readBytesCount);
}
public async Task<(string stdout, string stderr)> ReadOutputToEndAsync(CancellationToken cancellationToken)
{
- using (MemoryStream outMem = new MemoryStream(), outErr = new MemoryStream())
- {
- await CopyOutputToAsync(Stream.Null, outMem, outErr, cancellationToken);
+ using MemoryStream stdoutMemoryStream = new MemoryStream(), stderrMemoryStream = new MemoryStream();
- outMem.Seek(0, SeekOrigin.Begin);
- outErr.Seek(0, SeekOrigin.Begin);
+ using StreamReader stdoutStreamReader = new StreamReader(stdoutMemoryStream), stderrStreamReader = new StreamReader(stderrMemoryStream);
- using (StreamReader outRdr = new StreamReader(outMem), errRdr = new StreamReader(outErr))
- {
- var stdout = outRdr.ReadToEnd();
- var stderr = errRdr.ReadToEnd();
- return (stdout, stderr);
- }
- }
+ await CopyOutputToAsync(Stream.Null, stdoutMemoryStream, stderrMemoryStream, cancellationToken)
+ .ConfigureAwait(false);
+
+ stdoutMemoryStream.Seek(0, SeekOrigin.Begin);
+ stderrMemoryStream.Seek(0, SeekOrigin.Begin);
+
+ var stdoutReadTask = stdoutStreamReader.ReadToEndAsync();
+ var stderrReadTask = stderrStreamReader.ReadToEndAsync();
+ await Task.WhenAll(stdoutReadTask, stderrReadTask)
+ .ConfigureAwait(false);
+
+ return (stdoutReadTask.Result, stderrReadTask.Result);
}
public async Task CopyFromAsync(Stream input, CancellationToken cancellationToken)
{
-#if !NET45
var buffer = ArrayPool.Shared.Rent(BufferSize);
-#else
- var buffer = new byte[BufferSize];
-#endif
try
{
for (;;)
{
- var count = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
- if (count == 0)
+ var readBytesCount = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (readBytesCount == 0)
{
break;
}
- await WriteAsync(buffer, 0, count, cancellationToken).ConfigureAwait(false);
+ await WriteAsync(buffer, 0, readBytesCount, cancellationToken)
+ .ConfigureAwait(false);
}
}
finally
{
-#if !NET45
ArrayPool.Shared.Return(buffer);
-#endif
}
}
public async Task CopyOutputToAsync(Stream stdin, Stream stdout, Stream stderr, CancellationToken cancellationToken)
{
-#if !NET45
var buffer = ArrayPool.Shared.Rent(BufferSize);
-#else
- var buffer = new byte[BufferSize];
-#endif
try
{
for (;;)
{
- var result = await ReadOutputAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var result = await ReadOutputAsync(buffer, 0, buffer.Length, cancellationToken)
+ .ConfigureAwait(false);
+
if (result.EOF)
{
return;
}
Stream stream;
+
switch (result.Target)
{
case TargetStream.StandardIn:
@@ -197,22 +202,16 @@ public async Task CopyOutputToAsync(Stream stdin, Stream stdout, Stream stderr,
stream = stderr;
break;
default:
- throw new InvalidOperationException($"Unknown TargetStream: '{result.Target}'.");
+ throw new IOException($"Unknown stream type: '{result.Target}'.");
}
- await stream.WriteAsync(buffer, 0, result.Count, cancellationToken).ConfigureAwait(false);
+ await stream.WriteAsync(buffer, 0, result.Count, cancellationToken)
+ .ConfigureAwait(false);
}
}
finally
{
-#if !NET45
ArrayPool.Shared.Return(buffer);
-#endif
}
}
-
- public void Dispose()
- {
- ((IDisposable)_stream).Dispose();
- }
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/CommonCommands.cs b/test/Docker.DotNet.Tests/CommonCommands.cs
new file mode 100644
index 00000000..484f28db
--- /dev/null
+++ b/test/Docker.DotNet.Tests/CommonCommands.cs
@@ -0,0 +1,8 @@
+namespace Docker.DotNet.Tests;
+
+public static class CommonCommands
+{
+ public static readonly string[] SleepInfinity = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; sleep infinity"];
+
+ public static readonly string[] EchoToStdoutAndStderr = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; while true; do echo \"stdout message\"; echo \"stderr message\" >&2; sleep 1; done"];
+}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj
index 342e804d..470c3e68 100644
--- a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj
+++ b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj
@@ -14,6 +14,9 @@
+
+
+
@@ -24,7 +27,10 @@
+
+
+
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs
index e3c9b78c..533d5d25 100644
--- a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs
@@ -3,21 +3,21 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class IConfigOperationsTests
{
- private readonly DockerClient _dockerClient;
- private readonly TestOutput _output;
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
- public IConfigOperationsTests(TestFixture testFixture, ITestOutputHelper outputHelper)
+ public IConfigOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
{
- _dockerClient = testFixture.DockerClient;
- _output = new TestOutput(outputHelper);
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
}
[Fact]
public async Task SwarmConfig_CanCreateAndRead()
{
- var currentConfigs = await _dockerClient.Configs.ListConfigsAsync();
+ var currentConfigs = await _testFixture.DockerClient.Configs.ListConfigsAsync();
- _output.WriteLine($"Current Configs: {currentConfigs.Count}");
+ _testOutputHelper.WriteLine($"Current Configs: {currentConfigs.Count}");
var testConfigSpec = new SwarmConfigSpec
{
@@ -31,15 +31,15 @@ public async Task SwarmConfig_CanCreateAndRead()
Config = testConfigSpec
};
- var createdConfig = await _dockerClient.Configs.CreateConfigAsync(configParameters);
+ var createdConfig = await _testFixture.DockerClient.Configs.CreateConfigAsync(configParameters);
Assert.NotNull(createdConfig.ID);
- _output.WriteLine($"Config created: {createdConfig.ID}");
+ _testOutputHelper.WriteLine($"Config created: {createdConfig.ID}");
- var configs = await _dockerClient.Configs.ListConfigsAsync();
+ var configs = await _testFixture.DockerClient.Configs.ListConfigsAsync();
Assert.Contains(configs, c => c.ID == createdConfig.ID);
- _output.WriteLine($"Current Configs: {configs.Count}");
+ _testOutputHelper.WriteLine($"Current Configs: {configs.Count}");
- var configResponse = await _dockerClient.Configs.InspectConfigAsync(createdConfig.ID);
+ var configResponse = await _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID);
Assert.NotNull(configResponse);
@@ -49,10 +49,10 @@ public async Task SwarmConfig_CanCreateAndRead()
Assert.Equal(configResponse.Spec.Templating, testConfigSpec.Templating);
- _output.WriteLine("Config created is the same.");
+ _testOutputHelper.WriteLine("Config created is the same.");
- await _dockerClient.Configs.RemoveConfigAsync(createdConfig.ID);
+ await _testFixture.DockerClient.Configs.RemoveConfigAsync(createdConfig.ID);
- await Assert.ThrowsAsync(() => _dockerClient.Configs.InspectConfigAsync(createdConfig.ID));
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID));
}
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
index d8d2d10d..31e1b747 100644
--- a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs
@@ -3,101 +3,55 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class IContainerOperationsTests
{
- private readonly CancellationTokenSource _cts;
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
- private readonly TestOutput _output;
- private readonly string _imageId;
- private readonly DockerClientConfiguration _dockerClientConfiguration;
- private readonly DockerClient _dockerClient;
-
- public IContainerOperationsTests(TestFixture testFixture, ITestOutputHelper outputHelper)
+ public IContainerOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
{
- _output = new TestOutput(outputHelper);
-
- _dockerClientConfiguration = testFixture.DockerClientConfiguration;
- _dockerClient = _dockerClientConfiguration.CreateClient();
-
- // Do not wait forever in case it gets stuck
- _cts = CancellationTokenSource.CreateLinkedTokenSource(testFixture.Cts.Token);
- _cts.CancelAfter(TimeSpan.FromMinutes(5));
- _cts.Token.Register(() => throw new TimeoutException("ContainerOperationsTests timeout"));
-
- _imageId = testFixture.Image.ID;
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
}
[Fact]
public async Task CreateContainerAsync_CreatesContainer()
{
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr
},
- _cts.Token
+ _testFixture.Cts.Token
);
Assert.NotNull(createContainerResponse);
Assert.NotEmpty(createContainerResponse.ID);
}
- // Timeout causing task to be cancelled
- [Theory(Skip = "There is nothing we can do to delay CreateContainerAsync (aka HttpClient.SendAsync) deterministic. We cannot control if it responses successful before the timeout.")]
- [InlineData(1)]
- [InlineData(5)]
- [InlineData(10)]
- public async Task CreateContainerAsync_TimeoutExpires_Fails(int millisecondsTimeout)
- {
- using var dockerClientWithTimeout = _dockerClientConfiguration.CreateClient();
-
- dockerClientWithTimeout.DefaultTimeout = TimeSpan.FromMilliseconds(millisecondsTimeout);
-
- _output.WriteLine($"Time available for CreateContainer operation: {millisecondsTimeout} ms'");
-
- var timer = new Stopwatch();
- timer.Start();
-
- var createContainerTask = dockerClientWithTimeout.Containers.CreateContainerAsync(
- new CreateContainerParameters
- {
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
- },
- _cts.Token);
-
- _ = await Assert.ThrowsAsync(() => createContainerTask);
-
- timer.Stop();
- _output.WriteLine($"CreateContainerOperation finished after {timer.ElapsedMilliseconds} ms");
-
- Assert.True(createContainerTask.IsCanceled);
- Assert.True(createContainerTask.IsCompleted);
- }
-
[Fact]
public async Task GetContainerLogs_Tty_False_Follow_True_TaskIsCompleted()
{
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5));
- var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync(
+ var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -107,13 +61,13 @@ await _dockerClient.Containers.StartContainerAsync(
Follow = true
},
containerLogsCts.Token,
- new Progress(m => _output.WriteLine(m))
+ new Progress(m => _testOutputHelper.WriteLine(m))
);
- await _dockerClient.Containers.StopContainerAsync(
+ await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
new ContainerStopParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
await containerLogsTask;
@@ -125,25 +79,25 @@ public async Task GetContainerLogs_Tty_False_Follow_False_ReadsLogs()
{
var logList = new List();
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
await Task.Delay(TimeSpan.FromSeconds(5));
- await _dockerClient.Containers.GetContainerLogsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -152,17 +106,17 @@ await _dockerClient.Containers.GetContainerLogsAsync(
Timestamps = true,
Follow = false
},
- default,
- new Progress(m => { logList.Add(m); _output.WriteLine(m); })
+ _testFixture.Cts.Token,
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
);
- await _dockerClient.Containers.StopContainerAsync(
+ await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
new ContainerStopParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- _output.WriteLine($"Line count: {logList.Count}");
+ _testOutputHelper.WriteLine($"Line count: {logList.Count}");
Assert.NotEmpty(logList);
}
@@ -172,25 +126,25 @@ public async Task GetContainerLogs_Tty_True_Follow_False_ReadsLogs()
{
var logList = new List();
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
await Task.Delay(TimeSpan.FromSeconds(5));
- await _dockerClient.Containers.GetContainerLogsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -199,17 +153,17 @@ await _dockerClient.Containers.GetContainerLogsAsync(
Timestamps = true,
Follow = false
},
- default,
- new Progress(m => { _output.WriteLine(m); logList.Add(m); })
+ _testFixture.Cts.Token,
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
);
- await _dockerClient.Containers.StopContainerAsync(
+ await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
new ContainerStopParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- _output.WriteLine($"Line count: {logList.Count}");
+ _testOutputHelper.WriteLine($"Line count: {logList.Count}");
Assert.NotEmpty(logList);
}
@@ -219,25 +173,25 @@ public async Task GetContainerLogs_Tty_False_Follow_True_Requires_Task_To_Be_Can
{
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5));
- await Assert.ThrowsAsync(() => _dockerClient.Containers.GetContainerLogsAsync(
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -247,7 +201,7 @@ await Assert.ThrowsAsync(() => _dockerClient.Containers.G
Follow = true
},
containerLogsCts.Token,
- new Progress(m => _output.WriteLine(m))
+ new Progress(m => _testOutputHelper.WriteLine(m))
));
}
@@ -256,25 +210,25 @@ public async Task GetContainerLogs_Tty_True_Follow_True_Requires_Task_To_Be_Canc
{
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5));
- var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync(
+ var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -284,7 +238,7 @@ await _dockerClient.Containers.StartContainerAsync(
Follow = true
},
containerLogsCts.Token,
- new Progress(m => _output.WriteLine(m))
+ new Progress(m => _testOutputHelper.WriteLine(m))
);
await Assert.ThrowsAsync(() => containerLogsTask);
@@ -296,25 +250,25 @@ public async Task GetContainerLogs_Tty_True_Follow_True_ReadsLogs_TaskIsCancelle
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
var logList = new List();
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5));
- var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync(
+ var containerLogsTask = _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
@@ -324,20 +278,19 @@ await _dockerClient.Containers.StartContainerAsync(
Follow = true
},
containerLogsCts.Token,
- new Progress(m => { _output.WriteLine(m); logList.Add(m); })
+ new Progress(m => { _testOutputHelper.WriteLine(m); logList.Add(m); })
);
await Task.Delay(TimeSpan.FromSeconds(5));
- await _dockerClient.Containers.StopContainerAsync(
+ await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
new ContainerStopParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
-
await Assert.ThrowsAsync(() => containerLogsTask);
- _output.WriteLine($"Line count: {logList.Count}");
+ _testOutputHelper.WriteLine($"Line count: {logList.Count}");
Assert.NotEmpty(logList);
}
@@ -345,34 +298,34 @@ await _dockerClient.Containers.StopContainerAsync(
[Fact]
public async Task GetContainerStatsAsync_Tty_False_Stream_False_ReadsStats()
{
- using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
var containerStatsList = new List();
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
- _cts.Token
+ _testFixture.Cts.Token
);
- _ = await _dockerClient.Containers.StartContainerAsync(
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
tcs.CancelAfter(TimeSpan.FromSeconds(10));
- await _dockerClient.Containers.GetContainerStatsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerStatsAsync(
createContainerResponse.ID,
new ContainerStatsParameters
{
Stream = false
},
- new Progress(m => { _output.WriteLine(m.ID); containerStatsList.Add(m); }),
+ new Progress(m => { _testOutputHelper.WriteLine(m.ID); containerStatsList.Add(m); }),
tcs.Token
);
@@ -380,31 +333,33 @@ await _dockerClient.Containers.GetContainerStatsAsync(
Assert.NotEmpty(containerStatsList);
Assert.Single(containerStatsList);
- _output.WriteLine($"ConntainerStats count: {containerStatsList.Count}");
+ _testOutputHelper.WriteLine($"ConntainerStats count: {containerStatsList.Count}");
}
[Fact]
public async Task GetContainerStatsAsync_Tty_False_StreamStats()
{
- using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
using (tcs.Token.Register(() => throw new TimeoutException("GetContainerStatsAsync_Tty_False_StreamStats")))
{
- _output.WriteLine($"Running test {MethodBase.GetCurrentMethod().Module}->{MethodBase.GetCurrentMethod().Name}");
+ var method = MethodBase.GetCurrentMethod();
+
+ _testOutputHelper.WriteLine($"Running test '{method!.Module}' -> '{method!.Name}'");
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
- _cts.Token
+ _testFixture.Cts.Token
);
- _ = await _dockerClient.Containers.StartContainerAsync(
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
List containerStatsList = new List();
@@ -413,13 +368,13 @@ public async Task GetContainerStatsAsync_Tty_False_StreamStats()
linkedCts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
- await _dockerClient.Containers.GetContainerStatsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerStatsAsync(
createContainerResponse.ID,
new ContainerStatsParameters
{
Stream = true
},
- new Progress(m => { containerStatsList.Add(m); _output.WriteLine(JsonSerializer.Instance.Serialize(m)); }),
+ new Progress(m => { containerStatsList.Add(m); _testOutputHelper.WriteLine(JsonSerializer.Instance.Serialize(m)); }),
linkedCts.Token
);
}
@@ -428,7 +383,7 @@ await _dockerClient.Containers.GetContainerStatsAsync(
// this is expected to happen on task cancelaltion
}
- _output.WriteLine($"Container stats count: {containerStatsList.Count}");
+ _testOutputHelper.WriteLine($"Container stats count: {containerStatsList.Count}");
Assert.NotEmpty(containerStatsList);
}
}
@@ -436,34 +391,34 @@ await _dockerClient.Containers.GetContainerStatsAsync(
[Fact]
public async Task GetContainerStatsAsync_Tty_True_Stream_False_ReadsStats()
{
- using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
var containerStatsList = new List();
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- _ = await _dockerClient.Containers.StartContainerAsync(
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
tcs.CancelAfter(TimeSpan.FromSeconds(10));
- await _dockerClient.Containers.GetContainerStatsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerStatsAsync(
createContainerResponse.ID,
new ContainerStatsParameters
{
Stream = false
},
- new Progress(m => { _output.WriteLine(m.ID); containerStatsList.Add(m); }),
+ new Progress(m => { _testOutputHelper.WriteLine(m.ID); containerStatsList.Add(m); }),
tcs.Token
);
@@ -471,32 +426,32 @@ await _dockerClient.Containers.GetContainerStatsAsync(
Assert.NotEmpty(containerStatsList);
Assert.Single(containerStatsList);
- _output.WriteLine($"ConntainerStats count: {containerStatsList.Count}");
+ _testOutputHelper.WriteLine($"ConntainerStats count: {containerStatsList.Count}");
}
[Fact]
public async Task GetContainerStatsAsync_Tty_True_StreamStats()
{
- using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
using (tcs.Token.Register(() => throw new TimeoutException("GetContainerStatsAsync_Tty_True_StreamStats")))
{
- _output.WriteLine("Running test GetContainerStatsAsync_Tty_True_StreamStats");
+ _testOutputHelper.WriteLine("Running test GetContainerStatsAsync_Tty_True_StreamStats");
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- _ = await _dockerClient.Containers.StartContainerAsync(
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
List containerStatsList = new List();
@@ -506,23 +461,23 @@ public async Task GetContainerStatsAsync_Tty_True_StreamStats()
try
{
- await _dockerClient.Containers.GetContainerStatsAsync(
+ await _testFixture.DockerClient.Containers.GetContainerStatsAsync(
createContainerResponse.ID,
new ContainerStatsParameters
{
Stream = true
},
- new Progress(m => { containerStatsList.Add(m); _output.WriteLine(JsonSerializer.Instance.Serialize(m)); }),
+ new Progress(m => { containerStatsList.Add(m); _testOutputHelper.WriteLine(JsonSerializer.Instance.Serialize(m)); }),
linkedTcs.Token
);
}
catch (OperationCanceledException)
{
- // this is expected to happen on task cancelaltion
+ // This is expected to happen on task cancellation.
}
await Task.Delay(TimeSpan.FromSeconds(1));
- _output.WriteLine($"Container stats count: {containerStatsList.Count}");
+ _testOutputHelper.WriteLine($"Container stats count: {containerStatsList.Count}");
Assert.NotEmpty(containerStatsList);
}
}
@@ -530,63 +485,65 @@ await _dockerClient.Containers.GetContainerStatsAsync(
[Fact]
public async Task KillContainerAsync_ContainerRunning_Succeeds()
{
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
- Image = _imageId
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr
},
- _cts.Token);
+ _testFixture.Cts.Token);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- var inspectRunningContainerResponse = await _dockerClient.Containers.InspectContainerAsync(
+ var inspectRunningContainerResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync(
createContainerResponse.ID,
- _cts.Token);
+ _testFixture.Cts.Token);
- await _dockerClient.Containers.KillContainerAsync(
+ await _testFixture.DockerClient.Containers.KillContainerAsync(
createContainerResponse.ID,
new ContainerKillParameters(),
- _cts.Token);
+ _testFixture.Cts.Token);
- var inspectKilledContainerResponse = await _dockerClient.Containers.InspectContainerAsync(
+ var inspectKilledContainerResponse = await _testFixture.DockerClient.Containers.InspectContainerAsync(
createContainerResponse.ID,
- _cts.Token);
+ _testFixture.Cts.Token);
Assert.True(inspectRunningContainerResponse.State.Running);
Assert.False(inspectKilledContainerResponse.State.Running);
Assert.Equal("exited", inspectKilledContainerResponse.State.Status);
- _output.WriteLine("Killed");
- _output.WriteLine(JsonSerializer.Instance.Serialize(inspectKilledContainerResponse));
+ _testOutputHelper.WriteLine("Killed");
+ _testOutputHelper.WriteLine(JsonSerializer.Instance.Serialize(inspectKilledContainerResponse));
}
[Fact]
public async Task ListContainersAsync_ContainerExists_Succeeds()
{
- await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters()
+ await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString()
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
},
- _cts.Token);
+ _testFixture.Cts.Token);
- IList containerList = await _dockerClient.Containers.ListContainersAsync(
+ IList containerList = await _testFixture.DockerClient.Containers.ListContainersAsync(
new ContainersListParameters
{
Filters = new Dictionary>
{
["ancestor"] = new Dictionary
{
- [_imageId] = true
+ [_testFixture.Image.ID] = true
}
},
All = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
Assert.NotNull(containerList);
@@ -596,32 +553,32 @@ await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameter
[Fact]
public async Task ListProcessesAsync_RunningContainer_Succeeds()
{
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString()
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr
},
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.StartContainerAsync(
+ await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- var containerProcessesResponse = await _dockerClient.Containers.ListProcessesAsync(
+ var containerProcessesResponse = await _testFixture.DockerClient.Containers.ListProcessesAsync(
createContainerResponse.ID,
new ContainerListProcessesParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- _output.WriteLine($"Title '{containerProcessesResponse.Titles[0]}' - '{containerProcessesResponse.Titles[1]}' - '{containerProcessesResponse.Titles[2]}' - '{containerProcessesResponse.Titles[3]}'");
+ _testOutputHelper.WriteLine($"Title '{containerProcessesResponse.Titles[0]}' - '{containerProcessesResponse.Titles[1]}' - '{containerProcessesResponse.Titles[2]}' - '{containerProcessesResponse.Titles[3]}'");
foreach (var processes in containerProcessesResponse.Processes)
{
- _output.WriteLine($"Process '{processes[0]}' - ''{processes[1]}' - '{processes[2]}' - '{processes[3]}'");
+ _testOutputHelper.WriteLine($"Process '{processes[0]}' - ''{processes[1]}' - '{processes[2]}' - '{processes[3]}'");
}
Assert.NotNull(containerProcessesResponse);
@@ -631,32 +588,32 @@ await _dockerClient.Containers.StartContainerAsync(
[Fact]
public async Task RemoveContainerAsync_ContainerExists_Succeedes()
{
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString()
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
},
- _cts.Token
+ _testFixture.Cts.Token
);
- ContainerInspectResponse inspectCreatedContainer = await _dockerClient.Containers.InspectContainerAsync(
+ ContainerInspectResponse inspectCreatedContainer = await _testFixture.DockerClient.Containers.InspectContainerAsync(
createContainerResponse.ID,
- _cts.Token
+ _testFixture.Cts.Token
);
- await _dockerClient.Containers.RemoveContainerAsync(
+ await _testFixture.DockerClient.Containers.RemoveContainerAsync(
createContainerResponse.ID,
new ContainerRemoveParameters
{
Force = true
},
- _cts.Token
+ _testFixture.Cts.Token
);
- Task inspectRemovedContainerTask = _dockerClient.Containers.InspectContainerAsync(
+ Task inspectRemovedContainerTask = _testFixture.DockerClient.Containers.InspectContainerAsync(
createContainerResponse.ID,
- _cts.Token
+ _testFixture.Cts.Token
);
Assert.NotNull(inspectCreatedContainer.State);
@@ -666,19 +623,19 @@ await _dockerClient.Containers.RemoveContainerAsync(
[Fact]
public async Task StartContainerAsync_ContainerExists_Succeeds()
{
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters()
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
+ new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString()
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr,
},
- _cts.Token
+ _testFixture.Cts.Token
);
- var startContainerResult = await _dockerClient.Containers.StartContainerAsync(
+ var startContainerResult = await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
Assert.True(startContainerResult);
@@ -687,10 +644,10 @@ public async Task StartContainerAsync_ContainerExists_Succeeds()
[Fact]
public async Task StartContainerAsync_ContainerNotExists_ThrowsException()
{
- Task startContainerTask = _dockerClient.Containers.StartContainerAsync(
+ Task startContainerTask = _testFixture.DockerClient.Containers.StartContainerAsync(
Guid.NewGuid().ToString(),
new ContainerStartParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
await Assert.ThrowsAsync(() => startContainerTask);
@@ -703,20 +660,20 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio
using var waitContainerCts = new CancellationTokenSource(delay: TimeSpan.FromMinutes(5));
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
- Image = _imageId,
- Name = Guid.NewGuid().ToString(),
+ Image = _testFixture.Image.ID,
+ Entrypoint = CommonCommands.EchoToStdoutAndStderr
},
waitContainerCts.Token
);
- _output.WriteLine($"CreateContainerResponse: '{JsonSerializer.Instance.Serialize(createContainerResponse)}'");
+ _testOutputHelper.WriteLine($"CreateContainerResponse: '{JsonSerializer.Instance.Serialize(createContainerResponse)}'");
- _ = await _dockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), waitContainerCts.Token);
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), waitContainerCts.Token);
- _output.WriteLine("Starting timeout to cancel WaitContainer operation.");
+ _testOutputHelper.WriteLine("Starting timeout to cancel WaitContainer operation.");
TimeSpan delay = TimeSpan.FromSeconds(5);
@@ -724,14 +681,14 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio
stopWatch.Start();
// Will wait forever here if cancelation fails.
- var waitContainerTask = _dockerClient.Containers.WaitContainerAsync(createContainerResponse.ID, waitContainerCts.Token);
+ var waitContainerTask = _testFixture.DockerClient.Containers.WaitContainerAsync(createContainerResponse.ID, waitContainerCts.Token);
_ = await Assert.ThrowsAsync(() => waitContainerTask);
stopWatch.Stop();
- _output.WriteLine($"WaitContainerTask was cancelled after {stopWatch.ElapsedMilliseconds} ms");
- _output.WriteLine($"WaitContainerAsync: {stopWatch.Elapsed} elapsed");
+ _testOutputHelper.WriteLine($"WaitContainerTask was cancelled after {stopWatch.ElapsedMilliseconds} ms");
+ _testOutputHelper.WriteLine($"WaitContainerAsync: {stopWatch.Elapsed} elapsed");
// Task should be cancelled when CancelAfter timespan expires
TimeSpan tolerance = TimeSpan.FromMilliseconds(500);
@@ -741,46 +698,41 @@ public async Task WaitContainerAsync_TokenIsCancelled_OperationCancelledExceptio
}
[Fact]
- public async Task CreateImageAsync_NonexistantImage_ThrowsDockerImageNotFoundException()
+ public async Task CreateImageAsync_NonExistingImage_ThrowsDockerImageNotFoundException()
{
- var parameters = new CreateContainerParameters
- {
- Image = "no-such-image-ytfghbkufhresdhtrjygvb",
- };
- Func op = async () => await _dockerClient.Containers.CreateContainerAsync(parameters);
+ var createContainerParameters = new CreateContainerParameters();
+ createContainerParameters.Image = Guid.NewGuid().ToString("D");
+
+ Func op = () => _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters);
await Assert.ThrowsAsync(op);
}
- [Fact]
+ [Fact(Skip = "Refactor IExecOperations operations and writing/reading to/from stdin and stdout. It does not work reliably.")]
public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException()
{
// Given
- Exception exception;
+ var createContainerParameters = new CreateContainerParameters();
+ createContainerParameters.Image = _testFixture.Image.ID;
+ createContainerParameters.Entrypoint = CommonCommands.SleepInfinity;
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
- new CreateContainerParameters
- {
- Image = _imageId
- });
+ var containerExecCreateParameters = new ContainerExecCreateParameters();
+ containerExecCreateParameters.AttachStdout = true;
+ containerExecCreateParameters.AttachStderr = true;
+ containerExecCreateParameters.AttachStdin = true;
+ containerExecCreateParameters.Cmd = new[] { "/bin/sh", "-c", "read line; echo Done" };
- _ = await _dockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters());
+ var containerExecStartParameters = new ContainerExecStartParameters();
- var containerExecCreateResponse = await _dockerClient.Exec.ExecCreateContainerAsync(createContainerResponse.ID,
- new ContainerExecCreateParameters
- {
- AttachStdout = true,
- AttachStderr = true,
- AttachStdin = true,
- Cmd = new [] { string.Empty }
- });
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters);
+ _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters());
// When
- using (var stream = await _dockerClient.Exec.StartAndAttachContainerExecAsync(containerExecCreateResponse.ID, false))
- {
- var buffer = new byte[] { 10 };
- exception = await Record.ExceptionAsync(() => stream.WriteAsync(buffer, 0, buffer.Length, default));
- }
+ var containerExecCreateResponse = await _testFixture.DockerClient.Exec.ExecCreateContainerAsync(createContainerResponse.ID, containerExecCreateParameters);
+ using var stream = await _testFixture.DockerClient.Exec.StartWithConfigContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);
+
+ var buffer = new byte[] { 10 };
+ var exception = await Record.ExceptionAsync(() => stream.WriteAsync(buffer, 0, buffer.Length, _testFixture.Cts.Token));
// Then
Assert.Null(exception);
diff --git a/test/Docker.DotNet.Tests/IImageOperationsTests.cs b/test/Docker.DotNet.Tests/IImageOperationsTests.cs
index e74c80b5..50d522f7 100644
--- a/test/Docker.DotNet.Tests/IImageOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/IImageOperationsTests.cs
@@ -3,38 +3,25 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class IImageOperationsTests
{
- private readonly CancellationTokenSource _cts;
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
- private readonly TestOutput _output;
- private readonly string _repositoryName;
- private readonly string _tag;
- private readonly DockerClient _dockerClient;
-
- public IImageOperationsTests(TestFixture testFixture, ITestOutputHelper outputHelper)
+ public IImageOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
{
- _output = new TestOutput(outputHelper);
-
- _dockerClient = testFixture.DockerClient;
-
- // Do not wait forever in case it gets stuck
- _cts = CancellationTokenSource.CreateLinkedTokenSource(testFixture.Cts.Token);
- _cts.CancelAfter(TimeSpan.FromMinutes(5));
- _cts.Token.Register(() => throw new TimeoutException("ImageOperationTests timeout"));
-
- _repositoryName = testFixture.Repository;
- _tag = testFixture.Tag;
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
}
[Fact]
- public async Task CreateImageAsync_TaskCancelled_ThowsTaskCanceledException()
+ public async Task CreateImageAsync_TaskCancelled_ThrowsTaskCanceledException()
{
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
var newTag = Guid.NewGuid().ToString();
var newRepositoryName = Guid.NewGuid().ToString();
- await _dockerClient.Images.TagImageAsync(
- $"{_repositoryName}:{_tag}",
+ await _testFixture.DockerClient.Images.TagImageAsync(
+ $"{_testFixture.Repository}:{_testFixture.Tag}",
new ImageTagParameters
{
RepositoryName = newRepositoryName,
@@ -43,13 +30,13 @@ await _dockerClient.Images.TagImageAsync(
cts.Token
);
- var createImageTask = _dockerClient.Images.CreateImageAsync(
+ var createImageTask = _testFixture.DockerClient.Images.CreateImageAsync(
new ImagesCreateParameters
{
FromImage = $"{newRepositoryName}:{newTag}"
},
null,
- new Progress((message) => _output.WriteLine(JsonSerializer.Instance.Serialize(message))),
+ new Progress(message => _testOutputHelper.WriteLine(JsonSerializer.Instance.Serialize(message))),
cts.Token);
TimeSpan delay = TimeSpan.FromMilliseconds(5);
@@ -63,8 +50,8 @@ await _dockerClient.Images.TagImageAsync(
[Fact]
public Task CreateImageAsync_ErrorResponse_ThrowsDockerApiException()
{
- return Assert.ThrowsAsync(() => _dockerClient.Images.CreateImageAsync(
- new ImagesCreateParameters()
+ return Assert.ThrowsAsync(() => _testFixture.DockerClient.Images.CreateImageAsync(
+ new ImagesCreateParameters
{
FromImage = "1.2.3.Apparently&this$is+not-a_valid%repository//name",
Tag = "ancient-one"
@@ -76,30 +63,30 @@ public async Task DeleteImageAsync_RemovesImage()
{
var newImageTag = Guid.NewGuid().ToString();
- await _dockerClient.Images.TagImageAsync(
- $"{_repositoryName}:{_tag}",
+ await _testFixture.DockerClient.Images.TagImageAsync(
+ $"{_testFixture.Repository}:{_testFixture.Tag}",
new ImageTagParameters
{
- RepositoryName = _repositoryName,
+ RepositoryName = _testFixture.Repository,
Tag = newImageTag
},
- _cts.Token
+ _testFixture.Cts.Token
);
- var inspectExistingImageResponse = await _dockerClient.Images.InspectImageAsync(
- $"{_repositoryName}:{newImageTag}",
- _cts.Token
+ var inspectExistingImageResponse = await _testFixture.DockerClient.Images.InspectImageAsync(
+ $"{_testFixture.Repository}:{newImageTag}",
+ _testFixture.Cts.Token
);
- await _dockerClient.Images.DeleteImageAsync(
- $"{_repositoryName}:{newImageTag}",
+ await _testFixture.DockerClient.Images.DeleteImageAsync(
+ $"{_testFixture.Repository}:{newImageTag}",
new ImageDeleteParameters(),
- _cts.Token
+ _testFixture.Cts.Token
);
- Task inspectDeletedImageTask = _dockerClient.Images.InspectImageAsync(
- $"{_repositoryName}:{newImageTag}",
- _cts.Token
+ Task inspectDeletedImageTask = _testFixture.DockerClient.Images.InspectImageAsync(
+ $"{_testFixture.Repository}:{newImageTag}",
+ _testFixture.Cts.Token
);
Assert.NotNull(inspectExistingImageResponse);
diff --git a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs
index ce25fb04..e620446c 100644
--- a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs
@@ -3,20 +3,13 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class ISwarmOperationsTests
{
- private readonly CancellationTokenSource _cts;
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
- private readonly DockerClient _dockerClient;
- private readonly string _imageId;
-
- public ISwarmOperationsTests(TestFixture testFixture)
+ public ISwarmOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
{
- // Do not wait forever in case it gets stuck
- _cts = CancellationTokenSource.CreateLinkedTokenSource(testFixture.Cts.Token);
- _cts.CancelAfter(TimeSpan.FromMinutes(5));
- _cts.Token.Register(() => throw new TimeoutException("SwarmOperationTests timeout"));
-
- _dockerClient = testFixture.DockerClient;
- _imageId = testFixture.Image.ID;
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
}
[Fact]
@@ -24,34 +17,34 @@ public async Task GetFilteredServicesByName_Succeeds()
{
var serviceName = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}";
- var firstServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = serviceName,
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var secondServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var thirdServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var services = await _dockerClient.Swarm.ListServicesAsync(new ServiceListParameters
+ var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters
{
Filters = new Dictionary>
{
@@ -64,42 +57,42 @@ public async Task GetFilteredServicesByName_Succeeds()
Assert.Single(services);
- await _dockerClient.Swarm.RemoveServiceAsync(firstServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(secondServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
}
[Fact]
public async Task GetFilteredServicesById_Succeeds()
{
- var firstServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var secondServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var thirdServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var services = await _dockerClient.Swarm.ListServicesAsync(new ServiceListParameters
+ var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters
{
Filters = new Dictionary>
{
@@ -112,69 +105,69 @@ public async Task GetFilteredServicesById_Succeeds()
Assert.Single(services);
- await _dockerClient.Swarm.RemoveServiceAsync(firstServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(secondServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
}
[Fact]
public async Task GetServices_Succeeds()
{
- var initialServiceCount = (await _dockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None)).Count();
+ var initialServiceCount = (await _testFixture.DockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None)).Count();
- var firstServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var firstServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var secondServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var thirdServiceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}",
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } }
}
})).ID;
- var services = await _dockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None);
+ var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None);
Assert.True(services.Count() > initialServiceCount);
- await _dockerClient.Swarm.RemoveServiceAsync(firstServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(secondServiceId);
- await _dockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId);
}
[Fact]
public async Task GetServiceLogs_Succeeds()
{
var cts = new CancellationTokenSource();
- var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cts.Token);
+ var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token, cts.Token);
var serviceName = $"service-withLogs-{Guid.NewGuid().ToString().Substring(1, 10)}";
- var serviceId = (await _dockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
+ var serviceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters
{
Service = new ServiceSpec
{
Name = serviceName,
- TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID, Command = CommonCommands.EchoToStdoutAndStderr } }
}
})).ID;
- var stream = await _dockerClient.Swarm.GetServiceLogsAsync(serviceName, false, new ServiceLogsParameters
+ using var stream = await _testFixture.DockerClient.Swarm.GetServiceLogsAsync(serviceName, false, new ServiceLogsParameters
{
Follow = true,
ShowStdout = true,
@@ -231,12 +224,12 @@ public async Task GetServiceLogs_Succeeds()
// Reset the CancellationTokenSource for the next attempt
cts = new CancellationTokenSource();
- linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cts.Token);
+ linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token, cts.Token);
cts.CancelAfter(delay);
}
}
- if (logLines.Any() && logLines.First().Contains("[INF]"))
+ if (logLines.Any())
{
break;
}
@@ -252,8 +245,7 @@ public async Task GetServiceLogs_Succeeds()
Assert.NotNull(logLines);
Assert.NotEmpty(logLines);
- Assert.Contains("[INF]", logLines.First());
- await _dockerClient.Swarm.RemoveServiceAsync(serviceId);
+ await _testFixture.DockerClient.Swarm.RemoveServiceAsync(serviceId);
}
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs
index 5e1f814b..2857647d 100644
--- a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs
+++ b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs
@@ -3,46 +3,33 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class ISystemOperationsTests
{
- private readonly CancellationTokenSource _cts;
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
- private readonly TestOutput _output;
- private readonly string _repositoryName;
- private readonly string _tag;
- private readonly DockerClient _dockerClient;
-
- public ISystemOperationsTests(TestFixture testFixture, ITestOutputHelper outputHelper)
+ public ISystemOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
{
- _output = new TestOutput(outputHelper);
-
- _dockerClient = testFixture.DockerClient;
-
- // Do not wait forever in case it gets stuck
- _cts = CancellationTokenSource.CreateLinkedTokenSource(testFixture.Cts.Token);
- _cts.CancelAfter(TimeSpan.FromMinutes(5));
- _cts.Token.Register(() => throw new TimeoutException("SystemOperationsTests timeout"));
-
- _repositoryName = testFixture.Repository;
- _tag = testFixture.Tag;
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
}
[Fact]
public void Docker_IsRunning()
{
var dockerProcess = Process.GetProcesses().FirstOrDefault(process => process.ProcessName.Equals("docker", StringComparison.InvariantCultureIgnoreCase) || process.ProcessName.Equals("dockerd", StringComparison.InvariantCultureIgnoreCase));
- Assert.NotNull(dockerProcess); // docker is not running
+ Assert.NotNull(dockerProcess);
}
[Fact]
public async Task GetSystemInfoAsync_Succeeds()
{
- var info = await _dockerClient.System.GetSystemInfoAsync();
+ var info = await _testFixture.DockerClient.System.GetSystemInfoAsync();
Assert.NotNull(info.Architecture);
}
[Fact]
public async Task GetVersionAsync_Succeeds()
{
- var version = await _dockerClient.System.GetVersionAsync();
+ var version = await _testFixture.DockerClient.System.GetVersionAsync();
Assert.NotNull(version.APIVersion);
}
@@ -55,20 +42,20 @@ public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled()
await cts.CancelAsync();
await Task.Delay(1);
- await Assert.ThrowsAsync(() => _dockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token));
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token));
}
[Fact]
public async Task MonitorEventsAsync_NullParameters_Throws()
{
- await Assert.ThrowsAsync(() => _dockerClient.System.MonitorEventsAsync(null, null));
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(null, null));
}
[Fact]
public async Task MonitorEventsAsync_NullProgress_Throws()
{
- await Assert.ThrowsAsync(() => _dockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), null));
+ await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), null));
}
[Fact]
@@ -78,29 +65,29 @@ public async Task MonitorEventsAsync_Succeeds()
var wasProgressCalled = false;
- var progressMessage = new Progress((m) =>
+ var progressMessage = new Progress(m =>
{
- _output.WriteLine($"MonitorEventsAsync_Succeeds: Message - {m.Action} - {m.Status} {m.From} - {m.Type}");
+ _testOutputHelper.WriteLine($"MonitorEventsAsync_Succeeds: Message - {m.Action} - {m.Status} {m.From} - {m.Type}");
wasProgressCalled = true;
Assert.NotNull(m);
});
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
- var task = _dockerClient.System.MonitorEventsAsync(
+ var task = _testFixture.DockerClient.System.MonitorEventsAsync(
new ContainerEventsParameters(),
progressMessage,
cts.Token);
- await _dockerClient.Images.TagImageAsync($"{_repositoryName}:{_tag}", new ImageTagParameters { RepositoryName = _repositoryName, Tag = newTag }, _cts.Token);
+ await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }, _testFixture.Cts.Token);
- await _dockerClient.Images.DeleteImageAsync(
- name: $"{_repositoryName}:{newTag}",
+ await _testFixture.DockerClient.Images.DeleteImageAsync(
+ name: $"{_testFixture.Repository}:{newTag}",
new ImageDeleteParameters
{
Force = true
},
- _cts.Token);
+ _testFixture.Cts.Token);
// Give it some time for output operation to complete before cancelling task
await Task.Delay(TimeSpan.FromSeconds(1));
@@ -123,24 +110,24 @@ public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption()
try
{
// (1) Create monitor task
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
string newImageTag = Guid.NewGuid().ToString();
- var monitorTask = _dockerClient.System.MonitorEventsAsync(
+ var monitorTask = _testFixture.DockerClient.System.MonitorEventsAsync(
new ContainerEventsParameters(),
- new Progress((value) => _output.WriteLine($"DockerSystemEvent: {JsonSerializer.Instance.Serialize(value)}")),
+ new Progress(value => _testOutputHelper.WriteLine($"DockerSystemEvent: {JsonSerializer.Instance.Serialize(value)}")),
cts.Token);
// (2) Wait for some time to make sure we get into blocking IO call
await Task.Delay(100, CancellationToken.None);
// (3) Invoke another request that will attempt to grab the same buffer
- var listImagesTask1 = _dockerClient.Images.TagImageAsync(
- $"{_repositoryName}:{_tag}",
+ var listImagesTask1 = _testFixture.DockerClient.Images.TagImageAsync(
+ $"{_testFixture.Repository}:{_testFixture.Tag}",
new ImageTagParameters
{
- RepositoryName = _repositoryName,
+ RepositoryName = _testFixture.Repository,
Tag = newImageTag,
}, CancellationToken.None);
@@ -153,17 +140,17 @@ public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption()
// noop
}
- _output.WriteLine($"Waited for {sw.Elapsed.TotalMilliseconds} ms");
+ _testOutputHelper.WriteLine($"Waited for {sw.Elapsed.TotalMilliseconds} ms");
await cts.CancelAsync();
await listImagesTask1;
- await _dockerClient.Images.TagImageAsync(
- $"{_repositoryName}:{_tag}",
+ await _testFixture.DockerClient.Images.TagImageAsync(
+ $"{_testFixture.Repository}:{_testFixture.Tag}",
new ImageTagParameters
{
- RepositoryName = _repositoryName,
+ RepositoryName = _testFixture.Repository,
Tag = newImageTag,
}, CancellationToken.None);
@@ -180,31 +167,31 @@ await _dockerClient.Images.TagImageAsync(
public async Task MonitorEventsFiltered_Succeeds()
{
string newTag = $"MonitorTests-{Guid.NewGuid().ToString().Substring(1, 10)}";
- string newImageRespositoryName = Guid.NewGuid().ToString();
+ string newImageRepositoryName = Guid.NewGuid().ToString();
- await _dockerClient.Images.TagImageAsync(
- $"{_repositoryName}:{_tag}",
+ await _testFixture.DockerClient.Images.TagImageAsync(
+ $"{_testFixture.Repository}:{_testFixture.Tag}",
new ImageTagParameters
{
- RepositoryName = newImageRespositoryName,
+ RepositoryName = newImageRepositoryName,
Tag = newTag
},
- _cts.Token
+ _testFixture.Cts.Token
);
- ImageInspectResponse image = await _dockerClient.Images.InspectImageAsync(
- $"{newImageRespositoryName}:{newTag}",
- _cts.Token
+ ImageInspectResponse image = await _testFixture.DockerClient.Images.InspectImageAsync(
+ $"{newImageRepositoryName}:{newTag}",
+ _testFixture.Cts.Token
);
var progressCalledCounter = 0;
- var eventsParams = new ContainerEventsParameters()
+ var eventsParams = new ContainerEventsParameters
{
- Filters = new Dictionary>()
+ Filters = new Dictionary>
{
{
- "event", new Dictionary()
+ "event", new Dictionary
{
{
"tag", true
@@ -215,7 +202,7 @@ await _dockerClient.Images.TagImageAsync(
}
},
{
- "type", new Dictionary()
+ "type", new Dictionary
{
{
"image", true
@@ -223,7 +210,7 @@ await _dockerClient.Images.TagImageAsync(
}
},
{
- "image", new Dictionary()
+ "image", new Dictionary
{
{
image.ID, true
@@ -233,21 +220,21 @@ await _dockerClient.Images.TagImageAsync(
}
};
- var progress = new Progress((m) =>
+ var progress = new Progress(m =>
{
Interlocked.Increment(ref progressCalledCounter);
Assert.True(m.Status == "tag" || m.Status == "untag");
- _output.WriteLine($"MonitorEventsFiltered_Succeeds: Message received: {m.Action} - {m.Status} {m.From} - {m.Type}");
+ _testOutputHelper.WriteLine($"MonitorEventsFiltered_Succeeds: Message received: {m.Action} - {m.Status} {m.From} - {m.Type}");
});
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
- var task = Task.Run(() => _dockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token));
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token);
+ var task = Task.Run(() => _testFixture.DockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token));
- await _dockerClient.Images.TagImageAsync($"{_repositoryName}:{_tag}", new ImageTagParameters { RepositoryName = _repositoryName, Tag = newTag });
- await _dockerClient.Images.DeleteImageAsync($"{_repositoryName}:{newTag}", new ImageDeleteParameters());
+ await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag });
+ await _testFixture.DockerClient.Images.DeleteImageAsync($"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters());
- var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_repositoryName}:{_tag}" });
- await _dockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token);
+ var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_testFixture.Repository}:{_testFixture.Tag}", Entrypoint = CommonCommands.SleepInfinity });
+ await _testFixture.DockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token);
await Task.Delay(TimeSpan.FromSeconds(1));
await cts.CancelAsync();
@@ -261,6 +248,6 @@ await _dockerClient.Images.TagImageAsync(
[Fact]
public async Task PingAsync_Succeeds()
{
- await _dockerClient.System.PingAsync();
+ await _testFixture.DockerClient.System.PingAsync();
}
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs
index f1ed7d4c..e77ad7d1 100644
--- a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs
+++ b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs
@@ -3,46 +3,39 @@ namespace Docker.DotNet.Tests;
[Collection(nameof(TestCollection))]
public class IVolumeOperationsTests
{
- private readonly CancellationTokenSource _cts;
-
- private readonly DockerClient _dockerClient;
-
- public IVolumeOperationsTests(TestFixture testFixture)
- {
- _dockerClient = testFixture.DockerClient;
-
- // Do not wait forever in case it gets stuck
- _cts = CancellationTokenSource.CreateLinkedTokenSource(testFixture.Cts.Token);
- _cts.CancelAfter(TimeSpan.FromMinutes(5));
- _cts.Token.Register(() => throw new TimeoutException("VolumeOperationsTests timeout"));
- }
-
- [Fact]
- public async Task ListAsync_VolumeExists_Succeeds()
- {
- const string volumeName = "docker-dotnet-test-volume";
-
- await _dockerClient.Volumes.CreateAsync(new VolumesCreateParameters
- {
- Name = volumeName,
- },
- _cts.Token);
-
- try
- {
-
- var response = await _dockerClient.Volumes.ListAsync(new VolumesListParameters()
- {
- Filters = new Dictionary>(),
- },
- _cts.Token);
-
- Assert.Contains(volumeName, response.Volumes.Select(volume => volume.Name));
-
- }
- finally
- {
- await _dockerClient.Volumes.RemoveAsync(volumeName, force: true, _cts.Token);
- }
- }
+ private readonly TestFixture _testFixture;
+ private readonly ITestOutputHelper _testOutputHelper;
+
+ public IVolumeOperationsTests(TestFixture testFixture, ITestOutputHelper testOutputHelper)
+ {
+ _testFixture = testFixture;
+ _testOutputHelper = testOutputHelper;
+ }
+
+ [Fact]
+ public async Task ListAsync_VolumeExists_Succeeds()
+ {
+ const string volumeName = "docker-dotnet-test-volume";
+
+ await _testFixture.DockerClient.Volumes.CreateAsync(new VolumesCreateParameters
+ {
+ Name = volumeName,
+ },
+ _testFixture.Cts.Token);
+
+ try
+ {
+ var response = await _testFixture.DockerClient.Volumes.ListAsync(new VolumesListParameters
+ {
+ Filters = new Dictionary>(),
+ },
+ _testFixture.Cts.Token);
+
+ Assert.Contains(volumeName, response.Volumes.Select(volume => volume.Name));
+ }
+ finally
+ {
+ await _testFixture.DockerClient.Volumes.RemoveAsync(volumeName, force: true, _testFixture.Cts.Token);
+ }
+ }
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/TestFixture.cs b/test/Docker.DotNet.Tests/TestFixture.cs
index 9ae79c6b..8ae4634b 100644
--- a/test/Docker.DotNet.Tests/TestFixture.cs
+++ b/test/Docker.DotNet.Tests/TestFixture.cs
@@ -1,36 +1,30 @@
namespace Docker.DotNet.Tests;
-public sealed class TestFixture : IAsyncLifetime, IDisposable
+[CollectionDefinition(nameof(TestCollection))]
+public sealed class TestCollection : ICollectionFixture;
+
+public sealed class TestFixture : Progress, IAsyncLifetime, IDisposable, ILogger
{
- ///
- /// The Docker image name.
- ///
- private const string Name = "nats";
+ private const LogLevel MinLogLevel = LogLevel.Debug;
- private static readonly Progress WriteProgressOutput;
+ private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
- private bool _hasInitializedSwarm;
+ private readonly IMessageSink _messageSink;
- static TestFixture()
- {
- WriteProgressOutput = new Progress(jsonMessage =>
- {
- var message = JsonSerializer.Instance.Serialize(jsonMessage);
- Console.WriteLine(message);
- Debug.WriteLine(message);
- });
- }
+ private bool _hasInitializedSwarm;
///
/// Initializes a new instance of the class.
///
- /// Thrown when tests are not finished within 5 minutes.
- public TestFixture()
+ /// The message sink.
+ /// Thrown when tests are not completed within 5 minutes.
+ public TestFixture(IMessageSink messageSink)
{
+ _messageSink = messageSink;
DockerClientConfiguration = new DockerClientConfiguration();
- DockerClient = DockerClientConfiguration.CreateClient();
+ DockerClient = DockerClientConfiguration.CreateClient(logger: this);
Cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- Cts.Token.Register(() => throw new TimeoutException("Docker.DotNet test timeout exception"));
+ Cts.Token.Register(() => throw new TimeoutException("Docker.DotNet tests timed out."));
}
///
@@ -46,14 +40,14 @@ public TestFixture()
= Guid.NewGuid().ToString("N");
///
- /// Gets the Docker client.
+ /// Gets the Docker client configuration.
///
- public DockerClient DockerClient { get; }
+ public DockerClientConfiguration DockerClientConfiguration { get; }
///
- /// Gets the Docker client configuration.
+ /// Gets the Docker client.
///
- public DockerClientConfiguration DockerClientConfiguration { get; }
+ public DockerClient DockerClient { get; }
///
/// Gets the cancellation token source.
@@ -68,8 +62,12 @@ public TestFixture()
///
public async Task InitializeAsync()
{
+ const string repository = "alpine";
+
+ const string tag = "3.20";
+
// Create image
- await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImage = Name, Tag = "latest" }, null, WriteProgressOutput, Cts.Token)
+ await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImage = repository, Tag = tag }, null, this, Cts.Token)
.ConfigureAwait(false);
// Get images
@@ -80,7 +78,7 @@ await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImag
{
["reference"] = new Dictionary
{
- [Name] = true
+ [repository + ":" + tag] = true
}
}
}, Cts.Token)
@@ -103,9 +101,7 @@ await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImag
}
catch
{
- const string message = "Couldn't init a new swarm, the node should take part of an existing one.";
- Console.WriteLine(message);
- Debug.WriteLine(message);
+ this.LogDebug("Couldn't init a new swarm, the node should take part of an existing one.");
_hasInitializedSwarm = false;
}
@@ -141,7 +137,7 @@ await DockerClient.Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true
{
["reference"] = new Dictionary
{
- [Image.RepoDigests.Single()] = true
+ [Image.ID] = true
}
},
All = true
@@ -164,13 +160,44 @@ await DockerClient.Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true
///
public void Dispose()
{
+ Cts.Dispose();
DockerClient.Dispose();
DockerClientConfiguration.Dispose();
- Cts.Dispose();
}
-}
-[CollectionDefinition(nameof(TestCollection))]
-public sealed class TestCollection : ICollectionFixture
-{
+ ///
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (IsEnabled(logLevel))
+ {
+ var message = exception == null ? formatter.Invoke(state, null) : string.Join(Environment.NewLine, formatter.Invoke(state, exception), exception);
+ _messageSink.OnMessage(new DiagnosticMessage(string.Format("[Docker.DotNet {0:hh\\:mm\\:ss\\.ff}] {1}", _stopwatch.Elapsed, message)));
+ }
+ }
+
+ ///
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return logLevel >= MinLogLevel;
+ }
+
+ ///
+ public IDisposable BeginScope(TState state) where TState : notnull
+ {
+ return new Disposable();
+ }
+
+ ///
+ protected override void OnReport(JSONMessage value)
+ {
+ var message = JsonSerializer.Instance.Serialize(value);
+ this.LogDebug("Progress: '{Progress}'.", message);
+ }
+
+ private sealed class Disposable : IDisposable
+ {
+ public void Dispose()
+ {
+ }
+ }
}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/TestOutput.cs b/test/Docker.DotNet.Tests/TestOutput.cs
deleted file mode 100644
index 57e3f244..00000000
--- a/test/Docker.DotNet.Tests/TestOutput.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Docker.DotNet.Tests;
-
-public class TestOutput
-{
- private readonly ITestOutputHelper _outputHelper;
-
- public TestOutput(ITestOutputHelper outputHelper)
- {
- _outputHelper = outputHelper;
- }
-
- public void WriteLine(string line)
- {
- Console.WriteLine(line);
- _outputHelper.WriteLine(line);
- System.Diagnostics.Debug.WriteLine(line);
- }
-}
\ No newline at end of file
diff --git a/test/Docker.DotNet.Tests/xunit.runner.json b/test/Docker.DotNet.Tests/xunit.runner.json
new file mode 100644
index 00000000..0e2fef59
--- /dev/null
+++ b/test/Docker.DotNet.Tests/xunit.runner.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+ "diagnosticMessages" : true
+}
\ No newline at end of file