Skip to content

Commit

Permalink
Merge branch 'develop' into feature/assert-sdterr-empty
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn authored Feb 22, 2024
2 parents 0fcffb1 + 8aee376 commit 919254f
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 46 deletions.
8 changes: 4 additions & 4 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.2.1"/>
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.3.0"/>
<PackageVersion Include="Docker.DotNet.X509" Version="3.125.15"/>
<PackageVersion Include="Docker.DotNet" Version="3.125.15"/>
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0"/>
Expand All @@ -15,8 +15,8 @@
<!-- Unit and integration test dependencies: -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageVersion Include="coverlet.collector" Version="6.0.0"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6"/>
<PackageVersion Include="xunit" Version="2.6.6"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7"/>
<PackageVersion Include="xunit" Version="2.7.0"/>
<!-- Third-party client dependencies to connect and interact with the containers: -->
<PackageVersion Include="Apache.NMS.ActiveMQ" Version="2.1.0"/>
<PackageVersion Include="ArangoDBNetStandard" Version="2.0.1"/>
Expand Down Expand Up @@ -59,4 +59,4 @@
<PackageVersion Include="Selenium.WebDriver" Version="4.8.1"/>
<PackageVersion Include="StackExchange.Redis" Version="2.6.90"/>
</ItemGroup>
</Project>
</Project>
4 changes: 2 additions & 2 deletions examples/Flyway/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<!-- Unit and integration test dependencies: -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageVersion Include="Testcontainers.PostgreSql" Version="3.7.0"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6"/>
<PackageVersion Include="xunit" Version="2.6.5"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7"/>
<PackageVersion Include="xunit" Version="2.7.0"/>
<!-- Third-party client dependencies to connect and interact with the containers: -->
<PackageVersion Include="Npgsql" Version="6.0.10"/>
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions examples/WeatherForecast/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.1"/>
<PackageVersion Include="Testcontainers.SqlEdge" Version="3.7.0"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6"/>
<PackageVersion Include="xunit" Version="2.6.5"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7"/>
<PackageVersion Include="xunit" Version="2.7.0"/>
<!-- Third-party client dependencies to connect and interact with the containers: -->
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="106.0.5249.6100"/>
<PackageVersion Include="Selenium.WebDriver" Version="4.9.1"/>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"version": "8.0.200",
"rollForward": "latestPatch"
}
}
21 changes: 11 additions & 10 deletions src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,34 +126,35 @@ protected override PostgreSqlBuilder Merge(PostgreSqlConfiguration oldValue, Pos
/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
private readonly string[] _command;
private readonly IList<string> _command;

/// <summary>
/// Initializes a new instance of the <see cref="WaitUntil" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WaitUntil(PostgreSqlConfiguration configuration)
{
_command = new[] {
"pg_isready",
"--host", "localhost", // Explicitly specify localhost in order to be ready only after the initdb scripts have run and the server is listening over TCP/IP
"--dbname", configuration.Database,
"--username", configuration.Username,
};
// Explicitly specify the host to ensure readiness only after the initdb scripts have executed, and the server is listening on TCP/IP.
_command = new List<string> { "pg_isready", "--host", "localhost", "--dbname", configuration.Database, "--username", configuration.Username };
}

/// <summary>
/// Test whether the database is ready to accept connections or not with the <a href="https://www.postgresql.org/docs/current/app-pg-isready.html">pg_isready</a> command.
/// Checks whether the database is ready and accepts connections or not.
/// </summary>
/// <returns><see langword="true"/> if the database is ready to accept connections; <see langword="false"/> if the database is not yet ready.</returns>
/// <remarks>
/// The wait strategy uses <a href="https://www.postgresql.org/docs/current/app-pg-isready.html">pg_isready</a> to check the connection status of PostgreSql.
/// </remarks>
/// <param name="container">The starting container instance.</param>
/// <returns>Task that completes and returns true when the database is ready and accepts connections, otherwise false.</returns>
/// <exception cref="NotSupportedException">Thrown when the PostgreSql image does not contain <c>pg_isready</c>.</exception>
public async Task<bool> UntilAsync(IContainer container)
{
var execResult = await container.ExecAsync(_command)
.ConfigureAwait(false);

if (execResult.Stderr.Contains("pg_isready was not found"))
{
throw new NotSupportedException($"The {container.Image.FullName} image is not supported. Please use postgres:9.3 onwards.");
throw new NotSupportedException($"The '{container.Image.FullName}' image does not contain: pg_isready. Please use 'postgres:9.3' onwards.");
}

return 0L.Equals(execResult.ExitCode);
Expand Down
11 changes: 11 additions & 0 deletions src/Testcontainers/Builders/Base64Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
return null;
}

if (authProperty.Value.TryGetProperty("identitytoken", out var identityToken) && JsonValueKind.String.Equals(identityToken.ValueKind))
{
var identityTokenValue = identityToken.GetString();

if (!string.IsNullOrEmpty(identityTokenValue))
{
_logger.DockerRegistryCredentialFound(hostname);
return new DockerRegistryAuthenticationConfiguration(authProperty.Name, null, null, identityTokenValue);
}
}

if (!authProperty.Value.TryGetProperty("auth", out var auth))
{
return null;
Expand Down
18 changes: 18 additions & 0 deletions src/Testcontainers/Builders/ContainerBuilder`3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ public TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePat
/// <inheritdoc />
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)
Expand Down Expand Up @@ -234,6 +239,19 @@ public TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, Unix
}
}

/// <inheritdoc />
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));
}
}

/// <inheritdoc />
public TBuilderEntity WithMount(IMount mount)
{
Expand Down
33 changes: 30 additions & 3 deletions src/Testcontainers/Builders/IContainerBuilder`2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,18 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity> : I
TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePath, UnixFileModes fileMode = Unix.FileMode644);

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source directory or file to be copied.</param>
/// <param name="target">The target directory path to copy the files to.</param>
/// <remarks>
/// 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: <see cref="WithResourceMapping(FileInfo, FileInfo, UnixFileModes)" />.
/// </remarks>
/// <param name="source">The source URL, directory or file to be copied.</param>
/// <param name="target">The target directory or file path to copy the file to.</param>
/// <param name="fileMode">The POSIX file mode permission.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
[PublicAPI]
Expand Down Expand Up @@ -258,6 +266,25 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity> : I
[PublicAPI]
TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, UnixFileModes fileMode = Unix.FileMode644);

