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

chore: Use the pg_isready command to assess whether PostgreSQL is ready or not #1111

Merged
merged 1 commit into from
Feb 11, 2024
Merged
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
41 changes: 31 additions & 10 deletions src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <inheritdoc />
Expand All @@ -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");
}

/// <inheritdoc />
Expand Down Expand Up @@ -123,19 +126,37 @@ protected override PostgreSqlBuilder Merge(PostgreSqlConfiguration oldValue, Pos
/// <inheritdoc cref="IWaitUntil" />
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";
/// <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,
};
}

/// <inheritdoc />
/// <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.
/// </summary>
/// <returns><see langword="true"/> if the database is ready to accept connections; <see langword="false"/> if the database is not yet ready.</returns>
public async Task<bool> 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);
}
}
}
Loading