From 5bc9df84378f52d24d03401d53e1bebf5152a145 Mon Sep 17 00:00:00 2001 From: Deffiss Date: Thu, 17 Aug 2023 17:56:31 +0300 Subject: [PATCH 1/6] Bring more flexibility by allowing to use custom ContainerApi and ImageApi --- .../ContainerOperations/ContainerApi.cs | 65 ++++++++++--------- .../ContainerParameters.cs | 2 + .../DockerEnvironment.cs | 6 +- .../DockerEnvironmentBuilder.cs | 26 +++++++- .../IDockerEnvironmentBuilder.cs | 6 ++ .../CustomContainerApi.cs | 32 +++++++++ .../DockerEnvironmentTests.cs | 41 ++++++++++++ 7 files changed, 141 insertions(+), 37 deletions(-) create mode 100644 test/TestEnvironment.Docker.Tests/CustomContainerApi.cs 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..fec0577 100644 --- a/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs +++ b/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs @@ -19,6 +19,8 @@ public class DockerEnvironmentBuilder : IDockerEnvironmentBuilder 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; } @@ -102,13 +104,31 @@ public IDockerEnvironmentBuilder AddContainer(TParams containerParamete 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(); - return _isWsl2 - ? new DockerEnvironment(_environmentName, containers, DockerClient, new DockerInWs2Initializer(DockerClient, Logger), Logger) - : new DockerEnvironment(_environmentName, containers, DockerClient, Logger); + 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; + + 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..7965ab2 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,10 @@ public interface IDockerEnvironmentBuilder 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..29f67de --- /dev/null +++ b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs @@ -0,0 +1,32 @@ +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 _namePrefix; + + public CustomContainerApi(string namePrefix, IDockerClient dockerClient, ILogger logger) + : base(dockerClient, logger) + { + _namePrefix = namePrefix; + } + + protected override CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters) + { + var createParams = base.GetCreateContainerParameters(containerParameters); + + createParams.Name = $"{createParams.Name}-{_namePrefix}"; + + return createParams; + } + } +} diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index 7763567..f76bfc3 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 AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomName() + { + // Arrange + const string nameSuffix = "custom"; + const string containerName = "my-mssql"; + +#if DEBUG + var environment = new DockerEnvironmentBuilder(_logger) +#else + await using var environment = new DockerEnvironmentBuilder(_logger) +#endif + .SetName("test-env") + .WithContainerApi((api, l) => new CustomContainerApi(nameSuffix, api, l)) +#if WSL2 + .UseWsl2() +#endif +#if DEBUG + .AddMssqlContainer(p => p with + { + Name = containerName, + SAPassword = "HelloK11tt_0", + Reusable = true + }) +#else + .AddMssqlContainer(p => p with + { + Name = "my-mssql", + SAPassword = "HelloK11tt_0" + }) +#endif + .Build(); + + // Act + await environment.UpAsync(); + + // Assert + var mssql = environment.GetContainer("my-mssql"); + Assert.EndsWith(mssql.Name, nameSuffix); + } + [Fact] public async Task AddMariaDbContainer_WhenContainerIsUp_ShouldPrintMariaDbVersion() { From 21e6fe98c949399455dcc06b03de718952d1f3a6 Mon Sep 17 00:00:00 2001 From: Deffiss Date: Thu, 17 Aug 2023 17:59:46 +0300 Subject: [PATCH 2/6] Fix --- test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index f76bfc3..6fa3ae7 100644 --- a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs +++ b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs @@ -208,7 +208,7 @@ public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus #else .AddMssqlContainer(p => p with { - Name = "my-mssql", + Name = containerName, SAPassword = "HelloK11tt_0" }) #endif @@ -218,7 +218,7 @@ public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus await environment.UpAsync(); // Assert - var mssql = environment.GetContainer("my-mssql"); + var mssql = environment.GetContainer(containerName); Assert.EndsWith(mssql.Name, nameSuffix); } From 47ed7b2720f3665afd22394306f2527916ffa77e Mon Sep 17 00:00:00 2001 From: Deffiss Date: Mon, 21 Aug 2023 15:08:50 +0300 Subject: [PATCH 3/6] Fix test --- .../CustomContainerApi.cs | 11 ++++++---- .../DockerEnvironmentTests.cs | 20 +++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs index 29f67de..b98aa8a 100644 --- a/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs +++ b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs @@ -12,19 +12,22 @@ namespace TestEnvironment.Docker.Tests { internal class CustomContainerApi : ContainerApi { - private readonly string _namePrefix; + private readonly string _key; + private readonly string _val; - public CustomContainerApi(string namePrefix, IDockerClient dockerClient, ILogger logger) + public CustomContainerApi(string key, string val, IDockerClient dockerClient, ILogger logger) : base(dockerClient, logger) { - _namePrefix = namePrefix; + _key = key; + _val = val; } protected override CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters) { var createParams = base.GetCreateContainerParameters(containerParameters); - createParams.Name = $"{createParams.Name}-{_namePrefix}"; + createParams.Name = $"{createParams.Name}-{_key}"; + createParams.Env.Add($"{_key}={_val}"); return createParams; } diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index 6fa3ae7..aba3304 100644 --- a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs +++ b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs @@ -185,8 +185,9 @@ public async Task AddMsSqlContainer_WhenContainerIsUp_ShouldPrintMsSqlVersion() public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomName() { // Arrange - const string nameSuffix = "custom"; - const string containerName = "my-mssql"; + const string key = "hello"; + const string val = "world"; + const string containerName = "my-mongo-cust"; #if DEBUG var environment = new DockerEnvironmentBuilder(_logger) @@ -194,22 +195,20 @@ public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus await using var environment = new DockerEnvironmentBuilder(_logger) #endif .SetName("test-env") - .WithContainerApi((api, l) => new CustomContainerApi(nameSuffix, api, l)) + .WithContainerApi((api, l) => new CustomContainerApi(key, val, api, l)) #if WSL2 .UseWsl2() #endif #if DEBUG - .AddMssqlContainer(p => p with + .AddMongoContainer(p => p with { Name = containerName, - SAPassword = "HelloK11tt_0", Reusable = true }) #else - .AddMssqlContainer(p => p with + .AddMongoContainer(p => p with { - Name = containerName, - SAPassword = "HelloK11tt_0" + Name = "my-mongo" }) #endif .Build(); @@ -218,8 +217,9 @@ public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus await environment.UpAsync(); // Assert - var mssql = environment.GetContainer(containerName); - Assert.EndsWith(mssql.Name, nameSuffix); + var mongo = environment.GetContainer(containerName); + var varValue = await mongo.ExecAsync(new[] { $"echo {key}" }); + Assert.EndsWith(val, varValue); } [Fact] From d0306803bc236297e5545b9f90ab434dd06192e5 Mon Sep 17 00:00:00 2001 From: Deffiss Date: Mon, 21 Aug 2023 15:25:44 +0300 Subject: [PATCH 4/6] Fix test issue --- test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index aba3304..77bc27f 100644 --- a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs +++ b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs @@ -182,7 +182,7 @@ public async Task AddMsSqlContainer_WhenContainerIsUp_ShouldPrintMsSqlVersion() } [Fact] - public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomName() + public async Task AddMongoContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomVar() { // Arrange const string key = "hello"; @@ -218,7 +218,7 @@ public async Task AddMsSqlContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus // Assert var mongo = environment.GetContainer(containerName); - var varValue = await mongo.ExecAsync(new[] { $"echo {key}" }); + var varValue = await mongo.ExecAsync(new[] { $"echo ${key}" }); Assert.EndsWith(val, varValue); } From 35f0edea2439d1b87e48229ec6e6dbd9fe5094f8 Mon Sep 17 00:00:00 2001 From: Deffiss Date: Mon, 21 Aug 2023 15:33:30 +0300 Subject: [PATCH 5/6] More fixes --- test/TestEnvironment.Docker.Tests/CustomContainerApi.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs index b98aa8a..e05cfbf 100644 --- a/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs +++ b/test/TestEnvironment.Docker.Tests/CustomContainerApi.cs @@ -26,7 +26,6 @@ protected override CreateContainerParameters GetCreateContainerParameters(Contai { var createParams = base.GetCreateContainerParameters(containerParameters); - createParams.Name = $"{createParams.Name}-{_key}"; createParams.Env.Add($"{_key}={_val}"); return createParams; From ad16b9afc749a003ff9b047016db49e7f345b5b8 Mon Sep 17 00:00:00 2001 From: Deffiss Date: Tue, 22 Aug 2023 12:09:38 +0300 Subject: [PATCH 6/6] Try to resolve design issues --- .../DockerEnvironmentBuilder.cs | 25 ++++++++++++++++--- .../IDockerEnvironmentBuilder.cs | 3 +++ .../DockerEnvironmentTests.cs | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs b/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs index fec0577..42f4e2d 100644 --- a/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs +++ b/src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs @@ -14,7 +14,7 @@ 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; @@ -90,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 { @@ -104,6 +104,23 @@ 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; @@ -120,14 +137,14 @@ public IDockerEnvironmentBuilder WithImageApi(Func 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 new DockerEnvironment(_environmentName, containers, imageApi, containerApi, dockerInitializer, Logger); } } diff --git a/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs b/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs index 7965ab2..e0d9e04 100644 --- a/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs +++ b/src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs @@ -26,6 +26,9 @@ 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); diff --git a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs index 77bc27f..c2470c4 100644 --- a/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs +++ b/test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs @@ -218,7 +218,7 @@ public async Task AddMongoContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCus // Assert var mongo = environment.GetContainer(containerName); - var varValue = await mongo.ExecAsync(new[] { $"echo ${key}" }); + var varValue = await mongo.ExecAsync(new[] { $"printenv {key}" }); Assert.EndsWith(val, varValue); }