diff --git a/src/Testcontainers/Builders/ContainerBuilder`3.cs b/src/Testcontainers/Builders/ContainerBuilder`3.cs index b2288d44c..9fed8de2d 100644 --- a/src/Testcontainers/Builders/ContainerBuilder`3.cs +++ b/src/Testcontainers/Builders/ContainerBuilder`3.cs @@ -197,6 +197,11 @@ public TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePat /// public TBuilderEntity WithResourceMapping(string source, string target, UnixFileModes fileMode = Unix.FileMode644) { + if (Uri.IsWellFormedUriString(source, UriKind.Absolute) && Uri.TryCreate(source, UriKind.Absolute, out var uri) && new[] { Uri.UriSchemeHttp, Uri.UriSchemeHttps, Uri.UriSchemeFile }.Contains(uri.Scheme)) + { + return WithResourceMapping(uri, target, fileMode); + } + var fileAttributes = File.GetAttributes(source); if ((fileAttributes & FileAttributes.Directory) == FileAttributes.Directory) @@ -234,6 +239,19 @@ public TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, Unix } } + /// + public TBuilderEntity WithResourceMapping(Uri source, string target, UnixFileModes fileMode = Unix.FileMode644) + { + if (source.IsFile) + { + return WithResourceMapping(new FileResourceMapping(source.AbsolutePath, target, fileMode)); + } + else + { + return WithResourceMapping(new UriResourceMapping(source, target, fileMode)); + } + } + /// public TBuilderEntity WithMount(IMount mount) { diff --git a/src/Testcontainers/Builders/IContainerBuilder`2.cs b/src/Testcontainers/Builders/IContainerBuilder`2.cs index 6516c3c22..6fade9c82 100644 --- a/src/Testcontainers/Builders/IContainerBuilder`2.cs +++ b/src/Testcontainers/Builders/IContainerBuilder`2.cs @@ -219,10 +219,18 @@ public interface IContainerBuilder : I TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePath, UnixFileModes fileMode = Unix.FileMode644); /// - /// Copies a test host directory or file to the container before it starts. + /// Copies the contents of a URL, a test host directory or file to the container before it starts. /// - /// The source directory or file to be copied. - /// The target directory path to copy the files to. + /// + /// If the source corresponds to a file or the Uri scheme corresponds to a file, + /// the content is copied to the target directory path. If the Uri scheme + /// corresponds to HTTP or HTTPS, the content is copied to the target file path. + /// + /// If you prefer to copy a file to a specific target file path instead of a + /// directory, use: . + /// + /// The source URL, directory or file to be copied. + /// The target directory or file path to copy the file to. /// The POSIX file mode permission. /// A configured instance of . [PublicAPI] @@ -258,6 +266,25 @@ public interface IContainerBuilder : I [PublicAPI] TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, UnixFileModes fileMode = Unix.FileMode644); + /// + /// Copies a file from a URL to the container before it starts. + /// + /// + /// If the Uri scheme corresponds to a file, the content is copied to the target + /// directory path. If the Uri scheme corresponds to HTTP or HTTPS, the content is + /// copied to the target file path. + /// + /// The Uri scheme must be either http, https or file. + /// + /// If you prefer to copy a file to a specific target file path instead of a + /// directory, use: . + /// + /// The source URL of the file to be copied. + /// The target directory or file path to copy the file to. + /// The POSIX file mode permission. + /// A configured instance of . + TBuilderEntity WithResourceMapping(Uri source, string target, UnixFileModes fileMode = Unix.FileMode644); + /// /// Assigns the mount configuration to manage data in the container. /// diff --git a/src/Testcontainers/Configurations/Volumes/UriResourceMapping.cs b/src/Testcontainers/Configurations/Volumes/UriResourceMapping.cs new file mode 100644 index 000000000..132a56ed3 --- /dev/null +++ b/src/Testcontainers/Configurations/Volumes/UriResourceMapping.cs @@ -0,0 +1,60 @@ +namespace DotNet.Testcontainers.Configurations +{ + using System; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + /// + internal sealed class UriResourceMapping : IResourceMapping + { + private readonly Uri _uri; + + /// + /// Initializes a new instance of the class. + /// + /// The URL of the file to download. + /// The absolute path of the file to map in the container. + /// The POSIX file mode permission. + public UriResourceMapping(Uri uri, string containerPath, UnixFileModes fileMode) + { + _uri = uri; + Type = MountType.Bind; + Source = uri.AbsoluteUri; + Target = containerPath; + FileMode = fileMode; + AccessMode = AccessMode.ReadOnly; + } + + /// + public MountType Type { get; } + + /// + public AccessMode AccessMode { get; } + + /// + public string Source { get; } + + /// + public string Target { get; } + + /// + public UnixFileModes FileMode { get; } + + /// + public Task CreateAsync(CancellationToken ct = default) => Task.CompletedTask; + + /// + public Task DeleteAsync(CancellationToken ct = default) => Task.CompletedTask; + + /// + public async Task GetAllBytesAsync(CancellationToken ct = default) + { + using (var httpClient = new HttpClient()) + { + return await httpClient.GetByteArrayAsync(_uri) + .ConfigureAwait(false); + } + } + } +} diff --git a/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs b/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs index d1c986644..98ae58dd5 100644 --- a/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs +++ b/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs @@ -33,8 +33,18 @@ public void TestFileExistsInTarFile() } [UsedImplicitly] - public sealed class FromResourceMapping : TarOutputMemoryStreamTest, IResourceMapping, IAsyncLifetime, IDisposable + public sealed class FromResourceMapping : TarOutputMemoryStreamTest, IResourceMapping, IClassFixture, IAsyncLifetime, IDisposable { + private readonly string _testHttpUri; + + private readonly string _testFileUri; + + public FromResourceMapping(FromResourceMapping.HttpFixture httpFixture) + { + _testHttpUri = httpFixture.BaseAddress; + _testFileUri = new Uri(_testFile.FullName).ToString(); + } + public MountType Type => MountType.Bind; @@ -86,24 +96,23 @@ public async Task TestFileExistsInContainer() { // Given var targetFilePath1 = string.Join("/", string.Empty, "tmp", Guid.NewGuid(), _testFile.Name); - var targetFilePath2 = string.Join("/", string.Empty, "tmp", Guid.NewGuid(), _testFile.Name); - + var targetFilePath3 = string.Join("/", string.Empty, "tmp", Guid.NewGuid(), _testFile.Name); var targetDirectoryPath1 = string.Join("/", string.Empty, "tmp", Guid.NewGuid()); - var targetDirectoryPath2 = string.Join("/", string.Empty, "tmp", Guid.NewGuid()); - var targetDirectoryPath3 = string.Join("/", string.Empty, "tmp", Guid.NewGuid()); - var targetDirectoryPath4 = string.Join("/", string.Empty, "tmp", Guid.NewGuid()); + var targetDirectoryPath5 = string.Join("/", string.Empty, "tmp", Guid.NewGuid()); var targetFilePaths = new List(); targetFilePaths.Add(targetFilePath1); targetFilePaths.Add(targetFilePath2); + targetFilePaths.Add(targetFilePath3); targetFilePaths.Add(string.Join("/", targetDirectoryPath1, _testFile.Name)); targetFilePaths.Add(string.Join("/", targetDirectoryPath2, _testFile.Name)); targetFilePaths.Add(string.Join("/", targetDirectoryPath3, _testFile.Name)); targetFilePaths.Add(string.Join("/", targetDirectoryPath4, _testFile.Name)); + targetFilePaths.Add(string.Join("/", targetDirectoryPath5, _testFile.Name)); await using var container = new ContainerBuilder() .WithImage(CommonImages.Alpine) @@ -111,6 +120,8 @@ public async Task TestFileExistsInContainer() .WithResourceMapping(_testFile, new FileInfo(targetFilePath1)) .WithResourceMapping(_testFile.FullName, targetDirectoryPath1) .WithResourceMapping(_testFile.Directory.FullName, targetDirectoryPath2) + .WithResourceMapping(_testHttpUri, targetFilePath2) + .WithResourceMapping(_testFileUri, targetDirectoryPath3) .Build(); // When @@ -120,13 +131,13 @@ public async Task TestFileExistsInContainer() await container.StartAsync() .ConfigureAwait(true); - await container.CopyAsync(fileContent, targetFilePath2) + await container.CopyAsync(fileContent, targetFilePath3) .ConfigureAwait(true); - await container.CopyAsync(_testFile.FullName, targetDirectoryPath3) + await container.CopyAsync(_testFile.FullName, targetDirectoryPath4) .ConfigureAwait(true); - await container.CopyAsync(_testFile.Directory.FullName, targetDirectoryPath4) + await container.CopyAsync(_testFile.Directory.FullName, targetDirectoryPath5) .ConfigureAwait(true); // Then @@ -135,6 +146,31 @@ await container.CopyAsync(_testFile.Directory.FullName, targetDirectoryPath4) Assert.All(execResults, result => Assert.Equal(0, result.ExitCode)); } + + public sealed class HttpFixture : IAsyncLifetime + { + private const ushort HttpPort = 80; + + private readonly IContainer _container = new ContainerBuilder() + .WithImage(CommonImages.Alpine) + .WithEntrypoint("/bin/sh", "-c") + .WithCommand($"while true; do echo \"HTTP/1.1 200 OK\r\n\" | nc -l -p {HttpPort}; done") + .WithPortBinding(HttpPort, true) + .Build(); + + public string BaseAddress + => new UriBuilder(Uri.UriSchemeHttp, _container.Hostname, _container.GetMappedPublicPort(HttpPort)).ToString(); + + public Task InitializeAsync() + { + return _container.StartAsync(); + } + + public Task DisposeAsync() + { + return _container.DisposeAsync().AsTask(); + } + } } [UsedImplicitly]