Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Testcontainers/Builders/ContainerBuilder`3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace DotNet.Testcontainers.Builders
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Configurations.Containers;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
Expand Down Expand Up @@ -271,6 +272,13 @@ public TBuilderEntity WithResourceMapping(Uri source, string target, uint uid =
}
}

/// <inheritdoc />
public TBuilderEntity WithCopyTarArchive(Stream tarArchive, string containerPath = "/")
{
var tarArchiveMappings = new[] { new TarArchiveMapping(tarArchive, containerPath) };
return Clone(new ContainerConfiguration(tarArchiveMappings: tarArchiveMappings));
}

/// <inheritdoc />
public TBuilderEntity WithMount(IMount mount)
{
Expand Down
20 changes: 20 additions & 0 deletions src/Testcontainers/Builders/IContainerBuilder`2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity, out
/// <param name="acceptLicenseAgreement">A boolean value indicating whether the license agreement is accepted.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
/// <exception cref="InvalidOperationException">Thrown when the module does not require a license agreement.</exception>
[PublicAPI]
TBuilderEntity WithAcceptLicenseAgreement(bool acceptLicenseAgreement);

/// <summary>
Expand Down Expand Up @@ -321,8 +322,27 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity, out
/// <param name="gid">The group ID to set for the copied file or directory. Defaults to 0 (root).</param>
/// <param name="fileMode">The POSIX file mode permission.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
[PublicAPI]
TBuilderEntity WithResourceMapping(Uri source, string target, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644);

/// <summary>
/// Copies a tar archive contents to the container before it starts.
/// </summary>
/// <remarks>
/// <para>
/// Set the <paramref name="tarArchive"/> property <see cref="Stream.Position"/> to 0 before calling this method.
/// </para>
/// <para>
/// The caller retains ownership of the stream and is responsible for disposal.
/// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
/// </para>
/// </remarks>
/// <param name="tarArchive">The <see cref="Stream"/> with the tar archive contents.</param>
/// <param name="containerPath">The path where tar archive contents should be placed.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
[PublicAPI]
TBuilderEntity WithCopyTarArchive(Stream tarArchive, string containerPath = "/");

/// <summary>
/// Assigns the mount configuration to manage data in the container.
/// </summary>
Expand Down
14 changes: 7 additions & 7 deletions src/Testcontainers/Clients/DockerContainerOperations.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
namespace DotNet.Testcontainers.Clients
{
using Docker.DotNet;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.Extensions.Logging;

internal sealed class DockerContainerOperations : DockerApiClient, IDockerContainerOperations
{
Expand Down Expand Up @@ -109,9 +109,9 @@ public Task RemoveAsync(string id, CancellationToken ct = default)
return DockerClient.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true, RemoveVolumes = true }, ct);
}

