Skip to content

Commit

Permalink
fix(#449): Don't let Docker Desktop for Windows choose the random pub…
Browse files Browse the repository at this point in the history
…lic host port
  • Loading branch information
HofmeisterAn committed Jun 25, 2022
1 parent dcbbf5b commit 124f007
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
using System;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" />
internal sealed class NpipeEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
#pragma warning disable S1075

private static readonly Uri DockerEngine = new Uri("npipe://./pipe/docker_engine");
/// <summary>
/// Gets the named pipe Docker Engine endpoint.
/// </summary>
[PublicAPI]
public static Uri DockerEngine { get; }
= new Uri("npipe://./pipe/docker_engine");

#pragma warning restore S1075

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
using System;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" />
internal sealed class UnixEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
private static readonly Uri DockerEngine = new Uri("unix:/var/run/docker.sock");
/// <summary>
/// Gets the Unix socket Docker Engine endpoint.
/// </summary>
[NotNull]
public static Uri DockerEngine { get; }
= new Uri("unix:/var/run/docker.sock");

/// <inheritdoc />
public bool IsApplicable()
Expand Down
35 changes: 35 additions & 0 deletions src/Testcontainers/Configurations/TestcontainersSettings.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,6 +31,19 @@ public static class TestcontainersSettings
public static IDockerImage ResourceReaperImage { get; set; }
= new DockerImage("ghcr.io/psanetra/ryuk:2021.12.20");

/// <summary>
/// Gets or sets the <see cref="ResourceReaper" /> public host port.
/// </summary>
/// <remarks>
/// The <see cref="ResourceReaper" /> 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
/// </remarks>
[PublicAPI]
public static Func<IDockerEndpointAuthenticationConfiguration, ushort> ResourceReaperPublicHostPort { get; set; }
= GetResourceReaperPublicHostPort;

/// <summary>
/// Gets or sets a prefix that applies to every image that is pulled from Docker Hub.
/// </summary>
Expand All @@ -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;
}
}
}
}
8 changes: 5 additions & 3 deletions src/Testcontainers/Containers/ResourceReaper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestcontainersContainer>()
.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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public void GetDockerClientConfiguration(IDockerEndpointAuthenticationConfigurat
}
}


private sealed class AuthConfigTestData : List<object[]>
{
public AuthConfigTestData()
Expand Down

0 comments on commit 124f007

Please sign in to comment.