From 8aee376aa98018d07d270264920161113dfd8490 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:17:12 +0100 Subject: [PATCH] feat: Add remote container registry identity token support (#1124) --- Directory.Packages.props | 8 +++---- examples/Flyway/Directory.Packages.props | 4 ++-- .../WeatherForecast/Directory.Packages.props | 4 ++-- global.json | 2 +- .../PostgreSqlBuilder.cs | 21 ++++++++++--------- src/Testcontainers/Builders/Base64Provider.cs | 11 ++++++++++ ...ockerRegistryAuthenticationProviderTest.cs | 17 +++++++++------ 7 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3a9e4dbd2..6eeb39533 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ true - + @@ -15,8 +15,8 @@ - - + + @@ -59,4 +59,4 @@ - \ No newline at end of file + diff --git a/examples/Flyway/Directory.Packages.props b/examples/Flyway/Directory.Packages.props index 6fee13d9c..8549bb998 100644 --- a/examples/Flyway/Directory.Packages.props +++ b/examples/Flyway/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + diff --git a/examples/WeatherForecast/Directory.Packages.props b/examples/WeatherForecast/Directory.Packages.props index edbae9614..a281383a5 100644 --- a/examples/WeatherForecast/Directory.Packages.props +++ b/examples/WeatherForecast/Directory.Packages.props @@ -11,8 +11,8 @@ - - + + diff --git a/global.json b/global.json index b75deba61..d031a7632 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "8.0.200", "rollForward": "latestPatch" } } diff --git a/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs b/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs index 0cbb00e9c..c7a0334ac 100644 --- a/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs +++ b/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs @@ -126,7 +126,7 @@ protected override PostgreSqlBuilder Merge(PostgreSqlConfiguration oldValue, Pos /// private sealed class WaitUntil : IWaitUntil { - private readonly string[] _command; + private readonly IList _command; /// /// Initializes a new instance of the class. @@ -134,18 +134,19 @@ private sealed class WaitUntil : IWaitUntil /// The container configuration. 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 { "pg_isready", "--host", "localhost", "--dbname", configuration.Database, "--username", configuration.Username }; } /// - /// Test whether the database is ready to accept connections or not with the pg_isready command. + /// Checks whether the database is ready and accepts connections or not. /// - /// if the database is ready to accept connections; if the database is not yet ready. + /// + /// The wait strategy uses pg_isready to check the connection status of PostgreSql. + /// + /// The starting container instance. + /// Task that completes and returns true when the database is ready and accepts connections, otherwise false. + /// Thrown when the PostgreSql image does not contain pg_isready. public async Task UntilAsync(IContainer container) { var execResult = await container.ExecAsync(_command) @@ -153,7 +154,7 @@ public async Task UntilAsync(IContainer container) 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); diff --git a/src/Testcontainers/Builders/Base64Provider.cs b/src/Testcontainers/Builders/Base64Provider.cs index 7998aa5e8..29ed7b142 100644 --- a/src/Testcontainers/Builders/Base64Provider.cs +++ b/src/Testcontainers/Builders/Base64Provider.cs @@ -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; diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs index 27beda932..728a083b1 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs @@ -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 @@ -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 @@ -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); } } @@ -259,7 +264,7 @@ public void Dispose() private sealed class WarnLogger : ILogger { - private readonly List> _logMessages = new List>(); + private readonly IList> _logMessages = new List>(); public IEnumerable> LogMessages => _logMessages;