public Task ExtractArchiveToContainerAsync(string id, string path, TarOutputMemoryStream tarStream, CancellationToken ct = default)
public Task ExtractArchiveToContainerAsync(string id, string path, Stream tarStream, CancellationToken ct = default)
{
Logger.CopyArchiveToDockerContainer(id, tarStream.ContentLength);
Logger.CopyArchiveToDockerContainer(id, tarStream.Length);

var copyToContainerParameters = new CopyToContainerParameters
{
Expand Down
8 changes: 4 additions & 4 deletions src/Testcontainers/Clients/IDockerContainerOperations.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
namespace DotNet.Testcontainers.Clients
{
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;

internal interface IDockerContainerOperations : IHasListOperations<ContainerListResponse, ContainerInspectResponse>
{
Expand All @@ -25,7 +25,7 @@ internal interface IDockerContainerOperations : IHasListOperations<ContainerList

Task RemoveAsync(string id, CancellationToken ct = default);

Task ExtractArchiveToContainerAsync(string id, string path, TarOutputMemoryStream tarStream, CancellationToken ct = default);
Task ExtractArchiveToContainerAsync(string id, string path, Stream tarStream, CancellationToken ct = default);

Task<Stream> GetArchiveFromContainerAsync(string id, string path, CancellationToken ct = default);

Expand Down
19 changes: 19 additions & 0 deletions src/Testcontainers/Clients/ITestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ internal interface ITestcontainersClient
/// <returns>A task that completes when the file has been copied.</returns>
Task CopyAsync(string id, FileInfo source, string target, uint uid, uint gid, UnixFileModes fileMode, CancellationToken ct = default);

/// <summary>
/// Copies a tar archive contents to the container.
/// </summary>
/// <remarks>
/// <para>
/// Set the <paramref name="tarArchive"/> property <see cref="Stream.Position"/> to 0 before calling this method.
/// </para>
/// <para>
/// The caller retains ownership of the stream and is responsible for disposal.
/// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
/// </para>
/// </remarks>
/// <param name="id">The container id.</param>
/// <param name="tarArchive">The <see cref="Stream"/> with the tar archive contents.</param>
/// <param name="containerPath">The path where tar archive contents should be placed.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that completes when the tar archive has been copied.</returns>
Task CopyTarArchiveAsync(string id, Stream tarArchive, string containerPath = "/", CancellationToken ct = default);

/// <summary>
/// Reads a file from the container.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/Testcontainers/Clients/TestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@ await Container.ExtractArchiveToContainerAsync(id, "/", tarOutputMemStream, ct)
}
}

/// <inheritdoc />
public async Task CopyTarArchiveAsync(string id, Stream tarArchive, string containerPath = "/", CancellationToken ct = default)
{
await Container.ExtractArchiveToContainerAsync(id, containerPath, tarArchive, ct)
.ConfigureAwait(false);
}

/// <inheritdoc />
public async Task<byte[]> ReadFileAsync(string id, string filePath, CancellationToken ct = default)
{
Expand Down Expand Up @@ -353,6 +360,12 @@ await Task.WhenAll(configuration.ResourceMappings.Select(resourceMapping => Copy
.ConfigureAwait(false);
}

if (configuration.TarArchiveMappings.Any())
{
await Task.WhenAll(configuration.TarArchiveMappings.Select(tarArchive => CopyTarArchiveAsync(id, tarArchive.TarArchive, tarArchive.ContainerPath, ct)))
.ConfigureAwait(false);
}

return id;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations.Containers;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;

namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;

/// <inheritdoc cref="IContainerConfiguration" />
[PublicAPI]
public class ContainerConfiguration : ResourceConfiguration<CreateContainerParameters>, IContainerConfiguration
Expand Down Expand Up @@ -42,6 +43,7 @@ public class ContainerConfiguration : ResourceConfiguration<CreateContainerParam
/// <param name="connectionStringProvider">The connection string provider.</param>
/// <param name="autoRemove">A value indicating whether Docker removes the container after it exits or not.</param>
/// <param name="privileged">A value indicating whether the privileged flag is set or not.</param>
/// <param name="tarArchiveMappings">A list of tar archive mappings.</param>
public ContainerConfiguration(
IImage image = null,
Func<ImageInspectResponse, bool> imagePullPolicy = null,
Expand All @@ -65,7 +67,8 @@ public ContainerConfiguration(
Func<IContainer, IContainerConfiguration, CancellationToken, Task> startupCallback = null,
IConnectionStringProvider<IContainer, IContainerConfiguration> connectionStringProvider = null,
bool? autoRemove = null,
bool? privileged = null)
bool? privileged = null,
IEnumerable<TarArchiveMapping> tarArchiveMappings = null)
{
AutoRemove = autoRemove;
Privileged = privileged;
Expand All @@ -90,6 +93,7 @@ public ContainerConfiguration(
WaitStrategies = waitStrategies;
StartupCallback = startupCallback;
ConnectionStringProvider = connectionStringProvider;
TarArchiveMappings = tarArchiveMappings;
}

/// <summary>
Expand Down Expand Up @@ -130,6 +134,7 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
ExposedPorts = BuildConfiguration.Combine(oldValue.ExposedPorts, newValue.ExposedPorts);
PortBindings = BuildConfiguration.Combine(oldValue.PortBindings, newValue.PortBindings);
ResourceMappings = BuildConfiguration.Combine(oldValue.ResourceMappings, newValue.ResourceMappings);
TarArchiveMappings = BuildConfiguration.Combine(oldValue.TarArchiveMappings, newValue.TarArchiveMappings);
Containers = BuildConfiguration.Combine(oldValue.Containers, newValue.Containers);
Mounts = BuildConfiguration.Combine(oldValue.Mounts, newValue.Mounts);
Networks = BuildConfiguration.Combine(oldValue.Networks, newValue.Networks);
Expand Down Expand Up @@ -192,6 +197,10 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
[JsonIgnore]
public IEnumerable<IResourceMapping> ResourceMappings { get; }

/// <inheritdoc />
[JsonIgnore]
public IEnumerable<TarArchiveMapping> TarArchiveMappings { get; }

/// <inheritdoc />
[JsonIgnore]
public IEnumerable<IContainer> Containers { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations.Containers;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// A container configuration.
Expand Down Expand Up @@ -86,6 +88,11 @@ public interface IContainerConfiguration : IResourceConfiguration<CreateContaine
/// </summary>
IEnumerable<IResourceMapping> ResourceMappings { get; }

/// <summary>
/// Gets a list of tar archive mappings.
/// </summary>
IEnumerable<TarArchiveMapping> TarArchiveMappings { get; }

/// <summary>
/// Gets a list of dependent containers.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions src/Testcontainers/Configurations/Containers/TarArchiveMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.IO;

namespace DotNet.Testcontainers.Configurations.Containers
{
public sealed record TarArchiveMapping
{
public TarArchiveMapping(Stream tarArchive, string containerPath)
{
TarArchive = tarArchive;
ContainerPath = containerPath;
}

public Stream TarArchive { get; }

public string ContainerPath { get; }
}
}
38 changes: 22 additions & 16 deletions src/Testcontainers/Containers/DockerContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,28 +152,28 @@ public string Hostname
case "http":
case "https":
case "tcp":
{
return dockerEndpoint.Host;
}
{
return dockerEndpoint.Host;
}

case "npipe":
case "unix":
{
const string localhost = "127.0.0.1";

if (!Exists())
{
return localhost;
}
const string localhost = "127.0.0.1";

if (!_client.IsRunningInsideDocker)
{
return localhost;
}
if (!Exists())
{
return localhost;
}

if (!_client.IsRunningInsideDocker)
{
return localhost;
}

var endpointSettings = _container.NetworkSettings.Networks.First().Value;
return endpointSettings.Gateway;
}
var endpointSettings = _container.NetworkSettings.Networks.First().Value;
return endpointSettings.Gateway;
}

default:
throw new InvalidOperationException($"Docker endpoint {dockerEndpoint} is not supported.");
Expand Down Expand Up @@ -406,6 +406,12 @@ public Task CopyAsync(DirectoryInfo source, string target, uint uid = 0, uint gi
return _client.CopyAsync(Id, source, target, uid, gid, fileMode, ct);
}

/// <inheritdoc />
public Task CopyTarArchiveAsync(Stream tarArchive, string containerPath = "/", CancellationToken ct = default)
{
return _client.CopyTarArchiveAsync(Id, tarArchive, containerPath, ct);
}

/// <inheritdoc />
public Task<byte[]> ReadFileAsync(string filePath, CancellationToken ct = default)
{
Expand Down
18 changes: 18 additions & 0 deletions src/Testcontainers/Containers/IContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,24 @@ public interface IContainer : IConnectionStringProvider, IAsyncDisposable
/// <returns>A task that completes when the file has been copied.</returns>
Task CopyAsync(FileInfo source, string target, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644, CancellationToken ct = default);

/// <summary>
/// Copies a tar archive contents to the container.
/// </summary>
/// <remarks>
/// <para>
/// Set the <paramref name="tarArchive"/> property <see cref="Stream.Position"/> to 0 before calling this method.
/// </para>
/// <para>
/// The caller retains ownership of the stream and is responsible for disposal.
/// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
/// </para>
/// </remarks>
/// <param name="tarArchive">The <see cref="Stream"/> with the tar archive contents.</param>
/// <param name="containerPath">The path where tar archive contents should be placed.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that completes when the tar archive has been copied.</returns>
Task CopyTarArchiveAsync(Stream tarArchive, string containerPath = "/", CancellationToken ct = default);

/// <summary>
/// Reads a file from the container.
/// </summary>
Expand Down
Loading
Loading