-
Notifications
You must be signed in to change notification settings - Fork 99
/
docker.ts
100 lines (90 loc) · 3.26 KB
/
docker.ts
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
88
89
90
91
92
93
94
95
96
97
98
99
100
import path from "path";
import { executeCommand } from "./helpers.js";
import Logger from "./logger.js";
let dockerInstalled = false;
const checkDockerInstallation = async () => {
if (dockerInstalled) return;
try {
await executeCommand("docker --version", { silent: true });
dockerInstalled = true;
} catch {
throw new Error("Docker is not installed. Download: https://www.docker.com/get-started/");
}
};
const getComposeCommandBase = (dockerComposePath: string, projectDir?: string) => {
return `docker compose -f ${dockerComposePath} --project-directory ${projectDir ?? path.dirname(dockerComposePath)}`;
};
const createComposeCommand =
(action: string) => async (dockerComposePath: string, projectDir?: string, additionalArgs?: string[]) => {
await checkDockerInstallation();
const baseCommand = getComposeCommandBase(dockerComposePath, projectDir);
const args = additionalArgs ? `${additionalArgs.join(" ")}` : "";
return await executeCommand(`${baseCommand} ${action} ${args}`.trim());
};
enum ContainerStatus {
Running = "running",
Exited = "exited",
Paused = "paused",
Restarting = "restarting",
Dead = "dead",
Unknown = "unknown",
}
type Container = { Name: string; State: ContainerStatus };
interface ContainerInfo {
name: string;
isRunning: boolean;
}
export const composeStatus = async (dockerComposePath: string, projectDir?: string): Promise<ContainerInfo[]> => {
await checkDockerInstallation();
let statusJson = (
await executeCommand(`${getComposeCommandBase(dockerComposePath, projectDir)} ps --format json --all`, {
silent: true,
})
).trim(); // trim to remove leading and trailing whitespace
// if no containers are mounted, docker compose returns an empty string
if (!statusJson.length) {
return [];
}
// on windows, docker compose returns json objects separated by newlines
if (statusJson.startsWith("{") && statusJson.endsWith("}")) {
statusJson = "[" + statusJson.split("\n").join(",") + "]";
}
try {
const containers = JSON.parse(statusJson) as Array<Container>;
return containers.map((container) => ({
name: container.Name,
isRunning: container.State === ContainerStatus.Running || container.State === ContainerStatus.Restarting,
}));
} catch (error) {
Logger.debug(`Failed to JSON.parse compose status ${dockerComposePath}: ${error?.toString()}`);
Logger.debug(statusJson);
return [];
}
};
export const composeLogs = async (
dockerComposePath: string,
projectDir?: string,
totalLines = 15
): Promise<string[]> => {
await checkDockerInstallation();
const response = (
await executeCommand(`${getComposeCommandBase(dockerComposePath, projectDir)} logs --tail=${totalLines}`, {
silent: true,
})
).trim(); // trim to remove leading and trailing whitespace
try {
return response.split("\n");
} catch (error) {
Logger.debug(`Failed to split compose logs ${dockerComposePath}: ${error?.toString()}`);
return [];
}
};
export const compose = {
build: createComposeCommand("build"),
create: createComposeCommand("create"),
up: createComposeCommand("up -d"),
stop: createComposeCommand("stop"),
down: createComposeCommand("down --rmi all --volumes --remove-orphans"),
logs: composeLogs,
status: composeStatus,
};