Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add TC host strategy #885

Merged
merged 8 commits into from
May 12, 2023
3 changes: 0 additions & 3 deletions src/Testcontainers.Keycloak/KeycloakContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ namespace Testcontainers.Keycloak;
[PublicAPI]
public sealed class KeycloakContainer : DockerContainer
{
private readonly KeycloakConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="KeycloakContainer" /> class.
/// </summary>
Expand All @@ -14,7 +12,6 @@ public sealed class KeycloakContainer : DockerContainer
public KeycloakContainer(KeycloakConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
_configuration = configuration;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
[PublicAPI]
internal sealed class TestcontainersHostEndpointAuthenticationProvider : DockerEndpointAuthenticationProvider, ICustomConfiguration
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly ICustomConfiguration _customConfiguration;

private readonly Uri _dockerEngine;

/// <summary>
/// Initializes a new instance of the <see cref="TestcontainersHostEndpointAuthenticationProvider" /> class.
/// </summary>
public TestcontainersHostEndpointAuthenticationProvider()
{
_customConfiguration = new TestcontainersHostConfiguration();
_dockerEngine = GetDockerHost();
}

/// <summary>
/// Initializes a new instance of the <see cref="TestcontainersHostEndpointAuthenticationProvider" /> class.
/// </summary>
/// <param name="lines">A list of Java properties file lines.</param>
public TestcontainersHostEndpointAuthenticationProvider(params string[] lines)
{
_customConfiguration = new TestcontainersHostConfiguration(lines);
_dockerEngine = GetDockerHost();
}

/// <inheritdoc />
public override bool IsApplicable()
{
return _dockerEngine != null && "tcp".Equals(_dockerEngine.Scheme, StringComparison.OrdinalIgnoreCase);
}

/// <inheritdoc />
public override IDockerEndpointAuthenticationConfiguration GetAuthConfig()
{
return new DockerEndpointAuthenticationConfiguration(_dockerEngine);
}

/// <inheritdoc />
public string GetDockerConfig()
{
return _customConfiguration.GetDockerConfig();
}

/// <inheritdoc />
public Uri GetDockerHost()
{
return _customConfiguration.GetDockerHost();
}

/// <inheritdoc />
public string GetDockerHostOverride()
{
return _customConfiguration.GetDockerHostOverride();
Copy link
Contributor

@cristianrgreco cristianrgreco May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see _customConfiguration implements PropertiesFileConfiguration, does this also take into account environment variable configurations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it does not. It purely relies on the properties file. I asked myself the same question, if it should. Until now, TC for .NET use either of both ways to configure TC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand the question @cristianrgreco.

We effectively want TestcontainersHostConfiguration to only be configured through the testcontainers.host property in the property file, right? And this implementation follows this and hence is correct I assume.

}

/// <inheritdoc />
public string GetDockerSocketOverride()
{
return _customConfiguration.GetDockerSocketOverride();
}

/// <inheritdoc />
public JsonDocument GetDockerAuthConfig()
{
return _customConfiguration.GetDockerAuthConfig();
}

/// <inheritdoc />
public string GetDockerCertPath()
{
return _customConfiguration.GetDockerCertPath();
}

/// <inheritdoc />
public bool GetDockerTls()
{
return _customConfiguration.GetDockerTls();
}

/// <inheritdoc />
public bool GetDockerTlsVerify()
{
return _customConfiguration.GetDockerTlsVerify();
}

/// <inheritdoc />
public bool GetRyukDisabled()
{
return _customConfiguration.GetRyukDisabled();
}

/// <inheritdoc />
public bool GetRyukContainerPrivileged()
{
return _customConfiguration.GetRyukContainerPrivileged();
}

/// <inheritdoc />
public IImage GetRyukContainerImage()
{
return _customConfiguration.GetRyukContainerImage();
}

/// <inheritdoc />
public string GetHubImageNamePrefix()
{
return _customConfiguration.GetHubImageNamePrefix();
}

private sealed class TestcontainersHostConfiguration : PropertiesFileConfiguration
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}
}
}
}
24 changes: 12 additions & 12 deletions src/Testcontainers/Configurations/CustomConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,27 @@ protected CustomConfiguration(IReadOnlyDictionary<string, string> properties)
_properties = properties;
}

