Skip to content

Commit 5fd01d1

Browse files
committed
improved logging in InternalPortCheck
1 parent 6624fd2 commit 5fd01d1

File tree

5 files changed

+117
-22
lines changed

5 files changed

+117
-22
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node AS build
2+
WORKDIR /app
3+
RUN npm init -y \
4+
&& npm install [email protected]
5+
COPY index.js .
6+
7+
FROM gcr.io/distroless/nodejs20-debian12 AS app
8+
EXPOSE 8080
9+
COPY --from=build /app /app
10+
WORKDIR /app
11+
CMD ["index.js"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const http = require("http");
2+
const express = require("express");
3+
4+
const app = express();
5+
6+
app.get("/hello-world", (req, res) => {
7+
res.status(200).send("hello-world");
8+
});
9+
10+
const PORT = 8080;
11+
12+
http
13+
.createServer(app)
14+
.listen(PORT, () => console.log(`Listening on port ${PORT}`));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import path from "path";
2+
import { getContainerRuntimeClient } from "../../container-runtime";
3+
import { GenericContainer } from "../../generic-container/generic-container";
4+
import { checkContainerIsHealthy } from "../../utils/test-helper";
5+
import { Wait } from "../wait";
6+
import { InternalPortCheck } from "./port-check";
7+
8+
describe("PortCheck", { timeout: 120_000 }, () => {
9+
describe("InternalPortCheck", () => {
10+
it("should work on container with shell", async () => {
11+
const client = await getContainerRuntimeClient();
12+
const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
13+
.withExposedPorts(8080)
14+
.withWaitStrategy(Wait.forLogMessage(/Listening on port 8080/))
15+
.start();
16+
17+
await checkContainerIsHealthy(container);
18+
19+
const lowerLevelContainer = client.container.dockerode.getContainer(container.getId());
20+
const portCheck = new InternalPortCheck(client, lowerLevelContainer);
21+
22+
const isBound = await portCheck.isBound(8080);
23+
expect(isBound).toBeTruthy();
24+
25+
await container.stop();
26+
});
27+
28+
const fixtures = path.resolve(__dirname, "..", "..", "..", "fixtures", "docker");
29+
it("should fail on container without shell (distroless)", async () => {
30+
const context = path.resolve(fixtures, "docker-no-shell");
31+
const container = await GenericContainer.fromDockerfile(context).build();
32+
const startedContainer = await container
33+
.withExposedPorts(8080)
34+
.withWaitStrategy(Wait.forLogMessage(/Listening on port 8080/))
35+
.start();
36+
37+
await checkContainerIsHealthy(startedContainer);
38+
39+
const client = await getContainerRuntimeClient();
40+
const lowerLevelContainer = client.container.dockerode.getContainer(startedContainer.getId());
41+
const portCheck = new InternalPortCheck(client, lowerLevelContainer);
42+
43+
const isBound = await portCheck.isBound(8080);
44+
expect(isBound).toBeFalsy();
45+
46+
await startedContainer.stop();
47+
});
48+
});
49+
});

packages/testcontainers/src/wait-strategies/utils/port-check.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,28 @@ describe("PortCheck", () => {
140140
],
141141
]);
142142
});
143+
144+
it("should error log the distroless image once, regardless of logs enabled or not", async () => {
145+
// Make sure logging is disabled explicitly here
146+
mockLogger.enabled.mockImplementation(() => false);
147+
148+
mockContainerExec
149+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 1", exitCode: 126 }))
150+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 2", exitCode: 126 }))
151+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 2", exitCode: 126 }))
152+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 1", exitCode: 126 }))
153+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 2", exitCode: 126 }))
154+
.mockReturnValueOnce(Promise.resolve({ output: "ERROR 2", exitCode: 126 }));
155+
156+
await portCheck.isBound(8080);
157+
await portCheck.isBound(8080);
158+
159+
expect(mockLogger.error.mock.calls).toEqual([
160+
[
161+
"The HostPortWaitStrategy will not work on a distroless image, use an alternate wait strategy",
162+
{ containerId: "containerId" },
163+
],
164+
]);
165+
});
143166
});
144167
});

packages/testcontainers/src/wait-strategies/utils/port-check.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class HostPortCheck implements PortCheck {
3333

3434
export class InternalPortCheck implements PortCheck {
3535
private isDistroless = false;
36-
private commandOutputs = new Set<string>();
36+
private readonly commandOutputs = new Set<string>();
3737

3838
constructor(
3939
private readonly client: ContainerRuntimeClient,
@@ -53,28 +53,26 @@ export class InternalPortCheck implements PortCheck {
5353
);
5454
const isBound = commandResults.some((result) => result.exitCode === 0);
5555

56+
const shellExists = commandResults.some((result) => result.exitCode !== 126);
57+
if (!isBound && !shellExists && !this.isDistroless) {
58+
this.isDistroless = true;
59+
log.error(`The HostPortWaitStrategy will not work on a distroless image, use an alternate wait strategy`, {
60+
containerId: this.container.id,
61+
});
62+
}
63+
5664
if (!isBound && log.enabled()) {
57-
const shellExists = commandResults.some((result) => result.exitCode !== 126);
58-
if (!shellExists) {
59-
if (!this.isDistroless) {
60-
this.isDistroless = true;
61-
log.error(`The HostPortWaitStrategy will not work on a distroless image, use an alternate wait strategy`, {
62-
containerId: this.container.id,
63-
});
64-
}
65-
} else {
66-
commandResults
67-
.map((result) => ({ ...result, output: result.output.trim() }))
68-
.filter((result) => result.exitCode !== 126 && result.output.length > 0)
69-
.forEach((result) => {
70-
if (!this.commandOutputs.has(this.commandOutputsKey(result.output))) {
71-
log.trace(`Port check result exit code ${result.exitCode}: ${result.output}`, {
72-
containerId: this.container.id,
73-
});
74-
this.commandOutputs.add(this.commandOutputsKey(result.output));
75-
}
76-
});
77-
}
65+
commandResults
66+
.map((result) => ({ ...result, output: result.output.trim() }))
67+
.filter((result) => result.exitCode !== 126 && result.output.length > 0)
68+
.forEach((result) => {
69+
if (!this.commandOutputs.has(this.commandOutputsKey(result.output))) {
70+
log.trace(`Port check result exit code ${result.exitCode}: ${result.output}`, {
71+
containerId: this.container.id,
72+
});
73+
this.commandOutputs.add(this.commandOutputsKey(result.output));
74+
}
75+
});
7876
}
7977

8078
return isBound;

0 commit comments

Comments
 (0)