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]