From e2b090176f5f4e3764316bd4080f69591bb6ddad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Sat, 10 Feb 2024 22:01:04 +0100 Subject: [PATCH] Use the `pg_isready` command to asses whether PostgreSQL is ready or not This strategy aligns with the MySQL and MariaDb wait strategies. Also, logs have changed over time and the current strategy doesn't work with older PostgreSQL Docker images (for example v9.6) which does not produce the same logs as recent versions of the PostgreSQL docker image. --- .../PostgreSqlBuilder.cs | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs b/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs index e115d9100..0cbb00e9c 100644 --- a/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs +++ b/src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs @@ -73,7 +73,11 @@ public PostgreSqlBuilder WithPassword(string password) public override PostgreSqlContainer Build() { Validate(); - return new PostgreSqlContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + + // By default, the base builder waits until the container is running. However, for PostgreSql, a more advanced waiting strategy is necessary that requires access to the configured database and username. + // If the user does not provide a custom waiting strategy, append the default PostgreSql waiting strategy. + var postgreSqlBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration))); + return new PostgreSqlContainer(postgreSqlBuilder.DockerResourceConfiguration, TestcontainersSettings.Logger); } /// @@ -88,8 +92,7 @@ protected override PostgreSqlBuilder Init() // Disable durability: https://www.postgresql.org/docs/current/non-durability.html. .WithCommand("-c", "fsync=off") .WithCommand("-c", "full_page_writes=off") - .WithCommand("-c", "synchronous_commit=off") - .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil())); + .WithCommand("-c", "synchronous_commit=off"); } /// @@ -123,19 +126,37 @@ protected override PostgreSqlBuilder Merge(PostgreSqlConfiguration oldValue, Pos /// private sealed class WaitUntil : IWaitUntil { - private const string IPv4Listening = "listening on IPv4"; - - private const string IPv6Listening = "listening on IPv6"; + private readonly string[] _command; - private const string DatabaseSystemReady = "database system is ready to accept connections"; + /// + /// Initializes a new instance of the class. + /// + /// 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, + }; + } - /// + /// + /// Test whether the database is ready to accept connections or not with the pg_isready command. + /// + /// if the database is ready to accept connections; if the database is not yet ready. public async Task UntilAsync(IContainer container) { - var (_, stderr) = await container.GetLogsAsync(since: container.StoppedTime, timestampsEnabled: false) + var execResult = await container.ExecAsync(_command) .ConfigureAwait(false); - return new[] { IPv4Listening, IPv6Listening, DatabaseSystemReady }.All(stderr.Contains); + 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."); + } + + return 0L.Equals(execResult.ExitCode); } } } \ No newline at end of file