Skip to content

Commit ba6a7a2

Browse files
committed
Throw the new DockerUnavailableException when Docker is not available
(because it is either not running or misconfigured) The `ArgumentException` was not the appropriate exception to throw since the root of the problem is not a bad argument but the state of the system which is wrong. Also improve the error message to mention all the endpoints are applicable but not available.
1 parent 8470afd commit ba6a7a2

File tree

4 files changed

+64
-24
lines changed

4 files changed

+64
-24
lines changed

src/Testcontainers/Builders/AbstractBuilder`4.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,15 @@ protected virtual void Validate()
141141
_ = Guard.Argument(DockerResourceConfiguration.Logger, nameof(IResourceConfiguration<TCreateResourceEntity>.Logger))
142142
.NotNull();
143143

144-
const string containerRuntimeNotFound = "Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured. You can customize your configuration using either the environment variables or the ~/.testcontainers.properties file. For more information, visit:\nhttps://dotnet.testcontainers.org/custom_configuration/";
145-
_ = Guard.Argument(DockerResourceConfiguration.DockerEndpointAuthConfig, nameof(IResourceConfiguration<TCreateResourceEntity>.DockerEndpointAuthConfig))
146-
.ThrowIf(argument => argument.Value == null, argument => new ArgumentException(containerRuntimeNotFound, argument.Name));
144+
if (DockerResourceConfiguration.DockerEndpointAuthConfig == null)
145+
{
146+
var message = TestcontainersSettings.UnavailableEndpoints.Count == 0
147+
? "Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured."
148+
: $"Docker is either not running or misconfigured. Please ensure that Docker is available at {string.Join(" or ", TestcontainersSettings.UnavailableEndpoints)}";
149+
150+
throw new DockerUnavailableException(message + "\nYou can customize your configuration using either the environment variables or the ~/.testcontainers.properties file. " +
151+
"For more information, visit:\nhttps://dotnet.testcontainers.org/custom_configuration/");
152+
}
147153

148154
const string reuseNotSupported = "Reuse cannot be used in conjunction with WithCleanUp(true).";
149155
_ = Guard.Argument(DockerResourceConfiguration, nameof(IResourceConfiguration<TCreateResourceEntity>.Reuse))

src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Builders
33
using System;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using JetBrains.Annotations;
67
using DotNet.Testcontainers.Configurations;
78
using DotNet.Testcontainers.Containers;
89

@@ -11,6 +12,9 @@ internal class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticat
1112
{
1213
private static readonly TaskFactory TaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
1314

15+
[CanBeNull]
16+
public Uri UnavailableEndpoint;
17+
1418
/// <inheritdoc />
1519
public virtual bool IsApplicable()
1620
{
@@ -42,6 +46,7 @@ await dockerClient.System.PingAsync()
4246
}
4347
catch (Exception)
4448
{
49+
UnavailableEndpoint = dockerClientConfiguration.EndpointBaseUri;
4550
return false;
4651
}
4752
}

src/Testcontainers/Configurations/TestcontainersSettings.cs

+30-21
Original file line numberDiff line numberDiff line change
@@ -18,44 +18,54 @@ namespace DotNet.Testcontainers.Configurations
1818
public static class TestcontainersSettings
1919
{
2020
[CanBeNull]
21-
private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider
22-
= new IDockerEndpointAuthenticationProvider[]
23-
{
24-
new TestcontainersEndpointAuthenticationProvider(),
25-
new MTlsEndpointAuthenticationProvider(),
26-
new TlsEndpointAuthenticationProvider(),
27-
new EnvironmentEndpointAuthenticationProvider(),
28-
new NpipeEndpointAuthenticationProvider(),
29-
new UnixEndpointAuthenticationProvider(),
30-
new DockerDesktopEndpointAuthenticationProvider(),
31-
new RootlessUnixEndpointAuthenticationProvider(),
32-
}
33-
.Where(authProvider => authProvider.IsApplicable())
34-
.FirstOrDefault(authProvider => authProvider.IsAvailable());
21+
private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider;
3522

3623
[CanBeNull]
37-
private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig
38-
= DockerEndpointAuthProvider?.GetAuthConfig();
24+
private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig;
25+
26+
internal static readonly IReadOnlyList<Uri> UnavailableEndpoints;
3927

4028
static TestcontainersSettings()
4129
{
30+
var providers = new IDockerEndpointAuthenticationProvider[]
31+
{
32+
new TestcontainersEndpointAuthenticationProvider(),
33+
new MTlsEndpointAuthenticationProvider(),
34+
new TlsEndpointAuthenticationProvider(),
35+
new EnvironmentEndpointAuthenticationProvider(),
36+
new NpipeEndpointAuthenticationProvider(),
37+
new UnixEndpointAuthenticationProvider(),
38+
new DockerDesktopEndpointAuthenticationProvider(),
39+
new RootlessUnixEndpointAuthenticationProvider(),
40+
};
41+
42+
DockerEndpointAuthProvider = providers.Where(authProvider => authProvider.IsApplicable()).FirstOrDefault(authProvider => authProvider.IsAvailable());
43+
DockerEndpointAuthConfig = DockerEndpointAuthProvider?.GetAuthConfig();
44+
UnavailableEndpoints = providers.OfType<DockerEndpointAuthenticationProvider>().Select(e => e.UnavailableEndpoint).Where(e => e != null).Distinct().ToList();
45+
if (DockerEndpointAuthProvider is ICustomConfiguration config)
46+
{
47+
DockerHostOverride = config.GetDockerHostOverride();
48+
DockerSocketOverride = config.GetDockerSocketOverride();
49+
}
50+
else
51+
{
52+
DockerHostOverride = EnvironmentConfiguration.Instance.GetDockerHostOverride() ?? PropertiesFileConfiguration.Instance.GetDockerHostOverride();
53+
DockerSocketOverride = EnvironmentConfiguration.Instance.GetDockerSocketOverride() ?? PropertiesFileConfiguration.Instance.GetDockerSocketOverride();
54+
}
55+
OS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new Windows(DockerEndpointAuthConfig) : new Unix(DockerEndpointAuthConfig);
4256
}
4357

4458
/// <summary>
4559
/// Gets or sets the Docker host override value.
4660
/// </summary>
4761
[CanBeNull]
4862
public static string DockerHostOverride { get; set; }
49-
= DockerEndpointAuthProvider is ICustomConfiguration config
50-
? config.GetDockerHostOverride() : EnvironmentConfiguration.Instance.GetDockerHostOverride() ?? PropertiesFileConfiguration.Instance.GetDockerHostOverride();
5163

5264
/// <summary>
5365
/// Gets or sets the Docker socket override value.
5466
/// </summary>
5567
[CanBeNull]
5668
public static string DockerSocketOverride { get; set; }
57-
= DockerEndpointAuthProvider is ICustomConfiguration config
58-
? config.GetDockerSocketOverride() : EnvironmentConfiguration.Instance.GetDockerSocketOverride() ?? PropertiesFileConfiguration.Instance.GetDockerSocketOverride();
5969

6070
/// <summary>
6171
/// Gets or sets a value indicating whether the <see cref="ResourceReaper" /> is enabled or not.
@@ -141,7 +151,6 @@ static TestcontainersSettings()
141151
/// </summary>
142152
[NotNull]
143153
public static IOperatingSystem OS { get; set; }
144-
= RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new Windows(DockerEndpointAuthConfig) : new Unix(DockerEndpointAuthConfig);
145154

146155
/// <inheritdoc cref="PortForwardingContainer.ExposeHostPortsAsync" />
147156
public static Task ExposeHostPortsAsync(ushort port, CancellationToken ct = default)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace DotNet.Testcontainers
2+
{
3+
using System;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// The exception that is thrown when Docker is not available (because it is either not running or misconfigured).
8+
/// </summary>
9+
[PublicAPI]
10+
public sealed class DockerUnavailableException : Exception
11+
{
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="DockerUnavailableException"/> class, using the provided message.
14+
/// </summary>
15+
/// <param name="message">The error message that explains the reason for the exception.</param>
16+
public DockerUnavailableException(string message) : base(message)
17+
{
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)