protected string GetDockerConfig(string propertyName)
protected virtual string GetDockerConfig(string propertyName)
{
return GetPropertyValue<string>(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<string>(propertyName);
}

protected string GetDockerSocketOverride(string propertyName)
protected virtual string GetDockerSocketOverride(string propertyName)
{
return GetPropertyValue<string>(propertyName);
}

protected JsonDocument GetDockerAuthConfig(string propertyName)
protected virtual JsonDocument GetDockerAuthConfig(string propertyName)
{
_ = _properties.TryGetValue(propertyName, out var propertyValue);

Expand All @@ -53,32 +53,32 @@ protected JsonDocument GetDockerAuthConfig(string propertyName)
}
}

protected string GetDockerCertPath(string propertyName)
protected virtual string GetDockerCertPath(string propertyName)
{
return GetPropertyValue<string>(propertyName);
}

protected bool GetDockerTls(string propertyName)
protected virtual bool GetDockerTls(string propertyName)
{
return GetPropertyValue<bool>(propertyName);
}

protected bool GetDockerTlsVerify(string propertyName)
protected virtual bool GetDockerTlsVerify(string propertyName)
{
return GetPropertyValue<bool>(propertyName);
}

protected bool GetRyukDisabled(string propertyName)
protected virtual bool GetRyukDisabled(string propertyName)
{
return GetPropertyValue<bool>(propertyName);
}

protected bool GetRyukContainerPrivileged(string propertyName)
protected virtual bool GetRyukContainerPrivileged(string propertyName)
{
return GetPropertyValue<bool>(propertyName);
}

protected IImage GetRyukContainerImage(string propertyName)
protected virtual IImage GetRyukContainerImage(string propertyName)
{
_ = _properties.TryGetValue(propertyName, out var propertyValue);

Expand All @@ -97,7 +97,7 @@ protected IImage GetRyukContainerImage(string propertyName)
}
}

protected string GetHubImageNamePrefix(string propertyName)
protected virtual string GetHubImageNamePrefix(string propertyName)
{
return GetPropertyValue<string>(propertyName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace DotNet.Testcontainers.Configurations
/// <summary>
/// Reads and maps the custom configurations from the environment variables.
/// </summary>
internal sealed class EnvironmentConfiguration : CustomConfiguration, ICustomConfiguration
internal class EnvironmentConfiguration : CustomConfiguration, ICustomConfiguration
{
private const string DockerConfig = "DOCKER_CONFIG";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace DotNet.Testcontainers.Configurations
/// <summary>
/// Reads and maps the custom configurations from the Testcontainers properties file.
/// </summary>
internal sealed class PropertiesFileConfiguration : CustomConfiguration, ICustomConfiguration
internal class PropertiesFileConfiguration : CustomConfiguration, ICustomConfiguration
{
static PropertiesFileConfiguration()
{
Expand Down
18 changes: 12 additions & 6 deletions src/Testcontainers/Configurations/TestcontainersSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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()
{
Expand Down Expand Up @@ -103,14 +107,16 @@ static TestcontainersSettings()
/// </summary>
[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();

/// <summary>
/// Gets or sets the Docker socket override value.
/// </summary>
[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();

/// <summary>
/// Gets or sets a value indicating whether the <see cref="ResourceReaper" /> is enabled or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<object[]>
{
public AuthProviderTestData()
{
var defaultConfiguration = new PropertiesFileConfiguration(Array.Empty<string>());
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<ICustomConfiguration>()), false });
Expand All @@ -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 });
}
}

Expand All @@ -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) });
}
}
}
Expand Down