diff --git a/src/TestEnvironment.Docker/ContainerOperations/ContainerApi.cs b/src/TestEnvironment.Docker/ContainerOperations/ContainerApi.cs index c1b1f43..828c969 100644 --- a/src/TestEnvironment.Docker/ContainerOperations/ContainerApi.cs +++ b/src/TestEnvironment.Docker/ContainerOperations/ContainerApi.cs @@ -106,37 +106,7 @@ public async Task StopContainerAsync(string id, CancellationToken cancellationTo public async Task RemoveContainerAsync(string id, CancellationToken cancellationToken = default) => await _dockerClient.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true }); - private async Task CreateContainer(ContainerParameters containerParameters, CancellationToken cancellationToken) - { - // Create new container - var createParams = GetCreateContainerParameters(containerParameters); - - var containerInstance = await _dockerClient.Containers.CreateContainerAsync(createParams, cancellationToken); - - // Run container - await _dockerClient.Containers.StartContainerAsync(containerInstance.ID, new ContainerStartParameters(), cancellationToken); - - // Try to find container in docker session -#pragma warning disable CS8603 // Possible null reference return. - return await GetContainerAsync(containerParameters.Name, cancellationToken); -#pragma warning restore CS8603 // Possible null reference return. - } - - private async Task GetContainerAsync(string name, CancellationToken cancellationToken) - { - var containerName = $"/{name}"; - - var containers = await _dockerClient.Containers.ListContainersAsync( - new ContainersListParameters - { - All = true - }, - cancellationToken); - - return containers?.FirstOrDefault(x => x.Names.Contains(containerName)); - } - - private CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters) + protected virtual CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters) { var (name, imageName, tag, environmentVariables, ports, entrypoint, exposedPorts) = (containerParameters.Name, containerParameters.ImageName, containerParameters.Tag, containerParameters.EnvironmentVariables, containerParameters.Ports, containerParameters.Entrypoint, containerParameters.ExposedPorts); @@ -153,7 +123,8 @@ private CreateContainerParameters GetCreateContainerParameters(ContainerParamete Hostname = name, HostConfig = new HostConfig { - PublishAllPorts = ports == null + PublishAllPorts = ports == null, + Binds = containerParameters.Binds } }; @@ -178,5 +149,35 @@ private CreateContainerParameters GetCreateContainerParameters(ContainerParamete return createParams; } + + private async Task CreateContainer(ContainerParameters containerParameters, CancellationToken cancellationToken) + { + // Create new container + var createParams = GetCreateContainerParameters(containerParameters); + + var containerInstance = await _dockerClient.Containers.CreateContainerAsync(createParams, cancellationToken); + + // Run container + await _dockerClient.Containers.StartContainerAsync(containerInstance.ID, new ContainerStartParameters(), cancellationToken); + + // Try to find container in docker session +#pragma warning disable CS8603 // Possible null reference return. + return await GetContainerAsync(containerParameters.Name, cancellationToken); +#pragma warning restore CS8603 // Possible null reference return. + } + + private async Task GetContainerAsync(string name, CancellationToken cancellationToken) + { + var containerName = $"/{name}"; + + var containers = await _dockerClient.Containers.ListContainersAsync( + new ContainersListParameters + { + All = true + }, + cancellationToken); + + return containers?.FirstOrDefault(x => x.Names.Contains(containerName)); + } } } diff --git a/src/TestEnvironment.Docker/ContainerParameters.cs b/src/TestEnvironment.Docker/ContainerParameters.cs index 4c72297..12da465 100644 --- a/src/TestEnvironment.Docker/ContainerParameters.cs +++ b/src/TestEnvironment.Docker/ContainerParameters.cs @@ -19,6 +19,8 @@ public record ContainerParameters(string Name, string ImageName) public IList? ExposedPorts { get; init; } + public IList? Binds { get; init; } + public IContainerInitializer? ContainerInitializer { get; init; } public IContainerWaiter? ContainerWaiter { get; init; } diff --git a/src/TestEnvironment.Docker/DockerEnvironment.cs b/src/TestEnvironment.Docker/DockerEnvironment.cs index 90fa8c3..d2e6663 100644 --- a/src/TestEnvironment.Docker/DockerEnvironment.cs +++ b/src/TestEnvironment.Docker/DockerEnvironment.cs @@ -52,8 +52,10 @@ public DockerEnvironment(string name, Container[] containers, IDockerClient dock { } - public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, ILogger? logger) => - (Name, Containers, _imageApi, _containerApi, _logger) = (name, containers, imageApi, containerApi, logger); + public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, ILogger? logger) + : this(name, containers, imageApi, containerApi, null, logger) + { + } public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, IDockerInitializer? dockerInitializer, ILogger? logger) => (Name, Containers, _imageApi, _containerApi, _dockerInitializer, _logger) = (name, containers, imageApi, containerApi, dockerInitializer, logger); diff --git a/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs b/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs index d3cbd12..42f4e2d 100644 --- a/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs +++ b/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs @@ -14,11 +14,13 @@ namespace TestEnvironment.Docker { public class DockerEnvironmentBuilder : IDockerEnvironmentBuilder { - private readonly Dictionary> _containerFactories = new(); + private readonly Dictionary> _containerFactories = new(); private IDictionary _environmentVariables = new Dictionary(); private bool _isWsl2 = false; private bool _isDockerInDocker = false; private string _environmentName = Guid.NewGuid().ToString().Substring(0, 10); + private Func? _containerApiFactory; + private Func? _imageApiFactory; public IDockerClient DockerClient { get; private set; } @@ -88,7 +90,7 @@ public IDockerEnvironmentBuilder AddContainer(Func(TParams containerParameters, Func containerFactory) where TParams : ContainerParameters { - _containerFactories.Add(containerParameters, () => + _containerFactories.Add(containerParameters, (containerApi, imageApi) => { var envParameters = containerParameters with { @@ -102,13 +104,48 @@ public IDockerEnvironmentBuilder AddContainer(TParams containerParamete return this; } + public IDockerEnvironmentBuilder AddContainer(TParams containerParameters, Func containerFactory) + where TParams : ContainerParameters + { + _containerFactories.Add(containerParameters, (containerApi, imageApi) => + { + var envParameters = containerParameters with + { + Name = GetContainerName(_environmentName, containerParameters.Name), + EnvironmentVariables = _environmentVariables.MergeDictionaries(containerParameters.EnvironmentVariables), + IsDockerInDocker = _isDockerInDocker, + }; + + return containerFactory(envParameters, containerApi, imageApi, Logger); + }); + return this; + } + + public IDockerEnvironmentBuilder WithContainerApi(Func containerApiFactory) + { + _containerApiFactory = containerApiFactory; + + return this; + } + + public IDockerEnvironmentBuilder WithImageApi(Func imageApiFactory) + { + _imageApiFactory = imageApiFactory; + + return this; + } + public IDockerEnvironment Build() { - var containers = _containerFactories.Values.Select(cf => cf()).ToArray(); + var containerApi = _containerApiFactory?.Invoke(DockerClient, Logger) ?? new ContainerApi(DockerClient, Logger); + + var imageApi = _imageApiFactory?.Invoke(DockerClient, Logger) ?? new ImageApi(DockerClient, Logger); + + var dockerInitializer = _isWsl2 ? new DockerInWs2Initializer(DockerClient, Logger) : null; + + var containers = _containerFactories.Values.Select(cf => cf(containerApi, imageApi)).ToArray(); - return _isWsl2 - ? new DockerEnvironment(_environmentName, containers, DockerClient, new DockerInWs2Initializer(DockerClient, Logger), Logger) - : new DockerEnvironment(_environmentName, containers, DockerClient, Logger); + return new DockerEnvironment(_environmentName, containers, imageApi, containerApi, dockerInitializer, Logger); } } } diff --git a/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs b/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs index 269bfcc..e0d9e04 100644 --- a/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs +++ b/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using Docker.DotNet; using Microsoft.Extensions.Logging; +using TestEnvironment.Docker.ContainerOperations; +using TestEnvironment.Docker.ImageOperations; namespace TestEnvironment.Docker { @@ -24,6 +26,13 @@ public interface IDockerEnvironmentBuilder IDockerEnvironmentBuilder AddContainer(TParams containerParameters, Func containerFactory) where TParams : ContainerParameters; + IDockerEnvironmentBuilder AddContainer(TParams containerParameters, Func containerFactory) + where TParams : ContainerParameters; + + IDockerEnvironmentBuilder WithContainerApi(Func containerApiFactory); + + IDockerEnvironmentBuilder WithImageApi(Func imageApiFactory); + IDockerEnvironment Build(); } } diff --git a/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs new file mode 100644 index 0000000..e05cfbf --- /dev/null +++ b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Docker.DotNet; +using Docker.DotNet.Models; +using Microsoft.Extensions.Logging; +using TestEnvironment.Docker.ContainerOperations; + +namespace TestEnvironment.Docker.Tests +{ + internal class CustomContainerApi : ContainerApi + { + private readonly string _key; + private readonly string _val; + + public CustomContainerApi(string key, string val, IDockerClient dockerClient, ILogger logger) + : base(dockerClient, logger) + { + _key = key; + _val = val; + } + + protected override CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters) + { + var createParams = base.GetCreateContainerParameters(containerParameters); + + createParams.Env.Add($"{_key}={_val}"); + + return createParams; + } + } +} diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index 7763567..c2470c4 100644 --- a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs +++ b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs @@ -181,6 +181,47 @@ public async Task AddMsSqlContainer_WhenContainerIsUp_ShouldPrintMsSqlVersion() await PrintMssqlVersion(mssql); } + [Fact] + public async Task AddMongoContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomVar() + { + // Arrange + const string key = "hello"; + const string val = "world"; + const string containerName = "my-mongo-cust"; + +#if DEBUG + var environment = new DockerEnvironmentBuilder(_logger) +#else + await using var environment = new DockerEnvironmentBuilder(_logger) +#endif + .SetName("test-env") + .WithContainerApi((api, l) => new CustomContainerApi(key, val, api, l)) +#if WSL2 + .UseWsl2() +#endif +#if DEBUG + .AddMongoContainer(p => p with + { + Name = containerName, + Reusable = true + }) +#else + .AddMongoContainer(p => p with + { + Name = "my-mongo" + }) +#endif + .Build(); + + // Act + await environment.UpAsync(); + + // Assert + var mongo = environment.GetContainer(containerName); + var varValue = await mongo.ExecAsync(new[] { $"printenv {key}" }); + Assert.EndsWith(val, varValue); + } + [Fact] public async Task AddMariaDbContainer_WhenContainerIsUp_ShouldPrintMariaDbVersion() {