Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add remote container registry identity token support #1124

Merged
merged 3 commits into from
Feb 21, 2024
Merged
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: 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
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public void ResolvePartialDockerRegistry(string jsonDocument)
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":{}}}}", true, "The \"auth\" property value kind for https://index.docker.io/v1/ is invalid: Object")]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"Not_Base64_encoded\"}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ is not a valid Base64 string")]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"dXNlcm5hbWU=\"}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ should contain one colon separating the username and the password (basic authentication)")]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"identitytoken\":null}}}", true, null)]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"identitytoken\":\"\"}}}", true, null)]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"identitytoken\":{}}}}", true, null)]
public void ShouldGetNull(string jsonDocument, bool isApplicable, string logMessage)
{
// Given
Expand All @@ -116,11 +119,12 @@ public void ShouldGetNull(string jsonDocument, bool isApplicable, string logMess
}
}

[Fact]
public void ShouldGetAuthConfig()
[Theory]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"dXNlcm5hbWU6cGFzc3dvcmQ=\"}}}", "username", "password", null)]
[InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"identitytoken\":\"identitytoken\"}}}", null, null, "identitytoken")]
public void ShouldGetAuthConfig(string jsonDocument, string expectedUsername, string expectedPassword, string expectedIdentityToken)
{
// Given
const string jsonDocument = "{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"dXNlcm5hbWU6cGFzc3dvcmQ=\"}}}";
var jsonElement = JsonDocument.Parse(jsonDocument).RootElement;

// When
Expand All @@ -131,8 +135,9 @@ public void ShouldGetAuthConfig()
Assert.True(authenticationProvider.IsApplicable(DockerRegistry));
Assert.NotNull(authConfig);
Assert.Equal(DockerRegistry, authConfig.RegistryEndpoint);
Assert.Equal("username", authConfig.Username);
Assert.Equal("password", authConfig.Password);
Assert.Equal(expectedUsername, authConfig.Username);
Assert.Equal(expectedPassword, authConfig.Password);
Assert.Equal(expectedIdentityToken, authConfig.IdentityToken);
}
}

Expand Down Expand Up @@ -259,7 +264,7 @@ public void Dispose()

private sealed class WarnLogger : ILogger
{
private readonly List<Tuple<LogLevel, string>> _logMessages = new List<Tuple<LogLevel, string>>();
private readonly IList<Tuple<LogLevel, string>> _logMessages = new List<Tuple<LogLevel, string>>();

public IEnumerable<Tuple<LogLevel, string>> LogMessages => _logMessages;

Expand Down
Loading