Skip to content

Commit cc62dbd

Browse files
committed
add docker diagnostics
1 parent 9bf7a19 commit cc62dbd

File tree

3 files changed

+160
-4
lines changed

3 files changed

+160
-4
lines changed

internal-packages/testcontainers/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@testcontainers/postgresql": "^10.25.0",
1414
"@testcontainers/redis": "^10.25.0",
1515
"@trigger.dev/core": "workspace:*",
16+
"std-env": "^3.9.0",
1617
"testcontainers": "^10.25.0",
1718
"tinyexec": "^0.3.0",
1819
"vitest": "^1.4.0"

internal-packages/testcontainers/src/index.ts

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { RedisOptions } from "ioredis";
55
import { Network, type StartedNetwork } from "testcontainers";
66
import { test } from "vitest";
77
import { createElectricContainer, createPostgresContainer, createRedisContainer } from "./utils";
8+
import { x } from "tinyexec";
9+
import { isCI } from "std-env";
810

911
export { assertNonNullable } from "./utils";
1012
export { StartedRedisContainer };
@@ -45,31 +47,177 @@ async function logCleanup(resource: string, promise: Promise<unknown>) {
4547
const activeAtStart = ++activeCleanups;
4648

4749
let error: unknown = null;
50+
4851
try {
4952
await promise;
5053
} catch (err) {
5154
error = err instanceof Error ? err.message : String(err);
5255
}
5356

5457
const end = new Date();
58+
const durationMs = end.getTime() - start.getTime();
5559
const activeAtEnd = --activeCleanups;
5660
const parallel = activeAtStart > 1 || activeAtEnd > 0;
5761

62+
if (!isCI) {
63+
return;
64+
}
65+
66+
let dockerDiagnostics: DockerDiagnostics = {};
67+
68+
// Only run docker diagnostics if there was an error or cleanup took longer than 5s
69+
if (error || durationMs > 5000) {
70+
try {
71+
dockerDiagnostics = await getDockerDiagnostics();
72+
} catch (diagnosticErr) {
73+
console.error("Failed to get docker diagnostics:", diagnosticErr);
74+
}
75+
}
76+
5877
console.log(
5978
JSON.stringify({
6079
order,
6180
resource,
62-
durationMs: end.getTime() - start.getTime(),
81+
durationMs,
6382
start: start.toISOString(),
6483
end: end.toISOString(),
6584
parallel,
6685
error,
6786
activeAtStart,
6887
activeAtEnd,
88+
...dockerDiagnostics,
6989
})
7090
);
7191
}
7292

93+
function stringToLines(str: string): string[] {
94+
return str.split("\n").filter(Boolean);
95+
}
96+
97+
async function getDockerNetworks(): Promise<string[]> {
98+
try {
99+
const result = await x("docker", ["network", "ls" /* , "--no-trunc" */]);
100+
return stringToLines(result.stdout);
101+
} catch (error) {
102+
console.error(error);
103+
return ["error: check additional logs for more details"];
104+
}
105+
}
106+
107+
async function getDockerContainers(): Promise<string[]> {
108+
try {
109+
const result = await x("docker", ["ps", "-a" /* , "--no-trunc" */]);
110+
return stringToLines(result.stdout);
111+
} catch (error) {
112+
console.error(error);
113+
return ["error: check additional logs for more details"];
114+
}
115+
}
116+
117+
type DockerNetworkAttachment = {
118+
networkId: string;
119+
networkName: string;
120+
containers: string[];
121+
};
122+
123+
export async function getDockerNetworkAttachments(): Promise<DockerNetworkAttachment[]> {
124+
let attachments: DockerNetworkAttachment[] = [];
125+
let networkIds: string[] = [];
126+
127+
try {
128+
const result = await x("docker", ["network", "ls", "-q"]);
129+
networkIds = stringToLines(result.stdout);
130+
} catch (err) {
131+
console.error("Failed to list docker networks:", err);
132+
}
133+
134+
for (const networkId of networkIds) {
135+
try {
136+
const inspectResult = await x("docker", [
137+
"network",
138+
"inspect",
139+
"--format",
140+
'{{ .Name }}{{ range $k, $v := .Containers }} {{ printf "%.12s %s" $k .Name }}{{ end }}',
141+
networkId,
142+
]);
143+
144+
const [networkName, ...containers] = inspectResult.stdout.trim().split(/\s+/);
145+
attachments.push({ networkId, networkName, containers });
146+
} catch (err) {
147+
console.error(`Failed to inspect network ${networkId}:`, err);
148+
attachments.push({ networkId, networkName: String(err), containers: [] });
149+
}
150+
}
151+
152+
return attachments;
153+
}
154+
155+
type DockerContainerNetwork = {
156+
containerId: string;
157+
containerName: string;
158+
networks: string[];
159+
};
160+
161+
export async function getDockerContainerNetworks(): Promise<DockerContainerNetwork[]> {
162+
let results: DockerContainerNetwork[] = [];
163+
let containers: string[] = [];
164+
165+
try {
166+
const result = await x("docker", [
167+
"ps",
168+
"-a",
169+
"--format",
170+
'{{.ID | printf "%.12s"}} {{.Names}}',
171+
]);
172+
containers = stringToLines(result.stdout);
173+
} catch (err) {
174+
console.error("Failed to list docker containers:", err);
175+
}
176+
177+
for (const [containerId, containerName] of containers.map((c) => c.trim().split(/\s+/))) {
178+
try {
179+
const inspectResult = await x("docker", [
180+
"inspect",
181+
"--format",
182+
"{{ range $k, $v := .NetworkSettings.Networks }}{{ $k }}{{ end }}",
183+
containerId,
184+
]);
185+
186+
const networks = inspectResult.stdout.trim().split(/\s+/);
187+
188+
results.push({ containerId, containerName, networks });
189+
} catch (err) {
190+
console.error(`Failed to inspect container ${containerId}:`, err);
191+
results.push({ containerId, containerName: String(err), networks: [] });
192+
}
193+
}
194+
195+
return results;
196+
}
197+
198+
type DockerDiagnostics = {
199+
containers?: string[];
200+
networks?: string[];
201+
containerNetworks?: DockerContainerNetwork[];
202+
networkAttachments?: DockerNetworkAttachment[];
203+
};
204+
205+
async function getDockerDiagnostics(): Promise<DockerDiagnostics> {
206+
const [containers, networks, networkAttachments, containerNetworks] = await Promise.all([
207+
getDockerContainers(),
208+
getDockerNetworks(),
209+
getDockerNetworkAttachments(),
210+
getDockerContainerNetworks(),
211+
]);
212+
213+
return {
214+
containers,
215+
networks,
216+
containerNetworks,
217+
networkAttachments,
218+
};
219+
}
220+
73221
const network = async ({}, use: Use<StartedNetwork>) => {
74222
const network = await new Network().start();
75223
try {

pnpm-lock.yaml

Lines changed: 10 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)