├── .editorconfig ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── README.md ├── TestEnvironment.sln ├── appveyor.yml ├── samples ├── aspnetcore-integration-tests │ ├── AspNetCoreSample.Tests │ │ ├── AspNetCoreSample.Tests.csproj │ │ ├── EnvironmentFixture.cs │ │ ├── ReadEnvironmentFixture.cs │ │ ├── ReadTest.cs │ │ ├── WriteEnvironmentFixture.cs │ │ └── WriteTests.cs │ ├── AspNetCoreSample.sln │ ├── AspNetCoreSample │ │ ├── AspNetCoreSample.csproj │ │ ├── Controllers │ │ │ └── WeatherForecastController.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── WeatherForecast.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ └── README.md └── test-bll-with-nunit │ └── BLLlWithNunitSample │ ├── BLL │ ├── BLL.csproj │ ├── IPizzaService.cs │ └── PizzaService.cs │ ├── BLLTests │ ├── BLLTests.csproj │ ├── PizzaServiceTests.cs │ └── TestBase.cs │ ├── BLLWithNunitSample.sln │ └── DAL │ ├── DAL.csproj │ ├── Order.cs │ ├── Pizza.cs │ ├── PizzaContext.cs │ └── PizzaOrder.cs ├── src ├── TestEnvironment.Docker.Containers.Elasticsearch │ ├── ElasticsearchContainer.cs │ ├── ElasticsearchContainerCleaner.cs │ ├── ElasticsearchContainerWaiter.cs │ ├── IDockerEnvironmentBuilderExtensions.cs │ └── TestEnvironment.Docker.Containers.Elasticsearch.csproj ├── TestEnvironment.Docker.Containers.Ftp │ ├── FtpContainer.cs │ ├── FtpContainerCleaner.cs │ ├── FtpContainerParameters.cs │ ├── FtpContainerWaiter.cs │ ├── IDockerEnvironmentBuilderExtensions.cs │ └── TestEnvironment.Docker.Containers.Ftp.csproj ├── TestEnvironment.Docker.Containers.Kafka │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── KafkaContainer.cs │ ├── KafkaContainerCleaner.cs │ ├── KafkaContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Kafka.csproj ├── TestEnvironment.Docker.Containers.Mail │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── MailContainer.cs │ ├── MailContainerCleaner.cs │ ├── MailContainerParameters.cs │ ├── MailContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Mail.csproj ├── TestEnvironment.Docker.Containers.MariaDB │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── MariaDBContainer.cs │ ├── MariaDBContainerCleaner.cs │ ├── MariaDBContainerParameters.cs │ ├── MariaDBContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.MariaDB.csproj ├── TestEnvironment.Docker.Containers.Mongo │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── IMongoContainer.cs │ ├── MongoContainer.cs │ ├── MongoContainerCleaner.cs │ ├── MongoContainerParameters.cs │ ├── MongoContainerWaiter.cs │ ├── MongoSingleReplicaSetContainer.cs │ ├── MongoSingleReplicaSetContainerInitializer.cs │ ├── MongoSingleReplicaSetContainerParameters.cs │ ├── MongoSingleReplicaSetContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Mongo.csproj ├── TestEnvironment.Docker.Containers.Mssql │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── MssqlContainer.cs │ ├── MssqlContainerCleaner.cs │ ├── MssqlContainerParameters.cs │ ├── MssqlContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Mssql.csproj ├── TestEnvironment.Docker.Containers.Postgres │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── PostgresContainer.cs │ ├── PostgresContainerCleaner.cs │ ├── PostgresContainerParameters.cs │ ├── PostgresContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Postgres.csproj ├── TestEnvironment.Docker.Containers.RabbitMQ │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── RabbitMQContainer.cs │ ├── RabbitMQContainerCleaner.cs │ ├── RabbitMQContainerParameters.cs │ ├── RabbitMQContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.RabbitMQ.csproj ├── TestEnvironment.Docker.Containers.Redis │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── IRedisContainer.cs │ ├── RedisConnectionConfiguration.cs │ ├── RedisContainer.cs │ ├── RedisContainerCleaner.cs │ ├── RedisContainerParameters.cs │ ├── RedisContainerWaiter.cs │ └── TestEnvironment.Docker.Containers.Redis.csproj └── TestEnvironment.Docker │ ├── CollectionExtensions.cs │ ├── Container.cs │ ├── ContainerFromDockerfile.cs │ ├── ContainerFromDockerfileParameters.cs │ ├── ContainerLifecycle │ ├── BaseContainerWaiter.cs │ ├── FuncContainerWaiter.cs │ ├── HttpContainerWaiter.cs │ ├── IContainerCleaner.cs │ ├── IContainerInitializer.cs │ └── IContainerWaiter.cs │ ├── ContainerOperations │ ├── ContainerApi.cs │ ├── ContainerOperationException.cs │ └── IContainerApi.cs │ ├── ContainerParameters.cs │ ├── ContainerRuntimeInfo.cs │ ├── DictionaryExtensions.cs │ ├── DockerClientExtentions.cs │ ├── DockerEnvironment.cs │ ├── DockerEnvironmentBuilder.cs │ ├── DockerOperations │ ├── DockerInWs2Initializer.cs │ └── IDockerInitializer.cs │ ├── IDockerEnvironment.cs │ ├── IDockerEnvironmentBuilder.cs │ ├── IDockerEnvironmentBuilderExtensions.cs │ ├── ImageOperations │ ├── Archiver.cs │ ├── IArchiver.cs │ ├── IImageApi.cs │ └── ImageApi.cs │ ├── PolicyFactory.cs │ ├── StringExtensions.cs │ └── TestEnvironment.Docker.csproj ├── stylecop.json ├── stylecop.ruleset └── test └── TestEnvironment.Docker.Tests ├── DockerEnvironmentTests.cs ├── Dockerfile ├── TestEnvironment.Docker.Tests.csproj ├── XUnitLogger.cs └── index.html /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # Default severity for all analyzer diagnostics 4 | dotnet_analyzer_diagnostic.severity = none 5 | 6 | # Default severity for analyzer diagnostics with category 'StyleCop.CSharp.OrderingRules' 7 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = none 8 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | $(MSBuildThisFileDirectory)stylecop.ruleset 7 | https://github.com/Deffiss/testenvironment-docker 8 | True 9 | https://github.com/Deffiss/testenvironment-docker 10 | Latest 11 | MIT 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aliaksei Harshkalep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/1xh2d15gkmij0mp8/branch/master?svg=true)](https://ci.appveyor.com/project/Deffiss/testenvironment-docker/branch/master) [![NuGet](https://img.shields.io/nuget/v/TestEnvironment.Docker.svg)](https://www.nuget.org/packages/TestEnvironment.Docker/) 2 | 3 | # Test environment with Docker containers 4 | 5 | ### Pre requirements 6 | 7 | You need docker to be installed on your machine. Tested both on Windows and Linux. 8 | 9 | ### Installation 10 | 11 | Run this in your package manager console: 12 | 13 | ``` 14 | Install-Package TestEnvironment.Docker 15 | ``` 16 | 17 | To add container specific functionality for MSSQL, Elasticsearch or MongoDB: 18 | 19 | ``` 20 | Install-Package TestEnvironment.Docker.Containers.Elasticsearch 21 | Install-Package TestEnvironment.Docker.Containers.Mssql 22 | Install-Package TestEnvironment.Docker.Containers.Mongo 23 | Install-Package TestEnvironment.Docker.Containers.Mail 24 | Install-Package TestEnvironment.Docker.Containers.Ftp 25 | Install-Package TestEnvironment.Docker.Containers.MariaDB 26 | Install-Package TestEnvironment.Docker.Containers.Postgres 27 | Install-Package TestEnvironment.Docker.Containers.Kafka 28 | Install-Package TestEnvironment.Docker.Containers.Redis 29 | Install-Package TestEnvironment.Docker.Containers.RabbitMQ 30 | ``` 31 | ### Example 32 | Latest version is heavily using C# 9 [records](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types) feature. Please make sure you are targeting `net5.0` and above. 33 | 34 | ```csharp 35 | // Create the environment using builder pattern. 36 | await using var environment = new DockerEnvironmentBuilder() 37 | .AddContainer(p => p with 38 | { 39 | Name = "my-nginx", 40 | ImageName = "nginx" 41 | }) 42 | .AddElasticsearchContainer(p => p with 43 | { 44 | Name = "my-elastic" 45 | }) 46 | .AddMssqlContainer(p => p with 47 | { 48 | Name = "my-mssql", 49 | SAPassword = "HelloK11tt_0" 50 | }) 51 | .AddPostgresContainer(p => p with 52 | { 53 | Name = "my-postgres" 54 | }) 55 | .AddFromDockerfile(p => p with 56 | { 57 | Name = "from-file", 58 | Dockerfile = "Dockerfile", 59 | ContainerWaiter = new HttpContainerWaiter("/", port: 8080) 60 | }) 61 | .Build(); 62 | 63 | // Up it. 64 | await environment.UpAsync(); 65 | 66 | // Play with containers. 67 | var mssql = environment.GetContainer("my-mssql"); 68 | var elastic = environment.GetContainer("my-elastic"); 69 | var postgres = environment.GetContainer("my-postgres"); 70 | ``` 71 | 72 | ### Docker Desktop subscription changes 73 | As you may know, Docker announced subscription [changes](https://www.docker.com/blog/updating-product-subscriptions/). So you can not use Docker Desktop for free anymore (except for personal purposes). Good news is that Docker for Linux is still [free](https://www.docker.com/blog/looking-for-a-docker-alternative-consider-this/) (including Docker Engine, Docker Daemon, Docker CLI, Docker Compose, BuildKit, libraries, etc). So we have several options: 74 | 75 | #### Option 1 - Run tests on Linux/WSL2 76 | You can setup [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) on your Windows machine, install [.NET](https://docs.microsoft.com/en-us/dotnet/core/install/linux) and [Docker](https://docs.docker.com/engine/install/ubuntu/) (also, full instruction for Docker setup could be found [here](https://gist.github.com/Guddiny/e2cac200d8100a3926777109f770228b)). Then just navigate to your source code and simply run: 77 | 78 | ``` 79 | dotnet test 80 | ``` 81 | 82 | #### Option 2 - Expose Docker Daemon from WSL2 to Windows and proceed using Visual Studio 83 | Ok, you still want to use Visual Studio to run and debug your tests. So this is still possible. First, you need to expose your Docker Daemon installed to WSL2. For this, go to `/etc/docker/` and create/edit `daemon.js` file: 84 | 85 | ```json 86 | { 87 | "hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"] 88 | } 89 | ``` 90 | 91 | Then add `UseWsl2()` option to your tests: 92 | 93 | ```csharp 94 | await using var environment = new DockerEnvironmentBuilder() 95 | .UseWsl2() 96 | .AddMssqlContainer(p => p with 97 | { 98 | Name = "my-mssql", 99 | SAPassword = "HelloK11tt_0" 100 | }) 101 | ``` 102 | 103 | And that's it! 104 | 105 | Official instruction regarding Docker remote access are [here](https://docs.docker.com/config/daemon/remote-access/). 106 | 107 | ### Podman support 108 | 109 | As a container engine you can use [Podman](https://podman.io/) instead of docker. It works the same and even has the same api. 110 | To install podman on Windows and use it follow this [instruction](https://gist.github.com/Guddiny/893555b398e86fc9c33bbeee41ff154a) (Super easy). 111 | 112 | ### Troubleshooting 113 | 114 | In case of unpredictable behaviour try to remove the containers manually via command line: 115 | 116 | ``` 117 | docker rm -f (docker ps -a -q) 118 | ``` 119 | 120 | If you use **AddFromDockerfile()** then it is recommended to prune images time to time: 121 | 122 | ``` 123 | docker image prune -f 124 | ``` 125 | 126 | Ideally, use the [`--filter`](https://docs.docker.com/engine/reference/commandline/image_prune/#filtering) option on the `docker image prune` commandline task. Simply add `LABEL "CI_BUILD=True"` in your Dockerfile, and force delete all images with that LABEL: 127 | 128 | ``` 129 | docker image prune -f --filter "CI_BUILD=True" 130 | ``` 131 | 132 | ### Release Notes 133 | 134 | * Add WSL2 Support 135 | * Migrated to `net5.0` framework. Please reference 1.x.x packages if you need `netstandard2.0` support. This version will continue getting critical fixes in [branch](https://github.com/Deffiss/testenvironment-docker/tree/netstandard20). 136 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '1.0.0.{build}' 2 | image: Ubuntu 3 | services: 4 | - docker 5 | configuration: Release 6 | environment: 7 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 8 | build_script: 9 | - dotnet build TestEnvironment.sln -c Release 10 | test_script: 11 | - dotnet test ./test/TestEnvironment.Docker.Tests/TestEnvironment.Docker.Tests.csproj -c Release 12 | artifacts: 13 | - path: '**\*.nupkg' -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/AspNetCoreSample.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/EnvironmentFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.TestHost; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using TestEnvironment.Docker; 9 | using TestEnvironment.Docker.Containers.Mongo; 10 | using Xunit; 11 | 12 | namespace AspNetCoreSample.Tests 13 | { 14 | public class EnvironmentFixture : IAsyncLifetime 15 | { 16 | private readonly string _environmentName; 17 | private IDockerEnvironment _environment; 18 | private IHost _host; 19 | 20 | public HttpClient TestClient { get; private set; } 21 | 22 | #pragma warning disable SA1201 // Elements should appear in the correct order 23 | public EnvironmentFixture(string environmentName) => 24 | #pragma warning restore SA1201 // Elements should appear in the correct order 25 | _environmentName = environmentName; 26 | 27 | public async Task InitializeAsync() 28 | { 29 | // Docker environment seutup. 30 | _environment = CreateTestEnvironmentBuilder().Build(); 31 | await _environment.UpAsync(); 32 | 33 | // API Test host setup 34 | var mongoContainer = _environment.GetContainer("mongo"); 35 | _host = await CreateHostBuilder(mongoContainer).StartAsync(); 36 | TestClient = _host.GetTestClient(); 37 | 38 | await OnInitialized(mongoContainer); 39 | } 40 | 41 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 42 | public async Task DisposeAsync() 43 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 44 | { 45 | TestClient.Dispose(); 46 | _host.Dispose(); 47 | 48 | #if !DEBUG 49 | await _environment.DisposeAsync(); 50 | #endif 51 | } 52 | 53 | protected virtual Task OnInitialized(MongoContainer mongoContainer) => Task.CompletedTask; 54 | 55 | private IDockerEnvironmentBuilder CreateTestEnvironmentBuilder() => 56 | new DockerEnvironmentBuilder() 57 | .SetName(_environmentName) 58 | #if DEBUG 59 | .AddMongoContainer(p => p with 60 | { 61 | Name = "mongo", 62 | Reusable = true 63 | }); 64 | #else 65 | .AddMongoContainer(p => p with 66 | { 67 | Name = "mongo" 68 | }); 69 | #endif 70 | 71 | private IHostBuilder CreateHostBuilder(MongoContainer mongoContainer) 72 | { 73 | var config = new ConfigurationBuilder() 74 | .AddInMemoryCollection(new[] 75 | { 76 | new KeyValuePair("MongoDbConnectionString", mongoContainer.GetConnectionString()) 77 | }); 78 | 79 | var builder = new HostBuilder() 80 | .ConfigureWebHost(webBuilder => 81 | { 82 | webBuilder 83 | .UseStartup() 84 | .UseConfiguration(config.Build()) 85 | .UseTestServer(); 86 | }); 87 | 88 | return builder; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/ReadEnvironmentFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MongoDB.Driver; 4 | using TestEnvironment.Docker.Containers.Mongo; 5 | 6 | namespace AspNetCoreSample.Tests 7 | { 8 | public class ReadEnvironmentFixture : EnvironmentFixture 9 | { 10 | public ReadEnvironmentFixture() 11 | : base("env2") 12 | { 13 | } 14 | 15 | protected override async Task OnInitialized(MongoContainer mongoContainer) 16 | { 17 | await base.OnInitialized(mongoContainer); 18 | 19 | var client = new MongoClient(mongoContainer.GetConnectionString()); 20 | await client.GetDatabase("Forecast").GetCollection("WeatherForecast").InsertManyAsync(new[] 21 | { 22 | new WeatherForecast { Date = DateTime.Today.ToUniversalTime(), Summary = "Good weather", TemperatureC = 7 }, 23 | new WeatherForecast { Date = DateTime.Today.AddDays(-1).ToUniversalTime(), Summary = "Usual weather", TemperatureC = 3 }, 24 | new WeatherForecast { Date = DateTime.Today.AddDays(-2).ToUniversalTime(), Summary = "Bad weather", TemperatureC = 0 } 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/ReadTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace AspNetCoreSample.Tests 7 | { 8 | public class ReadTest : IClassFixture 9 | { 10 | private readonly ReadEnvironmentFixture _environmentFixture; 11 | 12 | public ReadTest(ReadEnvironmentFixture environmentTwoFixture) 13 | { 14 | _environmentFixture = environmentTwoFixture; 15 | } 16 | 17 | [Fact] 18 | public async Task Test1() 19 | { 20 | // Arrange. 21 | var client = _environmentFixture.TestClient; 22 | var data = new WeatherForecast { Date = DateTime.Today.ToUniversalTime(), Summary = "Hello", TemperatureC = 10 }; 23 | 24 | // Act. 25 | var getResponse = await client.GetAsync($"/WeatherForecast"); 26 | var dataGot = await getResponse.Content.ReadAsAsync(); 27 | 28 | // Assert. 29 | Assert.Equal(3, dataGot.Length); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/WriteEnvironmentFixture.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreSample.Tests 2 | { 3 | public class WriteEnvironmentFixture : EnvironmentFixture 4 | { 5 | public WriteEnvironmentFixture() 6 | : base("env1") 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.Tests/WriteTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace AspNetCoreSample.Tests 7 | { 8 | public class WriteTests : IClassFixture 9 | { 10 | private readonly WriteEnvironmentFixture _environmentFixture; 11 | 12 | public WriteTests(WriteEnvironmentFixture environmentOneFixture) 13 | { 14 | _environmentFixture = environmentOneFixture; 15 | } 16 | 17 | [Fact] 18 | public async Task Test1() 19 | { 20 | // Arrange. 21 | var client = _environmentFixture.TestClient; 22 | var data = new WeatherForecast { Date = DateTime.Today.ToUniversalTime(), Summary = "Hello", TemperatureC = 10 }; 23 | 24 | // Act. 25 | var postResponse = await client.PostAsJsonAsync("/WeatherForecast", data); 26 | var dataPosted = await postResponse.Content.ReadAsAsync(); 27 | 28 | var getResponse = await client.GetAsync($"/WeatherForecast/{dataPosted.Id}"); 29 | var dataGot = await getResponse.Content.ReadAsAsync(); 30 | 31 | // Assert. 32 | Assert.Equal(dataPosted.Id, dataGot.Id); 33 | Assert.Equal(dataPosted.Summary, dataGot.Summary); 34 | Assert.Equal(dataPosted.Date, dataGot.Date); 35 | Assert.Equal(dataPosted.TemperatureC, dataGot.TemperatureC); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreSample", "AspNetCoreSample\AspNetCoreSample.csproj", "{C025ABBF-131A-4C85-942F-7D9AF7B37D0D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreSample.Tests", "AspNetCoreSample.Tests\AspNetCoreSample.Tests.csproj", "{A6017074-035B-4204-83E1-BF34268516D6}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C025ABBF-131A-4C85-942F-7D9AF7B37D0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C025ABBF-131A-4C85-942F-7D9AF7B37D0D}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C025ABBF-131A-4C85-942F-7D9AF7B37D0D}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C025ABBF-131A-4C85-942F-7D9AF7B37D0D}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {A6017074-035B-4204-83E1-BF34268516D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {A6017074-035B-4204-83E1-BF34268516D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {A6017074-035B-4204-83E1-BF34268516D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {A6017074-035B-4204-83E1-BF34268516D6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3539C25C-B73E-4721-BD50-31CB46646B38} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/AspNetCoreSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Logging; 6 | using MongoDB.Driver; 7 | 8 | namespace AspNetCoreSample.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private readonly IConfiguration _configuration; 15 | private readonly ILogger _logger; 16 | 17 | public WeatherForecastController(IConfiguration configuration, ILogger logger) 18 | { 19 | _configuration = configuration; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | public IEnumerable Get() 25 | { 26 | var client = new MongoClient(_configuration.GetValue("MongoDbConnectionString")); 27 | return client.GetDatabase("Forecast").GetCollection("WeatherForecast").AsQueryable().ToArray(); 28 | } 29 | 30 | [HttpGet("{id}")] 31 | public WeatherForecast GetById(string id) 32 | { 33 | var client = new MongoClient(_configuration.GetValue("MongoDbConnectionString")); 34 | return client.GetDatabase("Forecast").GetCollection("WeatherForecast") 35 | .AsQueryable().Where(w => w.Id == id).FirstOrDefault(); 36 | } 37 | 38 | [HttpPost] 39 | public IActionResult Post(WeatherForecast weatherForecast) 40 | { 41 | var client = new MongoClient(_configuration.GetValue("MongoDbConnectionString")); 42 | client.GetDatabase("Forecast").GetCollection("WeatherForecast").InsertOne(weatherForecast); 43 | 44 | return Ok(weatherForecast); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace AspNetCoreSample 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:62673", 8 | "sslPort": 44301 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "AspNetCoreWithTests": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.OpenApi.Models; 7 | 8 | namespace AspNetCoreSample 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | 24 | services.AddSwaggerGen(c => 25 | { 26 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); 27 | }); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseHttpsRedirection(); 39 | 40 | app.UseRouting(); 41 | 42 | app.UseAuthorization(); 43 | 44 | app.UseSwagger(); 45 | 46 | app.UseSwaggerUI(c => 47 | { 48 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); 49 | }); 50 | 51 | app.UseEndpoints(endpoints => 52 | { 53 | endpoints.MapControllers(); 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.Serialization.Attributes; 4 | 5 | namespace AspNetCoreSample 6 | { 7 | public class WeatherForecast 8 | { 9 | [BsonId] 10 | [BsonRepresentation(BsonType.ObjectId)] 11 | public string Id { get; set; } 12 | 13 | public DateTime Date { get; set; } 14 | 15 | public int TemperatureC { get; set; } 16 | 17 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 18 | 19 | public string Summary { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/AspNetCoreSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "MongoDbConnectionString": "mongodb://localhost:27017", 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /samples/aspnetcore-integration-tests/README.md: -------------------------------------------------------------------------------- 1 | # AspNetCore application with integration tests 2 | 3 | ### Overview 4 | 5 | This is a sample AspNetCore application that does CRUD operations and stores its state in MongoDB. You can run it and navigate to `/swagger`. The application uses AspNetCore configuration system to obtain the connection string which by default is `mongodb://localhost:27017` so make sure that you run local instance of MongoDB. You can do that using Docker: 6 | 7 | ``` 8 | docker run --name some-mongo -d -p "27017:27017" mongo:latest 9 | ``` 10 | 11 | ### Tests 12 | 13 | Integration tests project consists of `EnvironmentFixture` class that setups tesing environment by creating MongoDB container and hosting sample API inside `TestHost`. It overrides the configuration so the API uses the connection string to MongoDB inside a just created container. There are two inherited fixture classes creating read and write environments which are used by read only tests and tests that change the state. You can run everything just as usual tests from Visual Studio IDE or from command line: 14 | 15 | ``` 16 | dotnet test 17 | ``` 18 | 19 | Please make sure that you run Docker. If you don't have MongoDB image locally it will automatically download it for you but it might take some time to run the tests very first time. 20 | 21 | ### Debug/Release 22 | 23 | Although, MongoDB startup shouldn't take too much time you'll still might notice some extra time spending on containers creation and disposing. That's why `EnvironmentFixture` class contains the conditionally compiled code so each time when you run your tests using `Debug` configuration it will try to cleanup and reuse existing containers and not dispose them at the end. This allows to improve local testing experience significantly. You should still prefer to use `Release` configuration when you run your tests inside CI/CD. 24 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLL/BLL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLL/IPizzaService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace BLL 5 | { 6 | public interface IPizzaService 7 | { 8 | Task OrderPizza(string customer, List pizzaIds); 9 | 10 | Task RemoveOrder(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLL/PizzaService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using DAL; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | namespace BLL 9 | { 10 | public class PizzaService : IPizzaService 11 | { 12 | private readonly PizzaContext _dbContext; 13 | 14 | public PizzaService(PizzaContext dbContext) 15 | { 16 | _dbContext = dbContext; 17 | } 18 | 19 | public async Task OrderPizza(string customer, List pizzaIds) 20 | { 21 | var order = new Order 22 | { 23 | Customer = customer, 24 | CreatedAt = DateTime.UtcNow.Date 25 | }; 26 | 27 | _dbContext.Orders.Add(order); 28 | await _dbContext.SaveChangesAsync(); 29 | 30 | var pizzas = await _dbContext.Pizzas.Where(x => pizzaIds.Contains(x.Id)).ToListAsync(); 31 | 32 | _dbContext.PizzaOrders.AddRange(pizzas.Select(x => new PizzaOrder { OrderId = order.Id, PizzaId = x.Id })); 33 | await _dbContext.SaveChangesAsync(); 34 | } 35 | 36 | public async Task RemoveOrder(int id) 37 | { 38 | var order = await _dbContext.Orders.FirstOrDefaultAsync(x => x.Id == id); 39 | var pizzaOrders = await _dbContext.PizzaOrders.Where(x => x.OrderId == order.Id).ToListAsync(); 40 | 41 | _dbContext.PizzaOrders.RemoveRange(pizzaOrders); 42 | _dbContext.Orders.Remove(order); 43 | 44 | await _dbContext.SaveChangesAsync(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLLTests/BLLTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLLTests/PizzaServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using BLL; 5 | using DAL; 6 | using FluentAssertions; 7 | using Microsoft.EntityFrameworkCore; 8 | using NUnit.Framework; 9 | 10 | namespace BLLTests 11 | { 12 | public class PizzaServiceTests : TestBase 13 | { 14 | private IPizzaService _pizzaService; 15 | 16 | [OneTimeSetUp] 17 | public void Initialization() 18 | { 19 | _pizzaService = new PizzaService(DbContext); 20 | } 21 | 22 | [Test] 23 | public async Task OrderPizza_WhenOrderSpecified_ShouldCreateOrder() 24 | { 25 | // Arrange 26 | var pepperoni = new Pizza { Name = "Pepperoni" }; 27 | var napoli = new Pizza { Name = "Spacca Napoli" }; 28 | 29 | DbContext.Pizzas.AddRange(new Pizza[] { pepperoni, napoli }); 30 | await DbContext.SaveChangesAsync(); 31 | 32 | // Act 33 | await _pizzaService.OrderPizza("John Smith", new List { pepperoni.Id, napoli.Id }); 34 | 35 | // Assert 36 | var order = await DbContext.Orders.FirstOrDefaultAsync(); 37 | var pizzaOrders = await DbContext.PizzaOrders.ToListAsync(); 38 | 39 | order.Should().BeEquivalentTo( 40 | new Order 41 | { 42 | Customer = "John Smith", 43 | CreatedAt = DateTime.UtcNow.Date 44 | }, options => options.Excluding(x => x.Id)); 45 | pizzaOrders.Should().BeEquivalentTo(new List 46 | { 47 | new PizzaOrder { OrderId = order.Id, PizzaId = pepperoni.Id }, 48 | new PizzaOrder { OrderId = order.Id, PizzaId = napoli.Id } 49 | }); 50 | } 51 | 52 | [Test] 53 | public async Task RemoveOrder_WhenOrderIdSpecified_ShouldRemoveOrder() 54 | { 55 | // Arrange 56 | var pepperoni = new Pizza { Name = "Pepperoni" }; 57 | var napoli = new Pizza { Name = "Spacca Napoli" }; 58 | var order = new Order { CreatedAt = DateTime.UtcNow.Date, Customer = "Bart Simpson" }; 59 | 60 | DbContext.Pizzas.AddRange(new Pizza[] { pepperoni, napoli }); 61 | DbContext.Orders.Add(order); 62 | await DbContext.SaveChangesAsync(); 63 | 64 | DbContext.PizzaOrders.AddRange(new PizzaOrder[] 65 | { 66 | new PizzaOrder { OrderId = order.Id, PizzaId = pepperoni.Id }, 67 | new PizzaOrder { OrderId = order.Id, PizzaId = napoli.Id } 68 | }); 69 | await DbContext.SaveChangesAsync(); 70 | 71 | // Act 72 | await _pizzaService.RemoveOrder(order.Id); 73 | 74 | // Assert 75 | var orders = await DbContext.Orders.ToListAsync(); 76 | var pizzaOrders = await DbContext.PizzaOrders.ToListAsync(); 77 | 78 | orders.Should().BeEmpty(); 79 | pizzaOrders.Should().BeEmpty(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLLTests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DAL; 3 | using NUnit.Framework; 4 | using TestEnvironment.Docker; 5 | using TestEnvironment.Docker.Containers.Mssql; 6 | 7 | namespace BLLTests 8 | { 9 | public class TestBase 10 | { 11 | private IDockerEnvironment _dockerEnvironment; 12 | 13 | protected PizzaContext DbContext { get; set; } 14 | 15 | [OneTimeSetUp] 16 | public async Task OneTimeSetup() 17 | { 18 | var environmentBuilder = new DockerEnvironmentBuilder(); 19 | _dockerEnvironment = PrepareDockerEnvironment(environmentBuilder); 20 | await _dockerEnvironment.UpAsync(); 21 | 22 | var connectionString = _dockerEnvironment.GetContainer("testDb").GetConnectionString(); 23 | 24 | DbContext = new PizzaContext(connectionString); 25 | await DbContext.Database.EnsureCreatedAsync(); 26 | } 27 | 28 | [OneTimeTearDown] 29 | public async Task OneTimeTearDown() 30 | { 31 | await _dockerEnvironment.DisposeAsync(); 32 | await DbContext.DisposeAsync(); 33 | } 34 | 35 | [TearDown] 36 | public async Task ClearDb() 37 | { 38 | DbContext.PizzaOrders.RemoveRange(DbContext.PizzaOrders); 39 | DbContext.Pizzas.RemoveRange(DbContext.Pizzas); 40 | DbContext.Orders.RemoveRange(DbContext.Orders); 41 | await DbContext.SaveChangesAsync(); 42 | } 43 | 44 | private IDockerEnvironment PrepareDockerEnvironment(DockerEnvironmentBuilder environmentBuilder) 45 | { 46 | return environmentBuilder 47 | .SetName("nunit-tests") 48 | .AddMssqlContainer(p => p with 49 | { 50 | Name = "testDb", 51 | SAPassword = "HelloK11tt_0" 52 | }) 53 | .Build(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/BLLWithNunitSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BLL", "BLL\BLL.csproj", "{B33D23CC-A796-49EC-837E-D57780135D9D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BLLTests", "BLLTests\BLLTests.csproj", "{40201E89-2D12-484F-972D-276B2FFF70AB}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DAL", "DAL\DAL.csproj", "{1D30A89A-22E4-48C6-AE65-ACDFD89A23D9}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B33D23CC-A796-49EC-837E-D57780135D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {B33D23CC-A796-49EC-837E-D57780135D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {B33D23CC-A796-49EC-837E-D57780135D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {B33D23CC-A796-49EC-837E-D57780135D9D}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {40201E89-2D12-484F-972D-276B2FFF70AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {40201E89-2D12-484F-972D-276B2FFF70AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {40201E89-2D12-484F-972D-276B2FFF70AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {40201E89-2D12-484F-972D-276B2FFF70AB}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {1D30A89A-22E4-48C6-AE65-ACDFD89A23D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {1D30A89A-22E4-48C6-AE65-ACDFD89A23D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {1D30A89A-22E4-48C6-AE65-ACDFD89A23D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {1D30A89A-22E4-48C6-AE65-ACDFD89A23D9}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {F4518924-8A34-4209-9FF3-C762587532A9} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/DAL/DAL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/DAL/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DAL 4 | { 5 | public class Order 6 | { 7 | public int Id { get; set; } 8 | 9 | public string Customer { get; set; } 10 | 11 | public DateTime CreatedAt { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/DAL/Pizza.cs: -------------------------------------------------------------------------------- 1 | namespace DAL 2 | { 3 | public class Pizza 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/DAL/PizzaContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace DAL 4 | { 5 | public class PizzaContext : DbContext 6 | { 7 | private readonly string _connectionString; 8 | 9 | public PizzaContext(string connectionString) 10 | { 11 | _connectionString = connectionString; 12 | } 13 | 14 | public DbSet Pizzas { get; set; } 15 | 16 | public DbSet Orders { get; set; } 17 | 18 | public DbSet PizzaOrders { get; set; } 19 | 20 | protected override void OnModelCreating(ModelBuilder modelBuilder) 21 | { 22 | modelBuilder.Entity(entity => 23 | { 24 | entity.HasKey(e => new { e.OrderId, e.PizzaId }); 25 | entity.HasOne().WithMany().HasForeignKey(po => po.OrderId); 26 | entity.HasOne().WithMany().HasForeignKey(po => po.PizzaId); 27 | }); 28 | } 29 | 30 | protected override void OnConfiguring(DbContextOptionsBuilder dbContextOptionsBuilder) 31 | { 32 | dbContextOptionsBuilder.UseSqlServer(_connectionString); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/test-bll-with-nunit/BLLlWithNunitSample/DAL/PizzaOrder.cs: -------------------------------------------------------------------------------- 1 | namespace DAL 2 | { 3 | public class PizzaOrder 4 | { 5 | public int OrderId { get; set; } 6 | 7 | public int PizzaId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Elasticsearch/ElasticsearchContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Elasticsearch 9 | { 10 | public class ElasticsearchContainer : Container 11 | { 12 | public ElasticsearchContainer(ContainerParameters containerParameters) 13 | : base(containerParameters) 14 | { 15 | } 16 | 17 | public ElasticsearchContainer(ContainerParameters containerParameters, IDockerClient dockerClient) 18 | : base(containerParameters, dockerClient) 19 | { 20 | } 21 | 22 | public ElasticsearchContainer(ContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) 24 | { 25 | } 26 | 27 | public ElasticsearchContainer(ContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 28 | : base(containerParameters, containerApi, imageApi, logger) 29 | { 30 | } 31 | 32 | public string GetUrl() => IsDockerInDocker ? $"http://{IPAddress}:9200" : $"http://{IP.Loopback}:{Ports![9200]}"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Elasticsearch/ElasticsearchContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Nest; 5 | using TestEnvironment.Docker.ContainerLifecycle; 6 | 7 | namespace TestEnvironment.Docker.Containers.Elasticsearch 8 | { 9 | public class ElasticsearchContainerCleaner : IContainerCleaner 10 | { 11 | public async Task CleanupAsync(ElasticsearchContainer container, CancellationToken cancellationToken = default) 12 | { 13 | if (container == null) 14 | { 15 | throw new ArgumentNullException(nameof(container)); 16 | } 17 | 18 | var elastic = new ElasticClient(new Uri(container.GetUrl())); 19 | 20 | await elastic.Indices.DeleteTemplateAsync("*", ct: cancellationToken); 21 | await elastic.Indices.DeleteAsync("*", ct: cancellationToken); 22 | } 23 | 24 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => 25 | CleanupAsync((ElasticsearchContainer)container, cancellationToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Elasticsearch/ElasticsearchContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Elasticsearch.Net; 5 | using Microsoft.Extensions.Logging; 6 | using Nest; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | 9 | namespace TestEnvironment.Docker.Containers.Elasticsearch 10 | { 11 | public class ElasticsearchContainerWaiter : BaseContainerWaiter 12 | { 13 | public ElasticsearchContainerWaiter() 14 | { 15 | } 16 | 17 | public ElasticsearchContainerWaiter(ILogger logger) 18 | : base(logger) 19 | { 20 | } 21 | 22 | protected override async Task PerformCheckAsync(ElasticsearchContainer container, CancellationToken cancellationToken) 23 | { 24 | var elastic = new ElasticClient(new Uri(container.GetUrl())); 25 | var health = await elastic.Cluster.HealthAsync( 26 | selector: ch => ch 27 | .WaitForStatus(WaitForStatus.Yellow) 28 | .Level(Level.Cluster) 29 | .ErrorTrace(true), 30 | ct: cancellationToken); 31 | 32 | Logger?.LogDebug(health.DebugInformation); 33 | 34 | return health.IsValid; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Elasticsearch/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker.Containers.Elasticsearch 7 | { 8 | public static class IDockerEnvironmentBuilderExtensions 9 | { 10 | public static ContainerParameters DefaultParameters => new("elastic", "docker.elastic.co/elasticsearch/elasticsearch-oss") 11 | { 12 | Tag = "7.0.1", 13 | EnvironmentVariables = new Dictionary 14 | { 15 | ["discovery.type"] = "single-node", 16 | }, 17 | ContainerCleaner = new ElasticsearchContainerCleaner(), 18 | ContainerWaiter = new ElasticsearchContainerWaiter() 19 | }; 20 | 21 | public static IDockerEnvironmentBuilder AddElasticsearchContainer( 22 | this IDockerEnvironmentBuilder builder, 23 | Func paramsBuilder) 24 | { 25 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 26 | builder.AddContainer(parameters, (p, d, l) => new ElasticsearchContainer(p, d, l)); 27 | 28 | return builder; 29 | } 30 | 31 | public static IDockerEnvironmentBuilder AddElasticsearchContainer( 32 | this IDockerEnvironmentBuilder builder, 33 | Func paramsBuilder) 34 | { 35 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 36 | builder.AddContainer(parameters, (p, d, l) => new ElasticsearchContainer(p, d, l)); 37 | 38 | return builder; 39 | } 40 | 41 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 42 | public static IDockerEnvironmentBuilder AddElasticsearchContainer( 43 | this IDockerEnvironmentBuilder builder, 44 | string name, 45 | string imageName = "docker.elastic.co/elasticsearch/elasticsearch-oss", 46 | string tag = "7.0.1", 47 | IDictionary? environmentVariables = null, 48 | IDictionary? ports = null, 49 | bool reuseContainer = false) 50 | { 51 | builder.AddElasticsearchContainer(p => p with 52 | { 53 | Name = name, 54 | ImageName = imageName, 55 | Tag = tag, 56 | EnvironmentVariables = environmentVariables, 57 | Ports = ports, 58 | Reusable = reuseContainer 59 | }); 60 | 61 | return builder; 62 | } 63 | 64 | private static ContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 65 | builder.Logger switch 66 | { 67 | { } => DefaultParameters with 68 | { 69 | ContainerWaiter = new ElasticsearchContainerWaiter(builder.Logger) 70 | }, 71 | null => DefaultParameters 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Elasticsearch/TestEnvironment.Docker.Containers.Elasticsearch.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Elasticsearch 6 | TestEnvironment.Docker.Containers.Elasticsearch 7 | 2.1.5 8 | TestEnvironment.Docker.Containers.Elasticsearch 9 | enable 10 | Aliaksei Harshkalep 11 | Add Elasticsearch container specific functionality. 12 | false 13 | docker tests elasticsearch container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/FtpContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Ftp 9 | { 10 | public class FtpContainer : Container 11 | { 12 | private readonly FtpContainerParameters _parameters; 13 | 14 | public string FtpUserName => _parameters.FtpUserName; 15 | 16 | public string FtpPassword => _parameters.FtpPassword; 17 | 18 | public string? FtpHost => IsDockerInDocker ? IPAddress : IP.Loopback.ToString(); 19 | 20 | #pragma warning disable SA1201 // Elements should appear in the correct order 21 | public FtpContainer(FtpContainerParameters containerParameters) 22 | #pragma warning restore SA1201 // Elements should appear in the correct order 23 | : base(containerParameters) => 24 | _parameters = containerParameters; 25 | 26 | public FtpContainer(FtpContainerParameters containerParameters, IDockerClient dockerClient) 27 | : base(containerParameters, dockerClient) => 28 | _parameters = containerParameters; 29 | 30 | public FtpContainer(FtpContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 31 | : base(containerParameters, dockerClient, logger) => 32 | _parameters = containerParameters; 33 | 34 | public FtpContainer(FtpContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 35 | : base(containerParameters, containerApi, imageApi, logger) => 36 | _parameters = containerParameters; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/FtpContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using FluentFTP; 5 | using Microsoft.Extensions.Logging; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Ftp 9 | { 10 | public class FtpContainerCleaner : IContainerCleaner 11 | { 12 | private readonly ILogger? _logger; 13 | 14 | public FtpContainerCleaner() 15 | { 16 | } 17 | 18 | public FtpContainerCleaner(ILogger logger) => 19 | _logger = logger; 20 | 21 | public async Task CleanupAsync(FtpContainer container, CancellationToken cancellationToken = default) 22 | { 23 | using var ftpClient = new FtpClient(container.FtpHost, container.IsDockerInDocker ? 21 : container.Ports![21], container.FtpUserName, container.FtpPassword); 24 | try 25 | { 26 | await ftpClient.ConnectAsync(cancellationToken); 27 | foreach (var item in await ftpClient.GetListingAsync("/")) 28 | { 29 | if (item.Type == FtpFileSystemObjectType.Directory) 30 | { 31 | await ftpClient.DeleteDirectoryAsync(item.FullName, cancellationToken); 32 | } 33 | else if (item.Type == FtpFileSystemObjectType.File) 34 | { 35 | await ftpClient.DeleteFileAsync(item.FullName, cancellationToken); 36 | } 37 | } 38 | } 39 | catch (Exception e) 40 | { 41 | _logger?.LogInformation($"Cleanup issue: {e.Message}"); 42 | } 43 | } 44 | 45 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => 46 | CleanupAsync((FtpContainer)container, cancellationToken); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/FtpContainerParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TestEnvironment.Docker.Containers.Ftp 8 | { 9 | public record FtpContainerParameters(string Name, string FtpUserName, string FtpPassword) 10 | : ContainerParameters(Name, "stilliard/pure-ftpd") 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/FtpContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using FluentFTP; 4 | using Microsoft.Extensions.Logging; 5 | using TestEnvironment.Docker.ContainerLifecycle; 6 | 7 | namespace TestEnvironment.Docker.Containers.Ftp 8 | { 9 | public class FtpContainerWaiter : BaseContainerWaiter 10 | { 11 | public FtpContainerWaiter() 12 | { 13 | } 14 | 15 | public FtpContainerWaiter(ILogger logger) 16 | : base(logger) 17 | { 18 | } 19 | 20 | protected override async Task PerformCheckAsync(FtpContainer container, CancellationToken cancellationToken) 21 | { 22 | using var ftpClient = new FtpClient( 23 | container.FtpHost, 24 | container.IsDockerInDocker ? 21 : container.Ports![21], 25 | container.FtpUserName, 26 | container.FtpPassword); 27 | 28 | await ftpClient.ConnectAsync(cancellationToken); 29 | 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | using IP = System.Net.IPAddress; 6 | 7 | namespace TestEnvironment.Docker.Containers.Ftp 8 | { 9 | public static class IDockerEnvironmentBuilderExtensions 10 | { 11 | public static FtpContainerParameters DefaultParameters => new("ftp", "admin", "admin") 12 | { 13 | ImageName = "stilliard/pure-ftpd", 14 | Tag = "hardened", 15 | EnvironmentVariables = new Dictionary 16 | { 17 | ["PUBLICHOST"] = IP.Loopback.ToString(), 18 | ["FTP_USER_NAME"] = "admin", 19 | ["FTP_USER_PASS"] = "admin", 20 | ["FTP_USER_HOME"] = "/home/ftpusers/admin" 21 | }, 22 | ContainerCleaner = new FtpContainerCleaner(), 23 | ContainerWaiter = new FtpContainerWaiter() 24 | }; 25 | 26 | public static IDockerEnvironmentBuilder AddFtpContainer( 27 | this IDockerEnvironmentBuilder builder, 28 | Func paramsBuilder) 29 | { 30 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 31 | builder.AddContainer(parameters, (p, d, l) => new FtpContainer(FixEnvironmentVariables(p), d, l)); 32 | 33 | return builder; 34 | } 35 | 36 | public static IDockerEnvironmentBuilder AddFtpContainer( 37 | this IDockerEnvironmentBuilder builder, 38 | Func paramsBuilder) 39 | { 40 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 41 | builder.AddContainer(parameters, (p, d, l) => new FtpContainer(FixEnvironmentVariables(p), d, l)); 42 | 43 | return builder; 44 | } 45 | 46 | public static IDockerEnvironmentBuilder AddFtpContainer( 47 | this IDockerEnvironmentBuilder builder, 48 | string name, 49 | string ftpUserName, 50 | string ftpPassword, 51 | string imageName = "stilliard/pure-ftpd", 52 | string tag = "hardened", 53 | IDictionary? environmentVariables = null, 54 | IDictionary? ports = null, 55 | bool reuseContainer = false) 56 | { 57 | builder.AddFtpContainer(p => p with 58 | { 59 | Name = name, 60 | FtpUserName = ftpUserName, 61 | FtpPassword = ftpPassword, 62 | ImageName = imageName, 63 | Tag = tag, 64 | EnvironmentVariables = environmentVariables, 65 | Ports = ports, 66 | Reusable = reuseContainer 67 | }); 68 | 69 | return builder; 70 | } 71 | 72 | private static FtpContainerParameters FixEnvironmentVariables(FtpContainerParameters p) => 73 | p with 74 | { 75 | EnvironmentVariables = new Dictionary 76 | { 77 | ["FTP_USER_NAME"] = p.FtpUserName, 78 | ["FTP_USER_PASS"] = p.FtpPassword, 79 | ["FTP_USER_HOME"] = $"/home/ftpusers/{p.FtpUserName}" 80 | }.MergeDictionaries(p.EnvironmentVariables), 81 | }; 82 | 83 | private static FtpContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 84 | builder.Logger switch 85 | { 86 | { } => DefaultParameters with 87 | { 88 | ContainerWaiter = new FtpContainerWaiter(builder.Logger), 89 | ContainerCleaner = new FtpContainerCleaner(builder.Logger) 90 | }, 91 | null => DefaultParameters 92 | }; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Ftp/TestEnvironment.Docker.Containers.Ftp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Ftp 6 | TestEnvironment.Docker.Containers.Ftp 7 | 2.1.5 8 | TestEnvironment.Docker.Containers.Ftp 9 | enable 10 | Aliaksei Harshkalep 11 | Add FTP container specific functionality. 12 | false 13 | docker tests ftp container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Kafka/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker.Containers.Kafka 7 | { 8 | public static class IDockerEnvironmentBuilderExtensions 9 | { 10 | public static ContainerParameters DefaultParameters => new("kafka", "johnnypark/kafka-zookeeper") 11 | { 12 | EnvironmentVariables = new Dictionary 13 | { 14 | ["ADVERTISED_HOST"] = "localhost", 15 | }, 16 | Ports = new Dictionary 17 | { 18 | [9092] = 9092, 19 | [2181] = 2181, 20 | }, 21 | Tag = "2.6.0", 22 | ContainerCleaner = new KafkaContainerCleaner(), 23 | ContainerWaiter = new KafkaContainerWaiter() 24 | }; 25 | 26 | public static IDockerEnvironmentBuilder AddKafkaContainer( 27 | this IDockerEnvironmentBuilder builder, 28 | Func paramsBuilder) 29 | { 30 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 31 | builder.AddContainer(parameters, (p, d, l) => new KafkaContainer(p, d, l)); 32 | 33 | return builder; 34 | } 35 | 36 | public static IDockerEnvironmentBuilder AddKafkaContainer( 37 | this IDockerEnvironmentBuilder builder, 38 | Func paramsBuilder) 39 | { 40 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 41 | builder.AddContainer(parameters, (p, d, l) => new KafkaContainer(p, d, l)); 42 | 43 | return builder; 44 | } 45 | 46 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 47 | public static IDockerEnvironmentBuilder AddKafkaContainer( 48 | this IDockerEnvironmentBuilder builder, 49 | string name, 50 | string imageName = "johnnypark/kafka-zookeeper", 51 | string tag = "latest", 52 | IDictionary? environmentVariables = null, 53 | IDictionary? ports = null, 54 | bool reuseContainer = false) 55 | { 56 | builder.AddKafkaContainer(p => p with 57 | { 58 | Name = name, 59 | ImageName = imageName, 60 | Tag = tag, 61 | EnvironmentVariables = environmentVariables, 62 | Ports = ports, 63 | Reusable = reuseContainer 64 | }); 65 | 66 | return builder; 67 | } 68 | 69 | private static ContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 70 | builder.Logger switch 71 | { 72 | { } => DefaultParameters with 73 | { 74 | ContainerWaiter = new KafkaContainerWaiter(builder.Logger) 75 | }, 76 | null => DefaultParameters 77 | }; 78 | } 79 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Kafka/KafkaContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Kafka 9 | { 10 | public class KafkaContainer : Container 11 | { 12 | public KafkaContainer(ContainerParameters containerParameters) 13 | : base(containerParameters) 14 | { 15 | } 16 | 17 | public KafkaContainer(ContainerParameters containerParameters, IDockerClient dockerClient) 18 | : base(containerParameters, dockerClient) 19 | { 20 | } 21 | 22 | public KafkaContainer(ContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) 24 | { 25 | } 26 | 27 | public KafkaContainer(ContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 28 | : base(containerParameters, containerApi, imageApi, logger) 29 | { 30 | } 31 | 32 | public string GetUrl() => IsDockerInDocker ? $"{IPAddress}:9092" : $"{IP.Loopback}:{Ports![9092]}"; 33 | } 34 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Kafka/KafkaContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Confluent.Kafka; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Kafka 9 | { 10 | public class KafkaContainerCleaner : IContainerCleaner 11 | { 12 | public async Task CleanupAsync(KafkaContainer container, CancellationToken token = default) 13 | { 14 | if (container == null) 15 | { 16 | throw new ArgumentNullException(nameof(container)); 17 | } 18 | 19 | using var adminClient = new AdminClientBuilder(new AdminClientConfig 20 | { 21 | BootstrapServers = container.GetUrl() 22 | }) 23 | .Build(); 24 | var metadata = adminClient.GetMetadata(TimeSpan.FromSeconds(1)); 25 | if (!metadata.Topics.Any()) 26 | { 27 | return; 28 | } 29 | 30 | var topicNames = metadata.Topics.Select(t => t.Topic).ToArray(); 31 | await adminClient.DeleteTopicsAsync(topicNames); 32 | } 33 | 34 | public Task CleanupAsync(Container container, CancellationToken token = default) => 35 | CleanupAsync((KafkaContainer)container, token); 36 | } 37 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Kafka/KafkaContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Confluent.Kafka; 7 | using Confluent.Kafka.Admin; 8 | using Microsoft.Extensions.Logging; 9 | using TestEnvironment.Docker.ContainerLifecycle; 10 | 11 | namespace TestEnvironment.Docker.Containers.Kafka 12 | { 13 | public class KafkaContainerWaiter : BaseContainerWaiter 14 | { 15 | private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(5); 16 | 17 | public KafkaContainerWaiter() 18 | : base() 19 | { 20 | } 21 | 22 | public KafkaContainerWaiter(ILogger logger) 23 | : base(logger) 24 | { 25 | } 26 | 27 | protected override async Task PerformCheckAsync(KafkaContainer container, CancellationToken cancellationToken) 28 | { 29 | using var adminClient = new AdminClientBuilder(new AdminClientConfig 30 | { 31 | BootstrapServers = container.GetUrl(), 32 | }) 33 | .Build(); 34 | var topicName = $"{container.Name}_topic_health"; 35 | var topics = adminClient.GetMetadata(topicName, TimeSpan.FromSeconds(10)).Topics.Select(t => t.Topic).ToArray(); 36 | if (!topics.Contains(topicName)) 37 | { 38 | await adminClient.CreateTopicsAsync(new[] 39 | { 40 | new TopicSpecification() 41 | { 42 | NumPartitions = 1, 43 | Name = topicName, 44 | ReplicationFactor = 1 45 | }, 46 | }); 47 | } 48 | 49 | await ProduceMessage(topicName, container, cancellationToken); 50 | return true; 51 | } 52 | 53 | private async Task ProduceMessage(string topicName, KafkaContainer container, CancellationToken cancellationToken) 54 | { 55 | var producerConfig = new ProducerConfig 56 | { 57 | BootstrapServers = container.GetUrl() 58 | }; 59 | using var p = new ProducerBuilder(producerConfig).Build(); 60 | await p.ProduceAsync(topicName, new Message { Value = "test-message", Key = null }, cancellationToken); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Kafka/TestEnvironment.Docker.Containers.Kafka.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Kafka 6 | TestEnvironment.Docker.Containers.Kafka 7 | 2.1.6 8 | TestEnvironment.Docker.Containers.Kafka 9 | Oğuzhan Eren 10 | Add Kafka container specific functionality. 11 | false 12 | docker tests kafka container 13 | enable 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | using IP = System.Net.IPAddress; 6 | 7 | namespace TestEnvironment.Docker.Containers.Mail 8 | { 9 | public static class IDockerEnvironmentBuilderExtensions 10 | { 11 | public static MailContainerParameters DefaultParameters => new("mail") 12 | { 13 | ImageName = "mailhog/mailhog", 14 | ContainerCleaner = new MailContainerCleaner(), 15 | ContainerWaiter = new MailContainerWaiter() 16 | }; 17 | 18 | public static IDockerEnvironmentBuilder AddMailContainer( 19 | this IDockerEnvironmentBuilder builder, 20 | Func paramsBuilder) 21 | { 22 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 23 | builder.AddContainer(parameters, (p, d, l) => new MailContainer(FixEnvironmentVariables(p), d, l)); 24 | 25 | return builder; 26 | } 27 | 28 | public static IDockerEnvironmentBuilder AddMailContainer( 29 | this IDockerEnvironmentBuilder builder, 30 | Func paramsBuilder) 31 | { 32 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 33 | builder.AddContainer(parameters, (p, d, l) => new MailContainer(FixEnvironmentVariables(p), d, l)); 34 | 35 | return builder; 36 | } 37 | 38 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 39 | public static IDockerEnvironmentBuilder AddMailContainer( 40 | this IDockerEnvironmentBuilder builder, 41 | string name, 42 | string imageName = "mailhog/mailhog", 43 | string tag = "latest", 44 | ushort smptPort = 1025, 45 | ushort apiPort = 8025, 46 | string deleteEndpoint = "api/v1/messages", 47 | IDictionary? environmentVariables = null, 48 | IDictionary? ports = null, 49 | bool reuseContainer = false) 50 | { 51 | builder.AddMailContainer(p => p with 52 | { 53 | Name = name, 54 | ImageName = imageName, 55 | Tag = tag, 56 | SmptPort = smptPort, 57 | ApiPort = apiPort, 58 | DeleteEndpoint = deleteEndpoint, 59 | EnvironmentVariables = environmentVariables, 60 | Ports = ports, 61 | Reusable = reuseContainer 62 | }); 63 | 64 | return builder; 65 | } 66 | 67 | private static MailContainerParameters FixEnvironmentVariables(MailContainerParameters p) => 68 | p with 69 | { 70 | EnvironmentVariables = new Dictionary 71 | { 72 | // TODO: allign env vars 73 | }.MergeDictionaries(p.EnvironmentVariables), 74 | }; 75 | 76 | private static MailContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 77 | builder.Logger switch 78 | { 79 | { } => DefaultParameters with 80 | { 81 | ContainerWaiter = new MailContainerWaiter(builder.Logger), 82 | ContainerCleaner = new MailContainerCleaner(builder.Logger) 83 | }, 84 | null => DefaultParameters 85 | }; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/MailContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | 7 | namespace TestEnvironment.Docker.Containers.Mail 8 | { 9 | public class MailContainer : Container 10 | { 11 | public MailContainer(MailContainerParameters containerParameters) 12 | : base(containerParameters) 13 | { 14 | } 15 | 16 | public MailContainer(MailContainerParameters containerParameters, IDockerClient dockerClient) 17 | : base(containerParameters, dockerClient) 18 | { 19 | } 20 | 21 | public MailContainer(MailContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 22 | : base(containerParameters, dockerClient, logger) 23 | { 24 | } 25 | 26 | public MailContainer(MailContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 27 | : base(containerParameters, containerApi, imageApi, logger) 28 | { 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/MailContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | 9 | namespace TestEnvironment.Docker.Containers.Mail 10 | { 11 | public class MailContainerCleaner : IContainerCleaner 12 | { 13 | private readonly ushort _apiPort; 14 | private readonly string _deleteEndpoint; 15 | private readonly ILogger? _logger; 16 | 17 | public MailContainerCleaner(ushort apiPort = 8025, string deleteEndpoint = "api/v1/messages") => 18 | (_apiPort, _deleteEndpoint) = (apiPort, deleteEndpoint); 19 | 20 | public MailContainerCleaner(ILogger logger, ushort apiPort = 8025, string deleteEndpoint = "api/v1/messages") => 21 | (_logger, _apiPort, _deleteEndpoint) = (logger, apiPort, deleteEndpoint); 22 | 23 | public async Task CleanupAsync(MailContainer container, CancellationToken cancellationToken = default) 24 | { 25 | var uri = new Uri($"http://" + 26 | $"{(container.IsDockerInDocker ? container.IPAddress : IPAddress.Loopback.ToString())}:" + 27 | $"{(container.IsDockerInDocker ? _apiPort : container.Ports![_apiPort])}"); 28 | 29 | using (var httpClient = new HttpClient { BaseAddress = uri }) 30 | { 31 | try 32 | { 33 | var response = await httpClient.DeleteAsync(_deleteEndpoint); 34 | 35 | if (!response.IsSuccessStatusCode) 36 | { 37 | _logger?.LogWarning($"Cleanup issue: server replied with {response.StatusCode} code."); 38 | } 39 | } 40 | catch (Exception e) 41 | { 42 | _logger?.LogWarning($"Cleanup issue: {e.Message}"); 43 | } 44 | } 45 | } 46 | 47 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => 48 | CleanupAsync((MailContainer)container, cancellationToken); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/MailContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Mail 2 | { 3 | public record MailContainerParameters(string Name) 4 | : ContainerParameters(Name, "mailhog/mailhog") 5 | { 6 | public ushort SmptPort { get; init; } = 1025; 7 | 8 | public ushort ApiPort { get; init; } = 8025; 9 | 10 | public string DeleteEndpoint { get; init; } = "api/v1/messages"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/MailContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MailKit.Net.Smtp; 5 | using Microsoft.Extensions.Logging; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mail 9 | { 10 | public class MailContainerWaiter : BaseContainerWaiter 11 | { 12 | private readonly ushort _smtpPort; 13 | 14 | public MailContainerWaiter(ushort smtpPort = 1025) => 15 | _smtpPort = smtpPort; 16 | 17 | public MailContainerWaiter(ILogger logger, ushort smtpPort = 1025) 18 | : base(logger) => 19 | _smtpPort = smtpPort; 20 | 21 | protected override async Task PerformCheckAsync(MailContainer container, CancellationToken cancellationToken) 22 | { 23 | using var client = new SmtpClient(); 24 | 25 | await client.ConnectAsync( 26 | container.IsDockerInDocker ? container.IPAddress : IPAddress.Loopback.ToString(), 27 | container.IsDockerInDocker ? _smtpPort : container.Ports![_smtpPort], 28 | cancellationToken: cancellationToken); 29 | 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mail/TestEnvironment.Docker.Containers.Mail.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Mail 6 | TestEnvironment.Docker.Containers.Mail 7 | 2.1.5 8 | TestEnvironment.Docker.Containers.Mail 9 | enable 10 | Aliaksei Harshkalep 11 | Add SMTP container specific functionality. 12 | false 13 | docker tests smtp container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker.Containers.MariaDB 7 | { 8 | public static class IDockerEnvironmentBuilderExtensions 9 | { 10 | public static MariaDBContainerParameters DefaultParameters => new("maria", "password") 11 | { 12 | ImageName = "mariadb", 13 | ContainerCleaner = new MariaDBContainerCleaner(), 14 | ContainerWaiter = new MariaDBContainerWaiter() 15 | }; 16 | 17 | public static IDockerEnvironmentBuilder AddMariaDBContainer( 18 | this IDockerEnvironmentBuilder builder, 19 | Func paramsBuilder) 20 | { 21 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 22 | builder.AddContainer(parameters, (p, d, l) => new MariaDBContainer(FixEnvironmentVariables(p), d, l)); 23 | 24 | return builder; 25 | } 26 | 27 | public static IDockerEnvironmentBuilder AddMariaDBContainer( 28 | this IDockerEnvironmentBuilder builder, 29 | Func paramsBuilder) 30 | { 31 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 32 | builder.AddContainer(parameters, (p, d, l) => new MariaDBContainer(FixEnvironmentVariables(p), d, l)); 33 | 34 | return builder; 35 | } 36 | 37 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 38 | public static IDockerEnvironmentBuilder AddMariaDBContainer(this IDockerEnvironmentBuilder builder, string name, string rootPassword, string imageName = "mariadb", string tag = "latest", IDictionary? environmentVariables = null, IDictionary? ports = null, bool reuseContainer = false) 39 | { 40 | builder.AddMariaDBContainer(p => p with 41 | { 42 | Name = name, 43 | RootPassword = rootPassword, 44 | ImageName = imageName, 45 | Tag = tag, 46 | EnvironmentVariables = environmentVariables, 47 | Ports = ports, 48 | Reusable = reuseContainer 49 | }); 50 | 51 | return builder; 52 | } 53 | 54 | private static MariaDBContainerParameters FixEnvironmentVariables(MariaDBContainerParameters p) => 55 | p with 56 | { 57 | EnvironmentVariables = new Dictionary 58 | { 59 | ["MYSQL_ROOT_PASSWORD"] = p.RootPassword 60 | }.MergeDictionaries(p.EnvironmentVariables), 61 | }; 62 | 63 | private static MariaDBContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 64 | builder.Logger switch 65 | { 66 | { } => DefaultParameters with 67 | { 68 | ContainerWaiter = new MariaDBContainerWaiter(builder.Logger), 69 | ContainerCleaner = new MariaDBContainerCleaner(builder.Logger) 70 | }, 71 | null => DefaultParameters 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/MariaDBContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.MariaDB 9 | { 10 | public class MariaDBContainer : Container 11 | { 12 | private readonly MariaDBContainerParameters _parameters; 13 | 14 | public MariaDBContainer(MariaDBContainerParameters containerParameters) 15 | : base(containerParameters) => 16 | _parameters = containerParameters; 17 | 18 | public MariaDBContainer(MariaDBContainerParameters containerParameters, IDockerClient dockerClient) 19 | : base(containerParameters, dockerClient) => 20 | _parameters = containerParameters; 21 | 22 | public MariaDBContainer(MariaDBContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) => 24 | _parameters = containerParameters; 25 | 26 | public MariaDBContainer(MariaDBContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 27 | : base(containerParameters, containerApi, imageApi, logger) => 28 | _parameters = containerParameters; 29 | 30 | public string GetConnectionString() => 31 | $"server={(IsDockerInDocker ? IPAddress : IP.Loopback.ToString())};user=root;password={_parameters.RootPassword};port={(IsDockerInDocker ? 3306 : Ports![3306])};allow user variables=true"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/MariaDBContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using MySqlConnector; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | 9 | namespace TestEnvironment.Docker.Containers.MariaDB 10 | { 11 | public class MariaDBContainerCleaner : IContainerCleaner 12 | { 13 | private const string GetAllDatabasesCommand = "show databases;"; 14 | private const string DropDatabaseCommand = "drop database {0}"; 15 | 16 | private static readonly string[] SystemDatabases = { "information_schema", "mysql", "performance_schema" }; 17 | 18 | private readonly ILogger? _logger; 19 | 20 | public MariaDBContainerCleaner() 21 | { 22 | } 23 | 24 | public MariaDBContainerCleaner(ILogger logger) => 25 | _logger = logger; 26 | 27 | public async Task CleanupAsync(MariaDBContainer container, CancellationToken cancellationToken = default) 28 | { 29 | using var connection = new MySqlConnection(container.GetConnectionString()); 30 | using var getDatabasesCommand = new MySqlCommand(GetAllDatabasesCommand, connection); 31 | 32 | await getDatabasesCommand.Connection!.OpenAsync(); 33 | 34 | try 35 | { 36 | var reader = await getDatabasesCommand.ExecuteReaderAsync(); 37 | while (await reader.ReadAsync()) 38 | { 39 | var databaseName = reader.GetString(0); 40 | 41 | if (SystemDatabases.All(dn => !dn.Equals(databaseName, StringComparison.OrdinalIgnoreCase))) 42 | { 43 | using (var dropConnection = new MySqlConnection(container.GetConnectionString())) 44 | using (var dropCommand = new MySqlCommand(string.Format(DropDatabaseCommand, databaseName), dropConnection)) 45 | { 46 | await dropConnection.OpenAsync(); 47 | await dropCommand.ExecuteNonQueryAsync(); 48 | } 49 | } 50 | } 51 | } 52 | catch (MySqlException e) 53 | { 54 | _logger?.LogWarning($"Cleanup issue: {e.Message}"); 55 | } 56 | } 57 | 58 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) 59 | => CleanupAsync((MariaDBContainer)container, cancellationToken); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/MariaDBContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.MariaDB 2 | { 3 | public record MariaDBContainerParameters(string Name, string RootPassword) 4 | : ContainerParameters(Name, "mariadb") 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/MariaDBContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using MySqlConnector; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.MariaDB 9 | { 10 | public class MariaDBContainerWaiter : BaseContainerWaiter 11 | { 12 | public MariaDBContainerWaiter() 13 | { 14 | } 15 | 16 | public MariaDBContainerWaiter(ILogger logger) 17 | : base(logger) 18 | { 19 | } 20 | 21 | protected override async Task PerformCheckAsync(MariaDBContainer container, CancellationToken cancellationToken) 22 | { 23 | // don't use await using here due to 24 | // System.MissingMethodException : Method not found: 'System.Threading.Tasks.Task MySql.Data.MySqlClient.MySqlConnection.DisposeAsync()'. 25 | using var connection = new MySqlConnection(container.GetConnectionString()); 26 | using var command = new MySqlCommand("select @@version", connection); 27 | 28 | await command.Connection!.OpenAsync(cancellationToken); 29 | await command.ExecuteNonQueryAsync(cancellationToken); 30 | 31 | return true; 32 | } 33 | 34 | protected override bool IsRetryable(Exception exception) => 35 | exception is InvalidOperationException || exception is NotSupportedException || exception is MySqlException; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.MariaDB/TestEnvironment.Docker.Containers.MariaDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.MariaDB 6 | TestEnvironment.Docker.Containers.MariaDB 7 | 2.1.5 8 | TestEnvironment.Docker.Containers.MariaDB 9 | enable 10 | Aliaksei Harshkalep 11 | Add MariaDB container specific functionality. 12 | false 13 | docker tests mariadb mysql container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/IMongoContainer.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Mongo 2 | { 3 | public interface IMongoContainer 4 | { 5 | string GetConnectionString(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mongo 9 | { 10 | public class MongoContainer : Container, IMongoContainer 11 | { 12 | private readonly MongoContainerParameters _parameters; 13 | 14 | public MongoContainer(MongoContainerParameters containerParameters) 15 | : base(containerParameters) => 16 | _parameters = containerParameters; 17 | 18 | public MongoContainer(MongoContainerParameters containerParameters, IDockerClient dockerClient) 19 | : base(containerParameters, dockerClient) => 20 | _parameters = containerParameters; 21 | 22 | public MongoContainer(MongoContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) => 24 | _parameters = containerParameters; 25 | 26 | public MongoContainer(MongoContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 27 | : base(containerParameters, containerApi, imageApi, logger) => 28 | _parameters = containerParameters; 29 | 30 | public string GetConnectionString() 31 | { 32 | var hostname = IsDockerInDocker ? IPAddress : IP.Loopback.ToString(); 33 | var port = IsDockerInDocker ? 27017 : Ports![27017]; 34 | 35 | return string.IsNullOrEmpty(_parameters.UserName) || string.IsNullOrEmpty(_parameters.Password) 36 | ? $@"mongodb://{hostname}:{port}" 37 | : $@"mongodb://{_parameters.UserName}:{_parameters.Password}@{hostname}:{port}"; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using MongoDB.Driver; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | 9 | namespace TestEnvironment.Docker.Containers.Mongo 10 | { 11 | public class MongoContainerCleaner 12 | : IContainerCleaner, 13 | IContainerCleaner 14 | { 15 | private readonly ILogger? _logger; 16 | 17 | public MongoContainerCleaner() 18 | { 19 | } 20 | 21 | public MongoContainerCleaner(ILogger logger) => 22 | _logger = logger; 23 | 24 | public Task CleanupAsync(MongoContainer container, CancellationToken cancellationToken = default) => 25 | CleanupAsync((IMongoContainer)container, cancellationToken); 26 | 27 | public Task CleanupAsync(MongoSingleReplicaSetContainer container, CancellationToken cancellationToken = default) => 28 | CleanupAsync((IMongoContainer)container, cancellationToken); 29 | 30 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => 31 | CleanupAsync((IMongoContainer)container, cancellationToken); 32 | 33 | private async Task CleanupAsync(IMongoContainer container, CancellationToken cancellationToken) 34 | { 35 | var client = new MongoClient(container.GetConnectionString()); 36 | var databaseNames = (await client.ListDatabasesAsync(cancellationToken)) 37 | .ToList() 38 | .Select(x => x["name"].AsString); 39 | 40 | try 41 | { 42 | foreach (var databaseName in databaseNames) 43 | { 44 | if (databaseName != "admin" && databaseName != "local") 45 | { 46 | await client.DropDatabaseAsync(databaseName, cancellationToken); 47 | } 48 | } 49 | } 50 | catch (Exception e) 51 | { 52 | _logger?.LogInformation($"MongoDB cleanup issue: {e.Message}"); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Mongo 2 | { 3 | public record MongoContainerParameters(string Name, string UserName, string Password) 4 | : ContainerParameters(Name, "mongo") 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using MongoDB.Driver; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mongo 9 | { 10 | public class MongoContainerWaiter : BaseContainerWaiter 11 | { 12 | public MongoContainerWaiter() 13 | { 14 | } 15 | 16 | public MongoContainerWaiter(ILogger logger) 17 | : base(logger) 18 | { 19 | } 20 | 21 | protected override async Task PerformCheckAsync(MongoContainer container, CancellationToken cancellationToken) 22 | { 23 | await new MongoClient(container.GetConnectionString()).ListDatabasesAsync(cancellationToken); 24 | return true; 25 | } 26 | 27 | protected override bool IsRetryable(Exception exception) => 28 | base.IsRetryable(exception) && !(exception is MongoAuthenticationException); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoSingleReplicaSetContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Docker.DotNet.Models; 5 | using Microsoft.Extensions.Logging; 6 | using TestEnvironment.Docker.ContainerOperations; 7 | using TestEnvironment.Docker.ImageOperations; 8 | using IP = System.Net.IPAddress; 9 | 10 | namespace TestEnvironment.Docker.Containers.Mongo 11 | { 12 | public class MongoSingleReplicaSetContainer : Container, IMongoContainer 13 | { 14 | private readonly MongoSingleReplicaSetContainerParameters _parameters; 15 | 16 | public string ReplicaSetName => _parameters.ReplicaSetName; 17 | 18 | #pragma warning disable SA1201 // Elements should appear in the correct order 19 | public MongoSingleReplicaSetContainer(MongoSingleReplicaSetContainerParameters containerParameters) 20 | #pragma warning restore SA1201 // Elements should appear in the correct order 21 | : base(containerParameters) => 22 | _parameters = containerParameters; 23 | 24 | public MongoSingleReplicaSetContainer(MongoSingleReplicaSetContainerParameters containerParameters, IDockerClient dockerClient) 25 | : base(containerParameters, dockerClient) => 26 | _parameters = containerParameters; 27 | 28 | public MongoSingleReplicaSetContainer(MongoSingleReplicaSetContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 29 | : base(containerParameters, dockerClient, logger) => 30 | _parameters = containerParameters; 31 | 32 | public MongoSingleReplicaSetContainer(MongoSingleReplicaSetContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 33 | : base(containerParameters, containerApi, imageApi, logger) => 34 | _parameters = containerParameters; 35 | 36 | public string GetDirectNodeConnectionString() 37 | { 38 | var hostname = IsDockerInDocker ? IPAddress : IP.Loopback.ToString(); 39 | var port = _parameters.CustomReplicaSetPort ?? (IsDockerInDocker ? 27017 : Ports![27017]); 40 | 41 | return $@"mongodb://{hostname}:{port}/?connect=direct"; 42 | } 43 | 44 | public string GetConnectionString() 45 | { 46 | var hostname = IsDockerInDocker ? IPAddress : IP.Loopback.ToString(); 47 | var port = _parameters.CustomReplicaSetPort ?? (IsDockerInDocker ? 27017 : Ports![27017]); 48 | 49 | return $@"mongodb://{hostname}:{port}/?replicaSet={_parameters.ReplicaSetName}"; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoSingleReplicaSetContainerInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MongoDB.Bson; 5 | using MongoDB.Driver; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mongo 9 | { 10 | public class MongoSingleReplicaSetContainerInitializer : IContainerInitializer 11 | { 12 | public async Task InitializeAsync( 13 | MongoSingleReplicaSetContainer container, 14 | CancellationToken cancellationToken) 15 | { 16 | var mongoClient = new MongoClient(container.GetDirectNodeConnectionString()); 17 | 18 | if (await IsInitialized(container, mongoClient, cancellationToken)) 19 | { 20 | return true; 21 | } 22 | 23 | await mongoClient.GetDatabase("admin").RunCommandAsync( 24 | new BsonDocumentCommand(new BsonDocument 25 | { 26 | { 27 | "replSetInitiate", 28 | new BsonDocument 29 | { 30 | { "_id", container.ReplicaSetName }, 31 | { 32 | "members", 33 | new BsonArray 34 | { 35 | new BsonDocument 36 | { 37 | { "_id", 0 }, 38 | { 39 | "host", 40 | mongoClient.Settings.Server.ToString() 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | }), 48 | cancellationToken: cancellationToken); 49 | 50 | return true; 51 | } 52 | 53 | public Task InitializeAsync(Container container, CancellationToken cancellationToken) => 54 | InitializeAsync((MongoSingleReplicaSetContainer)container, cancellationToken); 55 | 56 | private async Task IsInitialized(MongoSingleReplicaSetContainer container, IMongoClient mongoClient, CancellationToken cancellationToken) 57 | { 58 | try 59 | { 60 | var configuration = await mongoClient.GetDatabase("admin") 61 | .RunCommandAsync( 62 | new BsonDocumentCommand(new BsonDocument { { "replSetGetConfig", 1 } }), 63 | cancellationToken: cancellationToken); 64 | 65 | return configuration["config"]["_id"].AsString == container.ReplicaSetName && 66 | configuration["config"]["members"].AsBsonArray.Count == 1 && 67 | configuration["config"]["members"].AsBsonArray[0]["_id"] == 0 && 68 | configuration["config"]["members"].AsBsonArray[0]["host"] == 69 | mongoClient.Settings.Server.ToString(); 70 | } 71 | catch (MongoCommandException exception) when (exception.Code == 94 /*"NotYetInitialized"*/) 72 | { 73 | return false; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoSingleReplicaSetContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Mongo 2 | { 3 | public record MongoSingleReplicaSetContainerParameters(string Name, string ReplicaSetName) 4 | : ContainerParameters(Name, "mongo") 5 | { 6 | public ushort? CustomReplicaSetPort { get; init; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/MongoSingleReplicaSetContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using MongoDB.Bson; 6 | using MongoDB.Driver; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | 9 | namespace TestEnvironment.Docker.Containers.Mongo 10 | { 11 | public class MongoSingleReplicaSetContainerWaiter : BaseContainerWaiter 12 | { 13 | public MongoSingleReplicaSetContainerWaiter() 14 | { 15 | } 16 | 17 | public MongoSingleReplicaSetContainerWaiter(ILogger logger) 18 | : base(logger) 19 | { 20 | } 21 | 22 | protected override async Task PerformCheckAsync( 23 | MongoSingleReplicaSetContainer container, 24 | CancellationToken cancellationToken) 25 | { 26 | await new MongoClient(container.GetDirectNodeConnectionString()).GetDatabase("admin") 27 | .RunCommandAsync( 28 | new BsonDocumentCommand(new BsonDocument("ping", 1)), 29 | cancellationToken: cancellationToken); 30 | return true; 31 | } 32 | 33 | protected override bool IsRetryable(Exception exception) => 34 | base.IsRetryable(exception) && !(exception is MongoAuthenticationException); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mongo/TestEnvironment.Docker.Containers.Mongo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Mongo 6 | TestEnvironment.Docker.Containers.Mongo 7 | 2.1.6 8 | TestEnvironment.Docker.Containers.Mongo 9 | enable 10 | Uladzimir Lashkevich, Aliaksei Harshkalep, vhatsura 11 | Add MongoDB container specific functionality. 12 | false 13 | docker tests mongo mongodb container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker.Containers.Mssql 7 | { 8 | public static class IDockerEnvironmentBuilderExtensions 9 | { 10 | public static MssqlContainerParameters DefaultParameters => new("mssql", "password") 11 | { 12 | ImageName = "mcr.microsoft.com/mssql/server", 13 | ContainerCleaner = new MssqlContainerCleaner(), 14 | ContainerWaiter = new MssqlContainerWaiter() 15 | }; 16 | 17 | public static IDockerEnvironmentBuilder AddMssqlContainer( 18 | this IDockerEnvironmentBuilder builder, 19 | Func paramsBuilder) 20 | { 21 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 22 | builder.AddContainer(parameters, (p, d, l) => new MssqlContainer(FixEnvironmentVariables(p), d, l)); 23 | 24 | return builder; 25 | } 26 | 27 | public static IDockerEnvironmentBuilder AddMssqlContainer( 28 | this IDockerEnvironmentBuilder builder, 29 | Func paramsBuilder) 30 | { 31 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 32 | builder.AddContainer(parameters, (p, d, l) => new MssqlContainer(FixEnvironmentVariables(p), d, l)); 33 | 34 | return builder; 35 | } 36 | 37 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 38 | public static IDockerEnvironmentBuilder AddMssqlContainer( 39 | this IDockerEnvironmentBuilder builder, 40 | string name, 41 | string saPassword, 42 | string imageName = "mcr.microsoft.com/mssql/server", 43 | string tag = "latest", 44 | IDictionary? environmentVariables = null, 45 | IDictionary? ports = null, 46 | bool reuseContainer = false) 47 | { 48 | builder.AddMssqlContainer(p => p with 49 | { 50 | Name = name, 51 | SAPassword = saPassword, 52 | ImageName = imageName, 53 | Tag = tag, 54 | EnvironmentVariables = environmentVariables, 55 | Ports = ports, 56 | Reusable = reuseContainer 57 | }); 58 | 59 | return builder; 60 | } 61 | 62 | private static MssqlContainerParameters FixEnvironmentVariables(MssqlContainerParameters p) => 63 | p with 64 | { 65 | EnvironmentVariables = new Dictionary 66 | { 67 | ["ACCEPT_EULA"] = "Y", 68 | ["SA_PASSWORD"] = p.SAPassword 69 | }.MergeDictionaries(p.EnvironmentVariables), 70 | }; 71 | 72 | private static MssqlContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 73 | builder.Logger switch 74 | { 75 | { } => DefaultParameters with 76 | { 77 | ContainerWaiter = new MssqlContainerWaiter(builder.Logger), 78 | ContainerCleaner = new MssqlContainerCleaner(builder.Logger) 79 | }, 80 | null => DefaultParameters 81 | }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/MssqlContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mssql 9 | { 10 | public sealed class MssqlContainer : Container 11 | { 12 | private readonly MssqlContainerParameters _parameters; 13 | 14 | public MssqlContainer(MssqlContainerParameters containerParameters) 15 | : base(containerParameters) => 16 | _parameters = containerParameters; 17 | 18 | public MssqlContainer(MssqlContainerParameters containerParameters, IDockerClient dockerClient) 19 | : base(containerParameters, dockerClient) => 20 | _parameters = containerParameters; 21 | 22 | public MssqlContainer(MssqlContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) => 24 | _parameters = containerParameters; 25 | 26 | public MssqlContainer(MssqlContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 27 | : base(containerParameters, containerApi, imageApi, logger) => 28 | _parameters = containerParameters; 29 | 30 | public string GetConnectionString() => 31 | $"Data Source={(IsDockerInDocker ? IPAddress : IP.Loopback.ToString())}, {(IsDockerInDocker ? 1433 : Ports![1433])}; UID=sa; pwd={_parameters.SAPassword};"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/MssqlContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mssql 9 | { 10 | public class MssqlContainerCleaner : IContainerCleaner 11 | { 12 | private const string CleanupCommand = "EXEC sp_MSforeachdb " + 13 | @"'IF DB_ID(''?'') > 4 BEGIN 14 | ALTER DATABASE [?] SET SINGLE_USER WITH ROLLBACK IMMEDIATE 15 | DROP DATABASE [?] 16 | END'"; 17 | 18 | private readonly ILogger? _logger; 19 | 20 | public MssqlContainerCleaner() 21 | { 22 | } 23 | 24 | public MssqlContainerCleaner(ILogger logger) => 25 | _logger = logger; 26 | 27 | public async Task CleanupAsync(MssqlContainer container, CancellationToken cancellationToken = default) 28 | { 29 | using var connection = new SqlConnection(container.GetConnectionString()); 30 | using var command = new SqlCommand(CleanupCommand, connection); 31 | try 32 | { 33 | await connection.OpenAsync(); 34 | await command.ExecuteNonQueryAsync(); 35 | } 36 | catch (SqlException e) 37 | { 38 | _logger?.LogInformation($"Cleanup issue: {e.Message}"); 39 | } 40 | } 41 | 42 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => CleanupAsync((MssqlContainer)container, cancellationToken); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/MssqlContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Mssql 2 | { 3 | public record MssqlContainerParameters(string Name, string SAPassword) 4 | : ContainerParameters(Name, "mcr.microsoft.com/mssql/server") 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/MssqlContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Mssql 9 | { 10 | public class MssqlContainerWaiter : BaseContainerWaiter 11 | { 12 | public MssqlContainerWaiter() 13 | { 14 | } 15 | 16 | public MssqlContainerWaiter(ILogger logger) 17 | : base(logger) 18 | { 19 | } 20 | 21 | protected override async Task PerformCheckAsync(MssqlContainer container, CancellationToken cancellationToken) 22 | { 23 | using var connection = new SqlConnection(container.GetConnectionString()); 24 | using var command = new SqlCommand("SELECT @@VERSION", connection); 25 | 26 | await connection.OpenAsync(cancellationToken); 27 | await command.ExecuteNonQueryAsync(cancellationToken); 28 | 29 | return true; 30 | } 31 | 32 | protected override bool IsRetryable(Exception exception) => 33 | exception is InvalidOperationException || exception is NotSupportedException || exception is SqlException; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Mssql/TestEnvironment.Docker.Containers.Mssql.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Mssql 6 | TestEnvironment.Docker.Containers.Mssql 7 | 2.1.8 8 | TestEnvironment.Docker.Containers.Mssql 9 | enable 10 | Aliaksei Harshkalep 11 | Add MSSQL container specific functionality. 12 | false 13 | docker tests mssql container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker.Containers.Postgres 7 | { 8 | public static class IDockerEnvironmentBuilderExtensions 9 | { 10 | public static PostgresContainerParameters DefaultParameters => new("postgres", "root", "securepass") 11 | { 12 | ImageName = "postgres", 13 | ContainerCleaner = new PostgresContainerCleaner(), 14 | ContainerWaiter = new PostgresContainerWaiter() 15 | }; 16 | 17 | public static IDockerEnvironmentBuilder AddPostgresContainer( 18 | this IDockerEnvironmentBuilder builder, 19 | Func paramsBuilder) 20 | { 21 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 22 | builder.AddContainer(parameters, (p, d, l) => new PostgresContainer(FixEnvironmentVariables(p), d, l)); 23 | 24 | return builder; 25 | } 26 | 27 | public static IDockerEnvironmentBuilder AddPostgresContainer( 28 | this IDockerEnvironmentBuilder builder, 29 | Func paramsBuilder) 30 | { 31 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 32 | builder.AddContainer(parameters, (p, d, l) => new PostgresContainer(FixEnvironmentVariables(p), d, l)); 33 | 34 | return builder; 35 | } 36 | 37 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 38 | public static IDockerEnvironmentBuilder AddPostgresContainer( 39 | this IDockerEnvironmentBuilder builder, 40 | string name, 41 | string userName = "root", 42 | string password = "securepass", 43 | string imageName = "postgres", 44 | string tag = "latest", 45 | IDictionary? environmentVariables = null, 46 | IDictionary? ports = null, 47 | bool reuseContainer = false) 48 | { 49 | builder.AddPostgresContainer(p => p with 50 | { 51 | Name = name, 52 | UserName = userName, 53 | Password = password, 54 | ImageName = imageName, 55 | Tag = tag, 56 | EnvironmentVariables = environmentVariables, 57 | Ports = ports, 58 | Reusable = reuseContainer 59 | }); 60 | 61 | return builder; 62 | } 63 | 64 | private static PostgresContainerParameters FixEnvironmentVariables(PostgresContainerParameters p) => 65 | p with 66 | { 67 | EnvironmentVariables = new Dictionary 68 | { 69 | ["POSTGRES_USER"] = p.UserName, 70 | ["POSTGRES_PASSWORD"] = p.Password 71 | }.MergeDictionaries(p.EnvironmentVariables), 72 | }; 73 | 74 | private static PostgresContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 75 | builder.Logger switch 76 | { 77 | { } => DefaultParameters with 78 | { 79 | ContainerWaiter = new PostgresContainerWaiter(builder.Logger), 80 | ContainerCleaner = new PostgresContainerCleaner(builder.Logger) 81 | }, 82 | null => DefaultParameters 83 | }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/PostgresContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Docker.DotNet; 3 | using Microsoft.Extensions.Logging; 4 | using TestEnvironment.Docker.ContainerOperations; 5 | using TestEnvironment.Docker.ImageOperations; 6 | using IP = System.Net.IPAddress; 7 | 8 | namespace TestEnvironment.Docker.Containers.Postgres 9 | { 10 | public class PostgresContainer : Container 11 | { 12 | private readonly PostgresContainerParameters _parameters; 13 | 14 | public string UserName => _parameters.UserName; 15 | 16 | #pragma warning disable SA1201 // Elements should appear in the correct order 17 | public PostgresContainer(PostgresContainerParameters containerParameters) 18 | #pragma warning restore SA1201 // Elements should appear in the correct order 19 | : base(containerParameters) => 20 | _parameters = containerParameters; 21 | 22 | public PostgresContainer(PostgresContainerParameters containerParameters, IDockerClient dockerClient) 23 | : base(containerParameters, dockerClient) => 24 | _parameters = containerParameters; 25 | 26 | public PostgresContainer(PostgresContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 27 | : base(containerParameters, dockerClient, logger) => 28 | _parameters = containerParameters; 29 | 30 | public PostgresContainer(PostgresContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 31 | : base(containerParameters, containerApi, imageApi, logger) => 32 | _parameters = containerParameters; 33 | 34 | public string GetConnectionString() => 35 | $"Host={(IsDockerInDocker ? IPAddress : IP.Loopback.ToString())};Port={(IsDockerInDocker ? 5432 : Ports![5432])};Username={_parameters.UserName};Password={_parameters.Password}"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/PostgresContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Npgsql; 6 | using TestEnvironment.Docker.ContainerLifecycle; 7 | 8 | namespace TestEnvironment.Docker.Containers.Postgres 9 | { 10 | public class PostgresContainerCleaner : IContainerCleaner 11 | { 12 | private readonly ILogger? _logger; 13 | 14 | public PostgresContainerCleaner() 15 | { 16 | } 17 | 18 | public PostgresContainerCleaner(ILogger logger) => 19 | _logger = logger; 20 | 21 | public async Task CleanupAsync(PostgresContainer container, CancellationToken token = default) 22 | { 23 | var cleanUpQuery = $"DROP OWNED BY {container.UserName}"; 24 | 25 | using var connection = new NpgsqlConnection(container.GetConnectionString()); 26 | using var cleanUpCommand = new NpgsqlCommand(cleanUpQuery, connection); 27 | try 28 | { 29 | await connection.OpenAsync(); 30 | await cleanUpCommand.ExecuteNonQueryAsync(); 31 | } 32 | catch (Exception ex) 33 | { 34 | _logger?.LogError($"Postgres cleanup issue: {ex.Message}"); 35 | } 36 | } 37 | 38 | public Task CleanupAsync(Container container, CancellationToken token = default) => CleanupAsync((PostgresContainer)container, token); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/PostgresContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.Postgres 2 | { 3 | public record PostgresContainerParameters(string Name, string UserName, string Password) 4 | : ContainerParameters(Name, "postgres") 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/PostgresContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Npgsql; 5 | using TestEnvironment.Docker.ContainerLifecycle; 6 | 7 | namespace TestEnvironment.Docker.Containers.Postgres 8 | { 9 | public class PostgresContainerWaiter : BaseContainerWaiter 10 | { 11 | public PostgresContainerWaiter() 12 | { 13 | } 14 | 15 | public PostgresContainerWaiter(ILogger logger) 16 | : base(logger) 17 | { 18 | } 19 | 20 | protected override async Task PerformCheckAsync(PostgresContainer container, CancellationToken cancellationToken) 21 | { 22 | using var connection = new NpgsqlConnection(container.GetConnectionString()); 23 | using var command = new NpgsqlCommand("select version()", connection); 24 | 25 | await connection.OpenAsync(cancellationToken); 26 | await command.ExecuteNonQueryAsync(cancellationToken); 27 | 28 | return true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Postgres/TestEnvironment.Docker.Containers.Postgres.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Postgres 6 | TestEnvironment.Docker.Containers.Postgres 7 | 2.1.6 8 | TestEnvironment.Docker.Containers.Postgres 9 | enable 10 | Kiryl Kiryanchykau 11 | Add Postgres container specific functionality. 12 | false 13 | docker tests postgres container 14 | Update to net5.0. For netstandard2.0, please use 1.x.x version of the package. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Docker.DotNet; 2 | using Microsoft.Extensions.Logging; 3 | using IP = System.Net.IPAddress; 4 | 5 | namespace TestEnvironment.Docker.Containers.RabbitMQ 6 | { 7 | public static class IDockerEnvironmentBuilderExtensions 8 | { 9 | public static RabbitMQContainerParameters DefaultParameters => new("rabbitmq", "guest", "guest") 10 | { 11 | ImageName = "rabbitmq", 12 | Tag = "3", 13 | EnvironmentVariables = new Dictionary 14 | { 15 | ["RABBITMQ_DEFAULT_USER"] = "guest", 16 | ["RABBITMQ_DEFAULT_PASS"] = "guest" 17 | }, 18 | ContainerCleaner = new RabbitMQContainerCleaner(), 19 | ContainerWaiter = new RabbitMQContainerWaiter() 20 | }; 21 | 22 | public static IDockerEnvironmentBuilder AddRabbitMQContainer( 23 | this IDockerEnvironmentBuilder builder, 24 | Func paramsBuilder) 25 | { 26 | var parameters = paramsBuilder(builder.GetDefaultParameters()); 27 | builder.AddContainer(parameters, (p, d, l) => new RabbitMQContainer(FixEnvironmentVariables(p), d, l)); 28 | 29 | return builder; 30 | } 31 | 32 | public static IDockerEnvironmentBuilder AddRabbitMQContainer( 33 | this IDockerEnvironmentBuilder builder, 34 | Func paramsBuilder) 35 | { 36 | var parameters = paramsBuilder(builder.GetDefaultParameters(), builder.DockerClient, builder.Logger); 37 | builder.AddContainer(parameters, (p, d, l) => new RabbitMQContainer(FixEnvironmentVariables(p), d, l)); 38 | 39 | return builder; 40 | } 41 | 42 | public static IDockerEnvironmentBuilder AddRabbitMQContainer( 43 | this IDockerEnvironmentBuilder builder, 44 | string name, 45 | string userName, 46 | string password, 47 | string imageName = "rabbitmq", 48 | string tag = "3", 49 | IDictionary? environmentVariables = null, 50 | IDictionary? ports = null, 51 | bool reuseContainer = false) 52 | { 53 | builder.AddRabbitMQContainer(p => p with 54 | { 55 | Name = name, 56 | UserName = userName, 57 | Password = password, 58 | ImageName = imageName, 59 | Tag = tag, 60 | EnvironmentVariables = environmentVariables, 61 | Ports = ports, 62 | Reusable = reuseContainer 63 | }); 64 | 65 | return builder; 66 | } 67 | 68 | private static RabbitMQContainerParameters FixEnvironmentVariables(RabbitMQContainerParameters p) => 69 | p with 70 | { 71 | EnvironmentVariables = new Dictionary 72 | { 73 | ["RABBITMQ_DEFAULT_USER"] = p.UserName, 74 | ["RABBITMQ_DEFAULT_PASS"] = p.Password 75 | }.MergeDictionaries(p.EnvironmentVariables), 76 | }; 77 | 78 | private static RabbitMQContainerParameters GetDefaultParameters(this IDockerEnvironmentBuilder builder) => 79 | builder.Logger switch 80 | { 81 | { } => DefaultParameters with 82 | { 83 | ContainerWaiter = new RabbitMQContainerWaiter(builder.Logger), 84 | ContainerCleaner = new RabbitMQContainerCleaner(builder.Logger) 85 | }, 86 | null => DefaultParameters 87 | }; 88 | } 89 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/RabbitMQContainer.cs: -------------------------------------------------------------------------------- 1 | using Docker.DotNet; 2 | using Microsoft.Extensions.Logging; 3 | using TestEnvironment.Docker.ContainerOperations; 4 | using TestEnvironment.Docker.ImageOperations; 5 | using IP = System.Net.IPAddress; 6 | 7 | namespace TestEnvironment.Docker.Containers.RabbitMQ 8 | { 9 | public class RabbitMQContainer : Container 10 | { 11 | private readonly RabbitMQContainerParameters _parameters; 12 | 13 | public string UserName => _parameters.UserName; 14 | 15 | public string Password => _parameters.Password; 16 | 17 | public int Port => IsDockerInDocker ? 5672 : Ports![5672]; 18 | 19 | public string? Host => IsDockerInDocker ? IPAddress : IP.Loopback.ToString(); 20 | 21 | #pragma warning disable SA1201 // Elements should appear in the correct order 22 | public RabbitMQContainer(RabbitMQContainerParameters containerParameters) 23 | #pragma warning restore SA1201 // Elements should appear in the correct order 24 | : base(containerParameters) => 25 | _parameters = containerParameters; 26 | 27 | public RabbitMQContainer(RabbitMQContainerParameters containerParameters, IDockerClient dockerClient) 28 | : base(containerParameters, dockerClient) => 29 | _parameters = containerParameters; 30 | 31 | public RabbitMQContainer(RabbitMQContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 32 | : base(containerParameters, dockerClient, logger) => 33 | _parameters = containerParameters; 34 | 35 | public RabbitMQContainer(RabbitMQContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 36 | : base(containerParameters, containerApi, imageApi, logger) => 37 | _parameters = containerParameters; 38 | } 39 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/RabbitMQContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using TestEnvironment.Docker.ContainerLifecycle; 3 | 4 | namespace TestEnvironment.Docker.Containers.RabbitMQ 5 | { 6 | public class RabbitMQContainerCleaner : IContainerCleaner 7 | { 8 | private readonly ILogger? _logger; 9 | 10 | public RabbitMQContainerCleaner() 11 | { 12 | } 13 | 14 | public RabbitMQContainerCleaner(ILogger logger) => 15 | _logger = logger; 16 | 17 | public async Task CleanupAsync(RabbitMQContainer container, CancellationToken cancellationToken = default) 18 | { 19 | try 20 | { 21 | await container.ExecAsync( 22 | new[] 23 | { 24 | "rabbitmqctl", 25 | "stop_app" 26 | }, 27 | cancellationToken: cancellationToken); 28 | 29 | await container.ExecAsync( 30 | new[] 31 | { 32 | "rabbitmqctl", 33 | "reset" 34 | }, 35 | cancellationToken: cancellationToken); 36 | 37 | await container.ExecAsync( 38 | new[] 39 | { 40 | "rabbitmqctl", 41 | "start_app" 42 | }, 43 | cancellationToken: cancellationToken); 44 | } 45 | catch (Exception e) 46 | { 47 | _logger?.LogInformation($"Cleanup issue: {e.Message}"); 48 | } 49 | } 50 | 51 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => 52 | CleanupAsync((RabbitMQContainer)container, cancellationToken); 53 | } 54 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/RabbitMQContainerParameters.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker.Containers.RabbitMQ 2 | { 3 | public record RabbitMQContainerParameters(string Name, string UserName, string Password) 4 | : ContainerParameters(Name, "rabbitmq"); 5 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/RabbitMQContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using RabbitMQ.Client; 3 | using TestEnvironment.Docker.ContainerLifecycle; 4 | 5 | namespace TestEnvironment.Docker.Containers.RabbitMQ 6 | { 7 | public class RabbitMQContainerWaiter : BaseContainerWaiter 8 | { 9 | public RabbitMQContainerWaiter() 10 | { 11 | } 12 | 13 | public RabbitMQContainerWaiter(ILogger logger) 14 | : base(logger) 15 | { 16 | } 17 | 18 | protected override Task PerformCheckAsync(RabbitMQContainer container, CancellationToken cancellationToken) 19 | { 20 | var factory = new ConnectionFactory 21 | { 22 | HostName = container.Host, 23 | Port = container.Port, 24 | UserName = container.UserName, 25 | Password = container.Password 26 | }; 27 | 28 | using var connection = factory.CreateConnection(); 29 | 30 | return Task.FromResult(connection.IsOpen); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.RabbitMQ/TestEnvironment.Docker.Containers.RabbitMQ.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.RabbitMQ 6 | TestEnvironment.Docker.Containers.RabbitMQ 7 | 2.1.5 8 | enable 9 | enable 10 | TestEnvironment.Docker.Containers.RabbitMQ 11 | ZaoralJ 12 | Add RabbitMQ container specific functionality. 13 | false 14 | docker tests rabbitmq container 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Docker.DotNet; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace TestEnvironment.Docker.Containers.Redis 10 | { 11 | public static class IDockerEnvironmentBuilderExtensions 12 | { 13 | public static RedisContainerParameters DefaultParameters => new("redis", "password") 14 | { 15 | ImageName = "bitnami/redis", 16 | ContainerCleaner = new RedisContainerCleaner(), 17 | ContainerWaiter = new RedisContainerWaiter(), 18 | }; 19 | 20 | public static IDockerEnvironmentBuilder AddRedisContainer( 21 | this IDockerEnvironmentBuilder builder, 22 | Func paramsBuilder) 23 | { 24 | var parameters = paramsBuilder(builder.GetDefaultRedisContainerParameters()); 25 | builder.AddContainer(parameters, (p, d, l) => new RedisContainer(FixRedisEnvironmentVariables(p), d, l)); 26 | 27 | return builder; 28 | } 29 | 30 | public static IDockerEnvironmentBuilder AddRedisContainer( 31 | this IDockerEnvironmentBuilder builder, 32 | Func paramsBuilder) 33 | { 34 | var parameters = paramsBuilder(builder.GetDefaultRedisContainerParameters(), builder.DockerClient, builder.Logger); 35 | builder.AddContainer(parameters, (p, d, l) => new RedisContainer(FixRedisEnvironmentVariables(p), d, l)); 36 | 37 | return builder; 38 | } 39 | 40 | private static RedisContainerParameters FixRedisEnvironmentVariables(RedisContainerParameters p) => 41 | p with 42 | { 43 | EnvironmentVariables = new Dictionary 44 | { 45 | ["REDIS_PASSWORD"] = p.Password 46 | }.MergeDictionaries(p.EnvironmentVariables), 47 | }; 48 | 49 | private static RedisContainerParameters GetDefaultRedisContainerParameters(this IDockerEnvironmentBuilder builder) => 50 | builder.Logger switch 51 | { 52 | { } => DefaultParameters with 53 | { 54 | ContainerWaiter = new RedisContainerWaiter(builder.Logger), 55 | ContainerCleaner = new RedisContainerCleaner(builder.Logger) 56 | }, 57 | null => DefaultParameters 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/IRedisContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TestEnvironment.Docker.Containers.Redis 8 | { 9 | public interface IRedisContainer 10 | { 11 | RedisConnectionConfiguration GetConnectionConfiguration(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/RedisConnectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TestEnvironment.Docker.Containers.Redis 8 | { 9 | public record RedisConnectionConfiguration(string Host, int Port, string Password) 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/RedisContainer.cs: -------------------------------------------------------------------------------- 1 | using Docker.DotNet; 2 | using Microsoft.Extensions.Logging; 3 | using TestEnvironment.Docker.ContainerOperations; 4 | using TestEnvironment.Docker.ImageOperations; 5 | using IP = System.Net.IPAddress; 6 | 7 | namespace TestEnvironment.Docker.Containers.Redis 8 | { 9 | public class RedisContainer : Container, IRedisContainer 10 | { 11 | private readonly RedisContainerParameters _parameters; 12 | 13 | public RedisContainer(RedisContainerParameters containerParameters) 14 | : base(containerParameters) 15 | { 16 | _parameters = containerParameters; 17 | } 18 | 19 | public RedisContainer(RedisContainerParameters containerParameters, IDockerClient dockerClient) 20 | : base(containerParameters, dockerClient) 21 | { 22 | _parameters = containerParameters; 23 | } 24 | 25 | public RedisContainer(RedisContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 26 | : base(containerParameters, dockerClient, logger) 27 | { 28 | _parameters = containerParameters; 29 | } 30 | 31 | public RedisContainer(RedisContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 32 | : base(containerParameters, containerApi, imageApi, logger) 33 | { 34 | _parameters = containerParameters; 35 | } 36 | 37 | public RedisConnectionConfiguration GetConnectionConfiguration() 38 | { 39 | var dindIpAddress = IsDockerInDocker ? IPAddress : null; 40 | string hostname = dindIpAddress ?? IP.Loopback.ToString(); 41 | 42 | var port = IsDockerInDocker ? 6379 : Ports![6379]; 43 | 44 | return new RedisConnectionConfiguration(hostname, port, _parameters.Password); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/RedisContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using StackExchange.Redis; 8 | using TestEnvironment.Docker.ContainerLifecycle; 9 | 10 | namespace TestEnvironment.Docker.Containers.Redis 11 | { 12 | public class RedisContainerCleaner : IContainerCleaner 13 | { 14 | private readonly ILogger? _logger; 15 | 16 | public RedisContainerCleaner() 17 | { 18 | } 19 | 20 | public RedisContainerCleaner(ILogger logger) => 21 | _logger = logger; 22 | 23 | public async Task CleanupAsync(RedisContainer container, CancellationToken cancellationToken = default) 24 | { 25 | RedisConnectionConfiguration redisConnectionConfiguration = container.GetConnectionConfiguration(); 26 | 27 | var redisConfigurationOptions = new ConfigurationOptions() 28 | { 29 | EndPoints = 30 | { 31 | { redisConnectionConfiguration.Host, redisConnectionConfiguration.Port }, 32 | }, 33 | Password = redisConnectionConfiguration.Password, 34 | AllowAdmin = true 35 | }; 36 | 37 | ConnectionMultiplexer redis = await ConnectionMultiplexer.ConnectAsync(redisConfigurationOptions); 38 | 39 | try 40 | { 41 | await redis.GetServer(redisConnectionConfiguration.Host).FlushAllDatabasesAsync(); 42 | await redis.CloseAsync(); 43 | } 44 | catch (Exception e) 45 | { 46 | _logger?.LogError($"Redis cleanup error: {e.Message}"); 47 | } 48 | } 49 | 50 | public Task CleanupAsync(Container container, CancellationToken cancellationToken = default) => CleanupAsync((RedisContainer)container, cancellationToken); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/RedisContainerParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TestEnvironment.Docker.Containers.Redis 8 | { 9 | public record RedisContainerParameters(string Name, string Password) 10 | : ContainerParameters(Name, "bitnami/redis") 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/RedisContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using StackExchange.Redis; 8 | using TestEnvironment.Docker.ContainerLifecycle; 9 | 10 | namespace TestEnvironment.Docker.Containers.Redis 11 | { 12 | public class RedisContainerWaiter : BaseContainerWaiter 13 | { 14 | public RedisContainerWaiter() 15 | { 16 | } 17 | 18 | public RedisContainerWaiter(ILogger logger) 19 | : base(logger) 20 | { 21 | } 22 | 23 | protected override async Task PerformCheckAsync(RedisContainer container, CancellationToken cancellationToken) 24 | { 25 | RedisConnectionConfiguration redisConnectionConfiguration = container.GetConnectionConfiguration(); 26 | 27 | var redisConfigurationOptions = new ConfigurationOptions() 28 | { 29 | EndPoints = 30 | { 31 | { redisConnectionConfiguration.Host, redisConnectionConfiguration.Port }, 32 | }, 33 | Password = redisConnectionConfiguration.Password, 34 | ConnectTimeout = 2000, 35 | ConnectRetry = 2 36 | }; 37 | 38 | var redis = await ConnectionMultiplexer.ConnectAsync(redisConfigurationOptions); 39 | 40 | _ = redis.GetStatus(); 41 | 42 | await redis.CloseAsync(); 43 | 44 | return true; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker.Containers.Redis/TestEnvironment.Docker.Containers.Redis.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker.Containers.Redis 6 | TestEnvironment.Docker.Containers.Redis 7 | 2.1.6 8 | enable 9 | enable 10 | TestEnvironment.Docker.Containers.Redis 11 | Manoj Kumar 12 | Add Redis container specific functionality. 13 | false 14 | docker tests redis container 15 | enable 16 | Update to net5.0. 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TestEnvironment.Docker 8 | { 9 | internal static class CollectionExtensions 10 | { 11 | internal static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) 12 | { 13 | var seenKeys = new HashSet(); 14 | foreach (TSource element in source) 15 | { 16 | if (seenKeys.Add(keySelector(element))) 17 | { 18 | yield return element; 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/Container.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Docker.DotNet; 6 | using Microsoft.Extensions.Logging; 7 | using TestEnvironment.Docker.ContainerLifecycle; 8 | using TestEnvironment.Docker.ContainerOperations; 9 | using TestEnvironment.Docker.ImageOperations; 10 | using static TestEnvironment.Docker.DockerClientExtentions; 11 | 12 | namespace TestEnvironment.Docker 13 | { 14 | public class Container : IAsyncDisposable 15 | { 16 | private readonly ContainerParameters _containerParameters; 17 | 18 | public string Name => _containerParameters.Name; 19 | 20 | public string ImageName => _containerParameters.ImageName; 21 | 22 | public string Tag => _containerParameters.Tag; 23 | 24 | public IDictionary? EnvironmentVariables => _containerParameters.EnvironmentVariables; 25 | 26 | public IDictionary? Ports { get; private set; } 27 | 28 | public bool Reusable => _containerParameters.Reusable; 29 | 30 | public IList? Entrypoint => _containerParameters.Entrypoint; 31 | 32 | public IList? ExposedPorts => _containerParameters.ExposedPorts; 33 | 34 | public bool IsDockerInDocker => _containerParameters.IsDockerInDocker; 35 | 36 | public IContainerInitializer? ContainerInitializer => _containerParameters.ContainerInitializer; 37 | 38 | public IContainerWaiter? ContainerWaiter => _containerParameters.ContainerWaiter; 39 | 40 | public IContainerCleaner? ContainerCleaner => _containerParameters.ContainerCleaner; 41 | 42 | public string? Id { get; private set; } 43 | 44 | public string? IPAddress { get; private set; } 45 | 46 | protected IContainerApi ContainerApi { get; init; } 47 | 48 | protected IImageApi ImageApi { get; init; } 49 | 50 | protected ILogger? Logger { get; init; } 51 | 52 | #pragma warning disable SA1201 // Elements should appear in the correct order 53 | public Container(ContainerParameters containerParameters) 54 | : this(containerParameters, CreateDefaultDockerClient()) 55 | { 56 | } 57 | 58 | public Container(ContainerParameters containerParameters, IDockerClient dockerClient) 59 | : this(containerParameters, new ContainerApi(dockerClient), new ImageApi(dockerClient), null) 60 | { 61 | } 62 | 63 | public Container(ContainerParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 64 | : this(containerParameters, new ContainerApi(dockerClient, logger), new ImageApi(dockerClient, logger), logger) 65 | { 66 | } 67 | 68 | public Container(ContainerParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) => 69 | #pragma warning restore SA1201 // Elements should appear in the correct order 70 | (_containerParameters, ContainerApi, ImageApi, Logger, Ports) = 71 | (containerParameters, containerApi, imageApi, logger, containerParameters.Ports); 72 | 73 | public async Task RunAsync(CancellationToken cancellationToken = default) 74 | { 75 | var runtimeInfo = await ContainerApi.RunContainerAsync(_containerParameters, cancellationToken); 76 | (Id, IPAddress, Ports) = runtimeInfo; 77 | 78 | if (ContainerWaiter is not null) 79 | { 80 | var isStarted = await ContainerWaiter.WaitAsync(this, cancellationToken); 81 | if (!isStarted) 82 | { 83 | Logger?.LogError($"Container {Name} didn't start."); 84 | throw new TimeoutException($"Container {Name} didn't start."); 85 | } 86 | } 87 | 88 | if (ContainerInitializer is not null) 89 | { 90 | await ContainerInitializer.InitializeAsync(this, cancellationToken); 91 | } 92 | 93 | if (Reusable && ContainerCleaner is not null) 94 | { 95 | await ContainerCleaner.CleanupAsync(this, cancellationToken); 96 | } 97 | } 98 | 99 | public async Task ExecAsync(string[] cmd, IDictionary? env = null, string? user = null, string workdir = "/", CancellationToken cancellationToken = default) 100 | { 101 | if (Id is null) 102 | { 103 | throw new InvalidOperationException("Container is not run."); 104 | } 105 | 106 | return await ContainerApi.ExecAsync(Id, cmd, env, user, workdir, cancellationToken); 107 | } 108 | 109 | public async Task StopAsync(CancellationToken cancellationToken = default) 110 | { 111 | if (Id is null) 112 | { 113 | throw new InvalidOperationException("Container is not run."); 114 | } 115 | 116 | await ContainerApi.StopContainerAsync(Id, cancellationToken); 117 | } 118 | 119 | public async virtual Task EnsureImageAvailableAsync(CancellationToken cancellationToken = default) => 120 | await ImageApi.PullImageAsync(ImageName, Tag, cancellationToken); 121 | 122 | public async ValueTask DisposeAsync() 123 | { 124 | if (Id is not null) 125 | { 126 | await ContainerApi.RemoveContainerAsync(Id); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerFromDockerfile.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | using TestEnvironment.Docker.ContainerOperations; 6 | using TestEnvironment.Docker.ImageOperations; 7 | 8 | namespace TestEnvironment.Docker 9 | { 10 | public class ContainerFromDockerfile : Container 11 | { 12 | private readonly ContainerFromDockerfileParameters _parameters; 13 | 14 | public ContainerFromDockerfile(ContainerFromDockerfileParameters containerParameters) 15 | : base(containerParameters) => 16 | _parameters = containerParameters; 17 | 18 | public ContainerFromDockerfile(ContainerFromDockerfileParameters containerParameters, IDockerClient dockerClient) 19 | : base(containerParameters, dockerClient) => 20 | _parameters = containerParameters; 21 | 22 | public ContainerFromDockerfile(ContainerFromDockerfileParameters containerParameters, IDockerClient dockerClient, ILogger? logger) 23 | : base(containerParameters, dockerClient, logger) => 24 | _parameters = containerParameters; 25 | 26 | public ContainerFromDockerfile(ContainerFromDockerfileParameters containerParameters, IContainerApi containerApi, ImageApi imageApi, ILogger? logger) 27 | : base(containerParameters, containerApi, imageApi, logger) => 28 | _parameters = containerParameters; 29 | 30 | public override async Task EnsureImageAvailableAsync(CancellationToken cancellationToken = default) => 31 | await ImageApi.BuildImageAsync(_parameters.Dockerfile, ImageName, Tag, _parameters.Context, _parameters.BuildArgs, _parameters.IgnoredContextFiles, cancellationToken); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerFromDockerfileParameters.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TestEnvironment.Docker 4 | { 5 | public record ContainerFromDockerfileParameters(string Name, string Dockerfile) 6 | : ContainerParameters(Name, Name) 7 | { 8 | public IDictionary? BuildArgs { get; init; } = new Dictionary(); 9 | 10 | public string Context { get; init; } = "."; 11 | 12 | public string[]? IgnoredContextFiles { get; init; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/BaseContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace TestEnvironment.Docker.ContainerLifecycle 10 | { 11 | public abstract class BaseContainerWaiter : IContainerWaiter 12 | where TContainer : Container 13 | { 14 | protected ILogger? Logger { get; init; } 15 | 16 | protected virtual int AttemptsCount => 60; 17 | 18 | protected virtual TimeSpan DelayTime => TimeSpan.FromSeconds(1); 19 | 20 | #pragma warning disable SA1201 // Elements should appear in the correct order 21 | public BaseContainerWaiter() 22 | #pragma warning restore SA1201 // Elements should appear in the correct order 23 | { 24 | } 25 | 26 | public BaseContainerWaiter(ILogger logger) => 27 | Logger = logger; 28 | 29 | public async Task WaitAsync(TContainer container, CancellationToken cancellationToken) 30 | { 31 | Logger?.LogInformation($"{container.Name}: checking container state..."); 32 | 33 | var policy = PolicyFactory.CreateWaitPolicy(AttemptsCount, DelayTime, IsRetryable, e => Logger?.LogError(e, $"{container.Name} check failed with exception {e.Message}")); 34 | var result = await policy.ExecuteAndCaptureAsync(async () => await PerformCheckAsync(container, cancellationToken)); 35 | 36 | if (result.Outcome == Polly.OutcomeType.Successful) 37 | { 38 | Logger?.LogInformation($"{container.Name}: container is Up!"); 39 | } 40 | else 41 | { 42 | Logger?.LogError($"Container {container.Name} didn't start."); 43 | } 44 | 45 | return result.Result; 46 | } 47 | 48 | public Task WaitAsync(Container container, CancellationToken cancellationToken) => 49 | WaitAsync((TContainer)container, cancellationToken); 50 | 51 | protected abstract Task PerformCheckAsync(TContainer container, CancellationToken cancellationToken); 52 | 53 | protected virtual bool IsRetryable(Exception exception) => true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/FuncContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace TestEnvironment.Docker.ContainerLifecycle 10 | { 11 | public class FuncContainerWaiter : BaseContainerWaiter 12 | { 13 | private readonly Func> _waitFunc; 14 | 15 | public FuncContainerWaiter(Func> waitFunc) => 16 | _waitFunc = waitFunc; 17 | 18 | public FuncContainerWaiter(Func> waitFunc, ILogger logger) 19 | : base(logger) 20 | { 21 | _waitFunc = waitFunc; 22 | } 23 | 24 | protected override Task PerformCheckAsync(Container container, CancellationToken cancellationToken) => 25 | _waitFunc(container, cancellationToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/HttpContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace TestEnvironment.Docker.ContainerLifecycle 12 | { 13 | public class HttpContainerWaiter : BaseContainerWaiter 14 | { 15 | private readonly string _path; 16 | private readonly bool _isHttps; 17 | private readonly ushort _port; 18 | private readonly HttpStatusCode[] _successfulCodes; 19 | 20 | public HttpContainerWaiter(string path = "", bool isHttps = false, ushort port = 80, HttpStatusCode successfulCode = HttpStatusCode.OK) => 21 | (_path, _isHttps, _port, _successfulCodes) = (path, isHttps, port, new[] { successfulCode }); 22 | 23 | public HttpContainerWaiter(ILogger logger, string path = "", bool isHttps = false, ushort port = 80, params HttpStatusCode[] successfulCodes) 24 | : base(logger) => 25 | (_path, _isHttps, _port, _successfulCodes) = (path, isHttps, port, successfulCodes); 26 | 27 | protected override async Task PerformCheckAsync(Container container, CancellationToken cancellationToken) 28 | { 29 | var uri = new Uri($"{(_isHttps ? "https" : "http")}://" + 30 | $"{(container.IsDockerInDocker ? container.IPAddress : IPAddress.Loopback.ToString())}:" + 31 | $"{(container.IsDockerInDocker ? _port : container.Ports?[_port])}"); 32 | 33 | using var client = new HttpClient { BaseAddress = uri }; 34 | var response = await client.GetAsync(_path, cancellationToken); 35 | 36 | return _successfulCodes.Contains(response.StatusCode); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/IContainerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace TestEnvironment.Docker.ContainerLifecycle 5 | { 6 | public interface IContainerCleaner 7 | { 8 | Task CleanupAsync(Container container, CancellationToken cancellationToken = default); 9 | } 10 | 11 | public interface IContainerCleaner : IContainerCleaner 12 | where TContainer : Container 13 | { 14 | Task CleanupAsync(TContainer container, CancellationToken cancellationToken = default); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/IContainerInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace TestEnvironment.Docker.ContainerLifecycle 5 | { 6 | public interface IContainerInitializer 7 | { 8 | Task InitializeAsync(Container container, CancellationToken cancellationToken = default); 9 | } 10 | 11 | public interface IContainerInitializer : IContainerInitializer 12 | where TContainer : Container 13 | { 14 | Task InitializeAsync(TContainer container, CancellationToken cancellationToken = default); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerLifecycle/IContainerWaiter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace TestEnvironment.Docker.ContainerLifecycle 5 | { 6 | public interface IContainerWaiter 7 | { 8 | Task WaitAsync(Container container, CancellationToken cancellationToken = default); 9 | } 10 | 11 | public interface IContainerWaiter : IContainerWaiter 12 | where TContainer : Container 13 | { 14 | Task WaitAsync(TContainer container, CancellationToken cancellationToken = default); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerOperations/ContainerOperationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace TestEnvironment.Docker.ContainerOperations 5 | { 6 | [Serializable] 7 | public class ContainerOperationException : Exception 8 | { 9 | public ContainerOperationException() 10 | { 11 | } 12 | 13 | public ContainerOperationException(string message) 14 | : base(message) 15 | { 16 | } 17 | 18 | public ContainerOperationException(string message, Exception inner) 19 | : base(message, inner) 20 | { 21 | } 22 | 23 | protected ContainerOperationException( 24 | SerializationInfo info, 25 | StreamingContext context) 26 | : base(info, context) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerOperations/IContainerApi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace TestEnvironment.Docker.ContainerOperations 6 | { 7 | public interface IContainerApi 8 | { 9 | Task RunContainerAsync(ContainerParameters containerParameters, CancellationToken cancellationToken = default); 10 | 11 | Task ExecAsync(string containerId, string[] cmd, IDictionary? env = null, string? user = null, string workdir = "/", CancellationToken cancellationToken = default); 12 | 13 | Task StopContainerAsync(string id, CancellationToken cancellationToken = default); 14 | 15 | Task RemoveContainerAsync(string id, CancellationToken cancellationToken = default); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerParameters.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using TestEnvironment.Docker.ContainerLifecycle; 4 | 5 | namespace TestEnvironment.Docker 6 | { 7 | public record ContainerParameters(string Name, string ImageName) 8 | { 9 | public string Tag { get; init; } = "latest"; 10 | 11 | public IDictionary? EnvironmentVariables { get; init; } 12 | 13 | public IDictionary? Ports { get; init; } 14 | 15 | public bool Reusable { get; init; } = false; 16 | 17 | public bool IsDockerInDocker { get; init; } = false; 18 | 19 | public IList? Entrypoint { get; init; } 20 | 21 | public IList? ExposedPorts { get; init; } 22 | 23 | public IList? Binds { get; init; } 24 | 25 | public IContainerInitializer? ContainerInitializer { get; init; } 26 | 27 | public IContainerWaiter? ContainerWaiter { get; init; } 28 | 29 | public IContainerCleaner? ContainerCleaner { get; init; } 30 | 31 | public void Deconstruct(out IContainerInitializer? containerInitializer, out IContainerWaiter? containerWaiter, out IContainerCleaner? containerCleaner) => 32 | (containerInitializer, containerWaiter, containerCleaner) = (ContainerInitializer, ContainerWaiter, ContainerCleaner); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ContainerRuntimeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TestEnvironment.Docker 4 | { 5 | #pragma warning disable SA1313 // Parameter names should begin with lower-case letter 6 | public record ContainerRuntimeInfo(string Id, string IPAddress, IDictionary Ports) 7 | #pragma warning restore SA1313 // Parameter names should begin with lower-case letter 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace TestEnvironment.Docker 5 | { 6 | public static class DictionaryExtensions 7 | { 8 | public static IDictionary MergeDictionaries(this IDictionary dictionary, IDictionary? other) 9 | where T1 : notnull 10 | { 11 | if (other is null) 12 | { 13 | return dictionary; 14 | } 15 | 16 | var nonExistentEnvironmentVariables = other.Where(e => !dictionary.ContainsKey(e.Key)); 17 | return dictionary.Concat(nonExistentEnvironmentVariables).ToDictionary(pair => pair.Key, pair => pair.Value); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DockerClientExtentions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Runtime.InteropServices; 4 | using Docker.DotNet; 5 | 6 | namespace TestEnvironment.Docker 7 | { 8 | public static class DockerClientExtentions 9 | { 10 | public static DockerClient CreateDefaultDockerClient() 11 | { 12 | var dockerHostVar = Environment.GetEnvironmentVariable("DOCKER_HOST"); 13 | var defaultDockerUrl = !string.IsNullOrEmpty(dockerHostVar) 14 | ? dockerHostVar 15 | : !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 16 | ? "unix:///var/run/docker.sock" 17 | : "npipe://./pipe/docker_engine"; 18 | 19 | return new DockerClientConfiguration(new Uri(defaultDockerUrl)).CreateClient(); 20 | } 21 | 22 | public static DockerClient CreateWSL2DockerClient(int port = 2375) => 23 | new DockerClientConfiguration(new Uri($"http://localhost:{port}")).CreateClient(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DockerEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Docker.DotNet; 8 | using Microsoft.Extensions.Logging; 9 | using TestEnvironment.Docker.ContainerOperations; 10 | using TestEnvironment.Docker.DockerOperations; 11 | using TestEnvironment.Docker.ImageOperations; 12 | using static TestEnvironment.Docker.DockerClientExtentions; 13 | using static TestEnvironment.Docker.StringExtensions; 14 | 15 | namespace TestEnvironment.Docker 16 | { 17 | public class DockerEnvironment : IDockerEnvironment 18 | { 19 | private readonly IImageApi _imageApi; 20 | private readonly IContainerApi _containerApi; 21 | private readonly IDockerInitializer? _dockerInitializer; 22 | private readonly ILogger? _logger; 23 | 24 | public string Name { get; init; } 25 | 26 | public Container[] Containers { get; init; } 27 | 28 | #pragma warning disable SA1201 // Elements should appear in the correct order 29 | public DockerEnvironment(string name, Container[] containers) 30 | #pragma warning restore SA1201 // Elements should appear in the correct order 31 | : this(name, containers, CreateDefaultDockerClient()) 32 | { 33 | } 34 | 35 | public DockerEnvironment(string name, Container[] containers, ILogger logger) 36 | : this(name, containers, new ImageApi(logger), new ContainerApi(logger), logger) 37 | { 38 | } 39 | 40 | public DockerEnvironment(string name, Container[] containers, IDockerClient dockerClient) 41 | : this(name, containers, new ImageApi(dockerClient), new ContainerApi(dockerClient), null) 42 | { 43 | } 44 | 45 | public DockerEnvironment(string name, Container[] containers, IDockerClient dockerClient, ILogger? logger) 46 | : this(name, containers, new ImageApi(dockerClient, logger), new ContainerApi(dockerClient, logger), logger) 47 | { 48 | } 49 | 50 | public DockerEnvironment(string name, Container[] containers, IDockerClient dockerClient, IDockerInitializer? dockerInitializer, ILogger? logger) 51 | : this(name, containers, new ImageApi(dockerClient, logger), new ContainerApi(dockerClient, logger), dockerInitializer, logger) 52 | { 53 | } 54 | 55 | public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, ILogger? logger) => 56 | (Name, Containers, _imageApi, _containerApi, _logger) = (name, containers, imageApi, containerApi, logger); 57 | 58 | public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, IDockerInitializer? dockerInitializer, ILogger? logger) => 59 | (Name, Containers, _imageApi, _containerApi, _dockerInitializer, _logger) = (name, containers, imageApi, containerApi, dockerInitializer, logger); 60 | 61 | public async Task UpAsync(CancellationToken cancellationToken = default) 62 | { 63 | if (_dockerInitializer is not null) 64 | { 65 | await _dockerInitializer.InitializeDockerAsync(cancellationToken); 66 | } 67 | 68 | // Pull/build all required images. 69 | var ensureTasks = Containers.Select(c => c.EnsureImageAvailableAsync(cancellationToken)); 70 | await Task.WhenAll(ensureTasks); 71 | 72 | // Run all containers. 73 | var runTasks = Containers.Select(c => c.RunAsync(cancellationToken)); 74 | await Task.WhenAll(runTasks); 75 | } 76 | 77 | public async Task DownAsync(CancellationToken cancellationToken = default) 78 | { 79 | var stopTasks = Containers.Select(c => c.StopAsync(cancellationToken)); 80 | await Task.WhenAll(stopTasks); 81 | } 82 | 83 | public Container? GetContainer(string name) => 84 | GetContainer(name); 85 | 86 | public TContainer? GetContainer(string name) 87 | where TContainer : Container 88 | { 89 | var containerName = GetContainerName(Name, name); 90 | return Containers.FirstOrDefault(container => container is TContainer c && c.Name == containerName) as TContainer; 91 | } 92 | 93 | public async ValueTask DisposeAsync() 94 | { 95 | var disposeTasks = Containers.Select(c => c.DisposeAsync()); 96 | foreach (var disposeTask in disposeTasks) 97 | { 98 | await disposeTask; 99 | } 100 | 101 | if (_dockerInitializer is not null) 102 | { 103 | await _dockerInitializer.DisposeAsync(); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using Docker.DotNet; 6 | using Microsoft.Extensions.Logging; 7 | using TestEnvironment.Docker.ContainerOperations; 8 | using TestEnvironment.Docker.DockerOperations; 9 | using TestEnvironment.Docker.ImageOperations; 10 | using static TestEnvironment.Docker.DockerClientExtentions; 11 | using static TestEnvironment.Docker.StringExtensions; 12 | 13 | namespace TestEnvironment.Docker 14 | { 15 | public class DockerEnvironmentBuilder : IDockerEnvironmentBuilder 16 | { 17 | private readonly Dictionary> _containerFactories = new(); 18 | private IDictionary _environmentVariables = new Dictionary(); 19 | private bool _isWsl2 = false; 20 | private bool _isDockerInDocker = false; 21 | private string _environmentName = Guid.NewGuid().ToString().Substring(0, 10); 22 | 23 | public IDockerClient DockerClient { get; private set; } 24 | 25 | public ILogger? Logger { get; init; } 26 | 27 | #pragma warning disable SA1201 // Elements should appear in the correct order 28 | public DockerEnvironmentBuilder() 29 | #pragma warning restore SA1201 // Elements should appear in the correct order 30 | : this(CreateDefaultDockerClient(), null) 31 | { 32 | } 33 | 34 | public DockerEnvironmentBuilder(IDockerClient dockerClient) 35 | : this(dockerClient, null) 36 | { 37 | } 38 | 39 | public DockerEnvironmentBuilder(ILogger logger) 40 | : this(CreateDefaultDockerClient(), logger) 41 | { 42 | } 43 | 44 | public DockerEnvironmentBuilder(IDockerClient dockerClient, ILogger? logger) => 45 | (DockerClient, Logger) = (dockerClient, logger ?? Logger); 46 | 47 | public IDockerEnvironmentBuilder DockerInDocker() 48 | { 49 | _isDockerInDocker = true; 50 | return this; 51 | } 52 | 53 | public IDockerEnvironmentBuilder UseWsl2(int port = 2375) 54 | { 55 | _isWsl2 = true; 56 | 57 | // Use loopback address to connect to Docker daemon in WSL2. 58 | DockerClient = CreateWSL2DockerClient(); 59 | return this; 60 | } 61 | 62 | public IDockerEnvironmentBuilder SetName(string environmentName) 63 | { 64 | _environmentName = environmentName; 65 | return this; 66 | } 67 | 68 | public IDockerEnvironmentBuilder SetEnvironmentVariables(IDictionary environmentVariables) 69 | { 70 | _environmentVariables = environmentVariables; 71 | return this; 72 | } 73 | 74 | public IDockerEnvironmentBuilder AddContainer(Func paramsBuilder) 75 | { 76 | var parameters = paramsBuilder(new ContainerParameters("hello", "docker/getting-started")); 77 | AddContainer(parameters, (p, d, l) => new Container(p, d, l)); 78 | return this; 79 | } 80 | 81 | public IDockerEnvironmentBuilder AddContainer(Func paramsBuilder) 82 | { 83 | var parameters = paramsBuilder(new ContainerParameters("hello", "docker/getting-started"), DockerClient, Logger); 84 | AddContainer(parameters, (p, d, l) => new Container(p, d, l)); 85 | return this; 86 | } 87 | 88 | public IDockerEnvironmentBuilder AddContainer(TParams containerParameters, Func containerFactory) 89 | where TParams : ContainerParameters 90 | { 91 | _containerFactories.Add(containerParameters, () => 92 | { 93 | var envParameters = containerParameters with 94 | { 95 | Name = GetContainerName(_environmentName, containerParameters.Name), 96 | EnvironmentVariables = _environmentVariables.MergeDictionaries(containerParameters.EnvironmentVariables), 97 | IsDockerInDocker = _isDockerInDocker, 98 | }; 99 | 100 | return containerFactory(envParameters, DockerClient, Logger); 101 | }); 102 | return this; 103 | } 104 | 105 | public IDockerEnvironment Build() 106 | { 107 | var containers = _containerFactories.Values.Select(cf => cf()).ToArray(); 108 | 109 | return _isWsl2 110 | ? new DockerEnvironment(_environmentName, containers, DockerClient, new DockerInWs2Initializer(DockerClient, Logger), Logger) 111 | : new DockerEnvironment(_environmentName, containers, DockerClient, Logger); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DockerOperations/DockerInWs2Initializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Docker.DotNet; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace TestEnvironment.Docker.DockerOperations 12 | { 13 | public class DockerInWs2Initializer : IDockerInitializer 14 | { 15 | private const string DockerServiceInitCommand = @"-u root -e sh -c ""service docker status || service docker start"""; 16 | private const string Wsl2Executable = "wsl.exe"; 17 | private const int DockerServicePollRetryCount = 30; 18 | private const int DockerServicePollRetryTimeout = 1; 19 | 20 | private readonly IDockerClient _dockerClient; 21 | private readonly ILogger? _logger; 22 | 23 | private Process? _wsl2ShellProcess; 24 | 25 | public DockerInWs2Initializer(IDockerClient dockerClient, ILogger? logger) => 26 | (_dockerClient, _logger) = (dockerClient, logger); 27 | 28 | public async Task InitializeDockerAsync(CancellationToken cancellationToken = default) 29 | { 30 | if (_wsl2ShellProcess is not null) 31 | { 32 | return; 33 | } 34 | 35 | var dockerServiceStarted = false; 36 | Process? wsl2ShellProcess = null; 37 | 38 | try 39 | { 40 | wsl2ShellProcess = new() 41 | { 42 | StartInfo = new ProcessStartInfo 43 | { 44 | FileName = Wsl2Executable, 45 | UseShellExecute = false, 46 | RedirectStandardOutput = true, 47 | RedirectStandardInput = true, 48 | CreateNoWindow = true, 49 | Arguments = DockerServiceInitCommand 50 | } 51 | }; 52 | 53 | wsl2ShellProcess.Start(); 54 | 55 | dockerServiceStarted = await WaitForDockerReadinessAsync(cancellationToken); 56 | 57 | if (dockerServiceStarted) 58 | { 59 | _wsl2ShellProcess = wsl2ShellProcess; 60 | } 61 | } 62 | finally 63 | { 64 | if (!dockerServiceStarted) 65 | { 66 | wsl2ShellProcess?.Dispose(); 67 | } 68 | } 69 | 70 | if (!dockerServiceStarted) 71 | { 72 | throw new InvalidOperationException("Unable to run Docker in WSL."); 73 | } 74 | } 75 | 76 | public ValueTask DisposeAsync() 77 | { 78 | // TODO: maybe stop Docker service 79 | _wsl2ShellProcess?.Dispose(); 80 | return ValueTask.CompletedTask; 81 | } 82 | 83 | private async Task WaitForDockerReadinessAsync(CancellationToken cancellationToken = default) 84 | { 85 | _logger?.LogInformation("Checking for docker service state..."); 86 | 87 | var policy = PolicyFactory.CreateWaitPolicy(DockerServicePollRetryCount, TimeSpan.FromSeconds(DockerServicePollRetryTimeout), null, e => _logger?.LogError(e, "Docker service is not available.")); 88 | var result = await policy.ExecuteAndCaptureAsync(async () => 89 | { 90 | await _dockerClient.System.PingAsync(cancellationToken); 91 | return true; 92 | }); 93 | 94 | if (result.Outcome == Polly.OutcomeType.Successful) 95 | { 96 | _logger?.LogInformation("Docker service is up!"); 97 | } 98 | else 99 | { 100 | _logger?.LogError("Docker service didn't start."); 101 | } 102 | 103 | return result.Result; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/DockerOperations/IDockerInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace TestEnvironment.Docker.DockerOperations 7 | { 8 | public interface IDockerInitializer : IAsyncDisposable 9 | { 10 | Task InitializeDockerAsync(CancellationToken cancellationToken = default); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/IDockerEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace TestEnvironment.Docker 6 | { 7 | public interface IDockerEnvironment : IAsyncDisposable 8 | { 9 | string Name { get; } 10 | 11 | Container[] Containers { get; } 12 | 13 | Task UpAsync(CancellationToken cancellationToken = default); 14 | 15 | Task DownAsync(CancellationToken cancellationToken = default); 16 | 17 | Container? GetContainer(string name); 18 | 19 | TContainer? GetContainer(string name) 20 | where TContainer : Container; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace TestEnvironment.Docker 7 | { 8 | public interface IDockerEnvironmentBuilder 9 | { 10 | IDockerClient DockerClient { get; } 11 | 12 | ILogger? Logger { get; } 13 | 14 | IDockerEnvironmentBuilder DockerInDocker(); 15 | 16 | IDockerEnvironmentBuilder SetName(string environmentName); 17 | 18 | IDockerEnvironmentBuilder UseWsl2(int port = 2375); 19 | 20 | IDockerEnvironmentBuilder SetEnvironmentVariables(IDictionary environmentVariables); 21 | 22 | IDockerEnvironmentBuilder AddContainer(Func paramsBuilder); 23 | 24 | IDockerEnvironmentBuilder AddContainer(TParams containerParameters, Func containerFactory) 25 | where TParams : ContainerParameters; 26 | 27 | IDockerEnvironment Build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/IDockerEnvironmentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Docker.DotNet; 4 | using Microsoft.Extensions.Logging; 5 | using TestEnvironment.Docker.ContainerLifecycle; 6 | 7 | namespace TestEnvironment.Docker 8 | { 9 | public static class IDockerEnvironmentBuilderExtensions 10 | { 11 | public static ContainerFromDockerfileParameters DefaultParameters => new("hello", "Dockerfile"); 12 | 13 | public static IDockerEnvironmentBuilder AddFromDockerfile( 14 | this IDockerEnvironmentBuilder builder, 15 | Func paramsBuilder) 16 | { 17 | var parameters = paramsBuilder(DefaultParameters); 18 | builder.AddContainer(parameters, (p, d, l) => new ContainerFromDockerfile(p, d, l)); 19 | 20 | return builder; 21 | } 22 | 23 | public static IDockerEnvironmentBuilder AddFromDockerfile( 24 | this IDockerEnvironmentBuilder builder, 25 | Func paramsBuilder) 26 | { 27 | var parameters = paramsBuilder(DefaultParameters, builder.DockerClient, builder.Logger); 28 | builder.AddContainer(parameters, (p, d, l) => new ContainerFromDockerfile(p, d, l)); 29 | 30 | return builder; 31 | } 32 | 33 | [Obsolete("This method is depricated and will be removed in upcoming versions.")] 34 | public static IDockerEnvironmentBuilder AddFromDockerfile( 35 | this IDockerEnvironmentBuilder builder, 36 | string name, 37 | string dockerfile, 38 | IDictionary? buildArgs = null, 39 | string context = ".", 40 | IDictionary? environmentVariables = null, 41 | IDictionary? ports = null, 42 | bool reuseContainer = false, 43 | IContainerWaiter? containerWaiter = null, 44 | IContainerCleaner? containerCleaner = null) 45 | { 46 | builder.AddFromDockerfile(p => p with 47 | { 48 | Name = name, 49 | Dockerfile = dockerfile, 50 | BuildArgs = buildArgs, 51 | Context = context, 52 | EnvironmentVariables = environmentVariables, 53 | Ports = ports, 54 | Reusable = reuseContainer, 55 | ContainerWaiter = containerWaiter, 56 | ContainerCleaner = containerCleaner 57 | }); 58 | 59 | return builder; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ImageOperations/Archiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using SharpCompress.Common; 8 | using SharpCompress.Writers; 9 | 10 | namespace TestEnvironment.Docker.ImageOperations 11 | { 12 | public class Archiver : IArchiver 13 | { 14 | private readonly ILogger? _logger; 15 | 16 | public Archiver() 17 | { 18 | } 19 | 20 | public Archiver(ILogger logger) => 21 | _logger = logger; 22 | 23 | #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 24 | public Task CreateTarArchiveAsync(string fileName, string directiry, string[]? ignoredFiles = default, CancellationToken cancellationToken = default) 25 | #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 26 | { 27 | using var stream = File.OpenWrite(fileName); 28 | using var writer = WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None); 29 | 30 | AddDirectoryFilesToTar(writer, fileName, directiry, directiry, true); 31 | 32 | return Task.CompletedTask; 33 | } 34 | 35 | // Adds recuresively files to tar archive. 36 | #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 37 | private void AddDirectoryFilesToTar(IWriter writer, string archiveFileName, string rootDirectory, string sourceDirectory, bool recurse, string[]? ignoredFiles = default) 38 | #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 39 | { 40 | if (ignoredFiles?.Any(excl => excl.Equals(Path.GetFileName(sourceDirectory))) == true) 41 | { 42 | return; 43 | } 44 | 45 | // Write each file to the tar. 46 | var filenames = Directory.GetFiles(sourceDirectory); 47 | foreach (string filename in filenames) 48 | { 49 | if (Path.GetFileName(filename).Equals(archiveFileName)) 50 | { 51 | continue; 52 | } 53 | 54 | if (new FileInfo(filename).Attributes.HasFlag(FileAttributes.Hidden)) 55 | { 56 | continue; 57 | } 58 | 59 | // Make sure that we can read the file 60 | try 61 | { 62 | File.OpenRead(filename); 63 | } 64 | catch (Exception) 65 | { 66 | continue; 67 | } 68 | 69 | try 70 | { 71 | var contextDirectoryIndex = filename.IndexOf(rootDirectory); 72 | var cleanPath = (contextDirectoryIndex < 0) 73 | ? filename 74 | : filename.Remove(contextDirectoryIndex, rootDirectory.Length); 75 | 76 | writer.Write(cleanPath, filename); 77 | } 78 | catch (Exception exc) 79 | { 80 | _logger?.LogWarning($"Can not add file {filename} to the context: {exc.Message}."); 81 | } 82 | } 83 | 84 | if (recurse) 85 | { 86 | var directories = Directory.GetDirectories(sourceDirectory); 87 | foreach (var directory in directories) 88 | { 89 | AddDirectoryFilesToTar(writer, archiveFileName, rootDirectory, directory, recurse, ignoredFiles); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ImageOperations/IArchiver.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace TestEnvironment.Docker.ImageOperations 5 | { 6 | public interface IArchiver 7 | { 8 | #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 9 | Task CreateTarArchiveAsync(string fileName, string directiry, string[]? ignoredFiles = default, CancellationToken cancellationToken = default); 10 | #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ImageOperations/IImageApi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace TestEnvironment.Docker.ImageOperations 6 | { 7 | public interface IImageApi 8 | { 9 | Task PullImageAsync(string imageName, string tag, CancellationToken cancellationToken = default); 10 | 11 | #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 12 | Task BuildImageAsync(string dockerfile, string imageName, string tag = "latest", string context = ".", IDictionary? buildArgs = default, string[]? ignoredFiles = default, CancellationToken cancellationToken = default); 13 | #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/ImageOperations/ImageApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Docker.DotNet; 8 | using Docker.DotNet.Models; 9 | using Microsoft.Extensions.Logging; 10 | using static TestEnvironment.Docker.DockerClientExtentions; 11 | 12 | namespace TestEnvironment.Docker.ImageOperations 13 | { 14 | public class ImageApi : IImageApi 15 | { 16 | private readonly IDockerClient _dockerClient; 17 | private readonly IArchiver _archiver; 18 | private readonly ILogger? _logger; 19 | 20 | public ImageApi() 21 | : this(CreateDefaultDockerClient(), new Archiver(), null) 22 | { 23 | } 24 | 25 | public ImageApi(IDockerClient dockerClient) 26 | : this(dockerClient, new Archiver(), null) 27 | { 28 | } 29 | 30 | public ImageApi(IDockerClient dockerClient, ILogger? logger) 31 | : this(dockerClient, new Archiver(), logger) 32 | { 33 | } 34 | 35 | public ImageApi(ILogger logger) 36 | : this(CreateDefaultDockerClient(), new Archiver(logger), null) 37 | { 38 | } 39 | 40 | public ImageApi(IDockerClient dockerClient, IArchiver archiver, ILogger? logger) 41 | { 42 | _dockerClient = dockerClient; 43 | _archiver = archiver; 44 | _logger = logger; 45 | } 46 | 47 | #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 48 | public async Task BuildImageAsync(string dockerfile, string imageName, string tag = "latest", string context = ".", IDictionary? buildArgs = null, string[]? ignoredFiles = default, CancellationToken cancellationToken = default) 49 | #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 50 | { 51 | var tarFileName = Guid.NewGuid().ToString(); 52 | 53 | try 54 | { 55 | // In order to pass the context we have to create tar file and use it as an argument. 56 | await _archiver.CreateTarArchiveAsync(tarFileName, context, ignoredFiles, cancellationToken); 57 | 58 | // Now call docker api. 59 | await CreateNewImageAsync(dockerfile, imageName, tag, tarFileName, buildArgs, cancellationToken); 60 | } 61 | catch (Exception exc) 62 | { 63 | _logger?.LogError(exc, $"Unable to create the image from dockerfile."); 64 | throw; 65 | } 66 | finally 67 | { 68 | // And don't forget to remove created tar. 69 | try 70 | { 71 | File.Delete(tarFileName); 72 | } 73 | catch (Exception exc) 74 | { 75 | _logger?.LogError(exc, $"Unable to delete tar file {tarFileName} with context. Please, cleanup manually."); 76 | } 77 | } 78 | } 79 | 80 | public async Task PullImageAsync(string imageName, string tag, CancellationToken cancellationToken = default) 81 | { 82 | var images = await _dockerClient.Images.ListImagesAsync( 83 | new ImagesListParameters 84 | { 85 | All = true, 86 | Filters = new Dictionary> 87 | { 88 | ["reference"] = new Dictionary { [$"{imageName}:{tag}"] = true } 89 | } 90 | }, 91 | cancellationToken); 92 | 93 | if (!images.Any()) 94 | { 95 | _logger?.LogInformation($"Pulling the image {imageName}:{tag}"); 96 | 97 | // Pull the image. 98 | try 99 | { 100 | await _dockerClient.Images.CreateImageAsync( 101 | new ImagesCreateParameters 102 | { 103 | FromImage = imageName, 104 | Tag = tag 105 | }, 106 | null, 107 | new Progress(m => _logger?.LogDebug($"Pulling image {imageName}:{tag}:\n{m.ProgressMessage}")), 108 | cancellationToken); 109 | } 110 | catch (Exception exc) 111 | { 112 | _logger?.LogError(exc, $"Unable to pull the image {imageName}:{tag}"); 113 | throw; 114 | } 115 | } 116 | } 117 | 118 | private async Task CreateNewImageAsync(string dockerfile, string imageName, string tag, string contextTarFile, IDictionary? buildArgs, CancellationToken cancellationToken) 119 | { 120 | using var tarContextStream = new FileStream(contextTarFile, FileMode.Open); 121 | 122 | await _dockerClient.Images 123 | .BuildImageFromDockerfileAsync( 124 | new ImageBuildParameters 125 | { 126 | Dockerfile = dockerfile, 127 | BuildArgs = buildArgs ?? new Dictionary(), 128 | Tags = new[] { $"{imageName}:{tag}" }, 129 | Remove = true, 130 | ForceRemove = true, 131 | }, 132 | tarContextStream, 133 | null, 134 | null, 135 | new Progress(m => _logger?.LogDebug($"Building image {imageName}:{tag}:\n{m.ProgressMessage}")), 136 | cancellationToken); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/PolicyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Polly; 7 | using Polly.Retry; 8 | 9 | namespace TestEnvironment.Docker 10 | { 11 | public static class PolicyFactory 12 | { 13 | public static AsyncRetryPolicy CreateWaitPolicy(int retryCount, TimeSpan retryInterval, Func? exceptionFilter, Action? onRetry) 14 | { 15 | var exceptionPb = exceptionFilter is null 16 | ? Policy.Handle() 17 | : Policy.Handle(e => exceptionFilter(e)); 18 | 19 | var withResultPb = exceptionPb.OrResult(res => !res); // if returned false from healthcheck retry as well 20 | 21 | return onRetry is null 22 | ? withResultPb.WaitAndRetryAsync(retryCount, i => retryInterval) 23 | : withResultPb.WaitAndRetryAsync(retryCount, i => retryInterval, (e, ts) => 24 | { 25 | if (e.Exception is not null) 26 | { 27 | onRetry.Invoke(e.Exception); 28 | } 29 | }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TestEnvironment.Docker 2 | { 3 | internal static class StringExtensions 4 | { 5 | public static string GetContainerName(string environmentName, string containerName) => 6 | $"{environmentName}-{containerName}"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/TestEnvironment.Docker/TestEnvironment.Docker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | TestEnvironment.Docker 6 | TestEnvironment.Docker 7 | 2.1.5 8 | TestEnvironment.Docker 9 | Aliaksei Harshkalep 10 | Testing framework for setting up real dependencies as Docker containers. 11 | false 12 | docker tests 13 | enable 14 | Add WSL2 support. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "orderingRules": { 5 | "usingDirectivesPlacement": "outsideNamespace" 6 | }, 7 | "namingRules": { 8 | "allowCommonHungarianPrefixes": true, 9 | "allowedHungarianPrefixes": [ 10 | "db", 11 | "do", 12 | "id", 13 | "if", 14 | "in", 15 | "is", 16 | "my", 17 | "no", 18 | "on", 19 | "to", 20 | "ui", 21 | "vm", 22 | "h", 23 | "up", 24 | "cv", 25 | "sa", 26 | "ip" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /stylecop.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /test/TestEnvironment.Docker.Tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM halverneus/static-file-server:latest 2 | 3 | COPY index.html /web 4 | 5 | ENTRYPOINT ["/serve"] -------------------------------------------------------------------------------- /test/TestEnvironment.Docker.Tests/TestEnvironment.Docker.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | false 6 | Debug;Release;WSL2_Debug;WSL2_Release 7 | 8 | 9 | 10 | $(DefineConstants)TRACE;DEBUG;WSL2 11 | 12 | 13 | 14 | $(DefineConstants)TRACE;RELEASE;WSL2 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Always 54 | 55 | 56 | Always 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /test/TestEnvironment.Docker.Tests/XUnitLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit.Abstractions; 4 | 5 | namespace TestEnvironment.Docker.Tests 6 | { 7 | public class XUnitLogger : ILogger 8 | { 9 | private readonly ITestOutputHelper _testOutputHelper; 10 | 11 | public XUnitLogger(ITestOutputHelper testOutputHelper) 12 | { 13 | _testOutputHelper = testOutputHelper; 14 | } 15 | 16 | public void Log( 17 | LogLevel logLevel, 18 | EventId eventId, 19 | TState state, 20 | Exception exception, 21 | Func formatter) 22 | { 23 | var message = formatter(state, exception); 24 | 25 | _testOutputHelper.WriteLine($"{logLevel.ToString()}: {message}"); 26 | } 27 | 28 | public bool IsEnabled(LogLevel logLevel) => true; 29 | 30 | public IDisposable BeginScope(TState state) 31 | { 32 | return null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/TestEnvironment.Docker.Tests/index.html: -------------------------------------------------------------------------------- 1 | Hello world! --------------------------------------------------------------------------------