Skip to content

Commit 5b2fbc3

Browse files
0xcedHofmeisterAn
andauthored
feat: Throw DockerUnavailableException when Docker is not available (#1308)
Co-authored-by: Andre Hofmeister <[email protected]>
1 parent 1854677 commit 5b2fbc3

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

src/Testcontainers/Builders/AbstractBuilder`4.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ namespace DotNet.Testcontainers.Builders
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
57
using DotNet.Testcontainers.Clients;
68
using DotNet.Testcontainers.Configurations;
79
using DotNet.Testcontainers.Containers;
@@ -141,9 +143,8 @@ protected virtual void Validate()
141143
_ = Guard.Argument(DockerResourceConfiguration.Logger, nameof(IResourceConfiguration<TCreateResourceEntity>.Logger))
142144
.NotNull();
143145

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/";
145146
_ = Guard.Argument(DockerResourceConfiguration.DockerEndpointAuthConfig, nameof(IResourceConfiguration<TCreateResourceEntity>.DockerEndpointAuthConfig))
146-
.ThrowIf(argument => argument.Value == null, argument => new ArgumentException(containerRuntimeNotFound, argument.Name));
147+
.ThrowIf(argument => argument.Value == null, CreateDockerUnavailableException);
147148

148149
const string reuseNotSupported = "Reuse cannot be used in conjunction with WithCleanUp(true).";
149150
_ = Guard.Argument(DockerResourceConfiguration, nameof(IResourceConfiguration<TCreateResourceEntity>.Reuse))
@@ -164,5 +165,23 @@ protected virtual void Validate()
164165
/// <param name="newValue">The new Docker resource configuration.</param>
165166
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
166167
protected abstract TBuilderEntity Merge(TConfigurationEntity oldValue, TConfigurationEntity newValue);
168+
169+
private static Exception CreateDockerUnavailableException(Guard.ArgumentInfo<IDockerEndpointAuthenticationConfiguration> argument)
170+
{
171+
var unavailableExceptions = TestcontainersSettings.DockerEndpointAuthProviders
172+
.Select(authProvider => authProvider.LastException)
173+
.Where(exception => exception != null);
174+
175+
var exception = new AggregateException(unavailableExceptions);
176+
177+
var exceptionInfo = new StringBuilder(512);
178+
exceptionInfo.AppendLine("Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured.");
179+
exceptionInfo.AppendLine("You can customize your configuration using either the environment variables or the ~/.testcontainers.properties file.");
180+
exceptionInfo.AppendLine("For more information, visit: https://dotnet.testcontainers.org/custom_configuration/.");
181+
exceptionInfo.AppendLine(" Details: ");
182+
exceptionInfo.Append(string.Join(Environment.NewLine, exception.InnerExceptions.Select(e => " " + e.Message)));
183+
184+
return new DockerUnavailableException(exceptionInfo.ToString(), exception);
185+
}
167186
}
168187
}

src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ namespace DotNet.Testcontainers.Builders
55
using System.Threading.Tasks;
66
using DotNet.Testcontainers.Configurations;
77
using DotNet.Testcontainers.Containers;
8+
using JetBrains.Annotations;
89

910
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
1011
internal class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
1112
{
1213
private static readonly TaskFactory TaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
1314

15+
[CanBeNull]
16+
private Exception _cachedException;
17+
18+
/// <summary>
19+
/// Exposes the exception that occurred during the last Docker availability check.
20+
/// </summary>
21+
[CanBeNull]
22+
public Exception LastException => _cachedException;
23+
1424
/// <inheritdoc />
1525
public virtual bool IsApplicable()
1626
{
@@ -38,10 +48,15 @@ public virtual bool IsAvailable()
3848
await dockerClient.System.PingAsync()
3949
.ConfigureAwait(false);
4050

51+
_cachedException = null;
52+
4153
return true;
4254
}
43-
catch (Exception)
55+
catch (Exception e)
4456
{
57+
var message = $"Failed to connect to Docker endpoint at '{dockerClientConfiguration.EndpointBaseUri}'.";
58+
_cachedException = new DockerUnavailableException(message, e);
59+
4560
return false;
4661
}
4762
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace DotNet.Testcontainers.Builders
2+
{
3+
using System;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// Represents an exception that is thrown when a connection to the Docker endpoint
8+
/// cannot be established successfully.
9+
/// </summary>
10+
[PublicAPI]
11+
public sealed class DockerUnavailableException : Exception
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="DockerUnavailableException" /> class.
15+
/// </summary>
16+
/// <param name="message">The message that describes the error.</param>
17+
public DockerUnavailableException(string message) : base(message)
18+
{
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="DockerUnavailableException" /> class.
23+
/// </summary>
24+
/// <param name="message">The message that describes the error.</param>
25+
/// <param name="innerException">The exception that is the cause of the current exception.</param>
26+
public DockerUnavailableException(string message, Exception innerException)
27+
: base(message, innerException)
28+
{
29+
}
30+
}
31+
}

src/Testcontainers/Configurations/TestcontainersSettings.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ namespace DotNet.Testcontainers.Configurations
1717
[PublicAPI]
1818
public static class TestcontainersSettings
1919
{
20+
internal static readonly List<DockerEndpointAuthenticationProvider> DockerEndpointAuthProviders
21+
= new List<DockerEndpointAuthenticationProvider>
22+
{
23+
new TestcontainersEndpointAuthenticationProvider(),
24+
new MTlsEndpointAuthenticationProvider(),
25+
new TlsEndpointAuthenticationProvider(),
26+
new EnvironmentEndpointAuthenticationProvider(),
27+
new NpipeEndpointAuthenticationProvider(),
28+
new UnixEndpointAuthenticationProvider(),
29+
new DockerDesktopEndpointAuthenticationProvider(),
30+
new RootlessUnixEndpointAuthenticationProvider(),
31+
};
32+
2033
[CanBeNull]
2134
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());
35+
= DockerEndpointAuthProviders.FirstOrDefault(authProvider => authProvider.IsApplicable() && authProvider.IsAvailable());
3536

3637
[CanBeNull]
3738
private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig

0 commit comments

Comments
 (0)