forked from testcontainers/testcontainers-node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathport-check.ts
More file actions
87 lines (76 loc) · 2.97 KB
/
port-check.ts
File metadata and controls
87 lines (76 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import Dockerode from "dockerode";
import { Socket } from "net";
import { log } from "../../common";
import { ContainerRuntimeClient } from "../../container-runtime";
export interface PortCheck {
isBound(port: number): Promise<boolean>;
}
export class HostPortCheck implements PortCheck {
constructor(private readonly client: ContainerRuntimeClient) {}
public isBound(port: number): Promise<boolean> {
return new Promise((resolve) => {
const socket = new Socket();
socket
.setTimeout(1000)
.on("error", () => {
socket.destroy();
resolve(false);
})
.on("timeout", () => {
socket.destroy();
resolve(false);
})
.connect(port, this.client.info.containerRuntime.host, () => {
socket.end();
resolve(true);
});
});
}
}
export class InternalPortCheck implements PortCheck {
private isDistroless = false;
private readonly commandOutputs = new Set<string>();
constructor(
private readonly client: ContainerRuntimeClient,
private readonly container: Dockerode.Container
) {}
public async isBound(port: number): Promise<boolean> {
const portHex = port.toString(16).padStart(4, "0");
const commands = [
["/bin/sh", "-c", `cat /proc/net/tcp* | awk '{print $2}' | grep -i :${portHex}`],
["/bin/sh", "-c", `nc -vz -w 1 localhost ${port}`],
["/bin/bash", "-c", `</dev/tcp/localhost/${port}`],
];
const commandResults = await Promise.all(
commands.map((command) => this.client.container.exec(this.container, command, { log: false }))
);
const isBound = commandResults.some((result) => result.exitCode === 0);
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
// If a command is not found, the child process created to execute it returns a status of 127.
// If a command is found but is not executable, the return status is 126.
const shellExists = commandResults.some((result) => result.exitCode !== 126 && result.exitCode !== 127);
if (!isBound && !shellExists && !this.isDistroless) {
this.isDistroless = true;
log.error(`The HostPortWaitStrategy will not work on a distroless image, use an alternate wait strategy`, {
containerId: this.container.id,
});
}
if (!isBound && log.enabled()) {
commandResults
.map((result) => ({ ...result, output: result.output.trim() }))
.filter((result) => result.exitCode !== 126 && result.output.length > 0)
.forEach((result) => {
if (!this.commandOutputs.has(this.commandOutputsKey(result.output))) {
log.trace(`Port check result exit code ${result.exitCode}: ${result.output}`, {
containerId: this.container.id,
});
this.commandOutputs.add(this.commandOutputsKey(result.output));
}
});
}
return isBound;
}
private commandOutputsKey(output: string) {
return `${this.container.id}:${output}`;
}
}