diff --git a/src/Testcontainers.Keycloak/KeycloakContainer.cs b/src/Testcontainers.Keycloak/KeycloakContainer.cs index b2a9b93fb..775b14834 100644 --- a/src/Testcontainers.Keycloak/KeycloakContainer.cs +++ b/src/Testcontainers.Keycloak/KeycloakContainer.cs @@ -4,8 +4,6 @@ namespace Testcontainers.Keycloak; [PublicAPI] public sealed class KeycloakContainer : DockerContainer { - private readonly KeycloakConfiguration _configuration; - /// /// Initializes a new instance of the class. /// @@ -14,7 +12,6 @@ public sealed class KeycloakContainer : DockerContainer public KeycloakContainer(KeycloakConfiguration configuration, ILogger logger) : base(configuration, logger) { - _configuration = configuration; } /// diff --git a/src/Testcontainers/Builders/TestcontainersCloudEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TestcontainersCloudEndpointAuthenticationProvider.cs new file mode 100644 index 000000000..89482881d --- /dev/null +++ b/src/Testcontainers/Builders/TestcontainersCloudEndpointAuthenticationProvider.cs @@ -0,0 +1,155 @@ +namespace DotNet.Testcontainers.Builders +{ + using System; + using System.Text.Json; + using DotNet.Testcontainers.Configurations; + using DotNet.Testcontainers.Images; + using JetBrains.Annotations; + + /// + /// When configuring the endpoint for a container runtime, the `DOCKER_HOST` + /// environment variable is commonly used. This approach can become messy due to + /// the variety of alternative container runtimes. Even though Testcontainers logs + /// the container runtime that is being used, developers find it difficult to + /// determine which runtime is driving the tests on their environment. If multiple + /// container runtimes are present in a development environment, we prioritize + /// Testcontainers Cloud if it is running. + /// + [PublicAPI] + internal sealed class TestcontainersHostEndpointAuthenticationProvider : DockerEndpointAuthenticationProvider, ICustomConfiguration + { + private readonly ICustomConfiguration _customConfiguration; + + private readonly Uri _dockerEngine; + + /// + /// Initializes a new instance of the class. + /// + public TestcontainersHostEndpointAuthenticationProvider() + { + _customConfiguration = new TestcontainersHostConfiguration(); + _dockerEngine = GetDockerHost(); + } + + /// + /// Initializes a new instance of the class. + /// + /// A list of Java properties file lines. + public TestcontainersHostEndpointAuthenticationProvider(params string[] lines) + { + _customConfiguration = new TestcontainersHostConfiguration(lines); + _dockerEngine = GetDockerHost(); + } + + /// + public override bool IsApplicable() + { + return _dockerEngine != null && "tcp".Equals(_dockerEngine.Scheme, StringComparison.OrdinalIgnoreCase); + } + + /// + public override IDockerEndpointAuthenticationConfiguration GetAuthConfig() + { + return new DockerEndpointAuthenticationConfiguration(_dockerEngine); + } + + /// + public string GetDockerConfig() + { + return _customConfiguration.GetDockerConfig(); + } + + /// + public Uri GetDockerHost() + { + return _customConfiguration.GetDockerHost(); + } + + /// + public string GetDockerHostOverride() + { + return _customConfiguration.GetDockerHostOverride(); + } + + /// + public string GetDockerSocketOverride() + { + return _customConfiguration.GetDockerSocketOverride(); + } + + /// + public JsonDocument GetDockerAuthConfig() + { + return _customConfiguration.GetDockerAuthConfig(); + } + + /// + public string GetDockerCertPath() + { + return _customConfiguration.GetDockerCertPath(); + } + + /// + public bool GetDockerTls() + { + return _customConfiguration.GetDockerTls(); + } + + /// + public bool GetDockerTlsVerify() + { + return _customConfiguration.GetDockerTlsVerify(); + } + + /// + public bool GetRyukDisabled() + { + return _customConfiguration.GetRyukDisabled(); + } + + /// + public bool GetRyukContainerPrivileged() + { + return _customConfiguration.GetRyukContainerPrivileged(); + } + + /// + public IImage GetRyukContainerImage() + { + return _customConfiguration.GetRyukContainerImage(); + } + + /// + public string GetHubImageNamePrefix() + { + return _customConfiguration.GetHubImageNamePrefix(); + } + + private sealed class TestcontainersHostConfiguration : PropertiesFileConfiguration + { + public TestcontainersHostConfiguration() + { + } + + public TestcontainersHostConfiguration(params string[] lines) + : base(lines) + { + } + + protected override Uri GetDockerHost(string propertyName) + { + return base.GetDockerHost("tc.host"); + } + + protected override string GetDockerHostOverride(string propertyName) + { + return null; + } + + protected override string GetDockerSocketOverride(string propertyName) + { + return null; + } + } + } +} diff --git a/src/Testcontainers/Configurations/CustomConfiguration.cs b/src/Testcontainers/Configurations/CustomConfiguration.cs index e062537fc..155e2dbe2 100644 --- a/src/Testcontainers/Configurations/CustomConfiguration.cs +++ b/src/Testcontainers/Configurations/CustomConfiguration.cs @@ -14,27 +14,27 @@ protected CustomConfiguration(IReadOnlyDictionary properties) _properties = properties; } - protected string GetDockerConfig(string propertyName) + protected virtual string GetDockerConfig(string propertyName) { return GetPropertyValue(propertyName); } - protected Uri GetDockerHost(string propertyName) + protected virtual Uri GetDockerHost(string propertyName) { return _properties.TryGetValue(propertyName, out var propertyValue) && Uri.TryCreate(propertyValue, UriKind.RelativeOrAbsolute, out var dockerHost) ? dockerHost : null; } - protected string GetDockerHostOverride(string propertyName) + protected virtual string GetDockerHostOverride(string propertyName) { return GetPropertyValue(propertyName); } - protected string GetDockerSocketOverride(string propertyName) + protected virtual string GetDockerSocketOverride(string propertyName) { return GetPropertyValue(propertyName); } - protected JsonDocument GetDockerAuthConfig(string propertyName) + protected virtual JsonDocument GetDockerAuthConfig(string propertyName) { _ = _properties.TryGetValue(propertyName, out var propertyValue); @@ -53,32 +53,32 @@ protected JsonDocument GetDockerAuthConfig(string propertyName) } } - protected string GetDockerCertPath(string propertyName) + protected virtual string GetDockerCertPath(string propertyName) { return GetPropertyValue(propertyName); } - protected bool GetDockerTls(string propertyName) + protected virtual bool GetDockerTls(string propertyName) { return GetPropertyValue(propertyName); } - protected bool GetDockerTlsVerify(string propertyName) + protected virtual bool GetDockerTlsVerify(string propertyName) { return GetPropertyValue(propertyName); } - protected bool GetRyukDisabled(string propertyName) + protected virtual bool GetRyukDisabled(string propertyName) { return GetPropertyValue(propertyName); } - protected bool GetRyukContainerPrivileged(string propertyName) + protected virtual bool GetRyukContainerPrivileged(string propertyName) { return GetPropertyValue(propertyName); } - protected IImage GetRyukContainerImage(string propertyName) + protected virtual IImage GetRyukContainerImage(string propertyName) { _ = _properties.TryGetValue(propertyName, out var propertyValue); @@ -97,7 +97,7 @@ protected IImage GetRyukContainerImage(string propertyName) } } - protected string GetHubImageNamePrefix(string propertyName) + protected virtual string GetHubImageNamePrefix(string propertyName) { return GetPropertyValue(propertyName); } diff --git a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs index d7635cd9a..471cfc9c8 100644 --- a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs +++ b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs @@ -8,7 +8,7 @@ namespace DotNet.Testcontainers.Configurations /// /// Reads and maps the custom configurations from the environment variables. /// - internal sealed class EnvironmentConfiguration : CustomConfiguration, ICustomConfiguration + internal class EnvironmentConfiguration : CustomConfiguration, ICustomConfiguration { private const string DockerConfig = "DOCKER_CONFIG"; diff --git a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs index fb91bd9da..6d0367c1f 100644 --- a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs +++ b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs @@ -9,7 +9,7 @@ namespace DotNet.Testcontainers.Configurations /// /// Reads and maps the custom configurations from the Testcontainers properties file. /// - internal sealed class PropertiesFileConfiguration : CustomConfiguration, ICustomConfiguration + internal class PropertiesFileConfiguration : CustomConfiguration, ICustomConfiguration { static PropertiesFileConfiguration() { diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs index f8308cfa9..9a926d8e0 100644 --- a/src/Testcontainers/Configurations/TestcontainersSettings.cs +++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs @@ -21,9 +21,11 @@ public static class TestcontainersSettings { private static readonly ManualResetEventSlim ManualResetEvent = new ManualResetEventSlim(false); - private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig = + [CanBeNull] + private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider = new IDockerEndpointAuthenticationProvider[] { + new TestcontainersHostEndpointAuthenticationProvider(), new MTlsEndpointAuthenticationProvider(), new TlsEndpointAuthenticationProvider(), new EnvironmentEndpointAuthenticationProvider(), @@ -32,9 +34,11 @@ public static class TestcontainersSettings new RootlessUnixEndpointAuthenticationProvider(), } .Where(authProvider => authProvider.IsApplicable()) - .Where(authProvider => authProvider.IsAvailable()) - .Select(authProvider => authProvider.GetAuthConfig()) - .FirstOrDefault(); + .FirstOrDefault(authProvider => authProvider.IsAvailable()); + + [CanBeNull] + private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig = + DockerEndpointAuthProvider?.GetAuthConfig(); static TestcontainersSettings() { @@ -103,14 +107,16 @@ static TestcontainersSettings() /// [CanBeNull] public static string DockerHostOverride { get; set; } - = PropertiesFileConfiguration.Instance.GetDockerHostOverride() ?? EnvironmentConfiguration.Instance.GetDockerHostOverride(); + = DockerEndpointAuthProvider is ICustomConfiguration config + ? config.GetDockerHostOverride() : PropertiesFileConfiguration.Instance.GetDockerHostOverride() ?? EnvironmentConfiguration.Instance.GetDockerHostOverride(); /// /// Gets or sets the Docker socket override value. /// [CanBeNull] public static string DockerSocketOverride { get; set; } - = PropertiesFileConfiguration.Instance.GetDockerSocketOverride() ?? EnvironmentConfiguration.Instance.GetDockerSocketOverride(); + = DockerEndpointAuthProvider is ICustomConfiguration config + ? config.GetDockerSocketOverride() : PropertiesFileConfiguration.Instance.GetDockerSocketOverride() ?? EnvironmentConfiguration.Instance.GetDockerSocketOverride(); /// /// Gets or sets a value indicating whether the is enabled or not. diff --git a/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs b/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs index bc0ea44d5..db4034d3e 100644 --- a/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs @@ -3,7 +3,6 @@ namespace DotNet.Testcontainers.Tests.Fixtures using System.IO; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; - using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Images; using JetBrains.Annotations; using Xunit; diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs index 8b7f999d5..61663a0a5 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs @@ -46,13 +46,30 @@ internal void AuthConfigShouldGetDockerClientEndpoint(IDockerEndpointAuthenticat } } + public sealed class TestcontainersHostEndpointAuthenticationProviderTest + { + [Fact] + public void GetDockerHostOverrideReturnsNull() + { + ICustomConfiguration customConfiguration = new TestcontainersHostEndpointAuthenticationProvider("host.override=host.docker.internal"); + Assert.Null(customConfiguration.GetDockerHostOverride()); + } + + [Fact] + public void GetDockerSocketOverrideReturnsNull() + { + ICustomConfiguration customConfiguration = new TestcontainersHostEndpointAuthenticationProvider("docker.socket.override=/var/run/docker.sock"); + Assert.Null(customConfiguration.GetDockerSocketOverride()); + } + } + private sealed class AuthProviderTestData : List { public AuthProviderTestData() { var defaultConfiguration = new PropertiesFileConfiguration(Array.Empty()); - var dockerTlsConfiguration = new PropertiesFileConfiguration("docker.tls=true", $"docker.cert.path={CertificatesDirectoryPath}"); - var dockerMTlsConfiguration = new PropertiesFileConfiguration("docker.tls.verify=true", $"docker.cert.path={CertificatesDirectoryPath}"); + var dockerTlsConfiguration = new PropertiesFileConfiguration("docker.tls=true", "docker.cert.path=" + CertificatesDirectoryPath); + var dockerMTlsConfiguration = new PropertiesFileConfiguration("docker.tls.verify=true", "docker.cert.path=" + CertificatesDirectoryPath); Add(new object[] { new MTlsEndpointAuthenticationProvider(defaultConfiguration), false }); Add(new object[] { new MTlsEndpointAuthenticationProvider(dockerMTlsConfiguration), true }); Add(new object[] { new MTlsEndpointAuthenticationProvider(Array.Empty()), false }); @@ -67,6 +84,8 @@ public AuthProviderTestData() Add(new object[] { new EnvironmentEndpointAuthenticationProvider(defaultConfiguration, DockerHostConfiguration), true }); Add(new object[] { new NpipeEndpointAuthenticationProvider(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) }); Add(new object[] { new UnixEndpointAuthenticationProvider(), !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) }); + Add(new object[] { new TestcontainersHostEndpointAuthenticationProvider(string.Empty), false }); + Add(new object[] { new TestcontainersHostEndpointAuthenticationProvider("tc.host=" + DockerHost), true }); } } @@ -78,6 +97,7 @@ public AuthConfigTestData() Add(new object[] { new EnvironmentEndpointAuthenticationProvider(DockerHostConfiguration).GetAuthConfig(), new Uri(DockerHost) }); Add(new object[] { new NpipeEndpointAuthenticationProvider().GetAuthConfig(), new Uri("npipe://./pipe/docker_engine") }); Add(new object[] { new UnixEndpointAuthenticationProvider().GetAuthConfig(), new Uri("unix:///var/run/docker.sock") }); + Add(new object[] { new TestcontainersHostEndpointAuthenticationProvider("tc.host=" + DockerHost).GetAuthConfig(), new Uri(DockerHost) }); } } }