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 { public AuthConfigTestData()