-
-
Notifications
You must be signed in to change notification settings - Fork 164
/
docker-client.ts
180 lines (151 loc) · 5.31 KB
/
docker-client.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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import Dockerode, { DockerOptions, NetworkInspectInfo } from "dockerode";
import path from "path";
import { log } from "../logger";
import { Host } from "./types";
import { URL } from "url";
import { existsSync, promises as fs } from "fs";
import { runInContainer } from "./functions/run-in-container";
import { logSystemDiagnostics } from "../log-system-diagnostics";
import "../testcontainers-properties-file";
type DockerClient = {
host: Host;
dockerode: Dockerode;
};
const getDockerClient = async (): Promise<DockerClient> => {
const strategies: DockerClientStrategy[] = [
new ConfigurationStrategy(),
new UnixSocketStrategy(),
new NpipeSocketStrategy(),
];
for (const strategy of strategies) {
if (strategy.isApplicable()) {
log.debug(`Found applicable Docker client strategy: ${strategy.getName()}`);
const { uri, dockerode } = await strategy.initialise();
log.debug(`Testing Docker client strategy URI: ${uri}`);
if (await isDockerDaemonReachable(dockerode)) {
const host = await resolveHost(dockerode, uri);
log.info(`Using Docker client strategy: ${strategy.getName()}, Docker host: ${host}`);
logSystemDiagnostics(dockerode);
return { host, dockerode };
} else {
log.warn(`Docker client strategy ${strategy.getName()} is not reachable`);
}
}
}
throw new Error("No Docker client strategy found");
};
const isDockerDaemonReachable = async (dockerode: Dockerode): Promise<boolean> => {
try {
const response = await dockerode.ping();
return response.toString() === "OK";
} catch (err) {
log.warn(`Docker daemon is not reachable: ${err}`);
return false;
}
};
interface DockerClientStrategy {
isApplicable(): boolean;
initialise(): Promise<{ uri: string; dockerode: Dockerode }>;
getName(): string;
}
class ConfigurationStrategy implements DockerClientStrategy {
async initialise(): Promise<{ uri: string; dockerode: Dockerode }> {
const { DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH } = process.env;
const dockerOptions: DockerOptions = {};
const { pathname, hostname, port } = new URL(DOCKER_HOST!);
if (hostname !== "") {
dockerOptions.host = hostname;
dockerOptions.port = port;
} else {
dockerOptions.socketPath = pathname;
}
if (DOCKER_TLS_VERIFY === "1" && DOCKER_CERT_PATH !== undefined) {
dockerOptions.ca = await fs.readFile(path.resolve(DOCKER_CERT_PATH, "ca.pem"));
dockerOptions.cert = await fs.readFile(path.resolve(DOCKER_CERT_PATH, "cert.pem"));
dockerOptions.key = await fs.readFile(path.resolve(DOCKER_CERT_PATH, "key.pem"));
}
return {
uri: DOCKER_HOST!,
dockerode: new Dockerode(dockerOptions),
};
}
isApplicable(): boolean {
return process.env.DOCKER_HOST !== undefined;
}
getName(): string {
return "ConfigurationStrategy";
}
}
class UnixSocketStrategy implements DockerClientStrategy {
async initialise(): Promise<{ uri: string; dockerode: Dockerode }> {
return {
uri: "unix:///var/run/docker.sock",
dockerode: new Dockerode({ socketPath: "/var/run/docker.sock" }),
};
}
isApplicable(): boolean {
return process.platform === "linux" || process.platform === "darwin";
}
getName(): string {
return "UnixSocketStrategy";
}
}
class NpipeSocketStrategy implements DockerClientStrategy {
async initialise(): Promise<{ uri: string; dockerode: Dockerode }> {
return {
uri: "npipe:////./pipe/docker_engine",
dockerode: new Dockerode({ socketPath: "//./pipe/docker_engine" }),
};
}
isApplicable(): boolean {
return process.platform === "win32";
}
getName(): string {
return "NpipeSocketStrategy";
}
}
const resolveHost = async (dockerode: Dockerode, uri: string): Promise<string> => {
if (process.env.TESTCONTAINERS_HOST_OVERRIDE !== undefined) {
return process.env.TESTCONTAINERS_HOST_OVERRIDE;
}
const { protocol, hostname } = new URL(uri);
switch (protocol) {
case "http:":
case "https:":
case "tcp:":
return hostname;
case "unix:":
case "npipe:": {
if (isInContainer()) {
const gateway = await findGateway(dockerode);
if (gateway !== undefined) {
return gateway;
}
const defaultGateway = await findDefaultGateway(dockerode);
if (defaultGateway !== undefined) {
return defaultGateway;
}
}
return "localhost";
}
default:
throw new Error(`Unsupported protocol: ${protocol}`);
}
};
const findGateway = async (dockerode: Dockerode): Promise<string | undefined> => {
log.debug(`Checking gateway for Docker host`);
const inspectResult: NetworkInspectInfo = await dockerode.getNetwork("bridge").inspect();
return inspectResult?.IPAM?.Config?.find((config) => config.Gateway !== undefined)?.Gateway;
};
const findDefaultGateway = async (dockerode: Dockerode): Promise<string | undefined> => {
log.debug(`Checking default gateway for Docker host`);
return runInContainer(dockerode, "alpine:3.14", ["sh", "-c", "ip route|awk '/default/ { print $3 }'"]);
};
const isInContainer = () => existsSync("/.dockerenv");
let _dockerClient: Promise<DockerClient>;
export const dockerClient: () => Promise<DockerClient> = () => {
if (!_dockerClient) {
_dockerClient = getDockerClient();
}
return _dockerClient;
};