diff --git a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs index f341070df..92f4e7cb8 100644 --- a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs +++ b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs @@ -25,7 +25,7 @@ /// /// [PublicAPI] - public class ImageFromDockerfileBuilder : AbstractBuilder, IImageFromDockerfileBuilder + public class ImageFromDockerfileBuilder : AbstractBuilder, IImageFromDockerfileBuilder { /// /// Initializes a new instance of the class. @@ -112,7 +112,7 @@ protected sealed override ImageFromDockerfileBuilder Init() } /// - protected override ImageFromDockerfileBuilder Clone(IResourceConfiguration resourceConfiguration) + protected override ImageFromDockerfileBuilder Clone(IResourceConfiguration resourceConfiguration) { return this.Merge(this.DockerResourceConfiguration, new ImageFromDockerfileConfiguration(resourceConfiguration)); } diff --git a/src/Testcontainers/Clients/DockerImageOperations.cs b/src/Testcontainers/Clients/DockerImageOperations.cs index 27f4130b7..c462dcada 100644 --- a/src/Testcontainers/Clients/DockerImageOperations.cs +++ b/src/Testcontainers/Clients/DockerImageOperations.cs @@ -106,11 +106,19 @@ await this.DeleteAsync(image, ct) var buildParameters = new ImageBuildParameters { Dockerfile = configuration.Dockerfile, - Tags = new[] { image.FullName }, + Tags = new List { image.FullName }, BuildArgs = configuration.BuildArguments?.ToDictionary(item => item.Key, item => item.Value), Labels = configuration.Labels?.ToDictionary(item => item.Key, item => item.Value), }; + if (configuration.ParameterModifiers != null) + { + foreach (var parameterModifier in configuration.ParameterModifiers) + { + parameterModifier(buildParameters); + } + } + var dockerfileArchiveFilePath = await dockerfileArchive.Tar(ct) .ConfigureAwait(false); diff --git a/src/Testcontainers/Clients/DockerNetworkOperations.cs b/src/Testcontainers/Clients/DockerNetworkOperations.cs index 8a714fd07..aa03dfc03 100644 --- a/src/Testcontainers/Clients/DockerNetworkOperations.cs +++ b/src/Testcontainers/Clients/DockerNetworkOperations.cs @@ -65,6 +65,14 @@ public async Task CreateAsync(INetworkConfiguration configuration, Cance Labels = configuration.Labels?.ToDictionary(item => item.Key, item => item.Value), }; + if (configuration.ParameterModifiers != null) + { + foreach (var parameterModifier in configuration.ParameterModifiers) + { + parameterModifier(createParameters); + } + } + var createNetworkResponse = await this.Docker.Networks.CreateNetworkAsync(createParameters, ct) .ConfigureAwait(false); diff --git a/src/Testcontainers/Clients/DockerVolumeOperations.cs b/src/Testcontainers/Clients/DockerVolumeOperations.cs index 5f12a192a..4e032389b 100644 --- a/src/Testcontainers/Clients/DockerVolumeOperations.cs +++ b/src/Testcontainers/Clients/DockerVolumeOperations.cs @@ -61,6 +61,14 @@ public async Task CreateAsync(IVolumeConfiguration configuration, Cancel Labels = configuration.Labels?.ToDictionary(item => item.Key, item => item.Value), }; + if (configuration.ParameterModifiers != null) + { + foreach (var parameterModifier in configuration.ParameterModifiers) + { + parameterModifier(createParameters); + } + } + var createVolumeResponse = await this.Docker.Volumes.CreateAsync(createParameters, ct) .ConfigureAwait(false); diff --git a/src/Testcontainers/Configurations/Images/IImageFromDockerfileConfiguration.cs b/src/Testcontainers/Configurations/Images/IImageFromDockerfileConfiguration.cs index c387b62c2..b00a99e66 100644 --- a/src/Testcontainers/Configurations/Images/IImageFromDockerfileConfiguration.cs +++ b/src/Testcontainers/Configurations/Images/IImageFromDockerfileConfiguration.cs @@ -9,7 +9,7 @@ /// An image configuration. /// [PublicAPI] - public interface IImageFromDockerfileConfiguration : IResourceConfiguration + public interface IImageFromDockerfileConfiguration : IResourceConfiguration { /// /// Gets a value indicating whether Testcontainers removes an existing image or not. diff --git a/src/Testcontainers/Configurations/Images/ImageFromDockerfileConfiguration.cs b/src/Testcontainers/Configurations/Images/ImageFromDockerfileConfiguration.cs index 85974bbb2..f5cc1ecf1 100644 --- a/src/Testcontainers/Configurations/Images/ImageFromDockerfileConfiguration.cs +++ b/src/Testcontainers/Configurations/Images/ImageFromDockerfileConfiguration.cs @@ -8,7 +8,7 @@ /// [PublicAPI] - internal sealed class ImageFromDockerfileConfiguration : ResourceConfiguration, IImageFromDockerfileConfiguration + internal sealed class ImageFromDockerfileConfiguration : ResourceConfiguration, IImageFromDockerfileConfiguration { /// /// Initializes a new instance of the class. @@ -36,7 +36,7 @@ public ImageFromDockerfileConfiguration( /// Initializes a new instance of the class. /// /// The Docker resource configuration. - public ImageFromDockerfileConfiguration(IResourceConfiguration resourceConfiguration) + public ImageFromDockerfileConfiguration(IResourceConfiguration resourceConfiguration) : base(resourceConfiguration) { } diff --git a/tests/Testcontainers.Tests/Unit/Configurations/TestcontainersAccessInformationTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/TestcontainersAccessInformationTest.cs index 3016d3a1e..92eacabe6 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/TestcontainersAccessInformationTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/TestcontainersAccessInformationTest.cs @@ -68,6 +68,7 @@ public async Task QueryContainerInformationOfCreatedContainer() Assert.NotEmpty(testcontainer.Name); Assert.NotEmpty(testcontainer.IpAddress); Assert.NotEmpty(testcontainer.MacAddress); + Assert.NotEmpty(testcontainer.Hostname); } } @@ -82,10 +83,13 @@ public async Task QueryContainerInformationOfNotCreatedContainer() // Then await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Build()) { + Assert.Throws(() => testcontainer.Id); Assert.Throws(() => testcontainer.Name); Assert.Throws(() => testcontainer.IpAddress); Assert.Throws(() => testcontainer.MacAddress); Assert.Throws(() => testcontainer.GetMappedPublicPort(0)); + Assert.Equal(TestcontainersStates.Undefined, testcontainer.State); + Assert.Equal(TestcontainersHealthStatus.Undefined, testcontainer.Health); } } diff --git a/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs b/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs index 74b75d41a..eba21b75e 100644 --- a/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs +++ b/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs @@ -84,11 +84,16 @@ public async Task ThrowsDockerfileDirectoryDoesNotExist() public async Task BuildsDockerImage() { // Given + IImage tag1 = new DockerImage("localhost/testcontainers", Guid.NewGuid().ToString("D"), string.Empty); + + IImage tag2 = new DockerImage("localhost/testcontainers", Guid.NewGuid().ToString("D"), string.Empty); + var imageFromDockerfileBuilder = new ImageFromDockerfileBuilder() - .WithName("alpine:custom") + .WithName(tag1) .WithDockerfile("Dockerfile") .WithDockerfileDirectory("Assets") .WithDeleteIfExists(true) + .WithCreateParameterModifier(parameterModifier => parameterModifier.Tags.Add(tag2.FullName)) .Build(); // When @@ -99,12 +104,13 @@ await imageFromDockerfileBuilder.CreateAsync() .ConfigureAwait(false); // Then - Assert.True(DockerCli.ResourceExists(DockerCli.DockerResource.Image, imageFromDockerfileBuilder.FullName)); - Assert.Null(Record.Exception(() => imageFromDockerfileBuilder.Repository)); - Assert.Null(Record.Exception(() => imageFromDockerfileBuilder.Name)); - Assert.Null(Record.Exception(() => imageFromDockerfileBuilder.Tag)); - Assert.Null(Record.Exception(() => imageFromDockerfileBuilder.FullName)); - Assert.Null(Record.Exception(() => imageFromDockerfileBuilder.GetHostname())); + Assert.True(DockerCli.ResourceExists(DockerCli.DockerResource.Image, tag1.FullName)); + Assert.True(DockerCli.ResourceExists(DockerCli.DockerResource.Image, tag2.FullName)); + Assert.NotNull(imageFromDockerfileBuilder.Repository); + Assert.NotNull(imageFromDockerfileBuilder.Name); + Assert.NotNull(imageFromDockerfileBuilder.Tag); + Assert.NotNull(imageFromDockerfileBuilder.FullName); + Assert.Null(imageFromDockerfileBuilder.GetHostname()); } } } diff --git a/tests/Testcontainers.Tests/Unit/Networks/TestcontainerNetworkBuilderTest.cs b/tests/Testcontainers.Tests/Unit/Networks/TestcontainerNetworkBuilderTest.cs index fec7996f4..fd0d7f967 100644 --- a/tests/Testcontainers.Tests/Unit/Networks/TestcontainerNetworkBuilderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Networks/TestcontainerNetworkBuilderTest.cs @@ -7,17 +7,21 @@ namespace DotNet.Testcontainers.Tests.Unit using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Clients; using DotNet.Testcontainers.Configurations; + using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Networks; using JetBrains.Annotations; + using Microsoft.Extensions.Logging.Abstractions; using Xunit; public sealed class TestcontainerNetworkBuilderTest : IClassFixture { private static readonly string NetworkName = Guid.NewGuid().ToString("D"); + private static readonly KeyValuePair Option = new KeyValuePair("com.docker.network.driver.mtu", "1350"); + private static readonly KeyValuePair Label = new KeyValuePair(TestcontainersClient.TestcontainersLabel + ".network.test", Guid.NewGuid().ToString("D")); - private static readonly KeyValuePair Option = new KeyValuePair("com.docker.network.driver.mtu", "1350"); + private static readonly KeyValuePair ParameterModifier = new KeyValuePair(TestcontainersClient.TestcontainersLabel + ".parameter.modifier", Guid.NewGuid().ToString("D")); private readonly IDockerNetwork network; @@ -50,33 +54,31 @@ public void GetNameReturnsNetworkName() } [Fact] - public async Task CreateNetworkAssignsLabels() + public async Task CreateNetworkAssignsOptions() { - // Given - using var dockerClientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); - using var dockerClient = dockerClientConfiguration.CreateClient(); + IDockerNetworkOperations networkOperations = new DockerNetworkOperations(ResourceReaper.DefaultSessionId, TestcontainersSettings.OS.DockerEndpointAuthConfig, NullLogger.Instance); // When - var networkResponse = await dockerClient.Networks.InspectNetworkAsync(this.network.Id) + var networkResponse = await networkOperations.ByNameAsync(this.network.Name) .ConfigureAwait(false); // Then - Assert.Equal(Label.Value, Assert.Contains(Label.Key, networkResponse.Labels)); + Assert.Equal(Option.Value, Assert.Contains(Option.Key, networkResponse.Options)); } [Fact] - public async Task CreateNetworkAssignsOptions() + public async Task CreateNetworkAssignsLabels() { // Given - using var dockerClientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); - using var dockerClient = dockerClientConfiguration.CreateClient(); + IDockerNetworkOperations networkOperations = new DockerNetworkOperations(ResourceReaper.DefaultSessionId, TestcontainersSettings.OS.DockerEndpointAuthConfig, NullLogger.Instance); // When - var networkResponse = await dockerClient.Networks.InspectNetworkAsync(this.network.Id) + var networkResponse = await networkOperations.ByNameAsync(this.network.Name) .ConfigureAwait(false); // Then - Assert.Equal(Option.Value, Assert.Contains(Option.Key, networkResponse.Options)); + Assert.Equal(Label.Value, Assert.Contains(Label.Key, networkResponse.Labels)); + Assert.Equal(ParameterModifier.Value, Assert.Contains(ParameterModifier.Key, networkResponse.Labels)); } [UsedImplicitly] @@ -84,8 +86,9 @@ public sealed class DockerNetwork : IDockerNetwork, IAsyncLifetime { private readonly IDockerNetwork network = new TestcontainersNetworkBuilder() .WithName(NetworkName) - .WithLabel(Label.Key, Label.Value) .WithOption(Option.Key, Option.Value) + .WithLabel(Label.Key, Label.Value) + .WithCreateParameterModifier(parameterModifier => parameterModifier.Labels.Add(ParameterModifier.Key, ParameterModifier.Value)) .Build(); public string Id diff --git a/tests/Testcontainers.Tests/Unit/Volumes/TestcontainersVolumeBuilderTest.cs b/tests/Testcontainers.Tests/Unit/Volumes/TestcontainersVolumeBuilderTest.cs index a7cbc6a31..3b254c22d 100644 --- a/tests/Testcontainers.Tests/Unit/Volumes/TestcontainersVolumeBuilderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Volumes/TestcontainersVolumeBuilderTest.cs @@ -1,46 +1,100 @@ namespace DotNet.Testcontainers.Tests.Unit { using System; + using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Clients; + using DotNet.Testcontainers.Configurations; + using DotNet.Testcontainers.Containers; + using DotNet.Testcontainers.Volumes; + using JetBrains.Annotations; + using Microsoft.Extensions.Logging.Abstractions; using Xunit; - public sealed class TestcontainersVolumeBuilderTest + public sealed class TestcontainersVolumeBuilderTest : IClassFixture { + private static readonly string VolumeName = Guid.NewGuid().ToString("D"); + + private static readonly KeyValuePair Label = new KeyValuePair(TestcontainersClient.TestcontainersLabel + ".volume.test", Guid.NewGuid().ToString("D")); + + private static readonly KeyValuePair ParameterModifier = new KeyValuePair(TestcontainersClient.TestcontainersLabel + ".parameter.modifier", Guid.NewGuid().ToString("D")); + + private readonly IDockerVolume volume; + + public TestcontainersVolumeBuilderTest(DockerVolume volume) + { + this.volume = volume; + } + [Fact] - public async Task ShouldCreateVolume() + public void GetIdOrNameThrowsInvalidOperationException() { - // Given - var volumeName = Guid.NewGuid().ToString(); + var noSuchVolume = new TestcontainersVolumeBuilder() + .WithName(VolumeName) + .Build(); - var volumeLabel = Guid.NewGuid().ToString(); + Assert.Throws(() => noSuchVolume.Name); + } + + [Fact] + public void GetNameReturnsVolumeName() + { + Assert.Equal(VolumeName, this.volume.Name); + } + + [Fact] + public async Task CreateVolumeAssignsLabels() + { + // Given + IDockerVolumeOperations volumeOperations = new DockerVolumeOperations(ResourceReaper.DefaultSessionId, TestcontainersSettings.OS.DockerEndpointAuthConfig, NullLogger.Instance); // When - var volume = new TestcontainersVolumeBuilder() - .WithName(volumeName) - .WithLabel("label", volumeLabel) + var volumeResponse = await volumeOperations.ByNameAsync(this.volume.Name) + .ConfigureAwait(false); + + // Then + Assert.Equal(Label.Value, Assert.Contains(Label.Key, volumeResponse.Labels)); + Assert.Equal(ParameterModifier.Value, Assert.Contains(ParameterModifier.Key, volumeResponse.Labels)); + } + + [UsedImplicitly] + public sealed class DockerVolume : IDockerVolume, IAsyncLifetime + { + private readonly IDockerVolume volume = new TestcontainersVolumeBuilder() + .WithName(VolumeName) + .WithLabel(Label.Key, Label.Value) + .WithCreateParameterModifier(parameterModifier => parameterModifier.Labels.Add(ParameterModifier.Key, ParameterModifier.Value)) .Build(); - await volume.CreateAsync(); + public string Name + { + get + { + return this.volume.Name; + } + } - // Then - try + public Task InitializeAsync() { - Assert.Equal(volumeName, volume.Name); + return this.CreateAsync(); } - finally + + public Task DisposeAsync() { - await volume.DeleteAsync(); + return this.DeleteAsync(); } - } - [Fact] - public void ShouldThrowInvalidOperationException() - { - Assert.Throws(() => new TestcontainersVolumeBuilder() - .WithName(Guid.Empty.ToString()) - .Build() - .Name); + public Task CreateAsync(CancellationToken ct = default) + { + return this.volume.CreateAsync(ct); + } + + public Task DeleteAsync(CancellationToken ct = default) + { + return this.volume.DeleteAsync(ct); + } } } }