From eb986f48f84078bb9f2cf19f92244d0912250ff4 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:34:36 +0300 Subject: [PATCH 01/11] feat: Add docker compose support (#122) --- Testcontainers.sln | 14 ++ .../.editorconfig | 1 + .../DockerComposeBuilder.cs | 123 ++++++++++++++++++ .../DockerComposeConfiguration.cs | 71 ++++++++++ .../DockerComposeContainer.cs | 30 +++++ .../DockerComposeLocal.cs | 40 ++++++ .../DockerComposeRemote.cs | 29 +++++ .../Testcontainers.DockerCompose.csproj | 15 +++ src/Testcontainers.DockerCompose/Usings.cs | 18 +++ .../Containers/DockerContainer.cs | 4 +- .../.editorconfig | 1 + .../DockerComposeRemoteTest.cs | 55 ++++++++ .../Testcontainers.DockerCompose.Tests.csproj | 17 +++ .../Usings.cs | 6 + .../docker-compose.yaml | 9 ++ 15 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 src/Testcontainers.DockerCompose/.editorconfig create mode 100644 src/Testcontainers.DockerCompose/DockerComposeBuilder.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeContainer.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeLocal.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeRemote.cs create mode 100644 src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj create mode 100644 src/Testcontainers.DockerCompose/Usings.cs create mode 100644 tests/Testcontainers.DockerCompose.Tests/.editorconfig create mode 100644 tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs create mode 100644 tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj create mode 100644 tests/Testcontainers.DockerCompose.Tests/Usings.cs create mode 100644 tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml diff --git a/Testcontainers.sln b/Testcontainers.sln index 7507b0faa..6494a2bb4 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -183,6 +183,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose", "src\Testcontainers.DockerCompose\Testcontainers.DockerCompose.csproj", "{5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose.Tests", "tests\Testcontainers.DockerCompose.Tests\Testcontainers.DockerCompose.Tests.csproj", "{D77017F1-9E38-4B06-8CEB-9B3D98B6497C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -532,6 +536,14 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.Build.0 = Release|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -619,5 +631,7 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.DockerCompose/.editorconfig b/src/Testcontainers.DockerCompose/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/src/Testcontainers.DockerCompose/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs new file mode 100644 index 000000000..121368d04 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -0,0 +1,123 @@ +namespace Testcontainers.DockerCompose; + +/// +[PublicAPI] +public sealed class DockerComposeBuilder : ContainerBuilder +{ + private const string NoComposeFile = "No docker compose file have been provided."; + + //Docker Compose is included as part of this image. + public const string DockerComposeImage = "docker:24-cli"; + + public const string DockerSocketPath = "/var/run/docker.sock"; + + /// + /// Initializes a new instance of the class. + /// + public DockerComposeBuilder() + : this(new DockerComposeConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private DockerComposeBuilder(DockerComposeConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + protected override DockerComposeConfiguration DockerResourceConfiguration { get; } + + /// + public override DockerComposeContainer Build() + { + Validate(); + + return new DockerComposeContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + } + + /// + /// Sets the compose file. + /// + /// The compose file. + /// + public DockerComposeBuilder WithComposeFile(string composeFile) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration + (composeFile: composeFile)); + } + + /// + /// If true use a local Docker Compose binary instead of a container. + /// + /// + /// + public DockerComposeBuilder WithLocalCompose(bool localCompose) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration + (localCompose: localCompose)); + } + + /// + protected override DockerComposeBuilder Init() + { + return base.Init() + .WithImage(DockerComposeImage) + .WithEntrypoint(CommonCommands.SleepInfinity) + .WithBindMount(DockerSocketPath, DockerSocketPath, AccessMode.ReadWrite) + .WithStartupCallback(ConfigureDockerComposeAsync); + } + + /// + protected override void Validate() + { + base.Validate(); + + _ = Guard.Argument(DockerResourceConfiguration.ComposeFile, nameof(DockerResourceConfiguration.ComposeFile)) + .NotEmpty(); + } + + /// + protected override DockerComposeBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(resourceConfiguration)); + } + + /// + protected override DockerComposeBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(resourceConfiguration)); + } + + /// + protected override DockerComposeBuilder Merge(DockerComposeConfiguration oldValue, DockerComposeConfiguration newValue) + { + return new DockerComposeBuilder(new DockerComposeConfiguration(oldValue, newValue)); + } + + /// + /// Configures the compose container. + /// + /// The container. + /// Cancellation token. + private async Task ConfigureDockerComposeAsync(IContainer container, CancellationToken ct = default) + { + if (container is DockerComposeRemote dockerComposeContainer && + !dockerComposeContainer.RuntimeConfiguration.LocalCompose ) + { + var fileInfo = new FileInfo(dockerComposeContainer.RuntimeConfiguration.ComposeFile); + if (fileInfo.Exists) + { + await container.CopyAsync(fileInfo, ".", Unix.FileMode644, ct) + .ConfigureAwait(false); + } + await container.ExecAsync(new[] { "docker", "compose", "up", "-d" }, ct) + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs new file mode 100644 index 000000000..1a163064c --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs @@ -0,0 +1,71 @@ +namespace Testcontainers.DockerCompose; + +/// +[PublicAPI] +public sealed class DockerComposeConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// The fully qualified path to the compose file. + /// Whether the local compose will be used. + public DockerComposeConfiguration( + string composeFile = null, + bool localCompose = false) + { + ComposeFile = composeFile; + LocalCompose = localCompose; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public DockerComposeConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public DockerComposeConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public DockerComposeConfiguration(DockerComposeConfiguration resourceConfiguration) + : this(new DockerComposeConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public DockerComposeConfiguration(DockerComposeConfiguration oldValue, DockerComposeConfiguration newValue) + : base(oldValue, newValue) + { + ComposeFile = BuildConfiguration.Combine(oldValue.ComposeFile, newValue.ComposeFile); + LocalCompose = BuildConfiguration.Combine(oldValue.LocalCompose, newValue.LocalCompose); + } + + /// + /// Gets the path to the compose file. + /// + public string ComposeFile { get; } + + /// + /// Indicates whether local compose is enabled. + /// + public bool LocalCompose { get; } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs new file mode 100644 index 000000000..bb8688dfc --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs @@ -0,0 +1,30 @@ +namespace Testcontainers.DockerCompose; + +public class DockerComposeContainer : DockerContainer +{ + private readonly IContainer _proxyContainer; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public DockerComposeContainer(DockerComposeConfiguration configuration, ILogger logger) : base(configuration, logger) + { + _proxyContainer = configuration.LocalCompose + ? new DockerComposeLocal(configuration, logger) + : new DockerComposeRemote(configuration, logger); + } + + /// + public override async Task StartAsync(CancellationToken ct = default) + { + await _proxyContainer.StartAsync(ct); + } + + /// + public override async Task StopAsync(CancellationToken ct = default) + { + await _proxyContainer.StopAsync(ct); + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs new file mode 100644 index 000000000..7ecf058f6 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -0,0 +1,40 @@ +namespace Testcontainers.DockerCompose; + +[PublicAPI] +public sealed class DockerComposeLocal : DockerContainer +{ + private readonly string _dockerComposeBinary = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker-compose.exe" : "docker-compose"; + + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logger) : base(configuration, logger) + { + } + + /// + /// Gets the runtime configuration. + /// + public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; + + /// + public override async Task StartAsync(CancellationToken ct = default) + { + await Cli.Wrap(_dockerComposeBinary) + .WithArguments(new[] {"up", "-d"}) + .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) + .ExecuteBufferedAsync(); + } + + /// + public override async Task StopAsync(CancellationToken ct = default) + { + await Cli.Wrap(_dockerComposeBinary) + .WithArguments(new[] {"down"}) + .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) + .ExecuteBufferedAsync(); + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs new file mode 100644 index 000000000..d2ca4e1b8 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs @@ -0,0 +1,29 @@ +namespace Testcontainers.DockerCompose; + +/// +[PublicAPI] +public class DockerComposeRemote : DockerContainer +{ + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + public DockerComposeRemote(DockerComposeConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + } + + /// + /// Gets the runtime configuration. + /// + public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; + + /// + public override async Task StopAsync(CancellationToken ct = default) + { + await ExecAsync(new[] { "docker", "compose", "down"}, ct) + .ConfigureAwait(false); + await base.StopAsync(ct); + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj new file mode 100644 index 000000000..894c19cd2 --- /dev/null +++ b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj @@ -0,0 +1,15 @@ + + + netstandard2.0;netstandard2.1 + latest + + + + + + + + + + + \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs new file mode 100644 index 000000000..fbfb35ec4 --- /dev/null +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -0,0 +1,18 @@ +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Threading; +global using System.Threading.Tasks; +global using Docker.DotNet.Models; +global using DotNet.Testcontainers; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; +global using Microsoft.Extensions.Logging; +global using System.Diagnostics; +global using System.Linq; +global using System.Runtime.InteropServices; +global using CliWrap; +global using CliWrap.Buffered; +global using DotNet.Testcontainers.Commons; \ No newline at end of file diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index 1e9bbf855..a84e9c64b 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -24,9 +24,9 @@ public class DockerContainer : Resource, IContainer private readonly ITestcontainersClient _client; - private readonly IContainerConfiguration _configuration; + protected ContainerInspectResponse _container = new ContainerInspectResponse(); - private ContainerInspectResponse _container = new ContainerInspectResponse(); + protected readonly IContainerConfiguration _configuration; /// /// Initializes a new instance of the class. diff --git a/tests/Testcontainers.DockerCompose.Tests/.editorconfig b/tests/Testcontainers.DockerCompose.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs b/tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs new file mode 100644 index 000000000..adfbee1b3 --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; + +namespace Testcontainers.DockerCompose; + +public abstract class DockerComposeRemoteTest : IAsyncLifetime +{ + private readonly DockerComposeContainer _dockerComposeContainer; + + protected DockerComposeRemoteTest(DockerComposeContainer dockerComposeContainer) + { + _dockerComposeContainer = dockerComposeContainer; + } + + public Task InitializeAsync() + { + return _dockerComposeContainer.StartAsync(); + } + + public async Task DisposeAsync() + { + await _dockerComposeContainer.StopAsync(); + await _dockerComposeContainer.DisposeAsync().AsTask(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public void ContainerStartedSuccessfully() + { + Assert.Equal(TestcontainersHealthStatus.Healthy, TestcontainersHealthStatus.Healthy); + } + + [UsedImplicitly] + public sealed class DockerComposeRemoteConfiguration : DockerComposeRemoteTest + { + public DockerComposeRemoteConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) + .Build()) + { + } + } + + [UsedImplicitly] + public sealed class DockerComposeLocalConfiguration : DockerComposeRemoteTest + { + public DockerComposeLocalConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) + .WithLocalCompose(true) + .Build()) + { + } + } +} + \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj new file mode 100644 index 000000000..3afc73302 --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj @@ -0,0 +1,17 @@ + + + net8.0 + false + false + + + + + + + + + + + + diff --git a/tests/Testcontainers.DockerCompose.Tests/Usings.cs b/tests/Testcontainers.DockerCompose.Tests/Usings.cs new file mode 100644 index 000000000..784a06819 --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/Usings.cs @@ -0,0 +1,6 @@ +global using System.IO; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Commons; +global using DotNet.Testcontainers.Containers; +global using Testcontainers.DockerCompose; +global using Xunit; \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml b/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml new file mode 100644 index 000000000..d7effad58 --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml @@ -0,0 +1,9 @@ +version: '3.9' + +name: test-nginx + +services: + nginx: + image: nginx + ports: + - "8080:80" From bee415bf2bb75a5256ff8ad003306bcc28d10573 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:49:38 +0300 Subject: [PATCH 02/11] Formatting fixes --- src/Testcontainers.DockerCompose/DockerComposeBuilder.cs | 2 +- src/Testcontainers.DockerCompose/DockerComposeContainer.cs | 1 + src/Testcontainers.DockerCompose/Usings.cs | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs index 121368d04..3fdb85c20 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -120,4 +120,4 @@ await container.ExecAsync(new[] { "docker", "compose", "up", "-d" }, ct) .ConfigureAwait(false); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs index bb8688dfc..d1fb2a0c5 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs @@ -1,5 +1,6 @@ namespace Testcontainers.DockerCompose; +[PublicAPI] public class DockerComposeContainer : DockerContainer { private readonly IContainer _proxyContainer; diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs index fbfb35ec4..4169a851a 100644 --- a/src/Testcontainers.DockerCompose/Usings.cs +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -1,5 +1,3 @@ -global using System; -global using System.Collections.Generic; global using System.IO; global using System.Threading; global using System.Threading.Tasks; @@ -10,8 +8,6 @@ global using DotNet.Testcontainers.Containers; global using JetBrains.Annotations; global using Microsoft.Extensions.Logging; -global using System.Diagnostics; -global using System.Linq; global using System.Runtime.InteropServices; global using CliWrap; global using CliWrap.Buffered; From 050a868c7599c18cb9cc55ad8b4a927797b8e3ef Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:35:52 +0300 Subject: [PATCH 03/11] docker-compose command is deprecated, only support v2 --- .../DockerComposeLocal.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs index 7ecf058f6..d73dffed8 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -3,8 +3,8 @@ [PublicAPI] public sealed class DockerComposeLocal : DockerContainer { - private readonly string _dockerComposeBinary = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker-compose.exe" : "docker-compose"; + private readonly string _dockerBinary = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker.exe" : "docker"; /// /// Initializes a new instance of the class. @@ -23,8 +23,8 @@ public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logg /// public override async Task StartAsync(CancellationToken ct = default) { - await Cli.Wrap(_dockerComposeBinary) - .WithArguments(new[] {"up", "-d"}) + await Cli.Wrap(_dockerBinary) + .WithArguments(new[] {"compose", "up", "-d"}) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync(); } @@ -32,8 +32,8 @@ await Cli.Wrap(_dockerComposeBinary) /// public override async Task StopAsync(CancellationToken ct = default) { - await Cli.Wrap(_dockerComposeBinary) - .WithArguments(new[] {"down"}) + await Cli.Wrap(_dockerBinary) + .WithArguments(new[] {"compose", "down"}) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync(); } From c1eacca93b4209b4b4dcec98338dc0089c4c997c Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:11:13 +0300 Subject: [PATCH 04/11] Add support for the docker compose option --- .../DockerCompose.cs | 31 ++++++++++++++++ .../DockerComposeBuilder.cs | 14 +++++++- .../DockerComposeConfiguration.cs | 10 +++++- .../DockerComposeLocal.cs | 13 +++---- .../DockerComposeRemote.cs | 12 ++----- src/Testcontainers.DockerCompose/Usings.cs | 3 ++ ...poseRemoteTest.cs => DockerComposeTest.cs} | 36 +++++++++++++++---- .../Usings.cs | 2 +- 8 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 src/Testcontainers.DockerCompose/DockerCompose.cs rename tests/Testcontainers.DockerCompose.Tests/{DockerComposeRemoteTest.cs => DockerComposeTest.cs} (56%) diff --git a/src/Testcontainers.DockerCompose/DockerCompose.cs b/src/Testcontainers.DockerCompose/DockerCompose.cs new file mode 100644 index 000000000..741c30f9b --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerCompose.cs @@ -0,0 +1,31 @@ +namespace Testcontainers.DockerCompose; + +/// +internal abstract class DockerCompose : DockerContainer +{ + private static readonly IList StartCommandLineArgs = new[] { "docker", "compose", "up", "-d" }; + private static readonly IList StopCommandLineArgs = new[] { "docker", "compose", "down" }; + + /// + /// Initializes a new instance of the class. + /// + protected DockerCompose(IContainerConfiguration configuration, ILogger logger) : base(configuration, logger) + { + } + + /// + /// Gets the runtime configuration. + /// + public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; + + /// + /// Gets the command line to start the docker compose + /// + public IList StartCommandLine => BuildConfiguration.Combine(StartCommandLineArgs, + RuntimeConfiguration.Options).ToList(); + + /// + /// Gets the command line to stop the docker compose + /// + public IList StopCommandLine => StopCommandLineArgs; +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs index 3fdb85c20..cbefcc214 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -63,6 +63,17 @@ public DockerComposeBuilder WithLocalCompose(bool localCompose) (localCompose: localCompose)); } + /// + /// Adds options to the docker-compose command, e.g. docker-compose --compatibility. + /// + /// + /// + public DockerComposeBuilder WithOptions(params string[] options) { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration + (options: options)); + } + + /// protected override DockerComposeBuilder Init() { @@ -116,7 +127,8 @@ private async Task ConfigureDockerComposeAsync(IContainer container, Cancellatio await container.CopyAsync(fileInfo, ".", Unix.FileMode644, ct) .ConfigureAwait(false); } - await container.ExecAsync(new[] { "docker", "compose", "up", "-d" }, ct) + + await container.ExecAsync(dockerComposeContainer.StartCommandLine, ct) .ConfigureAwait(false); } } diff --git a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs index 1a163064c..da839bd97 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs @@ -9,12 +9,15 @@ public sealed class DockerComposeConfiguration : ContainerConfiguration /// /// The fully qualified path to the compose file. /// Whether the local compose will be used. + /// Options for the docker-compose command. public DockerComposeConfiguration( string composeFile = null, - bool localCompose = false) + bool localCompose = false, + IEnumerable options = null) { ComposeFile = composeFile; LocalCompose = localCompose; + Options = options ?? Array.Empty(); } /// @@ -68,4 +71,9 @@ public DockerComposeConfiguration(DockerComposeConfiguration oldValue, DockerCom /// Indicates whether local compose is enabled. /// public bool LocalCompose { get; } + + /// + /// Options for the docker-compose command + /// + public IEnumerable Options { get; } = Array.Empty(); } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs index d73dffed8..2401da265 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -1,7 +1,7 @@ namespace Testcontainers.DockerCompose; -[PublicAPI] -public sealed class DockerComposeLocal : DockerContainer +/// +internal sealed class DockerComposeLocal : DockerCompose { private readonly string _dockerBinary = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker.exe" : "docker"; @@ -14,17 +14,12 @@ public sealed class DockerComposeLocal : DockerContainer public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logger) : base(configuration, logger) { } - - /// - /// Gets the runtime configuration. - /// - public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; /// public override async Task StartAsync(CancellationToken ct = default) { await Cli.Wrap(_dockerBinary) - .WithArguments(new[] {"compose", "up", "-d"}) + .WithArguments(StartCommandLine.Skip(1)) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync(); } @@ -33,7 +28,7 @@ await Cli.Wrap(_dockerBinary) public override async Task StopAsync(CancellationToken ct = default) { await Cli.Wrap(_dockerBinary) - .WithArguments(new[] {"compose", "down"}) + .WithArguments(StopCommandLine.Skip(1)) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync(); } diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs index d2ca4e1b8..9976d0c3d 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs @@ -1,8 +1,7 @@ namespace Testcontainers.DockerCompose; -/// -[PublicAPI] -public class DockerComposeRemote : DockerContainer +/// +internal sealed class DockerComposeRemote : DockerCompose { /// /// Initializes a new instance of the class. @@ -13,16 +12,11 @@ public DockerComposeRemote(DockerComposeConfiguration configuration, ILogger log : base(configuration, logger) { } - - /// - /// Gets the runtime configuration. - /// - public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; /// public override async Task StopAsync(CancellationToken ct = default) { - await ExecAsync(new[] { "docker", "compose", "down"}, ct) + await ExecAsync(StopCommandLine, ct) .ConfigureAwait(false); await base.StopAsync(ct); } diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs index 4169a851a..f66cf845a 100644 --- a/src/Testcontainers.DockerCompose/Usings.cs +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -1,4 +1,7 @@ +global using System; +global using System.Collections.Generic; global using System.IO; +global using System.Linq; global using System.Threading; global using System.Threading.Tasks; global using Docker.DotNet.Models; diff --git a/tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs similarity index 56% rename from tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs rename to tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs index adfbee1b3..626509549 100644 --- a/tests/Testcontainers.DockerCompose.Tests/DockerComposeRemoteTest.cs +++ b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs @@ -1,12 +1,10 @@ -using JetBrains.Annotations; +namespace Testcontainers.DockerCompose; -namespace Testcontainers.DockerCompose; - -public abstract class DockerComposeRemoteTest : IAsyncLifetime +public abstract class DockerComposeTest : IAsyncLifetime { private readonly DockerComposeContainer _dockerComposeContainer; - protected DockerComposeRemoteTest(DockerComposeContainer dockerComposeContainer) + protected DockerComposeTest(DockerComposeContainer dockerComposeContainer) { _dockerComposeContainer = dockerComposeContainer; } @@ -30,7 +28,7 @@ public void ContainerStartedSuccessfully() } [UsedImplicitly] - public sealed class DockerComposeRemoteConfiguration : DockerComposeRemoteTest + public sealed class DockerComposeRemoteConfiguration : DockerComposeTest { public DockerComposeRemoteConfiguration() : base(new DockerComposeBuilder() @@ -41,7 +39,7 @@ public DockerComposeRemoteConfiguration() } [UsedImplicitly] - public sealed class DockerComposeLocalConfiguration : DockerComposeRemoteTest + public sealed class DockerComposeLocalConfiguration : DockerComposeTest { public DockerComposeLocalConfiguration() : base(new DockerComposeBuilder() @@ -51,5 +49,29 @@ public DockerComposeLocalConfiguration() { } } + + [UsedImplicitly] + public sealed class DockerComposeRemoteWithOptionConfiguration : DockerComposeTest + { + public DockerComposeRemoteWithOptionConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) + .WithOptions("--compatibility") + .Build()) + { + } + } + + [UsedImplicitly] + public sealed class DockerComposeLocalWithOptionConfiguration : DockerComposeTest + { + public DockerComposeLocalWithOptionConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) + .WithOptions("--compatibility") + .Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/Usings.cs b/tests/Testcontainers.DockerCompose.Tests/Usings.cs index 784a06819..8cac48500 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Usings.cs +++ b/tests/Testcontainers.DockerCompose.Tests/Usings.cs @@ -2,5 +2,5 @@ global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using DotNet.Testcontainers.Containers; -global using Testcontainers.DockerCompose; +global using JetBrains.Annotations; global using Xunit; \ No newline at end of file From d7f7807c1303300fd2a5d4e9108344c1214d66a8 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:24:37 +0300 Subject: [PATCH 05/11] Adding support for deleting images --- .../DockerCompose.cs | 51 +++++++++++++++---- .../DockerComposeBuilder.cs | 34 +++++++++---- .../DockerComposeConfiguration.cs | 13 ++++- .../DockerComposeLocal.cs | 10 ++-- .../DockerComposeRemote.cs | 2 +- .../RemoveImages.cs | 17 +++++++ .../DockerComposeTest.cs | 25 +++++++++ .../docker-compose-rmi.yaml | 7 +++ 8 files changed, 132 insertions(+), 27 deletions(-) create mode 100644 src/Testcontainers.DockerCompose/RemoveImages.cs create mode 100644 tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml diff --git a/src/Testcontainers.DockerCompose/DockerCompose.cs b/src/Testcontainers.DockerCompose/DockerCompose.cs index 741c30f9b..fe5019975 100644 --- a/src/Testcontainers.DockerCompose/DockerCompose.cs +++ b/src/Testcontainers.DockerCompose/DockerCompose.cs @@ -3,8 +3,13 @@ /// internal abstract class DockerCompose : DockerContainer { - private static readonly IList StartCommandLineArgs = new[] { "docker", "compose", "up", "-d" }; - private static readonly IList StopCommandLineArgs = new[] { "docker", "compose", "down" }; + private static readonly IList AppLineArgs = new[] { "docker", "compose"}; + + private static readonly IList StartCommandLineArgs = new[] { "up", "-d" }; + private static readonly IList StopCommandLineArgs = new[] { "down" }; + + private static readonly IList RemoveImagesArgs = new[] { "--rmi" }; + private static readonly IList FilesArgs = new[] { "-f" }; /// /// Initializes a new instance of the class. @@ -12,20 +17,48 @@ internal abstract class DockerCompose : DockerContainer protected DockerCompose(IContainerConfiguration configuration, ILogger logger) : base(configuration, logger) { } - + /// /// Gets the runtime configuration. /// public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; /// - /// Gets the command line to start the docker compose + /// Builds a command line to start the docker compose /// - public IList StartCommandLine => BuildConfiguration.Combine(StartCommandLineArgs, - RuntimeConfiguration.Options).ToList(); - + public IList BuildStartCommandLine() + { + return BuildConfiguration.Combine(BuildIncludeFileCommand(), StartCommandLineArgs).ToList(); + } + /// - /// Gets the command line to stop the docker compose + /// Builds a command line to stop the docker compose /// - public IList StopCommandLine => StopCommandLineArgs; + public IList BuildStopCommandLine() + { + var removeImagesArgs = RuntimeConfiguration.RemoveImages switch + { + RemoveImages.All => [RemoveImages.All.ToString().ToLower()], + RemoveImages.Local => [RemoveImages.Local.ToString().ToLower()], + _ => Array.Empty(), + }; + + var stopCommand = BuildConfiguration.Combine(BuildIncludeFileCommand(), + StopCommandLineArgs.AsEnumerable()); + + return removeImagesArgs.Length > 0 + ? BuildConfiguration.Combine( + BuildConfiguration.Combine( + stopCommand, RemoveImagesArgs.AsEnumerable()), + removeImagesArgs) + .ToList() + : stopCommand.ToList(); + } + + private IEnumerable BuildIncludeFileCommand() + { + return BuildConfiguration.Combine( + BuildConfiguration.Combine(AppLineArgs.AsEnumerable(), FilesArgs.AsEnumerable()), + new[] { Path.GetFileName(RuntimeConfiguration.ComposeFile) }); + } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs index cbefcc214..dfab5bcb8 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -45,7 +45,7 @@ public override DockerComposeContainer Build() /// Sets the compose file. /// /// The compose file. - /// + /// A configured instance of . public DockerComposeBuilder WithComposeFile(string composeFile) { return Merge(DockerResourceConfiguration, new DockerComposeConfiguration @@ -55,23 +55,33 @@ public DockerComposeBuilder WithComposeFile(string composeFile) /// /// If true use a local Docker Compose binary instead of a container. /// - /// - /// + /// Whether the local compose will be used. + /// A configured instance of . public DockerComposeBuilder WithLocalCompose(bool localCompose) { return Merge(DockerResourceConfiguration, new DockerComposeConfiguration - (localCompose: localCompose)); + (localCompose: localCompose)); } /// /// Adds options to the docker-compose command, e.g. docker-compose --compatibility. /// - /// - /// + /// Options for the docker-compose command. + /// A configured instance of . public DockerComposeBuilder WithOptions(params string[] options) { return Merge(DockerResourceConfiguration, new DockerComposeConfiguration (options: options)); } + + /// + /// Remove images after containers shutdown. + /// + /// + /// A configured instance of . + public DockerComposeBuilder WithRemoveImages(RemoveImages removeImages) { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration + (removeImages: removeImages)); + } /// @@ -122,13 +132,15 @@ private async Task ConfigureDockerComposeAsync(IContainer container, Cancellatio !dockerComposeContainer.RuntimeConfiguration.LocalCompose ) { var fileInfo = new FileInfo(dockerComposeContainer.RuntimeConfiguration.ComposeFile); - if (fileInfo.Exists) + if (!fileInfo.Exists) { - await container.CopyAsync(fileInfo, ".", Unix.FileMode644, ct) - .ConfigureAwait(false); + throw new FileNotFoundException(NoComposeFile, fileInfo.Name); } - - await container.ExecAsync(dockerComposeContainer.StartCommandLine, ct) + + await container.CopyAsync(fileInfo, ".", Unix.FileMode644, ct) + .ConfigureAwait(false); + + await container.ExecAsync(dockerComposeContainer.BuildStartCommandLine(), ct) .ConfigureAwait(false); } } diff --git a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs index da839bd97..df71b6bd3 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs @@ -10,14 +10,17 @@ public sealed class DockerComposeConfiguration : ContainerConfiguration /// The fully qualified path to the compose file. /// Whether the local compose will be used. /// Options for the docker-compose command. + /// Options for remove images. public DockerComposeConfiguration( string composeFile = null, bool localCompose = false, - IEnumerable options = null) + IEnumerable options = null, + RemoveImages removeImages = RemoveImages.None) { ComposeFile = composeFile; LocalCompose = localCompose; Options = options ?? Array.Empty(); + RemoveImages = removeImages; } /// @@ -60,6 +63,7 @@ public DockerComposeConfiguration(DockerComposeConfiguration oldValue, DockerCom { ComposeFile = BuildConfiguration.Combine(oldValue.ComposeFile, newValue.ComposeFile); LocalCompose = BuildConfiguration.Combine(oldValue.LocalCompose, newValue.LocalCompose); + RemoveImages = BuildConfiguration.Combine(oldValue.RemoveImages, newValue.RemoveImages); } /// @@ -73,7 +77,12 @@ public DockerComposeConfiguration(DockerComposeConfiguration oldValue, DockerCom public bool LocalCompose { get; } /// - /// Options for the docker-compose command + /// Options for the docker-compose command. /// public IEnumerable Options { get; } = Array.Empty(); + + /// + /// Options for remove images. + /// + public RemoveImages RemoveImages { get; } = RemoveImages.None; } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs index 2401da265..58cede26b 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -19,17 +19,19 @@ public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logg public override async Task StartAsync(CancellationToken ct = default) { await Cli.Wrap(_dockerBinary) - .WithArguments(StartCommandLine.Skip(1)) + .WithArguments(BuildStartCommandLine().Skip(1)) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) - .ExecuteBufferedAsync(); + .ExecuteBufferedAsync() + .ConfigureAwait(false); } /// public override async Task StopAsync(CancellationToken ct = default) { await Cli.Wrap(_dockerBinary) - .WithArguments(StopCommandLine.Skip(1)) + .WithArguments(BuildStopCommandLine().Skip(1)) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) - .ExecuteBufferedAsync(); + .ExecuteBufferedAsync() + .ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs index 9976d0c3d..1466be752 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs @@ -16,7 +16,7 @@ public DockerComposeRemote(DockerComposeConfiguration configuration, ILogger log /// public override async Task StopAsync(CancellationToken ct = default) { - await ExecAsync(StopCommandLine, ct) + await ExecAsync(BuildStopCommandLine(), ct) .ConfigureAwait(false); await base.StopAsync(ct); } diff --git a/src/Testcontainers.DockerCompose/RemoveImages.cs b/src/Testcontainers.DockerCompose/RemoveImages.cs new file mode 100644 index 000000000..28182891d --- /dev/null +++ b/src/Testcontainers.DockerCompose/RemoveImages.cs @@ -0,0 +1,17 @@ +namespace Testcontainers.DockerCompose; + +[PublicAPI] +public enum RemoveImages +{ + None = 0, + + /// + /// Remove all images. + /// + All, + + /// + /// Remove only images that don't have a custom tag set by the `image` field. + /// + Local, +} \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs index 626509549..9bb26751d 100644 --- a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs +++ b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs @@ -73,5 +73,30 @@ public DockerComposeLocalWithOptionConfiguration() { } } + + [UsedImplicitly] + public sealed class DockerComposeRemoteWithRemoveImagesConfiguration : DockerComposeTest + { + public DockerComposeRemoteWithRemoveImagesConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose-rmi.yaml")) + .WithRemoveImages(RemoveImages.All) + .Build()) + { + } + } + + [UsedImplicitly] + public sealed class DockerComposeLocalWithRemoveImagesConfiguration : DockerComposeTest + { + public DockerComposeLocalWithRemoveImagesConfiguration() + : base(new DockerComposeBuilder() + .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose-rmi.yaml")) + .WithRemoveImages(RemoveImages.All) + .WithLocalCompose(true) + .Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml b/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml new file mode 100644 index 000000000..4a832aa75 --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml @@ -0,0 +1,7 @@ +version: '3.9' + +name: test-alpine + +services: + nginx: + image: alpine \ No newline at end of file From aa0e7708cfc4790e123a89f0426f3536da67003d Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:57:32 +0300 Subject: [PATCH 06/11] Changes related to CPM support --- Directory.Packages.props | 1 + .../Testcontainers.DockerCompose.csproj | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index dd5655c7f..548a12482 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -58,5 +58,6 @@ + \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj index 894c19cd2..a61ef31b0 100644 --- a/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj +++ b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj @@ -4,9 +4,8 @@ latest - - - + + From 6f2f2727ed6dd3f302a97dc064de68418a59a425 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:58:19 +0300 Subject: [PATCH 07/11] fix build --- .../Testcontainers.DockerCompose.Tests.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj index 3afc73302..cd421ffe2 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj +++ b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj @@ -5,13 +5,13 @@ false - - - - + + + + - - + + From d09de626c999b57e1d928774e40ac36aec70e129 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:42:23 +0300 Subject: [PATCH 08/11] code review fixes --- .../DockerCompose.cs | 50 +------- .../DockerComposeBuilder.cs | 107 ++++++++---------- .../DockerComposeCommandLineBuilder.cs | 95 ++++++++++++++++ .../DockerComposeLocal.cs | 8 +- .../DockerComposeRemote.cs | 6 +- src/Testcontainers.DockerCompose/Usings.cs | 3 +- 6 files changed, 156 insertions(+), 113 deletions(-) create mode 100644 src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs diff --git a/src/Testcontainers.DockerCompose/DockerCompose.cs b/src/Testcontainers.DockerCompose/DockerCompose.cs index fe5019975..b483c26d8 100644 --- a/src/Testcontainers.DockerCompose/DockerCompose.cs +++ b/src/Testcontainers.DockerCompose/DockerCompose.cs @@ -3,62 +3,16 @@ /// internal abstract class DockerCompose : DockerContainer { - private static readonly IList AppLineArgs = new[] { "docker", "compose"}; - - private static readonly IList StartCommandLineArgs = new[] { "up", "-d" }; - private static readonly IList StopCommandLineArgs = new[] { "down" }; - - private static readonly IList RemoveImagesArgs = new[] { "--rmi" }; - private static readonly IList FilesArgs = new[] { "-f" }; - /// /// Initializes a new instance of the class. /// protected DockerCompose(IContainerConfiguration configuration, ILogger logger) : base(configuration, logger) { + RuntimeConfiguration = _configuration as DockerComposeConfiguration; } /// /// Gets the runtime configuration. /// - public DockerComposeConfiguration RuntimeConfiguration => _configuration as DockerComposeConfiguration; - - /// - /// Builds a command line to start the docker compose - /// - public IList BuildStartCommandLine() - { - return BuildConfiguration.Combine(BuildIncludeFileCommand(), StartCommandLineArgs).ToList(); - } - - /// - /// Builds a command line to stop the docker compose - /// - public IList BuildStopCommandLine() - { - var removeImagesArgs = RuntimeConfiguration.RemoveImages switch - { - RemoveImages.All => [RemoveImages.All.ToString().ToLower()], - RemoveImages.Local => [RemoveImages.Local.ToString().ToLower()], - _ => Array.Empty(), - }; - - var stopCommand = BuildConfiguration.Combine(BuildIncludeFileCommand(), - StopCommandLineArgs.AsEnumerable()); - - return removeImagesArgs.Length > 0 - ? BuildConfiguration.Combine( - BuildConfiguration.Combine( - stopCommand, RemoveImagesArgs.AsEnumerable()), - removeImagesArgs) - .ToList() - : stopCommand.ToList(); - } - - private IEnumerable BuildIncludeFileCommand() - { - return BuildConfiguration.Combine( - BuildConfiguration.Combine(AppLineArgs.AsEnumerable(), FilesArgs.AsEnumerable()), - new[] { Path.GetFileName(RuntimeConfiguration.ComposeFile) }); - } + protected DockerComposeConfiguration RuntimeConfiguration { get; } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs index dfab5bcb8..b1b99cc8a 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -2,17 +2,18 @@ namespace Testcontainers.DockerCompose; /// [PublicAPI] -public sealed class DockerComposeBuilder : ContainerBuilder +public sealed class + DockerComposeBuilder : ContainerBuilder { private const string NoComposeFile = "No docker compose file have been provided."; - + //Docker Compose is included as part of this image. - public const string DockerComposeImage = "docker:24-cli"; - - public const string DockerSocketPath = "/var/run/docker.sock"; - - /// - /// Initializes a new instance of the class. + private const string DockerComposeImage = "docker:24-cli"; + private const string DockerSocketPath = "/var/run/docker.sock"; + private const string DockerComposeStartCommand = "docker compose up"; + + /// + /// Initializes a new instance of the class. /// public DockerComposeBuilder() : this(new DockerComposeConfiguration()) @@ -21,7 +22,7 @@ public DockerComposeBuilder() } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The Docker resource configuration. private DockerComposeBuilder(DockerComposeConfiguration resourceConfiguration) @@ -32,28 +33,29 @@ private DockerComposeBuilder(DockerComposeConfiguration resourceConfiguration) /// protected override DockerComposeConfiguration DockerResourceConfiguration { get; } - + /// public override DockerComposeContainer Build() { Validate(); - + return new DockerComposeContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); } - + /// - /// Sets the compose file. + /// Sets the compose file. /// /// The compose file. /// A configured instance of . public DockerComposeBuilder WithComposeFile(string composeFile) { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration - (composeFile: composeFile)); + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(composeFile)) + .WithResourceMapping(new FileInfo(composeFile), + new FileInfo(DockerComposeCommandLineBuilder.DockerComposeFileName)); } - + /// - /// If true use a local Docker Compose binary instead of a container. + /// If true use a local Docker Compose binary instead of a container. /// /// Whether the local compose will be used. /// A configured instance of . @@ -62,38 +64,41 @@ public DockerComposeBuilder WithLocalCompose(bool localCompose) return Merge(DockerResourceConfiguration, new DockerComposeConfiguration (localCompose: localCompose)); } - + /// - /// Adds options to the docker-compose command, e.g. docker-compose --compatibility. + /// Adds options to the docker-compose command, e.g. docker-compose --compatibility. /// /// Options for the docker-compose command. /// A configured instance of . - public DockerComposeBuilder WithOptions(params string[] options) { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration - (options: options)); + public DockerComposeBuilder WithOptions(params string[] options) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(options: options)) + .WithCommand(options); } - + /// - /// Remove images after containers shutdown. + /// Remove images after containers shutdown. /// /// /// A configured instance of . - public DockerComposeBuilder WithRemoveImages(RemoveImages removeImages) { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration - (removeImages: removeImages)); + public DockerComposeBuilder WithRemoveImages(RemoveImages removeImages) + { + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(removeImages: removeImages)); } - - + /// protected override DockerComposeBuilder Init() { return base.Init() .WithImage(DockerComposeImage) - .WithEntrypoint(CommonCommands.SleepInfinity) - .WithBindMount(DockerSocketPath, DockerSocketPath, AccessMode.ReadWrite) - .WithStartupCallback(ConfigureDockerComposeAsync); + .WithEntrypoint(DockerComposeCommandLineBuilder.DockerAppName) + .WithCommand(DockerComposeCommandLineBuilder + .FromRemoteConfiguration(DockerResourceConfiguration) + .BuildStartCommand() + .ToArray()) + .WithBindMount(DockerSocketPath, DockerSocketPath, AccessMode.ReadOnly); } - + /// protected override void Validate() { @@ -101,10 +106,15 @@ protected override void Validate() _ = Guard.Argument(DockerResourceConfiguration.ComposeFile, nameof(DockerResourceConfiguration.ComposeFile)) .NotEmpty(); + + _ = Guard.Argument(DockerResourceConfiguration.ComposeFile, nameof(DockerResourceConfiguration.ComposeFile)) + .ThrowIf(argument => !File.Exists(argument.Value), + argument => new FileNotFoundException(NoComposeFile, argument.Name)); } - + /// - protected override DockerComposeBuilder Clone(IResourceConfiguration resourceConfiguration) + protected override DockerComposeBuilder Clone( + IResourceConfiguration resourceConfiguration) { return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(resourceConfiguration)); } @@ -116,32 +126,9 @@ protected override DockerComposeBuilder Clone(IContainerConfiguration resourceCo } /// - protected override DockerComposeBuilder Merge(DockerComposeConfiguration oldValue, DockerComposeConfiguration newValue) + protected override DockerComposeBuilder Merge(DockerComposeConfiguration oldValue, + DockerComposeConfiguration newValue) { return new DockerComposeBuilder(new DockerComposeConfiguration(oldValue, newValue)); } - - /// - /// Configures the compose container. - /// - /// The container. - /// Cancellation token. - private async Task ConfigureDockerComposeAsync(IContainer container, CancellationToken ct = default) - { - if (container is DockerComposeRemote dockerComposeContainer && - !dockerComposeContainer.RuntimeConfiguration.LocalCompose ) - { - var fileInfo = new FileInfo(dockerComposeContainer.RuntimeConfiguration.ComposeFile); - if (!fileInfo.Exists) - { - throw new FileNotFoundException(NoComposeFile, fileInfo.Name); - } - - await container.CopyAsync(fileInfo, ".", Unix.FileMode644, ct) - .ConfigureAwait(false); - - await container.ExecAsync(dockerComposeContainer.BuildStartCommandLine(), ct) - .ConfigureAwait(false); - } - } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs new file mode 100644 index 000000000..732fd2280 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs @@ -0,0 +1,95 @@ +namespace Testcontainers.DockerCompose; + +/// +/// Instance of the Docker Compose command line builder +/// +internal sealed class DockerComposeCommandLineBuilder +{ + public const string DockerComposeFileName = "/docker-compose.yaml"; + public const string DockerAppName = "docker"; + + private const string DockerComposeArgs = "compose"; + private const string StartCommandArgs = "up"; + private const string DetachCommandArgs = "-d"; + private const string FileCommandArgs = "-f"; + private const string StopCommandLineArgs = "down"; + private static readonly IList RemoveImagesArgs = new[] { "--rmi" }; + + private readonly DockerComposeConfiguration _configuration; + private readonly bool _local; + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + /// Indicates whether local compose is enabled. + private DockerComposeCommandLineBuilder(DockerComposeConfiguration configuration, bool local) + { + _configuration = configuration; + _local = local; + } + + /// + /// Builds a command line for remote compose from the configuration. + /// + /// The Docker resource configuration. + /// A configured instance of . + public static DockerComposeCommandLineBuilder FromRemoteConfiguration(DockerComposeConfiguration configuration) + { + return new DockerComposeCommandLineBuilder(configuration, false); + } + + /// + /// Builds a command line for local compose from the configuration. + /// + /// The Docker resource configuration. + /// A configured instance of . + public static DockerComposeCommandLineBuilder FromLocalConfiguration(DockerComposeConfiguration configuration) + { + return new DockerComposeCommandLineBuilder(configuration, true); + } + + /// + /// Builds a command line to start the docker compose + /// + public IList BuildStartCommand() + { + var stopCommand = _local ? new[] { DockerComposeArgs, FileCommandArgs, + Path.GetFileName(_configuration.ComposeFile), StartCommandArgs, DetachCommandArgs } + : new [] { DockerComposeArgs, StartCommandArgs }; + + if (!_local) + { + return stopCommand; + } + + if (_configuration.Options.Any()) + { + stopCommand = stopCommand.Concat(_configuration.Options).ToArray(); + } + + return stopCommand; + } + + /// + /// Builds a command line to stop the docker compose + /// + public IList BuildStopCommand() + { + var removeImagesArgs = _configuration.RemoveImages switch + { + RemoveImages.All => [RemoveImages.All.ToString().ToLower()], + RemoveImages.Local => [RemoveImages.Local.ToString().ToLower()], + _ => Array.Empty(), + }; + + var stopCommand = _local + ? new[] { DockerComposeArgs, StopCommandLineArgs } + : new[] { DockerAppName, DockerComposeArgs, FileCommandArgs, + Path.GetFileName(_configuration.ComposeFile), StopCommandLineArgs }; + + return removeImagesArgs.Length > 0 + ? stopCommand.Concat(RemoveImagesArgs).ToList() + : stopCommand.ToList(); + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs index 58cede26b..008202f17 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -18,8 +18,10 @@ public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logg /// public override async Task StartAsync(CancellationToken ct = default) { + var builder = DockerComposeCommandLineBuilder.FromLocalConfiguration(RuntimeConfiguration); + await Cli.Wrap(_dockerBinary) - .WithArguments(BuildStartCommandLine().Skip(1)) + .WithArguments(builder.BuildStartCommand()) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync() .ConfigureAwait(false); @@ -28,8 +30,10 @@ await Cli.Wrap(_dockerBinary) /// public override async Task StopAsync(CancellationToken ct = default) { + var builder = DockerComposeCommandLineBuilder.FromLocalConfiguration(RuntimeConfiguration); + await Cli.Wrap(_dockerBinary) - .WithArguments(BuildStopCommandLine().Skip(1)) + .WithArguments(builder.BuildStopCommand()) .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) .ExecuteBufferedAsync() .ConfigureAwait(false); diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs index 1466be752..6c246f303 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs @@ -16,7 +16,11 @@ public DockerComposeRemote(DockerComposeConfiguration configuration, ILogger log /// public override async Task StopAsync(CancellationToken ct = default) { - await ExecAsync(BuildStopCommandLine(), ct) + var command = DockerComposeCommandLineBuilder + .FromRemoteConfiguration(RuntimeConfiguration) + .BuildStopCommand(); + + await ExecAsync(command, ct) .ConfigureAwait(false); await base.StopAsync(ct); } diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs index f66cf845a..2f9a67b7b 100644 --- a/src/Testcontainers.DockerCompose/Usings.cs +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -13,5 +13,4 @@ global using Microsoft.Extensions.Logging; global using System.Runtime.InteropServices; global using CliWrap; -global using CliWrap.Buffered; -global using DotNet.Testcontainers.Commons; \ No newline at end of file +global using CliWrap.Buffered; \ No newline at end of file From 18db5a90684161e54e7391b53ef6f442a146ce2c Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:48:03 +0100 Subject: [PATCH 09/11] chore: Align with project standard configuration --- Directory.Packages.props | 2 +- Testcontainers.sln | 28 ++++++++-------- .../Testcontainers.DockerCompose.csproj | 7 ++-- src/Testcontainers.DockerCompose/Usings.cs | 8 ++--- .../Testcontainers.DockerCompose.Tests.csproj | 32 +++++++++---------- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f529439dd..f684b4ee2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,6 +29,7 @@ + @@ -59,6 +60,5 @@ - diff --git a/Testcontainers.sln b/Testcontainers.sln index 51b1d9ebe..7046c518c 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Couchbase", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.CouchDb", "src\Testcontainers.CouchDb\Testcontainers.CouchDb.csproj", "{DCECB1F6-D9AA-431F-AE42-25D56B9E7DFC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose", "src\Testcontainers.DockerCompose\Testcontainers.DockerCompose.csproj", "{5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb", "src\Testcontainers.DynamoDb\Testcontainers.DynamoDb.csproj", "{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch", "src\Testcontainers.Elasticsearch\Testcontainers.Elasticsearch.csproj", "{641DDEA5-B6E0-41E6-BA11-7A28C0913127}" @@ -125,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.CouchDb.Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Databases.Tests", "tests\Testcontainers.Databases.Tests\Testcontainers.Databases.Tests.csproj", "{DA54916E-1128-4200-B6AE-9F5BF02D832D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose.Tests", "tests\Testcontainers.DockerCompose.Tests\Testcontainers.DockerCompose.Tests.csproj", "{D77017F1-9E38-4B06-8CEB-9B3D98B6497C}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb.Tests", "tests\Testcontainers.DynamoDb.Tests\Testcontainers.DynamoDb.Tests.csproj", "{101515E6-74C1-40F9-85C8-871F742A378D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch.Tests", "tests\Testcontainers.Elasticsearch.Tests\Testcontainers.Elasticsearch.Tests.csproj", "{DD5B3678-468F-4D73-AECE-705E3D66CD43}" @@ -195,10 +199,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose", "src\Testcontainers.DockerCompose\Testcontainers.DockerCompose.csproj", "{5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DockerCompose.Tests", "tests\Testcontainers.DockerCompose.Tests\Testcontainers.DockerCompose.Tests.csproj", "{D77017F1-9E38-4B06-8CEB-9B3D98B6497C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -252,6 +252,10 @@ Global {DCECB1F6-D9AA-431F-AE42-25D56B9E7DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCECB1F6-D9AA-431F-AE42-25D56B9E7DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCECB1F6-D9AA-431F-AE42-25D56B9E7DFC}.Release|Any CPU.Build.0 = Release|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.Build.0 = Release|Any CPU {2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -432,6 +436,10 @@ Global {DA54916E-1128-4200-B6AE-9F5BF02D832D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA54916E-1128-4200-B6AE-9F5BF02D832D}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA54916E-1128-4200-B6AE-9F5BF02D832D}.Release|Any CPU.Build.0 = Release|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.Build.0 = Release|Any CPU {101515E6-74C1-40F9-85C8-871F742A378D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {101515E6-74C1-40F9-85C8-871F742A378D}.Debug|Any CPU.Build.0 = Debug|Any CPU {101515E6-74C1-40F9-85C8-871F742A378D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -572,14 +580,6 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU - {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F}.Release|Any CPU.Build.0 = Release|Any CPU - {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D77017F1-9E38-4B06-8CEB-9B3D98B6497C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -593,6 +593,7 @@ Global {A724806F-8C94-4438-8011-04A9A1575318} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {58E94721-2681-4D82-8D94-0B2F9DB0D575} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {DCECB1F6-D9AA-431F-AE42-25D56B9E7DFC} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {641DDEA5-B6E0-41E6-BA11-7A28C0913127} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {84D707E0-C9FA-4327-85DC-0AFEBEA73572} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -638,6 +639,7 @@ Global {809322BA-D690-4F2B-B884-23F895663963} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {E4520FB1-4466-4DCA-AD08-4075102C68D3} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {DA54916E-1128-4200-B6AE-9F5BF02D832D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {D77017F1-9E38-4B06-8CEB-9B3D98B6497C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {101515E6-74C1-40F9-85C8-871F742A378D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {DD5B3678-468F-4D73-AECE-705E3D66CD43} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} @@ -673,7 +675,5 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} - {5CF21353-F3DB-4993-B9D8-DFAA8B2C4D2F} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} - {D77017F1-9E38-4B06-8CEB-9B3D98B6497C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj index a61ef31b0..4dea72a7c 100644 --- a/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj +++ b/src/Testcontainers.DockerCompose/Testcontainers.DockerCompose.csproj @@ -1,14 +1,13 @@ - netstandard2.0;netstandard2.1 + net6.0;net8.0;netstandard2.0;netstandard2.1 latest - + - - + \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs index 2f9a67b7b..1d8544f8d 100644 --- a/src/Testcontainers.DockerCompose/Usings.cs +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -2,15 +2,15 @@ global using System.Collections.Generic; global using System.IO; global using System.Linq; +global using System.Runtime.InteropServices; global using System.Threading; global using System.Threading.Tasks; +global using CliWrap; +global using CliWrap.Buffered; global using Docker.DotNet.Models; global using DotNet.Testcontainers; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Configurations; global using DotNet.Testcontainers.Containers; global using JetBrains.Annotations; -global using Microsoft.Extensions.Logging; -global using System.Runtime.InteropServices; -global using CliWrap; -global using CliWrap.Buffered; \ No newline at end of file +global using Microsoft.Extensions.Logging; \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj index cd421ffe2..02518599c 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj +++ b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj @@ -1,17 +1,17 @@  - - net8.0 - false - false - - - - - - - - - - - - + + net8.0 + false + false + + + + + + + + + + + + \ No newline at end of file From 1eead1946645da8b983a99b8ae826def46395b66 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:49:41 +0100 Subject: [PATCH 10/11] chore: Remove BOM from UTF-8 files --- src/Testcontainers.DockerCompose/DockerCompose.cs | 2 +- .../DockerComposeCommandLineBuilder.cs | 2 +- src/Testcontainers.DockerCompose/DockerComposeContainer.cs | 2 +- src/Testcontainers.DockerCompose/DockerComposeLocal.cs | 2 +- src/Testcontainers.DockerCompose/RemoveImages.cs | 2 +- tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs | 2 +- .../Testcontainers.DockerCompose.Tests.csproj | 2 +- .../Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Testcontainers.DockerCompose/DockerCompose.cs b/src/Testcontainers.DockerCompose/DockerCompose.cs index b483c26d8..24ab4452d 100644 --- a/src/Testcontainers.DockerCompose/DockerCompose.cs +++ b/src/Testcontainers.DockerCompose/DockerCompose.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; /// internal abstract class DockerCompose : DockerContainer diff --git a/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs index 732fd2280..a002e7d6d 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; /// /// Instance of the Docker Compose command line builder diff --git a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs index d1fb2a0c5..6f8d1984d 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; [PublicAPI] public class DockerComposeContainer : DockerContainer diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs index 008202f17..601275188 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; /// internal sealed class DockerComposeLocal : DockerCompose diff --git a/src/Testcontainers.DockerCompose/RemoveImages.cs b/src/Testcontainers.DockerCompose/RemoveImages.cs index 28182891d..a029716f5 100644 --- a/src/Testcontainers.DockerCompose/RemoveImages.cs +++ b/src/Testcontainers.DockerCompose/RemoveImages.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; [PublicAPI] public enum RemoveImages diff --git a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs index 9bb26751d..d1f80a4a6 100644 --- a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs +++ b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; public abstract class DockerComposeTest : IAsyncLifetime { diff --git a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj index 02518599c..cb437cbce 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj +++ b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 false diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml b/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml index 4a832aa75..e8856e029 100644 --- a/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml +++ b/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml @@ -1,4 +1,4 @@ -version: '3.9' +version: '3.9' name: test-alpine From 90f6ccbada5e6d92b92848fddab7fd85c272b047 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:37:03 +0100 Subject: [PATCH 11/11] refactor: Reuse the the builder configuration in local mode --- .../DockerCompose.cs | 18 ---- .../DockerComposeBuilder.cs | 100 +++++++----------- .../DockerComposeCommandLineBuilder.cs | 95 ----------------- .../DockerComposeConfiguration.cs | 43 +++----- .../DockerComposeContainer.cs | 31 ++---- .../DockerComposeLocal.cs | 41 ------- .../DockerComposeLocalContainer.cs | 42 ++++++++ .../DockerComposeMode.cs | 18 ++++ .../DockerComposeRemote.cs | 27 ----- .../DockerComposeRemoteContainer.cs | 15 +++ .../RemoveImages.cs | 17 --- src/Testcontainers.DockerCompose/Usings.cs | 2 - .../Containers/DockerContainer.cs | 4 +- .../DockerComposeTest.cs | 88 ++++----------- .../Testcontainers.DockerCompose.Tests.csproj | 7 +- .../Usings.cs | 1 - .../docker-compose-rmi.yaml | 7 -- .../docker-compose.yaml | 9 -- .../docker-compose.yml | 9 ++ 19 files changed, 173 insertions(+), 401 deletions(-) delete mode 100644 src/Testcontainers.DockerCompose/DockerCompose.cs delete mode 100644 src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs delete mode 100644 src/Testcontainers.DockerCompose/DockerComposeLocal.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeLocalContainer.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeMode.cs delete mode 100644 src/Testcontainers.DockerCompose/DockerComposeRemote.cs create mode 100644 src/Testcontainers.DockerCompose/DockerComposeRemoteContainer.cs delete mode 100644 src/Testcontainers.DockerCompose/RemoveImages.cs delete mode 100644 tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml delete mode 100644 tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml create mode 100644 tests/Testcontainers.DockerCompose.Tests/docker-compose.yml diff --git a/src/Testcontainers.DockerCompose/DockerCompose.cs b/src/Testcontainers.DockerCompose/DockerCompose.cs deleted file mode 100644 index 24ab4452d..000000000 --- a/src/Testcontainers.DockerCompose/DockerCompose.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Testcontainers.DockerCompose; - -/// -internal abstract class DockerCompose : DockerContainer -{ - /// - /// Initializes a new instance of the class. - /// - protected DockerCompose(IContainerConfiguration configuration, ILogger logger) : base(configuration, logger) - { - RuntimeConfiguration = _configuration as DockerComposeConfiguration; - } - - /// - /// Gets the runtime configuration. - /// - protected DockerComposeConfiguration RuntimeConfiguration { get; } -} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs index b1b99cc8a..9e9c930a8 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeBuilder.cs @@ -2,18 +2,17 @@ namespace Testcontainers.DockerCompose; /// [PublicAPI] -public sealed class - DockerComposeBuilder : ContainerBuilder +public sealed class DockerComposeBuilder : ContainerBuilder { - private const string NoComposeFile = "No docker compose file have been provided."; + public const string DockerComposeFilePath = "/docker-compose.yml"; - //Docker Compose is included as part of this image. - private const string DockerComposeImage = "docker:24-cli"; + public const string DockerComposeImage = "docker:25.0-cli"; + + // TODO: This does not support all container runtimes (host configurations). We should do something similar to what we are doing in the Resource Reaper implementation. private const string DockerSocketPath = "/var/run/docker.sock"; - private const string DockerComposeStartCommand = "docker compose up"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DockerComposeBuilder() : this(new DockerComposeConfiguration()) @@ -22,7 +21,7 @@ public DockerComposeBuilder() } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The Docker resource configuration. private DockerComposeBuilder(DockerComposeConfiguration resourceConfiguration) @@ -34,68 +33,52 @@ private DockerComposeBuilder(DockerComposeConfiguration resourceConfiguration) /// protected override DockerComposeConfiguration DockerResourceConfiguration { get; } - /// - public override DockerComposeContainer Build() - { - Validate(); - - return new DockerComposeContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); - } - /// - /// Sets the compose file. + /// Sets the Docker Compose file. /// - /// The compose file. + /// The Docker Compose file path. /// A configured instance of . - public DockerComposeBuilder WithComposeFile(string composeFile) + public DockerComposeBuilder WithComposeFile(string composeFilePath) { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(composeFile)) - .WithResourceMapping(new FileInfo(composeFile), - new FileInfo(DockerComposeCommandLineBuilder.DockerComposeFileName)); + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(composeFilePath: composeFilePath)) + .WithEntrypoint("docker", "compose", "--project-name", Guid.NewGuid().ToString("D"), "--file", composeFilePath) + .WithResourceMapping(new FileInfo(composeFilePath), new FileInfo(DockerComposeFilePath)); } /// - /// If true use a local Docker Compose binary instead of a container. + /// Sets the Docker Compose mode. /// - /// Whether the local compose will be used. + /// The Docker Compose mode. /// A configured instance of . - public DockerComposeBuilder WithLocalCompose(bool localCompose) + public DockerComposeBuilder WithComposeMode(DockerComposeMode mode) { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration - (localCompose: localCompose)); + return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(mode: mode)); } - /// - /// Adds options to the docker-compose command, e.g. docker-compose --compatibility. - /// - /// Options for the docker-compose command. - /// A configured instance of . - public DockerComposeBuilder WithOptions(params string[] options) + /// + public override DockerComposeContainer Build() { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(options: options)) - .WithCommand(options); - } + Validate(); - /// - /// Remove images after containers shutdown. - /// - /// - /// A configured instance of . - public DockerComposeBuilder WithRemoveImages(RemoveImages removeImages) - { - return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(removeImages: removeImages)); + switch (DockerResourceConfiguration.Mode) + { + case DockerComposeMode.Local: + return new DockerComposeLocalContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + case DockerComposeMode.Remote: + return new DockerComposeRemoteContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + default: + throw new ArgumentOutOfRangeException(nameof(DockerResourceConfiguration.Mode), "Docker Compose mode not supported."); + } } - + /// protected override DockerComposeBuilder Init() { return base.Init() .WithImage(DockerComposeImage) - .WithEntrypoint(DockerComposeCommandLineBuilder.DockerAppName) - .WithCommand(DockerComposeCommandLineBuilder - .FromRemoteConfiguration(DockerResourceConfiguration) - .BuildStartCommand() - .ToArray()) + .WithCommand("up") + .WithCommand("--detach") + .WithComposeMode(DockerComposeMode.Remote) .WithBindMount(DockerSocketPath, DockerSocketPath, AccessMode.ReadOnly); } @@ -104,17 +87,15 @@ protected override void Validate() { base.Validate(); - _ = Guard.Argument(DockerResourceConfiguration.ComposeFile, nameof(DockerResourceConfiguration.ComposeFile)) - .NotEmpty(); - - _ = Guard.Argument(DockerResourceConfiguration.ComposeFile, nameof(DockerResourceConfiguration.ComposeFile)) - .ThrowIf(argument => !File.Exists(argument.Value), - argument => new FileNotFoundException(NoComposeFile, argument.Name)); + const string dockerComposeFileNotFound = "Docker Compose file not found."; + _ = Guard.Argument(DockerResourceConfiguration.ComposeFilePath, nameof(DockerComposeConfiguration.ComposeFilePath)) + .NotNull() + .NotEmpty() + .ThrowIf(argument => !File.Exists(argument.Value), argument => new FileNotFoundException(dockerComposeFileNotFound, argument.Value)); } /// - protected override DockerComposeBuilder Clone( - IResourceConfiguration resourceConfiguration) + protected override DockerComposeBuilder Clone(IResourceConfiguration resourceConfiguration) { return Merge(DockerResourceConfiguration, new DockerComposeConfiguration(resourceConfiguration)); } @@ -126,8 +107,7 @@ protected override DockerComposeBuilder Clone(IContainerConfiguration resourceCo } /// - protected override DockerComposeBuilder Merge(DockerComposeConfiguration oldValue, - DockerComposeConfiguration newValue) + protected override DockerComposeBuilder Merge(DockerComposeConfiguration oldValue, DockerComposeConfiguration newValue) { return new DockerComposeBuilder(new DockerComposeConfiguration(oldValue, newValue)); } diff --git a/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs b/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs deleted file mode 100644 index a002e7d6d..000000000 --- a/src/Testcontainers.DockerCompose/DockerComposeCommandLineBuilder.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace Testcontainers.DockerCompose; - -/// -/// Instance of the Docker Compose command line builder -/// -internal sealed class DockerComposeCommandLineBuilder -{ - public const string DockerComposeFileName = "/docker-compose.yaml"; - public const string DockerAppName = "docker"; - - private const string DockerComposeArgs = "compose"; - private const string StartCommandArgs = "up"; - private const string DetachCommandArgs = "-d"; - private const string FileCommandArgs = "-f"; - private const string StopCommandLineArgs = "down"; - private static readonly IList RemoveImagesArgs = new[] { "--rmi" }; - - private readonly DockerComposeConfiguration _configuration; - private readonly bool _local; - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - /// Indicates whether local compose is enabled. - private DockerComposeCommandLineBuilder(DockerComposeConfiguration configuration, bool local) - { - _configuration = configuration; - _local = local; - } - - /// - /// Builds a command line for remote compose from the configuration. - /// - /// The Docker resource configuration. - /// A configured instance of . - public static DockerComposeCommandLineBuilder FromRemoteConfiguration(DockerComposeConfiguration configuration) - { - return new DockerComposeCommandLineBuilder(configuration, false); - } - - /// - /// Builds a command line for local compose from the configuration. - /// - /// The Docker resource configuration. - /// A configured instance of . - public static DockerComposeCommandLineBuilder FromLocalConfiguration(DockerComposeConfiguration configuration) - { - return new DockerComposeCommandLineBuilder(configuration, true); - } - - /// - /// Builds a command line to start the docker compose - /// - public IList BuildStartCommand() - { - var stopCommand = _local ? new[] { DockerComposeArgs, FileCommandArgs, - Path.GetFileName(_configuration.ComposeFile), StartCommandArgs, DetachCommandArgs } - : new [] { DockerComposeArgs, StartCommandArgs }; - - if (!_local) - { - return stopCommand; - } - - if (_configuration.Options.Any()) - { - stopCommand = stopCommand.Concat(_configuration.Options).ToArray(); - } - - return stopCommand; - } - - /// - /// Builds a command line to stop the docker compose - /// - public IList BuildStopCommand() - { - var removeImagesArgs = _configuration.RemoveImages switch - { - RemoveImages.All => [RemoveImages.All.ToString().ToLower()], - RemoveImages.Local => [RemoveImages.Local.ToString().ToLower()], - _ => Array.Empty(), - }; - - var stopCommand = _local - ? new[] { DockerComposeArgs, StopCommandLineArgs } - : new[] { DockerAppName, DockerComposeArgs, FileCommandArgs, - Path.GetFileName(_configuration.ComposeFile), StopCommandLineArgs }; - - return removeImagesArgs.Length > 0 - ? stopCommand.Concat(RemoveImagesArgs).ToList() - : stopCommand.ToList(); - } -} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs index df71b6bd3..0771ccd40 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeConfiguration.cs @@ -7,20 +7,14 @@ public sealed class DockerComposeConfiguration : ContainerConfiguration /// /// Initializes a new instance of the class. /// - /// The fully qualified path to the compose file. - /// Whether the local compose will be used. - /// Options for the docker-compose command. - /// Options for remove images. + /// The Docker Compose file path. + /// The Docker Compose mode. public DockerComposeConfiguration( - string composeFile = null, - bool localCompose = false, - IEnumerable options = null, - RemoveImages removeImages = RemoveImages.None) + string composeFilePath = null, + DockerComposeMode? mode = null) { - ComposeFile = composeFile; - LocalCompose = localCompose; - Options = options ?? Array.Empty(); - RemoveImages = removeImages; + ComposeFilePath = composeFilePath; + Mode = mode; } /// @@ -61,28 +55,17 @@ public DockerComposeConfiguration(DockerComposeConfiguration resourceConfigurati public DockerComposeConfiguration(DockerComposeConfiguration oldValue, DockerComposeConfiguration newValue) : base(oldValue, newValue) { - ComposeFile = BuildConfiguration.Combine(oldValue.ComposeFile, newValue.ComposeFile); - LocalCompose = BuildConfiguration.Combine(oldValue.LocalCompose, newValue.LocalCompose); - RemoveImages = BuildConfiguration.Combine(oldValue.RemoveImages, newValue.RemoveImages); + ComposeFilePath = BuildConfiguration.Combine(oldValue.ComposeFilePath, newValue.ComposeFilePath); + Mode = BuildConfiguration.Combine(oldValue.Mode, newValue.Mode); } - - /// - /// Gets the path to the compose file. - /// - public string ComposeFile { get; } - - /// - /// Indicates whether local compose is enabled. - /// - public bool LocalCompose { get; } /// - /// Options for the docker-compose command. + /// Gets the Docker Compose file path. /// - public IEnumerable Options { get; } = Array.Empty(); - + public string ComposeFilePath { get; } + /// - /// Options for remove images. + /// Gets the Docker Compose mode. /// - public RemoveImages RemoveImages { get; } = RemoveImages.None; + public DockerComposeMode? Mode { get; } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs index 6f8d1984d..1ddc29367 100644 --- a/src/Testcontainers.DockerCompose/DockerComposeContainer.cs +++ b/src/Testcontainers.DockerCompose/DockerComposeContainer.cs @@ -1,31 +1,16 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; +/// [PublicAPI] -public class DockerComposeContainer : DockerContainer +public abstract class DockerComposeContainer : DockerContainer { - private readonly IContainer _proxyContainer; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// - public DockerComposeContainer(DockerComposeConfiguration configuration, ILogger logger) : base(configuration, logger) + /// The container configuration. + /// The logger. + protected DockerComposeContainer(DockerComposeConfiguration configuration, ILogger logger) + : base(configuration, logger) { - _proxyContainer = configuration.LocalCompose - ? new DockerComposeLocal(configuration, logger) - : new DockerComposeRemote(configuration, logger); - } - - /// - public override async Task StartAsync(CancellationToken ct = default) - { - await _proxyContainer.StartAsync(ct); - } - - /// - public override async Task StopAsync(CancellationToken ct = default) - { - await _proxyContainer.StopAsync(ct); } } \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs b/src/Testcontainers.DockerCompose/DockerComposeLocal.cs deleted file mode 100644 index 601275188..000000000 --- a/src/Testcontainers.DockerCompose/DockerComposeLocal.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Testcontainers.DockerCompose; - -/// -internal sealed class DockerComposeLocal : DockerCompose -{ - private readonly string _dockerBinary = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker.exe" : "docker"; - - /// - /// Initializes a new instance of the class. - /// - /// The container configuration. - /// The logger. - public DockerComposeLocal(DockerComposeConfiguration configuration, ILogger logger) : base(configuration, logger) - { - } - - /// - public override async Task StartAsync(CancellationToken ct = default) - { - var builder = DockerComposeCommandLineBuilder.FromLocalConfiguration(RuntimeConfiguration); - - await Cli.Wrap(_dockerBinary) - .WithArguments(builder.BuildStartCommand()) - .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) - .ExecuteBufferedAsync() - .ConfigureAwait(false); - } - - /// - public override async Task StopAsync(CancellationToken ct = default) - { - var builder = DockerComposeCommandLineBuilder.FromLocalConfiguration(RuntimeConfiguration); - - await Cli.Wrap(_dockerBinary) - .WithArguments(builder.BuildStopCommand()) - .WithWorkingDirectory(Path.GetDirectoryName(RuntimeConfiguration.ComposeFile)!) - .ExecuteBufferedAsync() - .ConfigureAwait(false); - } -} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeLocalContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeLocalContainer.cs new file mode 100644 index 000000000..083fff8f5 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeLocalContainer.cs @@ -0,0 +1,42 @@ +namespace Testcontainers.DockerCompose; + +/// +internal sealed class DockerComposeLocalContainer : DockerComposeContainer +{ + private readonly DockerComposeConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + public DockerComposeLocalContainer(DockerComposeConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + _configuration = configuration; + } + + /// + public override Task StartAsync(CancellationToken ct = default) + { + var workingDirectoryPath = Path.GetDirectoryName(_configuration.ComposeFilePath); + + return Cli.Wrap(_configuration.Entrypoint.First()) + .WithArguments(_configuration.Entrypoint.Skip(1).Concat(_configuration.Command)) + .WithWorkingDirectory(workingDirectoryPath) + .WithValidation(CommandResultValidation.None) + .ExecuteBufferedAsync(ct); + } + + /// + public override Task StopAsync(CancellationToken ct = default) + { + var workingDirectoryPath = Path.GetDirectoryName(_configuration.ComposeFilePath); + + return Cli.Wrap(_configuration.Entrypoint.First()) + .WithArguments(_configuration.Entrypoint.Skip(1).Concat(new[] { "down" })) + .WithWorkingDirectory(workingDirectoryPath) + .WithValidation(CommandResultValidation.None) + .ExecuteBufferedAsync(ct); + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeMode.cs b/src/Testcontainers.DockerCompose/DockerComposeMode.cs new file mode 100644 index 000000000..66bceb2a3 --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeMode.cs @@ -0,0 +1,18 @@ +namespace Testcontainers.DockerCompose +{ + /// + /// Docker Compose mode. + /// + public enum DockerComposeMode + { + /// + /// The local Docker Compose mode utilizes the Docker Compose CLI to execute the configuration. + /// + Local, + + /// + /// The remote Docker Compose mode utilizes a sidecar container to execute the configuration. + /// + Remote, + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs b/src/Testcontainers.DockerCompose/DockerComposeRemote.cs deleted file mode 100644 index 6c246f303..000000000 --- a/src/Testcontainers.DockerCompose/DockerComposeRemote.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Testcontainers.DockerCompose; - -/// -internal sealed class DockerComposeRemote : DockerCompose -{ - /// - /// Initializes a new instance of the class. - /// - /// The container configuration. - /// The logger. - public DockerComposeRemote(DockerComposeConfiguration configuration, ILogger logger) - : base(configuration, logger) - { - } - - /// - public override async Task StopAsync(CancellationToken ct = default) - { - var command = DockerComposeCommandLineBuilder - .FromRemoteConfiguration(RuntimeConfiguration) - .BuildStopCommand(); - - await ExecAsync(command, ct) - .ConfigureAwait(false); - await base.StopAsync(ct); - } -} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/DockerComposeRemoteContainer.cs b/src/Testcontainers.DockerCompose/DockerComposeRemoteContainer.cs new file mode 100644 index 000000000..8f9b4827a --- /dev/null +++ b/src/Testcontainers.DockerCompose/DockerComposeRemoteContainer.cs @@ -0,0 +1,15 @@ +namespace Testcontainers.DockerCompose; + +/// +internal sealed class DockerComposeRemoteContainer : DockerComposeContainer +{ + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + public DockerComposeRemoteContainer(DockerComposeConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + } +} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/RemoveImages.cs b/src/Testcontainers.DockerCompose/RemoveImages.cs deleted file mode 100644 index a029716f5..000000000 --- a/src/Testcontainers.DockerCompose/RemoveImages.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Testcontainers.DockerCompose; - -[PublicAPI] -public enum RemoveImages -{ - None = 0, - - /// - /// Remove all images. - /// - All, - - /// - /// Remove only images that don't have a custom tag set by the `image` field. - /// - Local, -} \ No newline at end of file diff --git a/src/Testcontainers.DockerCompose/Usings.cs b/src/Testcontainers.DockerCompose/Usings.cs index 1d8544f8d..5090695fb 100644 --- a/src/Testcontainers.DockerCompose/Usings.cs +++ b/src/Testcontainers.DockerCompose/Usings.cs @@ -1,8 +1,6 @@ global using System; -global using System.Collections.Generic; global using System.IO; global using System.Linq; -global using System.Runtime.InteropServices; global using System.Threading; global using System.Threading.Tasks; global using CliWrap; diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index f2c812c19..3afaffc82 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -24,9 +24,9 @@ public class DockerContainer : Resource, IContainer private readonly ITestcontainersClient _client; - protected ContainerInspectResponse _container = new ContainerInspectResponse(); + private readonly IContainerConfiguration _configuration; - protected readonly IContainerConfiguration _configuration; + private ContainerInspectResponse _container = new ContainerInspectResponse(); /// /// Initializes a new instance of the class. diff --git a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs index d1f80a4a6..8f4c12b97 100644 --- a/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs +++ b/tests/Testcontainers.DockerCompose.Tests/DockerComposeTest.cs @@ -1,102 +1,54 @@ -namespace Testcontainers.DockerCompose; +namespace Testcontainers.DockerCompose; -public abstract class DockerComposeTest : IAsyncLifetime +public abstract class DockerComposeContainerTest : IAsyncLifetime { private readonly DockerComposeContainer _dockerComposeContainer; - protected DockerComposeTest(DockerComposeContainer dockerComposeContainer) + private DockerComposeContainerTest(DockerComposeContainer dockerComposeContainer) { _dockerComposeContainer = dockerComposeContainer; } - + public Task InitializeAsync() { return _dockerComposeContainer.StartAsync(); } - - public async Task DisposeAsync() + + public Task DisposeAsync() { - await _dockerComposeContainer.StopAsync(); - await _dockerComposeContainer.DisposeAsync().AsTask(); + return Task.CompletedTask; + return _dockerComposeContainer.DisposeAsync().AsTask(); } - + [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ContainerStartedSuccessfully() { + // TODO: How do we test that the Compose configuration is actually working? How do we access services? Assert.Equal(TestcontainersHealthStatus.Healthy, TestcontainersHealthStatus.Healthy); } - - [UsedImplicitly] - public sealed class DockerComposeRemoteConfiguration : DockerComposeTest - { - public DockerComposeRemoteConfiguration() - : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) - .Build()) - { - } - } - + [UsedImplicitly] - public sealed class DockerComposeLocalConfiguration : DockerComposeTest + public sealed class DockerComposeLocalConfiguration : DockerComposeContainerTest { public DockerComposeLocalConfiguration() : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) - .WithLocalCompose(true) + .WithComposeFile("docker-compose.yml") + .WithComposeMode(DockerComposeMode.Local) .Build()) { } } - - [UsedImplicitly] - public sealed class DockerComposeRemoteWithOptionConfiguration : DockerComposeTest - { - public DockerComposeRemoteWithOptionConfiguration() - : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) - .WithOptions("--compatibility") - .Build()) - { - } - } - - [UsedImplicitly] - public sealed class DockerComposeLocalWithOptionConfiguration : DockerComposeTest - { - public DockerComposeLocalWithOptionConfiguration() - : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose.yaml")) - .WithOptions("--compatibility") - .Build()) - { - } - } - - [UsedImplicitly] - public sealed class DockerComposeRemoteWithRemoveImagesConfiguration : DockerComposeTest - { - public DockerComposeRemoteWithRemoveImagesConfiguration() - : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose-rmi.yaml")) - .WithRemoveImages(RemoveImages.All) - .Build()) - { - } - } - + [UsedImplicitly] - public sealed class DockerComposeLocalWithRemoveImagesConfiguration : DockerComposeTest + public sealed class DockerComposeRemoteConfiguration : DockerComposeContainerTest { - public DockerComposeLocalWithRemoveImagesConfiguration() + public DockerComposeRemoteConfiguration() : base(new DockerComposeBuilder() - .WithComposeFile(Path.Combine(Directory.GetCurrentDirectory(), @"./../../../docker-compose-rmi.yaml")) - .WithRemoveImages(RemoveImages.All) - .WithLocalCompose(true) + .WithComposeFile("docker-compose.yml") + .WithComposeMode(DockerComposeMode.Remote) .Build()) { } } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj index cb437cbce..4c2e731a4 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj +++ b/tests/Testcontainers.DockerCompose.Tests/Testcontainers.DockerCompose.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 false @@ -14,4 +14,9 @@ + + + PreserveNewest + + \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/Usings.cs b/tests/Testcontainers.DockerCompose.Tests/Usings.cs index 8cac48500..aa9188c68 100644 --- a/tests/Testcontainers.DockerCompose.Tests/Usings.cs +++ b/tests/Testcontainers.DockerCompose.Tests/Usings.cs @@ -1,4 +1,3 @@ -global using System.IO; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using DotNet.Testcontainers.Containers; diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml b/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml deleted file mode 100644 index e8856e029..000000000 --- a/tests/Testcontainers.DockerCompose.Tests/docker-compose-rmi.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: '3.9' - -name: test-alpine - -services: - nginx: - image: alpine \ No newline at end of file diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml b/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml deleted file mode 100644 index d7effad58..000000000 --- a/tests/Testcontainers.DockerCompose.Tests/docker-compose.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3.9' - -name: test-nginx - -services: - nginx: - image: nginx - ports: - - "8080:80" diff --git a/tests/Testcontainers.DockerCompose.Tests/docker-compose.yml b/tests/Testcontainers.DockerCompose.Tests/docker-compose.yml new file mode 100644 index 000000000..37999d25d --- /dev/null +++ b/tests/Testcontainers.DockerCompose.Tests/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.9' +services: + test: + image: alpine:3.17 + entrypoint: + - /bin/sh + - -c + command: + - 'trap : TERM INT; sleep infinity & wait' \ No newline at end of file