/// <summary>
/// Copies a file from a URL to the container before it starts.
/// </summary>
/// <remarks>
/// 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 <c>http</c>, <c>https</c> or <c>file</c>.
///
/// If you prefer to copy a file to a specific target file path instead of a
/// directory, use: <see cref="WithResourceMapping(FileInfo, FileInfo, UnixFileModes)" />.
/// </remarks>
/// <param name="source">The source URL of the file to be copied.</param>
/// <param name="target">The target directory or file path to copy the file to.</param>
/// <param name="fileMode">The POSIX file mode permission.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
TBuilderEntity WithResourceMapping(Uri source, string target, UnixFileModes fileMode = Unix.FileMode644);

/// <summary>
/// Assigns the mount configuration to manage data in the container.
/// </summary>
Expand Down
60 changes: 60 additions & 0 deletions src/Testcontainers/Configurations/Volumes/UriResourceMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

/// <inheritdoc cref="IResourceMapping" />
internal sealed class UriResourceMapping : IResourceMapping
{
private readonly Uri _uri;

/// <summary>
/// Initializes a new instance of the <see cref="UriResourceMapping" /> class.
/// </summary>
/// <param name="uri">The URL of the file to download.</param>
/// <param name="containerPath">The absolute path of the file to map in the container.</param>
/// <param name="fileMode">The POSIX file mode permission.</param>
public UriResourceMapping(Uri uri, string containerPath, UnixFileModes fileMode)
{
_uri = uri;
Type = MountType.Bind;
Source = uri.AbsoluteUri;
Target = containerPath;
FileMode = fileMode;
AccessMode = AccessMode.ReadOnly;
}

/// <inheritdoc />
public MountType Type { get; }

/// <inheritdoc />
public AccessMode AccessMode { get; }

/// <inheritdoc />
public string Source { get; }

/// <inheritdoc />
public string Target { get; }

/// <inheritdoc />
public UnixFileModes FileMode { get; }

/// <inheritdoc />
public Task CreateAsync(CancellationToken ct = default) => Task.CompletedTask;

/// <inheritdoc />
public Task DeleteAsync(CancellationToken ct = default) => Task.CompletedTask;

/// <inheritdoc />
public async Task<byte[]> GetAllBytesAsync(CancellationToken ct = default)
{
using (var httpClient = new HttpClient())
{
return await httpClient.GetByteArrayAsync(_uri)
.ConfigureAwait(false);
}
}
}
}
19 changes: 11 additions & 8 deletions src/Testcontainers/Images/IgnoreFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,17 @@ public IgnoreFile(IEnumerable<string> patterns, ILogger logger)
// Prepare exact and partial patterns.
.Aggregate(new List<KeyValuePair<string, bool>>(), (lines, line) =>
{
var key = line.Key;
var value = line.Value;
lines.AddRange(key
.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1)
.Prepend(key)
.Select(ignorePattern => new KeyValuePair<string, bool>(ignorePattern, value)));
const string globstar = "**/";
if (line.Key.Contains(globstar))
{
lines.Add(line);
lines.Add(new KeyValuePair<string, bool>(line.Key.Replace(globstar, string.Empty), line.Value));
}
else
{
lines.Add(line);
}
return lines;
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,18 @@ public void TestFileExistsInTarFile()
}

[UsedImplicitly]
public sealed class FromResourceMapping : TarOutputMemoryStreamTest, IResourceMapping, IAsyncLifetime, IDisposable
public sealed class FromResourceMapping : TarOutputMemoryStreamTest, IResourceMapping, IClassFixture<FromResourceMapping.HttpFixture>, 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;

Expand Down Expand Up @@ -86,31 +96,32 @@ 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<string>();
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)
.WithEntrypoint(CommonCommands.SleepInfinity)
.WithResourceMapping(_testFile, new FileInfo(targetFilePath1))
.WithResourceMapping(_testFile.FullName, targetDirectoryPath1)
.WithResourceMapping(_testFile.Directory.FullName, targetDirectoryPath2)
.WithResourceMapping(_testHttpUri, targetFilePath2)
.WithResourceMapping(_testFileUri, targetDirectoryPath3)
.Build();

// When
Expand All @@ -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
Expand All @@ -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]
Expand Down
Loading

0 comments on commit 919254f

Please sign in to comment.