testcontainers 9.10.1 → 9.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +3 -1
- package/dist/src/docker/client/docker-client-types.d.ts +21 -0
- package/dist/src/docker/client/docker-client-types.js +2 -0
- package/dist/src/docker/client/docker-client.d.ts +1 -17
- package/dist/src/docker/client/docker-client.js +50 -23
- package/dist/src/docker/client/strategy/configuration-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/configuration-strategy.js +2 -4
- package/dist/src/docker/client/strategy/docker-client-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/npipe-socket-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/npipe-socket-strategy.js +1 -5
- package/dist/src/docker/client/strategy/rootless-unix-socket-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/rootless-unix-socket-strategy.js +1 -2
- package/dist/src/docker/client/strategy/testcontainers-host-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/testcontainers-host-strategy.js +2 -7
- package/dist/src/docker/client/strategy/unix-socket-strategy.d.ts +1 -1
- package/dist/src/docker/client/strategy/unix-socket-strategy.js +1 -5
- package/dist/src/docker/functions/container/create-container.d.ts +1 -1
- package/dist/src/docker/functions/container/create-container.js +2 -2
- package/dist/src/docker/functions/create-labels.d.ts +1 -2
- package/dist/src/docker/functions/create-labels.js +2 -3
- package/dist/src/docker/functions/image/build-image.d.ts +2 -1
- package/dist/src/docker/functions/image/build-image.js +2 -2
- package/dist/src/docker/functions/network/create-network.d.ts +1 -1
- package/dist/src/docker/functions/network/create-network.js +2 -2
- package/dist/src/docker/remote-docker-unix-socket-path.d.ts +2 -2
- package/dist/src/docker/types.d.ts +3 -0
- package/dist/src/docker-compose-environment/docker-compose-environment.js +2 -2
- package/dist/src/file-lock.d.ts +1 -0
- package/dist/src/file-lock.js +47 -0
- package/dist/src/generic-container/generic-container-builder.d.ts +4 -1
- package/dist/src/generic-container/generic-container-builder.js +4 -5
- package/dist/src/generic-container/generic-container.d.ts +8 -4
- package/dist/src/generic-container/generic-container.js +75 -72
- package/dist/src/index.d.ts +1 -1
- package/dist/src/modules/selenium/selenium-container.js +11 -8
- package/dist/src/network.js +3 -3
- package/dist/src/port-forwarder.js +2 -2
- package/dist/src/reaper.d.ts +5 -14
- package/dist/src/reaper.js +124 -114
- package/dist/src/test-container.d.ts +2 -1
- package/package.json +3 -1
- package/dist/src/docker/session-id.d.ts +0 -1
- package/dist/src/docker/session-id.js +0 -5
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import archiver from "archiver";
|
|
3
2
|
import { BoundPorts } from "../bound-ports";
|
|
4
3
|
import { PortWithOptionalBinding } from "../port";
|
|
5
4
|
import { PullPolicy } from "../pull-policy";
|
|
6
5
|
import { StartedTestContainer, TestContainer } from "../test-container";
|
|
7
6
|
import { WaitStrategy } from "../wait-strategy/wait-strategy";
|
|
8
|
-
import { BindMount, ContentToCopy, Environment, ExtraHost, FileToCopy, HealthCheck, Labels, ResourcesQuota, TmpFs, Ulimits } from "../docker/types";
|
|
7
|
+
import { BindMount, ContentToCopy, DirectoryToCopy, Environment, ExtraHost, FileToCopy, HealthCheck, Labels, ResourcesQuota, TmpFs, Ulimits } from "../docker/types";
|
|
9
8
|
import { CreateContainerOptions } from "../docker/functions/container/create-container";
|
|
10
9
|
import { InspectResult } from "../docker/functions/container/inspect-container";
|
|
11
10
|
import { GenericContainerBuilder } from "./generic-container-builder";
|
|
@@ -17,11 +16,13 @@ export declare class GenericContainer implements TestContainer {
|
|
|
17
16
|
protected opts: CreateContainerOptions;
|
|
18
17
|
protected startupTimeout?: number;
|
|
19
18
|
protected waitStrategy: WaitStrategy;
|
|
20
|
-
protected tarToCopy?: archiver.Archiver;
|
|
21
19
|
protected networkMode?: string;
|
|
22
20
|
protected networkAliases: string[];
|
|
23
21
|
protected pullPolicy: PullPolicy;
|
|
24
22
|
protected logConsumer?: (stream: Readable) => unknown;
|
|
23
|
+
protected filesToCopy: FileToCopy[];
|
|
24
|
+
protected directoriesToCopy: DirectoryToCopy[];
|
|
25
|
+
protected contentsToCopy: ContentToCopy[];
|
|
25
26
|
constructor(image: string);
|
|
26
27
|
/**
|
|
27
28
|
* @deprecated Since version 9.4.0. Will be removed in version 10.0.0. Use `beforeContainerStarted` instead.
|
|
@@ -31,8 +32,11 @@ export declare class GenericContainer implements TestContainer {
|
|
|
31
32
|
protected containerCreated?(containerId: string): Promise<void>;
|
|
32
33
|
protected containerStarting?(inspectResult: InspectResult, reused: boolean): Promise<void>;
|
|
33
34
|
start(): Promise<StartedTestContainer>;
|
|
35
|
+
private reuseOrStartContainer;
|
|
34
36
|
private reuseContainer;
|
|
35
37
|
private startContainer;
|
|
38
|
+
private connectContainerToPortForwarder;
|
|
39
|
+
private createArchiveToCopyToContainer;
|
|
36
40
|
/**
|
|
37
41
|
* @deprecated Since version 9.4.0. Will be removed in version 10.0.0. Use `containerStarted` instead.
|
|
38
42
|
*/
|
|
@@ -64,8 +68,8 @@ export declare class GenericContainer implements TestContainer {
|
|
|
64
68
|
withPullPolicy(pullPolicy: PullPolicy): this;
|
|
65
69
|
withIpcMode(ipcMode: string): this;
|
|
66
70
|
withCopyFilesToContainer(filesToCopy: FileToCopy[]): this;
|
|
71
|
+
withCopyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): this;
|
|
67
72
|
withCopyContentToContainer(contentsToCopy: ContentToCopy[]): this;
|
|
68
|
-
protected getTarToCopy(): archiver.Archiver;
|
|
69
73
|
withWorkingDir(workingDir: string): this;
|
|
70
74
|
withResourcesQuota({ memory, cpu }: ResourcesQuota): this;
|
|
71
75
|
withSharedMemorySize(bytes: number): this;
|
|
@@ -18,7 +18,6 @@ const async_lock_1 = __importDefault(require("async-lock"));
|
|
|
18
18
|
const bound_ports_1 = require("../bound-ports");
|
|
19
19
|
const logger_1 = require("../logger");
|
|
20
20
|
const pull_policy_1 = require("../pull-policy");
|
|
21
|
-
const reaper_1 = require("../reaper");
|
|
22
21
|
const docker_image_name_1 = require("../docker-image-name");
|
|
23
22
|
const port_forwarder_1 = require("../port-forwarder");
|
|
24
23
|
const pull_image_1 = require("../docker/functions/image/pull-image");
|
|
@@ -47,19 +46,19 @@ class GenericContainer {
|
|
|
47
46
|
this.waitStrategy = wait_1.Wait.forListeningPorts();
|
|
48
47
|
this.networkAliases = [];
|
|
49
48
|
this.pullPolicy = new pull_policy_1.DefaultPullPolicy();
|
|
49
|
+
this.filesToCopy = [];
|
|
50
|
+
this.directoriesToCopy = [];
|
|
51
|
+
this.contentsToCopy = [];
|
|
50
52
|
const imageName = docker_image_name_1.DockerImageName.fromString(image);
|
|
51
53
|
this.opts = (0, create_container_options_1.initCreateContainerOptions)(imageName, imageName.isReaper());
|
|
52
54
|
}
|
|
53
55
|
start() {
|
|
54
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
-
const
|
|
56
|
-
yield (0, pull_image_1.pullImage)(dockerode, info.dockerInfo.indexServerAddress, {
|
|
57
|
+
const dockerClient = yield (0, docker_client_1.getDockerClient)();
|
|
58
|
+
yield (0, pull_image_1.pullImage)(dockerClient.dockerode, dockerClient.info.dockerInfo.indexServerAddress, {
|
|
57
59
|
imageName: this.opts.imageName,
|
|
58
60
|
force: this.pullPolicy.shouldPull(),
|
|
59
61
|
});
|
|
60
|
-
if (!this.opts.reusable && !this.opts.imageName.isReaper()) {
|
|
61
|
-
yield reaper_1.ReaperInstance.getInstance();
|
|
62
|
-
}
|
|
63
62
|
if (this.beforeContainerStarted) {
|
|
64
63
|
yield this.beforeContainerStarted();
|
|
65
64
|
}
|
|
@@ -70,41 +69,33 @@ class GenericContainer {
|
|
|
70
69
|
const portForwarder = yield port_forwarder_1.PortForwarderInstance.getInstance();
|
|
71
70
|
this.opts.extraHosts.push({ host: "host.testcontainers.internal", ipAddress: portForwarder.getIpAddress() });
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
this.opts.networkMode = undefined;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
this.opts.networkMode = this.networkMode;
|
|
78
|
-
}
|
|
72
|
+
this.opts.networkMode = this.networkAliases.length > 0 ? undefined : this.networkMode;
|
|
79
73
|
if (this.opts.reusable) {
|
|
80
|
-
|
|
81
|
-
this.opts.labels = Object.assign(Object.assign({}, this.opts.labels), { [labels_1.LABEL_TESTCONTAINERS_CONTAINER_HASH]: containerHash });
|
|
82
|
-
logger_1.log.debug(`Container reuse has been enabled with hash "${containerHash}"`);
|
|
83
|
-
// We might have several async processes try to create a reusable container
|
|
84
|
-
// at once, to avoid possibly creating too many of these, use a lock
|
|
85
|
-
// on the containerHash, this ensures that only single reusable instance is created
|
|
86
|
-
return reusableContainerCreationLock.acquire(containerHash, () => __awaiter(this, void 0, void 0, function* () {
|
|
87
|
-
const container = yield (0, get_container_1.getContainerByHash)(containerHash);
|
|
88
|
-
if (container !== undefined) {
|
|
89
|
-
logger_1.log.debug(`Found container to reuse with hash "${containerHash}"`, { containerId: container.id });
|
|
90
|
-
return this.reuseContainer(container);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
logger_1.log.debug("No container found to reuse");
|
|
94
|
-
return this.startContainer(this.opts);
|
|
95
|
-
}
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
return this.startContainer(this.opts);
|
|
74
|
+
return this.reuseOrStartContainer(dockerClient);
|
|
100
75
|
}
|
|
76
|
+
return this.startContainer(dockerClient, this.opts);
|
|
101
77
|
});
|
|
102
78
|
}
|
|
103
|
-
|
|
79
|
+
reuseOrStartContainer(dockerClient) {
|
|
80
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
81
|
+
const containerHash = (0, hash_1.hash)(JSON.stringify(this.opts));
|
|
82
|
+
this.opts.labels = Object.assign(Object.assign({}, this.opts.labels), { [labels_1.LABEL_TESTCONTAINERS_CONTAINER_HASH]: containerHash });
|
|
83
|
+
logger_1.log.debug(`Container reuse has been enabled with hash "${containerHash}"`);
|
|
84
|
+
return reusableContainerCreationLock.acquire(containerHash, () => __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const container = yield (0, get_container_1.getContainerByHash)(containerHash);
|
|
86
|
+
if (container !== undefined) {
|
|
87
|
+
logger_1.log.debug(`Found container to reuse with hash "${containerHash}"`, { containerId: container.id });
|
|
88
|
+
return this.reuseContainer(dockerClient, container);
|
|
89
|
+
}
|
|
90
|
+
logger_1.log.debug("No container found to reuse");
|
|
91
|
+
return this.startContainer(dockerClient, this.opts);
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
reuseContainer(dockerClient, container) {
|
|
104
96
|
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
-
const { host, hostIps } = yield (0, docker_client_1.getDockerClient)();
|
|
106
97
|
const inspectResult = yield (0, inspect_container_1.inspectContainer)(container);
|
|
107
|
-
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(hostIps, inspectResult).filter(this.opts.exposedPorts);
|
|
98
|
+
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(dockerClient.hostIps, inspectResult).filter(this.opts.exposedPorts);
|
|
108
99
|
if (this.startupTimeout !== undefined) {
|
|
109
100
|
this.waitStrategy.withStartupTimeout(this.startupTimeout);
|
|
110
101
|
}
|
|
@@ -112,7 +103,7 @@ class GenericContainer {
|
|
|
112
103
|
yield this.containerStarting(inspectResult, true);
|
|
113
104
|
}
|
|
114
105
|
yield (0, wait_for_container_1.waitForContainer)(container, this.waitStrategy, boundPorts);
|
|
115
|
-
const startedContainer = new started_generic_container_1.StartedGenericContainer(container, host, inspectResult, boundPorts, inspectResult.name, this.waitStrategy);
|
|
106
|
+
const startedContainer = new started_generic_container_1.StartedGenericContainer(container, dockerClient.host, inspectResult, boundPorts, inspectResult.name, this.waitStrategy);
|
|
116
107
|
if (this.containerStarted) {
|
|
117
108
|
yield this.containerStarted(startedContainer, inspectResult, true);
|
|
118
109
|
}
|
|
@@ -122,20 +113,11 @@ class GenericContainer {
|
|
|
122
113
|
return startedContainer;
|
|
123
114
|
});
|
|
124
115
|
}
|
|
125
|
-
startContainer(createContainerOptions) {
|
|
116
|
+
startContainer(dockerClient, createContainerOptions) {
|
|
126
117
|
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
-
const container = yield (0, create_container_1.createContainer)(createContainerOptions);
|
|
118
|
+
const container = yield (0, create_container_1.createContainer)(dockerClient.sessionId, createContainerOptions);
|
|
128
119
|
if (!this.opts.imageName.isHelperContainer() && port_forwarder_1.PortForwarderInstance.isRunning()) {
|
|
129
|
-
|
|
130
|
-
const portForwarderNetworkId = portForwarder.getNetworkId();
|
|
131
|
-
const excludedNetworks = [portForwarderNetworkId, "none", "host"];
|
|
132
|
-
if (!this.networkMode || !excludedNetworks.includes(this.networkMode)) {
|
|
133
|
-
yield (0, connect_network_1.connectNetwork)({
|
|
134
|
-
containerId: container.id,
|
|
135
|
-
networkId: portForwarderNetworkId,
|
|
136
|
-
networkAliases: [],
|
|
137
|
-
});
|
|
138
|
-
}
|
|
120
|
+
yield this.connectContainerToPortForwarder(container);
|
|
139
121
|
}
|
|
140
122
|
if (this.networkMode && this.networkAliases.length > 0) {
|
|
141
123
|
yield (0, connect_network_1.connectNetwork)({
|
|
@@ -144,9 +126,10 @@ class GenericContainer {
|
|
|
144
126
|
networkAliases: this.networkAliases,
|
|
145
127
|
});
|
|
146
128
|
}
|
|
147
|
-
if (this.
|
|
148
|
-
this.
|
|
149
|
-
|
|
129
|
+
if (this.filesToCopy.length > 0 || this.directoriesToCopy.length > 0 || this.contentsToCopy.length > 0) {
|
|
130
|
+
const archive = this.createArchiveToCopyToContainer();
|
|
131
|
+
archive.finalize();
|
|
132
|
+
yield (0, put_container_archive_1.putContainerArchive)({ container, stream: archive, containerPath: "/" });
|
|
150
133
|
}
|
|
151
134
|
logger_1.log.info(`Starting container for image "${this.opts.imageName}"...`, { containerId: container.id });
|
|
152
135
|
if (this.containerCreated) {
|
|
@@ -154,19 +137,17 @@ class GenericContainer {
|
|
|
154
137
|
}
|
|
155
138
|
yield (0, start_container_1.startContainer)(container);
|
|
156
139
|
logger_1.log.info(`Started container for image "${this.opts.imageName}"`, { containerId: container.id });
|
|
157
|
-
const { host, hostIps } = yield (0, docker_client_1.getDockerClient)();
|
|
158
140
|
const inspectResult = yield (0, inspect_container_1.inspectContainer)(container);
|
|
159
|
-
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(hostIps, inspectResult).filter(this.opts.exposedPorts);
|
|
141
|
+
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(dockerClient.hostIps, inspectResult).filter(this.opts.exposedPorts);
|
|
160
142
|
if (this.startupTimeout !== undefined) {
|
|
161
143
|
this.waitStrategy.withStartupTimeout(this.startupTimeout);
|
|
162
144
|
}
|
|
163
145
|
if (logger_1.containerLog.enabled() || this.logConsumer !== undefined) {
|
|
164
|
-
const logStream = yield (0, container_logs_1.containerLogs)(container);
|
|
165
146
|
if (this.logConsumer !== undefined) {
|
|
166
|
-
this.logConsumer(
|
|
147
|
+
this.logConsumer(yield (0, container_logs_1.containerLogs)(container));
|
|
167
148
|
}
|
|
168
149
|
if (logger_1.containerLog.enabled()) {
|
|
169
|
-
|
|
150
|
+
(yield (0, container_logs_1.containerLogs)(container))
|
|
170
151
|
.on("data", (data) => logger_1.containerLog.trace(data.trim(), { containerId: container.id }))
|
|
171
152
|
.on("err", (data) => logger_1.containerLog.error(data.trim(), { containerId: container.id }));
|
|
172
153
|
}
|
|
@@ -175,7 +156,7 @@ class GenericContainer {
|
|
|
175
156
|
yield this.containerStarting(inspectResult, false);
|
|
176
157
|
}
|
|
177
158
|
yield (0, wait_for_container_1.waitForContainer)(container, this.waitStrategy, boundPorts);
|
|
178
|
-
const startedContainer = new started_generic_container_1.StartedGenericContainer(container, host, inspectResult, boundPorts, inspectResult.name, this.waitStrategy);
|
|
159
|
+
const startedContainer = new started_generic_container_1.StartedGenericContainer(container, dockerClient.host, inspectResult, boundPorts, inspectResult.name, this.waitStrategy);
|
|
179
160
|
if (this.containerStarted) {
|
|
180
161
|
yield this.containerStarted(startedContainer, inspectResult, false);
|
|
181
162
|
}
|
|
@@ -185,6 +166,33 @@ class GenericContainer {
|
|
|
185
166
|
return startedContainer;
|
|
186
167
|
});
|
|
187
168
|
}
|
|
169
|
+
connectContainerToPortForwarder(container) {
|
|
170
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
const portForwarder = yield port_forwarder_1.PortForwarderInstance.getInstance();
|
|
172
|
+
const portForwarderNetworkId = portForwarder.getNetworkId();
|
|
173
|
+
const excludedNetworks = [portForwarderNetworkId, "none", "host"];
|
|
174
|
+
if (!this.networkMode || !excludedNetworks.includes(this.networkMode)) {
|
|
175
|
+
yield (0, connect_network_1.connectNetwork)({
|
|
176
|
+
containerId: container.id,
|
|
177
|
+
networkId: portForwarderNetworkId,
|
|
178
|
+
networkAliases: [],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
createArchiveToCopyToContainer() {
|
|
184
|
+
const tar = (0, archiver_1.default)("tar");
|
|
185
|
+
for (const { source, target, mode } of this.filesToCopy) {
|
|
186
|
+
tar.file(source, { name: target, mode });
|
|
187
|
+
}
|
|
188
|
+
for (const { source, target, mode } of this.directoriesToCopy) {
|
|
189
|
+
tar.directory(source, target, { mode });
|
|
190
|
+
}
|
|
191
|
+
for (const { content, target, mode } of this.contentsToCopy) {
|
|
192
|
+
tar.append(content, { name: target, mode });
|
|
193
|
+
}
|
|
194
|
+
return tar;
|
|
195
|
+
}
|
|
188
196
|
get hasExposedPorts() {
|
|
189
197
|
return this.opts.exposedPorts.length !== 0;
|
|
190
198
|
}
|
|
@@ -287,31 +295,26 @@ class GenericContainer {
|
|
|
287
295
|
return this;
|
|
288
296
|
}
|
|
289
297
|
withCopyFilesToContainer(filesToCopy) {
|
|
290
|
-
|
|
291
|
-
filesToCopy.forEach(({ source, target }) => tar.file(source, { name: target }));
|
|
298
|
+
this.filesToCopy = [...this.filesToCopy, ...filesToCopy];
|
|
292
299
|
return this;
|
|
293
300
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
contentsToCopy.forEach(({ content, target }) => tar.append(content, { name: target }));
|
|
301
|
+
withCopyDirectoriesToContainer(directoriesToCopy) {
|
|
302
|
+
this.directoriesToCopy = [...this.directoriesToCopy, ...directoriesToCopy];
|
|
297
303
|
return this;
|
|
298
304
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
return this.tarToCopy;
|
|
305
|
+
withCopyContentToContainer(contentsToCopy) {
|
|
306
|
+
this.contentsToCopy = [...this.contentsToCopy, ...contentsToCopy];
|
|
307
|
+
return this;
|
|
304
308
|
}
|
|
305
309
|
withWorkingDir(workingDir) {
|
|
306
310
|
this.opts.workingDir = workingDir;
|
|
307
311
|
return this;
|
|
308
312
|
}
|
|
309
313
|
withResourcesQuota({ memory, cpu }) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.opts.resourcesQuota = { memory: ram, cpu: cpuQuota };
|
|
314
|
+
this.opts.resourcesQuota = {
|
|
315
|
+
memory: memory !== undefined ? memory * Math.pow(1024, 3) : undefined,
|
|
316
|
+
cpu: cpu !== undefined ? cpu * Math.pow(10, 9) : undefined,
|
|
317
|
+
};
|
|
315
318
|
return this;
|
|
316
319
|
}
|
|
317
320
|
withSharedMemorySize(bytes) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { TestContainer, StartedTestContainer, StoppedTestContainer } from "./test-container";
|
|
2
2
|
export { GenericContainer } from "./generic-container/generic-container";
|
|
3
|
-
export { GenericContainerBuilder } from "./generic-container/generic-container-builder";
|
|
3
|
+
export { GenericContainerBuilder, BuildOptions } from "./generic-container/generic-container-builder";
|
|
4
4
|
export { TestContainers } from "./test-containers";
|
|
5
5
|
export { DockerComposeEnvironment } from "./docker-compose-environment/docker-compose-environment";
|
|
6
6
|
export { StartedDockerComposeEnvironment } from "./docker-compose-environment/started-docker-compose-environment";
|
|
@@ -35,14 +35,17 @@ class SeleniumContainer extends generic_container_1.GenericContainer {
|
|
|
35
35
|
return __awaiter(this, void 0, void 0, function* () {
|
|
36
36
|
this.withExposedPorts(SELENIUM_PORT, VNC_PORT)
|
|
37
37
|
.withSharedMemorySize(2 * 1024 * 1024 * 1024)
|
|
38
|
-
.withWaitStrategy(wait_1.Wait.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
.withWaitStrategy(wait_1.Wait.forAll([
|
|
39
|
+
wait_1.Wait.forListeningPorts(),
|
|
40
|
+
wait_1.Wait.forHttp("/wd/hub/status", SELENIUM_PORT).forResponsePredicate((response) => {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(response).value.ready;
|
|
43
|
+
}
|
|
44
|
+
catch (_a) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
]));
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
withRecording() {
|
package/dist/src/network.js
CHANGED
|
@@ -12,9 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.StoppedNetwork = exports.StartedNetwork = exports.Network = void 0;
|
|
13
13
|
const uuid_1 = require("./uuid");
|
|
14
14
|
const logger_1 = require("./logger");
|
|
15
|
-
const reaper_1 = require("./reaper");
|
|
16
15
|
const create_network_1 = require("./docker/functions/network/create-network");
|
|
17
16
|
const remove_network_1 = require("./docker/functions/network/remove-network");
|
|
17
|
+
const docker_client_1 = require("./docker/client/docker-client");
|
|
18
18
|
class Network {
|
|
19
19
|
constructor(createNetworkOptions = {}, uuid = new uuid_1.RandomUuid()) {
|
|
20
20
|
this.createNetworkOptions = createNetworkOptions;
|
|
@@ -25,9 +25,9 @@ class Network {
|
|
|
25
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
26
|
const name = this.uuid.nextUuid();
|
|
27
27
|
const options = Object.assign({ name, driver: "bridge", checkDuplicate: true, internal: false, attachable: false, ingress: false, enableIPv6: false }, this.createNetworkOptions);
|
|
28
|
-
yield reaper_1.ReaperInstance.getInstance();
|
|
29
28
|
logger_1.log.info(`Starting network "${name}"...`);
|
|
30
|
-
const
|
|
29
|
+
const { sessionId } = yield (0, docker_client_1.getDockerClient)();
|
|
30
|
+
const id = yield (0, create_network_1.createNetwork)(sessionId, options);
|
|
31
31
|
logger_1.log.info(`Started network "${name}" with ID "${id}"`);
|
|
32
32
|
return new StartedNetwork(id, options);
|
|
33
33
|
});
|
|
@@ -14,7 +14,6 @@ const ssh_remote_port_forward_1 = require("ssh-remote-port-forward");
|
|
|
14
14
|
const logger_1 = require("./logger");
|
|
15
15
|
const generic_container_1 = require("./generic-container/generic-container");
|
|
16
16
|
const uuid_1 = require("./uuid");
|
|
17
|
-
const session_id_1 = require("./docker/session-id");
|
|
18
17
|
const docker_client_1 = require("./docker/client/docker-client");
|
|
19
18
|
const images_1 = require("./images");
|
|
20
19
|
class PortForwarder {
|
|
@@ -60,8 +59,9 @@ class PortForwarderInstance {
|
|
|
60
59
|
const containerPort = process.env["TESTCONTAINERS_SSHD_PORT"]
|
|
61
60
|
? { container: 22, host: Number(process.env["TESTCONTAINERS_SSHD_PORT"]) }
|
|
62
61
|
: 22;
|
|
62
|
+
const { sessionId } = yield (0, docker_client_1.getDockerClient)();
|
|
63
63
|
const container = yield new generic_container_1.GenericContainer(images_1.SSHD_IMAGE)
|
|
64
|
-
.withName(`testcontainers-port-forwarder-${
|
|
64
|
+
.withName(`testcontainers-port-forwarder-${sessionId}`)
|
|
65
65
|
.withExposedPorts(containerPort)
|
|
66
66
|
.withEnvironment({ PASSWORD: password })
|
|
67
67
|
.withCommand([
|
package/dist/src/reaper.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare class ReaperInstance {
|
|
7
|
-
private static instance?;
|
|
8
|
-
static getInstance(): Promise<Reaper>;
|
|
9
|
-
static stopInstance(): Promise<void>;
|
|
10
|
-
private static isEnabled;
|
|
11
|
-
private static isPrivileged;
|
|
12
|
-
private static createDisabledInstance;
|
|
13
|
-
private static createRealInstance;
|
|
14
|
-
}
|
|
1
|
+
import { PartialDockerClient } from "./docker/client/docker-client-types";
|
|
2
|
+
import Dockerode from "dockerode";
|
|
3
|
+
export declare function registerSessionIdForCleanup(sessionId: string): Promise<void>;
|
|
4
|
+
export declare function registerComposeProjectForCleanup(project: string): Promise<void>;
|
|
5
|
+
export declare function startReaper(dockerClient: PartialDockerClient, sessionId: string, existingReaperContainer?: Dockerode.ContainerInfo): Promise<void>;
|
package/dist/src/reaper.js
CHANGED
|
@@ -9,139 +9,149 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.startReaper = exports.registerComposeProjectForCleanup = exports.registerSessionIdForCleanup = void 0;
|
|
13
13
|
const net_1 = require("net");
|
|
14
14
|
const logger_1 = require("./logger");
|
|
15
15
|
const generic_container_1 = require("./generic-container/generic-container");
|
|
16
|
-
const session_id_1 = require("./docker/session-id");
|
|
17
16
|
const images_1 = require("./images");
|
|
18
|
-
const port_1 = require("./port");
|
|
19
17
|
const labels_1 = require("./labels");
|
|
20
18
|
const wait_1 = require("./wait-strategy/wait");
|
|
21
19
|
const retry_strategy_1 = require("./retry-strategy");
|
|
22
20
|
const remote_docker_unix_socket_path_1 = require("./docker/remote-docker-unix-socket-path");
|
|
23
21
|
const docker_client_1 = require("./docker/client/docker-client");
|
|
22
|
+
const isReaperEnabled = process.env.TESTCONTAINERS_RYUK_DISABLED !== "true";
|
|
23
|
+
let reaper;
|
|
24
|
+
function registerSessionIdForCleanup(sessionId) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
if (!reaper) {
|
|
27
|
+
yield (0, docker_client_1.getDockerClient)();
|
|
28
|
+
}
|
|
29
|
+
(yield reaper).addSession(sessionId);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
exports.registerSessionIdForCleanup = registerSessionIdForCleanup;
|
|
33
|
+
function registerComposeProjectForCleanup(project) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
if (!reaper) {
|
|
36
|
+
yield (0, docker_client_1.getDockerClient)();
|
|
37
|
+
}
|
|
38
|
+
(yield reaper).addProject(project);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
exports.registerComposeProjectForCleanup = registerComposeProjectForCleanup;
|
|
42
|
+
function startReaper(dockerClient, sessionId, existingReaperContainer) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
reaper = !isReaperEnabled
|
|
45
|
+
? yield createDisabledReaper()
|
|
46
|
+
: existingReaperContainer
|
|
47
|
+
? yield useExistingReaper(dockerClient, sessionId, existingReaperContainer)
|
|
48
|
+
: yield createNewReaper(dockerClient, sessionId);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
exports.startReaper = startReaper;
|
|
52
|
+
function createDisabledReaper() {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
return new DisabledReaper();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function useExistingReaper(dockerClient, sessionId, reaperContainer) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
logger_1.log.debug(`Reusing existing Reaper for session "${sessionId}"...`);
|
|
60
|
+
const reaperPort = getReaperPublicPort(reaperContainer);
|
|
61
|
+
const socket = yield connectToReaperSocket(dockerClient.host, reaperPort, sessionId, reaperContainer.Id);
|
|
62
|
+
return new RealReaper(sessionId, socket);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function getReaperPublicPort(reaperContainer) {
|
|
66
|
+
var _a;
|
|
67
|
+
const port = (_a = reaperContainer.Ports.find((port) => port.PrivatePort == 8080)) === null || _a === void 0 ? void 0 : _a.PublicPort;
|
|
68
|
+
if (!port) {
|
|
69
|
+
throw new Error("Expected Reaper to map exposed port 8080");
|
|
70
|
+
}
|
|
71
|
+
return port;
|
|
72
|
+
}
|
|
73
|
+
function createNewReaper(dockerClient, sessionId) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const remoteDockerUnixSocketPath = (0, remote_docker_unix_socket_path_1.getRemoteDockerUnixSocketPath)(dockerClient);
|
|
76
|
+
logger_1.log.debug(`Creating new Reaper for session "${sessionId}" with socket path "${remoteDockerUnixSocketPath}"...`);
|
|
77
|
+
const startedContainer = yield createAndStartReaperContainer(sessionId, remoteDockerUnixSocketPath);
|
|
78
|
+
const containerId = startedContainer.getId();
|
|
79
|
+
const host = startedContainer.getHost();
|
|
80
|
+
const port = startedContainer.getMappedPort(8080);
|
|
81
|
+
const socket = yield connectToReaperSocket(host, port, sessionId, containerId);
|
|
82
|
+
return new RealReaper(sessionId, socket);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function createAndStartReaperContainer(sessionId, socketPath) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const exposedPort = process.env["TESTCONTAINERS_RYUK_PORT"]
|
|
88
|
+
? { container: 8080, host: Number(process.env["TESTCONTAINERS_RYUK_PORT"]) }
|
|
89
|
+
: 8080;
|
|
90
|
+
const container = new generic_container_1.GenericContainer(images_1.REAPER_IMAGE)
|
|
91
|
+
.withName(`testcontainers-ryuk-${sessionId}`)
|
|
92
|
+
.withExposedPorts(exposedPort)
|
|
93
|
+
.withBindMounts([{ source: socketPath, target: "/var/run/docker.sock" }])
|
|
94
|
+
.withWaitStrategy(wait_1.Wait.forLogMessage(/.+ Started!/));
|
|
95
|
+
if (process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true") {
|
|
96
|
+
container.withPrivilegedMode();
|
|
97
|
+
}
|
|
98
|
+
return yield container.start();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function connectToReaperSocket(host, port, sessionId, containerId) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
const retryResult = yield new retry_strategy_1.IntervalRetryStrategy(1000).retryUntil((attempt) => {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
logger_1.log.debug(`Connecting to Reaper (attempt ${attempt + 1}) on "${host}:${port}"...`, { containerId });
|
|
106
|
+
const socket = new net_1.Socket();
|
|
107
|
+
socket
|
|
108
|
+
.unref()
|
|
109
|
+
.on("timeout", () => logger_1.log.error(`Reaper ${containerId} socket timed out`))
|
|
110
|
+
.on("error", (err) => logger_1.log.error(`Reaper ${containerId} socket error: ${err}`))
|
|
111
|
+
.on("close", (hadError) => {
|
|
112
|
+
if (hadError) {
|
|
113
|
+
logger_1.log.error(`Connection to Reaper closed with error`, { containerId });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
logger_1.log.warn(`Connection to Reaper closed`, { containerId });
|
|
117
|
+
}
|
|
118
|
+
resolve(undefined);
|
|
119
|
+
})
|
|
120
|
+
.connect(port, host, () => {
|
|
121
|
+
logger_1.log.debug(`Connected to Reaper`, { containerId });
|
|
122
|
+
resolve(socket);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}, (result) => result !== undefined, () => {
|
|
126
|
+
const message = `Failed to connect to Reaper`;
|
|
127
|
+
logger_1.log.error(message, { containerId });
|
|
128
|
+
return new Error(message);
|
|
129
|
+
}, 4000);
|
|
130
|
+
if (retryResult instanceof net_1.Socket) {
|
|
131
|
+
return retryResult;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
throw retryResult;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
24
138
|
class RealReaper {
|
|
25
|
-
constructor(
|
|
26
|
-
this.
|
|
139
|
+
constructor(sessionId, socket) {
|
|
140
|
+
this.sessionId = sessionId;
|
|
27
141
|
this.socket = socket;
|
|
28
142
|
}
|
|
143
|
+
addSession(sessionId) {
|
|
144
|
+
this.socket.write(`label=${labels_1.LABEL_TESTCONTAINERS_SESSION_ID}=${sessionId}\r\n`);
|
|
145
|
+
}
|
|
29
146
|
addProject(projectName) {
|
|
30
147
|
this.socket.write(`label=com.docker.compose.project=${projectName}\r\n`);
|
|
31
148
|
}
|
|
32
|
-
getContainerId() {
|
|
33
|
-
return this.container.getId();
|
|
34
|
-
}
|
|
35
|
-
stop() {
|
|
36
|
-
this.socket.end();
|
|
37
|
-
}
|
|
38
149
|
}
|
|
39
150
|
class DisabledReaper {
|
|
40
|
-
|
|
151
|
+
addSession() {
|
|
41
152
|
// noop
|
|
42
153
|
}
|
|
43
|
-
|
|
154
|
+
addProject() {
|
|
44
155
|
// noop
|
|
45
156
|
}
|
|
46
|
-
getContainerId() {
|
|
47
|
-
return "";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
class ReaperInstance {
|
|
51
|
-
static getInstance() {
|
|
52
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
-
if (!this.instance) {
|
|
54
|
-
if (this.isEnabled()) {
|
|
55
|
-
this.instance = this.createRealInstance();
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
this.instance = this.createDisabledInstance();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return this.instance;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
static stopInstance() {
|
|
65
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
-
if (this.instance) {
|
|
67
|
-
const reaper = yield this.instance;
|
|
68
|
-
reaper.stop();
|
|
69
|
-
this.instance = undefined;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
static isEnabled() {
|
|
74
|
-
return process.env.TESTCONTAINERS_RYUK_DISABLED !== "true";
|
|
75
|
-
}
|
|
76
|
-
static isPrivileged() {
|
|
77
|
-
return process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true";
|
|
78
|
-
}
|
|
79
|
-
static createDisabledInstance() {
|
|
80
|
-
logger_1.log.debug(`Not creating new Reaper for session "${session_id_1.sessionId}"`);
|
|
81
|
-
return Promise.resolve(new DisabledReaper());
|
|
82
|
-
}
|
|
83
|
-
static createRealInstance() {
|
|
84
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
const containerPort = process.env["TESTCONTAINERS_RYUK_PORT"]
|
|
86
|
-
? { container: 8080, host: Number(process.env["TESTCONTAINERS_RYUK_PORT"]) }
|
|
87
|
-
: 8080;
|
|
88
|
-
const dockerClient = yield (0, docker_client_1.getDockerClient)();
|
|
89
|
-
const remoteDockerUnixSocketPath = (0, remote_docker_unix_socket_path_1.getRemoteDockerUnixSocketPath)(dockerClient);
|
|
90
|
-
logger_1.log.debug(`Creating new Reaper for session "${session_id_1.sessionId}" with socket path "${remoteDockerUnixSocketPath}"...`);
|
|
91
|
-
const container = new generic_container_1.GenericContainer(images_1.REAPER_IMAGE)
|
|
92
|
-
.withName(`testcontainers-ryuk-${session_id_1.sessionId}`)
|
|
93
|
-
.withExposedPorts(containerPort)
|
|
94
|
-
.withBindMounts([
|
|
95
|
-
{
|
|
96
|
-
source: remoteDockerUnixSocketPath,
|
|
97
|
-
target: "/var/run/docker.sock",
|
|
98
|
-
},
|
|
99
|
-
])
|
|
100
|
-
.withWaitStrategy(wait_1.Wait.forLogMessage(/.+ Started!/));
|
|
101
|
-
if (this.isPrivileged()) {
|
|
102
|
-
container.withPrivilegedMode();
|
|
103
|
-
}
|
|
104
|
-
const startedContainer = yield container.start();
|
|
105
|
-
const containerId = startedContainer.getId();
|
|
106
|
-
const host = startedContainer.getHost();
|
|
107
|
-
const port = startedContainer.getMappedPort(8080);
|
|
108
|
-
const retryStrategy = new retry_strategy_1.IntervalRetryStrategy(1000);
|
|
109
|
-
const retryResult = yield retryStrategy.retryUntil((attempt) => {
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
logger_1.log.debug(`Connecting to Reaper (attempt ${attempt + 1}) on "${host}:${port}"...`, { containerId });
|
|
112
|
-
const socket = new net_1.Socket();
|
|
113
|
-
socket
|
|
114
|
-
.unref()
|
|
115
|
-
.on("timeout", () => logger_1.log.error(`Reaper ${containerId} socket timed out`))
|
|
116
|
-
.on("error", (err) => logger_1.log.error(`Reaper ${containerId} socket error: ${err}`))
|
|
117
|
-
.on("close", (hadError) => {
|
|
118
|
-
if (hadError) {
|
|
119
|
-
logger_1.log.error(`Connection to Reaper closed with error`, { containerId });
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
logger_1.log.warn(`Connection to Reaper closed`, { containerId });
|
|
123
|
-
}
|
|
124
|
-
resolve(undefined);
|
|
125
|
-
})
|
|
126
|
-
.connect((0, port_1.getContainerPort)(port), host, () => {
|
|
127
|
-
logger_1.log.debug(`Connected to Reaper`, { containerId });
|
|
128
|
-
socket.write(`label=${labels_1.LABEL_TESTCONTAINERS_SESSION_ID}=${session_id_1.sessionId}\r\n`);
|
|
129
|
-
const reaper = new RealReaper(startedContainer, socket);
|
|
130
|
-
resolve(reaper);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
}, (result) => result !== undefined, () => {
|
|
134
|
-
const message = `Failed to connect to Reaper`;
|
|
135
|
-
logger_1.log.error(message, { containerId });
|
|
136
|
-
return new Error(message);
|
|
137
|
-
}, 4000);
|
|
138
|
-
if (retryResult instanceof RealReaper) {
|
|
139
|
-
return retryResult;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
throw retryResult;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
157
|
}
|
|
147
|
-
exports.ReaperInstance = ReaperInstance;
|