From 9fccae7f45f419143589d11d1297fcb69e6b816b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 26 Nov 2024 17:35:13 +0100 Subject: [PATCH] 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. --- .../Builders/AbstractBuilder`4.cs | 12 +++++-- .../DockerEndpointAuthenticationProvider.cs | 5 +++ .../Configurations/TestcontainersSettings.cs | 35 ++++++++++--------- .../DockerUnavailableException.cs | 20 +++++++++++ 4 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 src/Testcontainers/DockerUnavailableException.cs diff --git a/src/Testcontainers/Builders/AbstractBuilder`4.cs b/src/Testcontainers/Builders/AbstractBuilder`4.cs index 6af5ceb79..10b0dfe9b 100644 --- a/src/Testcontainers/Builders/AbstractBuilder`4.cs +++ b/src/Testcontainers/Builders/AbstractBuilder`4.cs @@ -141,9 +141,15 @@ protected virtual void Validate() _ = Guard.Argument(DockerResourceConfiguration.Logger, nameof(IResourceConfiguration.Logger)) .NotNull(); - 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/"; - _ = Guard.Argument(DockerResourceConfiguration.DockerEndpointAuthConfig, nameof(IResourceConfiguration.DockerEndpointAuthConfig)) - .ThrowIf(argument => argument.Value == null, argument => new ArgumentException(containerRuntimeNotFound, argument.Name)); + if (DockerResourceConfiguration.DockerEndpointAuthConfig == null) + { + var message = TestcontainersSettings.UnavailableEndpoints.Count == 0 + ? "Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured." + : $"Docker is either not running or misconfigured. Please ensure that Docker is available at {string.Join(" or ", TestcontainersSettings.UnavailableEndpoints)}"; + + throw new DockerUnavailableException(message + "\nYou can customize your configuration using either the environment variables or the ~/.testcontainers.properties file. " + + "For more information, visit:\nhttps://dotnet.testcontainers.org/custom_configuration/"); + } const string reuseNotSupported = "Reuse cannot be used in conjunction with WithCleanUp(true)."; _ = Guard.Argument(DockerResourceConfiguration, nameof(IResourceConfiguration.Reuse)) diff --git a/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs index ad6b14af8..66ac47d64 100644 --- a/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs +++ b/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Builders using System; using System.Threading; using System.Threading.Tasks; + using JetBrains.Annotations; using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; @@ -11,6 +12,9 @@ internal class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticat { private static readonly TaskFactory TaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); + [CanBeNull] + public Uri UnavailableEndpoint; + /// public virtual bool IsApplicable() { @@ -42,6 +46,7 @@ await dockerClient.System.PingAsync() } catch (Exception) { + UnavailableEndpoint = dockerClientConfiguration.EndpointBaseUri; return false; } } diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs index efb611a91..62879a5f9 100644 --- a/src/Testcontainers/Configurations/TestcontainersSettings.cs +++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs @@ -18,27 +18,30 @@ namespace DotNet.Testcontainers.Configurations public static class TestcontainersSettings { [CanBeNull] - private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider - = new IDockerEndpointAuthenticationProvider[] - { - new TestcontainersEndpointAuthenticationProvider(), - new MTlsEndpointAuthenticationProvider(), - new TlsEndpointAuthenticationProvider(), - new EnvironmentEndpointAuthenticationProvider(), - new NpipeEndpointAuthenticationProvider(), - new UnixEndpointAuthenticationProvider(), - new DockerDesktopEndpointAuthenticationProvider(), - new RootlessUnixEndpointAuthenticationProvider(), - } - .Where(authProvider => authProvider.IsApplicable()) - .FirstOrDefault(authProvider => authProvider.IsAvailable()); + private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider; [CanBeNull] - private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig - = DockerEndpointAuthProvider?.GetAuthConfig(); + private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig; + + internal static readonly IReadOnlyList UnavailableEndpoints; static TestcontainersSettings() { + var providers = new IDockerEndpointAuthenticationProvider[] + { + new TestcontainersEndpointAuthenticationProvider(), + new MTlsEndpointAuthenticationProvider(), + new TlsEndpointAuthenticationProvider(), + new EnvironmentEndpointAuthenticationProvider(), + new NpipeEndpointAuthenticationProvider(), + new UnixEndpointAuthenticationProvider(), + new DockerDesktopEndpointAuthenticationProvider(), + new RootlessUnixEndpointAuthenticationProvider(), + }; + + DockerEndpointAuthProvider = providers.Where(authProvider => authProvider.IsApplicable()).FirstOrDefault(authProvider => authProvider.IsAvailable()); + DockerEndpointAuthConfig = DockerEndpointAuthProvider?.GetAuthConfig(); + UnavailableEndpoints = providers.OfType().Select(e => e.UnavailableEndpoint).Where(e => e != null).Distinct().ToList(); } /// diff --git a/src/Testcontainers/DockerUnavailableException.cs b/src/Testcontainers/DockerUnavailableException.cs new file mode 100644 index 000000000..09c47d2a2 --- /dev/null +++ b/src/Testcontainers/DockerUnavailableException.cs @@ -0,0 +1,20 @@ +namespace DotNet.Testcontainers +{ + using System; + using JetBrains.Annotations; + + /// + /// The exception that is thrown when Docker is not available (because it is either not running or misconfigured). + /// + [PublicAPI] + public sealed class DockerUnavailableException : Exception + { + /// + /// Initializes a new instance of the class, using the provided message. + /// + /// The error message that explains the reason for the exception. + public DockerUnavailableException(string message) : base(message) + { + } + } +}