From bf7c3ea035c74b0886592e3784da2f3ebede7186 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sat, 1 Mar 2025 16:20:33 +0000 Subject: [PATCH 1/5] Fix PostgreSQL container restart --- .../src/postgresql-container.test.ts | 20 +++++++++++++++++++ .../postgresql/src/postgresql-container.ts | 13 +++++++----- .../container/docker-container-client.ts | 2 -- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index 8b1e6511b..d91d662b8 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -83,4 +83,24 @@ describe("PostgreSqlContainer", () => { await container.stop(); }); // } + + it("should work with restarted container", async () => { + const container = await new PostgreSqlContainer().start(); + await container.restart(); + + const client = new Client({ + host: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + user: container.getUsername(), + password: container.getPassword(), + }); + await client.connect(); + + const result = await client.query("SELECT 1"); + expect(result.rows[0]).toEqual({ "?column?": 1 }); + + await client.end(); + await container.stop(); + }); }); diff --git a/packages/modules/postgresql/src/postgresql-container.ts b/packages/modules/postgresql/src/postgresql-container.ts index 745f46710..dd4fd8630 100755 --- a/packages/modules/postgresql/src/postgresql-container.ts +++ b/packages/modules/postgresql/src/postgresql-container.ts @@ -10,7 +10,13 @@ export class PostgreSqlContainer extends GenericContainer { constructor(image = "postgres:13.3-alpine") { super(image); this.withExposedPorts(POSTGRES_PORT) - .withWaitStrategy(Wait.forLogMessage(/.*database system is ready to accept connections.*/, 2)) + .withHealthCheck({ + test: ["CMD-SHELL", "pg_isready -U postgres"], + interval: 10000, + timeout: 5000, + retries: 3, + }) + .withWaitStrategy(Wait.forHealthCheck()) .withStartupTimeout(120_000); } @@ -40,8 +46,6 @@ export class PostgreSqlContainer extends GenericContainer { } export class StartedPostgreSqlContainer extends AbstractStartedContainer { - private readonly port: number; - constructor( startedTestContainer: StartedTestContainer, private readonly database: string, @@ -49,11 +53,10 @@ export class StartedPostgreSqlContainer extends AbstractStartedContainer { private readonly password: string ) { super(startedTestContainer); - this.port = startedTestContainer.getMappedPort(POSTGRES_PORT); } public getPort(): number { - return this.port; + return super.getMappedPort(POSTGRES_PORT); } public getDatabase(): string { diff --git a/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts b/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts index 7367b5d18..16d449824 100644 --- a/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts +++ b/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts @@ -120,9 +120,7 @@ export class DockerContainerClient implements ContainerClient { async inspect(container: Dockerode.Container): Promise { try { - log.debug(`Inspecting container...`, { containerId: container.id }); const inspectInfo = await container.inspect(); - log.debug(`Inspected container`, { containerId: container.id }); return inspectInfo; } catch (err) { log.error(`Failed to inspect container: ${err}`, { containerId: container.id }); From a35d699ac6f5a88ca0df77321b04010b6efa9314 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sat, 1 Mar 2025 20:39:57 +0000 Subject: [PATCH 2/5] Use a healthcheck which runs a `select 1` query Turns out if you try to connect to PG as soon as `pg_isready` exits 0, you fail to connect. Let's change the healthcheck to run an actual query. We need to compute this later as we need credentials. --- .../postgresql/src/postgresql-container.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/modules/postgresql/src/postgresql-container.ts b/packages/modules/postgresql/src/postgresql-container.ts index dd4fd8630..1ed36d6bb 100755 --- a/packages/modules/postgresql/src/postgresql-container.ts +++ b/packages/modules/postgresql/src/postgresql-container.ts @@ -9,15 +9,7 @@ export class PostgreSqlContainer extends GenericContainer { constructor(image = "postgres:13.3-alpine") { super(image); - this.withExposedPorts(POSTGRES_PORT) - .withHealthCheck({ - test: ["CMD-SHELL", "pg_isready -U postgres"], - interval: 10000, - timeout: 5000, - retries: 3, - }) - .withWaitStrategy(Wait.forHealthCheck()) - .withStartupTimeout(120_000); + this.withExposedPorts(POSTGRES_PORT).withStartupTimeout(120_000); } public withDatabase(database: string): this { @@ -41,6 +33,13 @@ export class PostgreSqlContainer extends GenericContainer { POSTGRES_USER: this.username, POSTGRES_PASSWORD: this.password, }); + this.withHealthCheck({ + test: ["CMD-SHELL", `PGPASSWORD=${this.password} psql -U ${this.username} -d ${this.database} -c 'SELECT 1;'`], + interval: 100, + timeout: 100, + retries: 1000, + }); + this.withWaitStrategy(Wait.forHealthCheck()); return new StartedPostgreSqlContainer(await super.start(), this.database, this.username, this.password); } } From fa8d71b7ff8e993d88de88a35aae3fc118bf3bb7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 2 Mar 2025 11:55:00 +0000 Subject: [PATCH 3/5] Increase healthcheck timeout --- packages/modules/postgresql/src/postgresql-container.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/postgresql/src/postgresql-container.ts b/packages/modules/postgresql/src/postgresql-container.ts index 1ed36d6bb..6144c3ae2 100755 --- a/packages/modules/postgresql/src/postgresql-container.ts +++ b/packages/modules/postgresql/src/postgresql-container.ts @@ -35,8 +35,8 @@ export class PostgreSqlContainer extends GenericContainer { }); this.withHealthCheck({ test: ["CMD-SHELL", `PGPASSWORD=${this.password} psql -U ${this.username} -d ${this.database} -c 'SELECT 1;'`], - interval: 100, - timeout: 100, + interval: 250, + timeout: 1000, retries: 1000, }); this.withWaitStrategy(Wait.forHealthCheck()); From f6327effbb51ca5e476ddf2755b3e31496fdad8d Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 2 Mar 2025 12:34:25 +0000 Subject: [PATCH 4/5] Allow user healthcheck override --- .../src/postgresql-container.test.ts | 12 ++++++++++++ .../postgresql/src/postgresql-container.ts | 19 +++++++++++-------- .../generic-container/generic-container.ts | 3 +++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index d91d662b8..68fee1b83 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -1,5 +1,6 @@ import { Client } from "pg"; import { PostgreSqlContainer } from "./postgresql-container"; +import { Wait } from "testcontainers"; describe("PostgreSqlContainer", () => { jest.setTimeout(180_000); @@ -103,4 +104,15 @@ describe("PostgreSqlContainer", () => { await client.end(); await container.stop(); }); + + it("should allow custom healthcheck", async () => { + const container = new PostgreSqlContainer().withHealthCheck({ + test: ["CMD-SHELL", "exit 1"], + interval: 100, + retries: 0, + timeout: 0, + }); + + await expect(() => container.start()).rejects.toThrow(); + }); }); diff --git a/packages/modules/postgresql/src/postgresql-container.ts b/packages/modules/postgresql/src/postgresql-container.ts index 6144c3ae2..3de192482 100755 --- a/packages/modules/postgresql/src/postgresql-container.ts +++ b/packages/modules/postgresql/src/postgresql-container.ts @@ -9,7 +9,9 @@ export class PostgreSqlContainer extends GenericContainer { constructor(image = "postgres:13.3-alpine") { super(image); - this.withExposedPorts(POSTGRES_PORT).withStartupTimeout(120_000); + this.withExposedPorts(POSTGRES_PORT); + this.withWaitStrategy(Wait.forHealthCheck()); + this.withStartupTimeout(120_000); } public withDatabase(database: string): this { @@ -33,13 +35,14 @@ export class PostgreSqlContainer extends GenericContainer { POSTGRES_USER: this.username, POSTGRES_PASSWORD: this.password, }); - this.withHealthCheck({ - test: ["CMD-SHELL", `PGPASSWORD=${this.password} psql -U ${this.username} -d ${this.database} -c 'SELECT 1;'`], - interval: 250, - timeout: 1000, - retries: 1000, - }); - this.withWaitStrategy(Wait.forHealthCheck()); + if (!this.healthCheck) { + this.withHealthCheck({ + test: ["CMD-SHELL", `PGPASSWORD=${this.password} psql -U ${this.username} -d ${this.database} -c 'SELECT 1;'`], + interval: 250, + timeout: 1000, + retries: 1000, + }); + } return new StartedPostgreSqlContainer(await super.start(), this.database, this.username, this.password); } } diff --git a/packages/testcontainers/src/generic-container/generic-container.ts b/packages/testcontainers/src/generic-container/generic-container.ts index fc6bea5c2..bf630bf22 100644 --- a/packages/testcontainers/src/generic-container/generic-container.ts +++ b/packages/testcontainers/src/generic-container/generic-container.ts @@ -57,6 +57,7 @@ export class GenericContainer implements TestContainer { protected filesToCopy: FileToCopy[] = []; protected directoriesToCopy: DirectoryToCopy[] = []; protected contentsToCopy: ContentToCopy[] = []; + protected healthCheck?: HealthCheck; constructor(image: string) { this.imageName = ImageName.fromString(image); @@ -387,6 +388,7 @@ export class GenericContainer implements TestContainer { public withHealthCheck(healthCheck: HealthCheck): this { const toNanos = (duration: number): number => duration * 1e6; + this.healthCheck = healthCheck; this.createOpts.Healthcheck = { Test: healthCheck.test, Interval: healthCheck.interval ? toNanos(healthCheck.interval) : 0, @@ -394,6 +396,7 @@ export class GenericContainer implements TestContainer { Retries: healthCheck.retries || 0, StartPeriod: healthCheck.startPeriod ? toNanos(healthCheck.startPeriod) : 0, }; + return this; } From 047e6322840658f637289e9d71294dd5e720136d Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 2 Mar 2025 13:07:44 +0000 Subject: [PATCH 5/5] Lint --- packages/modules/postgresql/src/postgresql-container.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index 68fee1b83..db102eb56 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -1,6 +1,5 @@ import { Client } from "pg"; import { PostgreSqlContainer } from "./postgresql-container"; -import { Wait } from "testcontainers"; describe("PostgreSqlContainer", () => { jest.setTimeout(180_000);