├── .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 | [](https://ci.appveyor.com/project/Deffiss/testenvironment-docker/branch/master) [](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!
--------------------------------------------------------------------------------