From 124f0070f102d2687a018a2d07096398c5345ccb Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Sat, 25 Jun 2022 12:30:49 +0200
Subject: [PATCH] fix(#449): Don't let Docker Desktop for Windows choose the
random public host port
---
.../NpipeEndpointAuthenticationProvider.cs | 8 ++++-
.../UnixEndpointAuthenticationProvider.cs | 8 ++++-
.../Configurations/TestcontainersSettings.cs | 35 +++++++++++++++++++
.../Containers/ResourceReaper.cs | 8 +++--
...ockerEndpointAuthenticationProviderTest.cs | 1 -
5 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/src/Testcontainers/Builders/NpipeEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/NpipeEndpointAuthenticationProvider.cs
index 0c04ba523..919349655 100644
--- a/src/Testcontainers/Builders/NpipeEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/NpipeEndpointAuthenticationProvider.cs
@@ -3,13 +3,19 @@
using System;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;
+ using JetBrains.Annotations;
///
internal sealed class NpipeEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
#pragma warning disable S1075
- private static readonly Uri DockerEngine = new Uri("npipe://./pipe/docker_engine");
+ ///
+ /// Gets the named pipe Docker Engine endpoint.
+ ///
+ [PublicAPI]
+ public static Uri DockerEngine { get; }
+ = new Uri("npipe://./pipe/docker_engine");
#pragma warning restore S1075
diff --git a/src/Testcontainers/Builders/UnixEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/UnixEndpointAuthenticationProvider.cs
index 55b84aa52..0403a9f16 100644
--- a/src/Testcontainers/Builders/UnixEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/UnixEndpointAuthenticationProvider.cs
@@ -3,11 +3,17 @@
using System;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;
+ using JetBrains.Annotations;
///
internal sealed class UnixEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
- private static readonly Uri DockerEngine = new Uri("unix:/var/run/docker.sock");
+ ///
+ /// Gets the Unix socket Docker Engine endpoint.
+ ///
+ [NotNull]
+ public static Uri DockerEngine { get; }
+ = new Uri("unix:/var/run/docker.sock");
///
public bool IsApplicable()
diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs
index f57feccc6..5d9da826f 100644
--- a/src/Testcontainers/Configurations/TestcontainersSettings.cs
+++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs
@@ -1,6 +1,10 @@
namespace DotNet.Testcontainers.Configurations
{
+ using System;
+ using System.Net;
+ using System.Net.Sockets;
using System.Runtime.InteropServices;
+ using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using JetBrains.Annotations;
@@ -27,6 +31,19 @@ public static class TestcontainersSettings
public static IDockerImage ResourceReaperImage { get; set; }
= new DockerImage("ghcr.io/psanetra/ryuk:2021.12.20");
+ ///
+ /// Gets or sets the public host port.
+ ///
+ ///
+ /// The might not be able to connect to Ryuk on Docker Desktop for Windows.
+ /// Assigning a random port might run into the excluded port range. The container starts, but we can not establish a TCP connection.
+ /// https://github.com/docker/for-win/issues/3171
+ /// https://github.com/docker/for-win/issues/11584
+ ///
+ [PublicAPI]
+ public static Func ResourceReaperPublicHostPort { get; set; }
+ = GetResourceReaperPublicHostPort;
+
///
/// Gets or sets a prefix that applies to every image that is pulled from Docker Hub.
///
@@ -53,5 +70,23 @@ public static class TestcontainersSettings
[NotNull]
public static IOperatingSystem OS { get; set; }
= RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IOperatingSystem)new Windows() : new Unix();
+
+ private static ushort GetResourceReaperPublicHostPort(IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig)
+ {
+ // Let Docker choose the random public host port.
+ if (!NpipeEndpointAuthenticationProvider.DockerEngine.Equals(dockerEndpointAuthConfig.Endpoint))
+ {
+ return 0;
+ }
+
+ // Get the next available public host port. This might run in rare cases into a race-condition.
+ // It's still much more stable than letting Docker Desktop for Windows choose the port.
+ // !Attention!: This won't include Docker Engines exposed via TCP.
+ using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+ {
+ socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ return (ushort)((IPEndPoint)socket.LocalEndPoint).Port;
+ }
+ }
}
}
diff --git a/src/Testcontainers/Containers/ResourceReaper.cs b/src/Testcontainers/Containers/ResourceReaper.cs
index 48b0da574..bf1c23725 100644
--- a/src/Testcontainers/Containers/ResourceReaper.cs
+++ b/src/Testcontainers/Containers/ResourceReaper.cs
@@ -37,14 +37,16 @@ public sealed class ResourceReaper : IAsyncDisposable
private ResourceReaper(Guid sessionId, IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, string ryukImage)
{
+ dockerEndpointAuthConfig = dockerEndpointAuthConfig ?? TestcontainersSettings.OS.DockerEndpointAuthConfig;
+ ryukImage = ryukImage ?? TestcontainersSettings.ResourceReaperImage.FullName;
this.resourceReaperContainer = new TestcontainersBuilder()
.WithName($"testcontainers-ryuk-{sessionId:D}")
- .WithDockerEndpoint(dockerEndpointAuthConfig ?? TestcontainersSettings.OS.DockerEndpointAuthConfig)
- .WithImage(ryukImage ?? TestcontainersSettings.ResourceReaperImage.FullName)
+ .WithDockerEndpoint(dockerEndpointAuthConfig)
+ .WithImage(ryukImage)
.WithAutoRemove(true)
.WithCleanUp(false)
.WithExposedPort(RyukPort)
- .WithPortBinding(RyukPort, true)
+ .WithPortBinding(TestcontainersSettings.ResourceReaperPublicHostPort.Invoke(dockerEndpointAuthConfig), RyukPort)
.WithBindMount("/var/run/docker.sock", "/var/run/docker.sock", AccessMode.ReadOnly)
.Build();
diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
index 5dacdbd79..28fb87550 100644
--- a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
+++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
@@ -21,7 +21,6 @@ public void GetDockerClientConfiguration(IDockerEndpointAuthenticationConfigurat
}
}
-
private sealed class AuthConfigTestData : List