testcontainers 11.14.0 → 12.0.1
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/build/common/file-lock.js +2 -35
- package/build/docker-compose-environment/docker-compose-environment.js +7 -5
- package/build/generic-container/generic-container.d.ts +4 -3
- package/build/generic-container/generic-container.js +23 -12
- package/build/reaper/reaper.d.ts +8 -1
- package/build/reaper/reaper.js +14 -5
- package/build/utils/map-inspect-result.js +4 -3
- package/build/utils/port-generator.js +1 -34
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/build/wait-strategies/health-check-wait-strategy.js +2 -1
- package/build/wait-strategies/utils/health-check.d.ts +12 -0
- package/build/wait-strategies/utils/health-check.js +63 -0
- package/build/wait-strategies/utils/wait-strategy-selector.d.ts +13 -0
- package/build/wait-strategies/utils/wait-strategy-selector.js +37 -0
- package/package.json +4 -4
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -40,6 +7,7 @@ exports.withFileLock = withFileLock;
|
|
|
40
7
|
const promises_1 = require("fs/promises");
|
|
41
8
|
const path_1 = __importDefault(require("path"));
|
|
42
9
|
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
10
|
+
const tmp_1 = __importDefault(require("tmp"));
|
|
43
11
|
const logger_1 = require("./logger");
|
|
44
12
|
async function withFileLock(fileName, fn) {
|
|
45
13
|
const file = await createEmptyTmpFile(fileName);
|
|
@@ -61,8 +29,7 @@ async function withFileLock(fileName, fn) {
|
|
|
61
29
|
}
|
|
62
30
|
}
|
|
63
31
|
async function createEmptyTmpFile(fileName) {
|
|
64
|
-
const
|
|
65
|
-
const file = path_1.default.resolve(tmp.tmpdir, fileName);
|
|
32
|
+
const file = path_1.default.resolve(tmp_1.default.tmpdir, fileName);
|
|
66
33
|
await (0, promises_1.writeFile)(file, "");
|
|
67
34
|
return file;
|
|
68
35
|
}
|
|
@@ -8,7 +8,7 @@ const reaper_1 = require("../reaper/reaper");
|
|
|
8
8
|
const bound_ports_1 = require("../utils/bound-ports");
|
|
9
9
|
const map_inspect_result_1 = require("../utils/map-inspect-result");
|
|
10
10
|
const pull_policy_1 = require("../utils/pull-policy");
|
|
11
|
-
const
|
|
11
|
+
const wait_strategy_selector_1 = require("../wait-strategies/utils/wait-strategy-selector");
|
|
12
12
|
const wait_for_container_1 = require("../wait-strategies/wait-for-container");
|
|
13
13
|
const started_docker_compose_environment_1 = require("./started-docker-compose-environment");
|
|
14
14
|
class DockerComposeEnvironment {
|
|
@@ -22,7 +22,7 @@ class DockerComposeEnvironment {
|
|
|
22
22
|
profiles = [];
|
|
23
23
|
environment = {};
|
|
24
24
|
pullPolicy = pull_policy_1.PullPolicy.defaultPolicy();
|
|
25
|
-
defaultWaitStrategy
|
|
25
|
+
defaultWaitStrategy;
|
|
26
26
|
waitStrategy = {};
|
|
27
27
|
startupTimeoutMs;
|
|
28
28
|
clientOptions = {};
|
|
@@ -129,9 +129,11 @@ class DockerComposeEnvironment {
|
|
|
129
129
|
const inspectResult = await client.container.inspect(container);
|
|
130
130
|
const mappedInspectResult = (0, map_inspect_result_1.mapInspectResult)(inspectResult);
|
|
131
131
|
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, mappedInspectResult);
|
|
132
|
-
const waitStrategy =
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
const waitStrategy = await (0, wait_strategy_selector_1.selectWaitStrategy)({
|
|
133
|
+
client,
|
|
134
|
+
inspectResult,
|
|
135
|
+
waitStrategy: this.waitStrategy[containerName] ?? this.defaultWaitStrategy,
|
|
136
|
+
});
|
|
135
137
|
if (this.startupTimeoutMs !== undefined) {
|
|
136
138
|
waitStrategy.withStartupTimeout(this.startupTimeoutMs);
|
|
137
139
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ContainerCreateOptions, HostConfig } from "dockerode";
|
|
1
|
+
import { ContainerCreateOptions, ContainerInspectInfo, HostConfig } from "dockerode";
|
|
2
2
|
import { Readable } from "stream";
|
|
3
|
-
import { ImageName } from "../container-runtime";
|
|
3
|
+
import { ContainerRuntimeClient, ImageName } from "../container-runtime";
|
|
4
4
|
import { StartedNetwork } from "../network/network";
|
|
5
5
|
import { StartedTestContainer, TestContainer } from "../test-container";
|
|
6
6
|
import { ArchiveToCopy, BindMount, ContentToCopy, CopyToContainerOptions, DirectoryToCopy, Environment, ExtraHost, FileToCopy, HealthCheck, InspectResult, Labels, ResourcesQuota, TmpFs, Ulimits } from "../types";
|
|
@@ -14,7 +14,7 @@ export declare class GenericContainer implements TestContainer {
|
|
|
14
14
|
protected hostConfig: HostConfig;
|
|
15
15
|
protected imageName: ImageName;
|
|
16
16
|
protected startupTimeoutMs?: number;
|
|
17
|
-
protected waitStrategy: WaitStrategy;
|
|
17
|
+
protected waitStrategy: WaitStrategy | undefined;
|
|
18
18
|
protected environment: Record<string, string>;
|
|
19
19
|
protected exposedPorts: PortWithOptionalBinding[];
|
|
20
20
|
protected reuse: boolean;
|
|
@@ -37,6 +37,7 @@ export declare class GenericContainer implements TestContainer {
|
|
|
37
37
|
protected containerCreated?(containerId: string): Promise<void>;
|
|
38
38
|
protected containerStarting?(inspectResult: InspectResult, reused: boolean): Promise<void>;
|
|
39
39
|
start(): Promise<StartedTestContainer>;
|
|
40
|
+
protected selectWaitStrategy(client: ContainerRuntimeClient, inspectResult: ContainerInspectInfo, waitStrategy?: WaitStrategy | undefined): Promise<WaitStrategy>;
|
|
40
41
|
private reuseOrStartContainer;
|
|
41
42
|
private reuseContainer;
|
|
42
43
|
private startContainer;
|
|
@@ -17,7 +17,7 @@ const labels_1 = require("../utils/labels");
|
|
|
17
17
|
const map_inspect_result_1 = require("../utils/map-inspect-result");
|
|
18
18
|
const port_1 = require("../utils/port");
|
|
19
19
|
const pull_policy_1 = require("../utils/pull-policy");
|
|
20
|
-
const
|
|
20
|
+
const wait_strategy_selector_1 = require("../wait-strategies/utils/wait-strategy-selector");
|
|
21
21
|
const wait_for_container_1 = require("../wait-strategies/wait-for-container");
|
|
22
22
|
const generic_container_builder_1 = require("./generic-container-builder");
|
|
23
23
|
const inspect_container_util_ports_exposed_1 = require("./inspect-container-util-ports-exposed");
|
|
@@ -31,7 +31,7 @@ class GenericContainer {
|
|
|
31
31
|
hostConfig;
|
|
32
32
|
imageName;
|
|
33
33
|
startupTimeoutMs;
|
|
34
|
-
waitStrategy
|
|
34
|
+
waitStrategy;
|
|
35
35
|
environment = {};
|
|
36
36
|
exposedPorts = [];
|
|
37
37
|
reuse = false;
|
|
@@ -50,13 +50,13 @@ class GenericContainer {
|
|
|
50
50
|
constructor(image) {
|
|
51
51
|
this.imageName = container_runtime_1.ImageName.fromString(image);
|
|
52
52
|
this.createOpts = { Image: this.imageName.string };
|
|
53
|
-
this.hostConfig = { AutoRemove: this.imageName.string === reaper_1.
|
|
53
|
+
this.hostConfig = { AutoRemove: this.imageName.string === (0, reaper_1.getReaperImage)() };
|
|
54
54
|
}
|
|
55
55
|
isHelperContainer() {
|
|
56
56
|
return this.isReaper() || this.imageName.string === port_forwarder_1.SSHD_IMAGE;
|
|
57
57
|
}
|
|
58
58
|
isReaper() {
|
|
59
|
-
return this.imageName.string === reaper_1.
|
|
59
|
+
return this.imageName.string === (0, reaper_1.getReaperImage)();
|
|
60
60
|
}
|
|
61
61
|
async start() {
|
|
62
62
|
const client = await (0, container_runtime_1.getContainerRuntimeClient)();
|
|
@@ -85,6 +85,15 @@ class GenericContainer {
|
|
|
85
85
|
}
|
|
86
86
|
return this.startContainer(client);
|
|
87
87
|
}
|
|
88
|
+
async selectWaitStrategy(client, inspectResult, waitStrategy = this.waitStrategy) {
|
|
89
|
+
return (0, wait_strategy_selector_1.selectWaitStrategy)({
|
|
90
|
+
client,
|
|
91
|
+
inspectResult,
|
|
92
|
+
waitStrategy,
|
|
93
|
+
healthCheck: this.healthCheck,
|
|
94
|
+
imageNames: [this.imageName.string],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
88
97
|
async reuseOrStartContainer(client) {
|
|
89
98
|
const containerHash = (0, common_1.hash)(JSON.stringify(this.createOpts));
|
|
90
99
|
this.createOpts.Labels = { ...this.createOpts.Labels, [labels_1.LABEL_TESTCONTAINERS_CONTAINER_HASH]: containerHash };
|
|
@@ -110,11 +119,12 @@ class GenericContainer {
|
|
|
110
119
|
}
|
|
111
120
|
const mappedInspectResult = (0, map_inspect_result_1.mapInspectResult)(inspectResult);
|
|
112
121
|
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, mappedInspectResult).filter(this.exposedPorts);
|
|
122
|
+
const waitStrategy = await this.selectWaitStrategy(client, inspectResult);
|
|
113
123
|
if (this.startupTimeoutMs !== undefined) {
|
|
114
|
-
|
|
124
|
+
waitStrategy.withStartupTimeout(this.startupTimeoutMs);
|
|
115
125
|
}
|
|
116
|
-
await (0, wait_for_container_1.waitForContainer)(client, container,
|
|
117
|
-
return new started_generic_container_1.StartedGenericContainer(container, client.info.containerRuntime.host, inspectResult, boundPorts, inspectResult.Name,
|
|
126
|
+
await (0, wait_for_container_1.waitForContainer)(client, container, waitStrategy, boundPorts);
|
|
127
|
+
return new started_generic_container_1.StartedGenericContainer(container, client.info.containerRuntime.host, inspectResult, boundPorts, inspectResult.Name, waitStrategy, this.autoRemove);
|
|
118
128
|
}
|
|
119
129
|
async startContainer(client) {
|
|
120
130
|
const container = await client.container.create({ ...this.createOpts, HostConfig: this.hostConfig });
|
|
@@ -142,9 +152,6 @@ class GenericContainer {
|
|
|
142
152
|
const inspectResult = await (0, inspect_container_util_ports_exposed_1.inspectContainerUntilPortsExposed)(() => client.container.inspect(container), container.id);
|
|
143
153
|
const mappedInspectResult = (0, map_inspect_result_1.mapInspectResult)(inspectResult);
|
|
144
154
|
const boundPorts = bound_ports_1.BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, mappedInspectResult).filter(this.exposedPorts);
|
|
145
|
-
if (this.startupTimeoutMs !== undefined) {
|
|
146
|
-
this.waitStrategy.withStartupTimeout(this.startupTimeoutMs);
|
|
147
|
-
}
|
|
148
155
|
if (common_1.containerLog.enabled() || this.logConsumer !== undefined) {
|
|
149
156
|
if (this.logConsumer !== undefined) {
|
|
150
157
|
this.logConsumer(await client.container.logs(container));
|
|
@@ -158,8 +165,12 @@ class GenericContainer {
|
|
|
158
165
|
if (this.containerStarting) {
|
|
159
166
|
await this.containerStarting(mappedInspectResult, false);
|
|
160
167
|
}
|
|
161
|
-
|
|
162
|
-
|
|
168
|
+
const waitStrategy = await this.selectWaitStrategy(client, inspectResult);
|
|
169
|
+
if (this.startupTimeoutMs !== undefined) {
|
|
170
|
+
waitStrategy.withStartupTimeout(this.startupTimeoutMs);
|
|
171
|
+
}
|
|
172
|
+
await (0, wait_for_container_1.waitForContainer)(client, container, waitStrategy, boundPorts);
|
|
173
|
+
const startedContainer = new started_generic_container_1.StartedGenericContainer(container, client.info.containerRuntime.host, inspectResult, boundPorts, inspectResult.Name, waitStrategy, this.autoRemove);
|
|
163
174
|
if (this.containerStarted) {
|
|
164
175
|
await this.containerStarted(startedContainer, mappedInspectResult, false);
|
|
165
176
|
}
|
package/build/reaper/reaper.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ContainerRuntimeClient } from "../container-runtime";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the Ryuk reaper image name. Read lazily so that callers (and tests)
|
|
4
|
+
* can set `process.env.RYUK_CONTAINER_IMAGE` _after_ this module is imported —
|
|
5
|
+
* including via `.env` files loaded by `dotenv` at runtime.
|
|
6
|
+
*
|
|
7
|
+
* See https://github.com/testcontainers/testcontainers-node/issues/1310.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getReaperImage(): string;
|
|
3
10
|
export interface Reaper {
|
|
4
11
|
sessionId: string;
|
|
5
12
|
containerId: string;
|
package/build/reaper/reaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getReaperImage = getReaperImage;
|
|
4
4
|
exports.getReaper = getReaper;
|
|
5
5
|
const net_1 = require("net");
|
|
6
6
|
const os_1 = require("os");
|
|
@@ -9,9 +9,18 @@ const container_runtime_1 = require("../container-runtime");
|
|
|
9
9
|
const generic_container_1 = require("../generic-container/generic-container");
|
|
10
10
|
const labels_1 = require("../utils/labels");
|
|
11
11
|
const wait_1 = require("../wait-strategies/wait");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the Ryuk reaper image name. Read lazily so that callers (and tests)
|
|
14
|
+
* can set `process.env.RYUK_CONTAINER_IMAGE` _after_ this module is imported —
|
|
15
|
+
* including via `.env` files loaded by `dotenv` at runtime.
|
|
16
|
+
*
|
|
17
|
+
* See https://github.com/testcontainers/testcontainers-node/issues/1310.
|
|
18
|
+
*/
|
|
19
|
+
function getReaperImage() {
|
|
20
|
+
return process.env["RYUK_CONTAINER_IMAGE"]
|
|
21
|
+
? container_runtime_1.ImageName.fromString(process.env["RYUK_CONTAINER_IMAGE"]).string
|
|
22
|
+
: container_runtime_1.ImageName.fromString("testcontainers/ryuk:0.14.0").string;
|
|
23
|
+
}
|
|
15
24
|
let reaper;
|
|
16
25
|
let sessionId;
|
|
17
26
|
async function getReaper(client) {
|
|
@@ -63,7 +72,7 @@ async function useExistingReaper(reaperContainer, sessionId, host) {
|
|
|
63
72
|
}
|
|
64
73
|
async function createNewReaper(sessionId, remoteSocketPath) {
|
|
65
74
|
common_1.log.debug(`Creating new Reaper for session "${sessionId}" with socket path "${remoteSocketPath}"...`);
|
|
66
|
-
const container = new generic_container_1.GenericContainer(
|
|
75
|
+
const container = new generic_container_1.GenericContainer(getReaperImage())
|
|
67
76
|
.withName(`testcontainers-ryuk-${sessionId}`)
|
|
68
77
|
.withExposedPorts(process.env["TESTCONTAINERS_RYUK_PORT"]
|
|
69
78
|
? { container: 8080, host: parseInt(process.env["TESTCONTAINERS_RYUK_PORT"]) }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mapInspectResult = mapInspectResult;
|
|
4
|
+
const health_check_1 = require("../wait-strategies/utils/health-check");
|
|
4
5
|
function mapInspectResult(inspectResult) {
|
|
5
6
|
const finishedAt = new Date(inspectResult.State.FinishedAt);
|
|
6
7
|
return {
|
|
@@ -34,12 +35,12 @@ function mapPorts(inspectInfo) {
|
|
|
34
35
|
.reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
|
35
36
|
}
|
|
36
37
|
function mapHealthCheckStatus(inspectResult) {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
38
|
+
const status = (0, health_check_1.getHealthCheckStatusFromInspect)(inspectResult);
|
|
39
|
+
if (status === undefined) {
|
|
39
40
|
return "none";
|
|
40
41
|
}
|
|
41
42
|
else {
|
|
42
|
-
return
|
|
43
|
+
return status;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
function mapNetworkSettings(inspectResult) {
|
|
@@ -1,42 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.FixedPortGenerator = exports.RandomPortGenerator = void 0;
|
|
37
4
|
class RandomPortGenerator {
|
|
38
5
|
async generatePort() {
|
|
39
|
-
const { default: getPort } = await
|
|
6
|
+
const { default: getPort } = await import("get-port");
|
|
40
7
|
return getPort();
|
|
41
8
|
}
|
|
42
9
|
}
|
package/build/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "
|
|
1
|
+
export declare const LIB_VERSION = "12.0.1";
|
package/build/version.js
CHANGED
|
@@ -3,12 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.HealthCheckWaitStrategy = void 0;
|
|
4
4
|
const common_1 = require("../common");
|
|
5
5
|
const container_runtime_1 = require("../container-runtime");
|
|
6
|
+
const health_check_1 = require("./utils/health-check");
|
|
6
7
|
const wait_strategy_1 = require("./wait-strategy");
|
|
7
8
|
class HealthCheckWaitStrategy extends wait_strategy_1.AbstractWaitStrategy {
|
|
8
9
|
async waitUntilReady(container) {
|
|
9
10
|
common_1.log.debug(`Waiting for health check...`, { containerId: container.id });
|
|
10
11
|
const client = await (0, container_runtime_1.getContainerRuntimeClient)();
|
|
11
|
-
const status = await new common_1.IntervalRetry(100).retryUntil(async () => (await client.container.inspect(container))
|
|
12
|
+
const status = await new common_1.IntervalRetry(100).retryUntil(async () => (0, health_check_1.getHealthCheckStatusFromInspect)(await client.container.inspect(container)), (healthCheckStatus) => healthCheckStatus === "healthy" || healthCheckStatus === "unhealthy", () => {
|
|
12
13
|
const timeout = this.startupTimeoutMs;
|
|
13
14
|
const message = `Health check not healthy after ${timeout}ms`;
|
|
14
15
|
common_1.log.error(message, { containerId: container.id });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ContainerInspectInfo, HealthConfig, ImageInspectInfo } from "dockerode";
|
|
2
|
+
import { HealthCheck, HealthCheckStatus } from "../../types";
|
|
3
|
+
type HealthCheckConfig = HealthConfig | HealthCheck;
|
|
4
|
+
type HealthCheckInspectInfo = ContainerInspectInfo | ImageInspectInfo;
|
|
5
|
+
export declare const isHealthCheckDisabled: (healthCheck: HealthCheckConfig | undefined) => boolean;
|
|
6
|
+
export declare const hasHealthCheck: (healthCheck: HealthCheckConfig | undefined) => boolean;
|
|
7
|
+
export declare const getHealthCheckConfig: (inspectResult: HealthCheckInspectInfo) => HealthConfig | undefined;
|
|
8
|
+
export declare const hasHealthCheckConfig: (inspectResult: HealthCheckInspectInfo) => boolean;
|
|
9
|
+
export declare const hasDisabledHealthCheckConfig: (inspectResult: HealthCheckInspectInfo) => boolean;
|
|
10
|
+
export declare const getHealthCheckStatusFromInspect: (inspectResult: ContainerInspectInfo) => HealthCheckStatus | undefined;
|
|
11
|
+
export declare const hasHealthCheckStatus: (inspectResult: ContainerInspectInfo) => boolean;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasHealthCheckStatus = exports.getHealthCheckStatusFromInspect = exports.hasDisabledHealthCheckConfig = exports.hasHealthCheckConfig = exports.getHealthCheckConfig = exports.hasHealthCheck = exports.isHealthCheckDisabled = void 0;
|
|
4
|
+
const DISABLED_HEALTH_CHECK_TEST = "NONE";
|
|
5
|
+
const getHealthCheckTest = (healthCheck) => {
|
|
6
|
+
if ("test" in healthCheck) {
|
|
7
|
+
return healthCheck.test;
|
|
8
|
+
}
|
|
9
|
+
return healthCheck.Test;
|
|
10
|
+
};
|
|
11
|
+
const isDisabledHealthCheck = (test) => {
|
|
12
|
+
return test[0].toUpperCase() === DISABLED_HEALTH_CHECK_TEST;
|
|
13
|
+
};
|
|
14
|
+
const isHealthCheckDisabled = (healthCheck) => {
|
|
15
|
+
if (healthCheck === undefined) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const test = getHealthCheckTest(healthCheck);
|
|
19
|
+
if (test === undefined || test.length === 0) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return isDisabledHealthCheck(test);
|
|
23
|
+
};
|
|
24
|
+
exports.isHealthCheckDisabled = isHealthCheckDisabled;
|
|
25
|
+
const hasHealthCheck = (healthCheck) => {
|
|
26
|
+
if (healthCheck === undefined) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const test = getHealthCheckTest(healthCheck);
|
|
30
|
+
if (test === undefined || test.length === 0) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return !(0, exports.isHealthCheckDisabled)(healthCheck);
|
|
34
|
+
};
|
|
35
|
+
exports.hasHealthCheck = hasHealthCheck;
|
|
36
|
+
const getHealthCheckConfig = (inspectResult) => {
|
|
37
|
+
const inspectWithHealthCheckConfig = inspectResult;
|
|
38
|
+
return (inspectWithHealthCheckConfig.Config?.Healthcheck ??
|
|
39
|
+
inspectWithHealthCheckConfig.Config?.HealthCheck ??
|
|
40
|
+
inspectWithHealthCheckConfig.ContainerConfig?.Healthcheck ??
|
|
41
|
+
inspectWithHealthCheckConfig.ContainerConfig?.HealthCheck ??
|
|
42
|
+
inspectWithHealthCheckConfig.Healthcheck ??
|
|
43
|
+
inspectWithHealthCheckConfig.HealthCheck);
|
|
44
|
+
};
|
|
45
|
+
exports.getHealthCheckConfig = getHealthCheckConfig;
|
|
46
|
+
const hasHealthCheckConfig = (inspectResult) => {
|
|
47
|
+
return (0, exports.hasHealthCheck)((0, exports.getHealthCheckConfig)(inspectResult));
|
|
48
|
+
};
|
|
49
|
+
exports.hasHealthCheckConfig = hasHealthCheckConfig;
|
|
50
|
+
const hasDisabledHealthCheckConfig = (inspectResult) => {
|
|
51
|
+
return (0, exports.isHealthCheckDisabled)((0, exports.getHealthCheckConfig)(inspectResult));
|
|
52
|
+
};
|
|
53
|
+
exports.hasDisabledHealthCheckConfig = hasDisabledHealthCheckConfig;
|
|
54
|
+
const getHealthCheckStatusFromInspect = (inspectResult) => {
|
|
55
|
+
const state = inspectResult.State;
|
|
56
|
+
const status = state.Health?.Status ?? state.Healthcheck?.Status;
|
|
57
|
+
return status === undefined || status === "" ? undefined : status;
|
|
58
|
+
};
|
|
59
|
+
exports.getHealthCheckStatusFromInspect = getHealthCheckStatusFromInspect;
|
|
60
|
+
const hasHealthCheckStatus = (inspectResult) => {
|
|
61
|
+
return (0, exports.getHealthCheckStatusFromInspect)(inspectResult) !== undefined;
|
|
62
|
+
};
|
|
63
|
+
exports.hasHealthCheckStatus = hasHealthCheckStatus;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerInspectInfo } from "dockerode";
|
|
2
|
+
import { ContainerRuntimeClient } from "../../container-runtime";
|
|
3
|
+
import { HealthCheck } from "../../types";
|
|
4
|
+
import { WaitStrategy } from "../wait-strategy";
|
|
5
|
+
type WaitStrategySelectorOptions = {
|
|
6
|
+
client: ContainerRuntimeClient;
|
|
7
|
+
inspectResult: ContainerInspectInfo;
|
|
8
|
+
waitStrategy?: WaitStrategy;
|
|
9
|
+
healthCheck?: HealthCheck;
|
|
10
|
+
imageNames?: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare const selectWaitStrategy: ({ client, inspectResult, waitStrategy, healthCheck, imageNames, }: WaitStrategySelectorOptions) => Promise<WaitStrategy>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectWaitStrategy = void 0;
|
|
4
|
+
const common_1 = require("../../common");
|
|
5
|
+
const container_runtime_1 = require("../../container-runtime");
|
|
6
|
+
const wait_1 = require("../wait");
|
|
7
|
+
const health_check_1 = require("./health-check");
|
|
8
|
+
const selectWaitStrategy = async ({ client, inspectResult, waitStrategy, healthCheck, imageNames = getImageNames(inspectResult), }) => {
|
|
9
|
+
if (waitStrategy)
|
|
10
|
+
return waitStrategy;
|
|
11
|
+
if ((0, health_check_1.hasHealthCheck)(healthCheck))
|
|
12
|
+
return wait_1.Wait.forHealthCheck();
|
|
13
|
+
if ((0, health_check_1.hasDisabledHealthCheckConfig)(inspectResult))
|
|
14
|
+
return wait_1.Wait.forListeningPorts();
|
|
15
|
+
if ((0, health_check_1.hasHealthCheckConfig)(inspectResult) || (0, health_check_1.hasHealthCheckStatus)(inspectResult))
|
|
16
|
+
return wait_1.Wait.forHealthCheck();
|
|
17
|
+
if (await imageHasHealthCheck(client, imageNames))
|
|
18
|
+
return wait_1.Wait.forHealthCheck();
|
|
19
|
+
return wait_1.Wait.forListeningPorts();
|
|
20
|
+
};
|
|
21
|
+
exports.selectWaitStrategy = selectWaitStrategy;
|
|
22
|
+
const getImageNames = (inspectResult) => {
|
|
23
|
+
return Array.from(new Set([inspectResult.Config.Image, inspectResult.Image].filter((imageName) => imageName !== undefined && imageName !== "")));
|
|
24
|
+
};
|
|
25
|
+
const imageHasHealthCheck = async (client, imageNames) => {
|
|
26
|
+
for (const imageName of imageNames) {
|
|
27
|
+
try {
|
|
28
|
+
if ((0, health_check_1.hasHealthCheckConfig)(await client.image.inspect(container_runtime_1.ImageName.fromString(imageName)))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
common_1.log.warn(`Failed to inspect image "${imageName}" for health check config: ${err}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testcontainers",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.1",
|
|
4
4
|
"author": "Cristian Greco",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"byline": "^5.0.0",
|
|
38
38
|
"debug": "^4.4.3",
|
|
39
39
|
"docker-compose": "^1.4.2",
|
|
40
|
-
"dockerode": "^
|
|
40
|
+
"dockerode": "^5.0.0",
|
|
41
41
|
"get-port": "^7.2.0",
|
|
42
42
|
"proper-lockfile": "^4.1.2",
|
|
43
43
|
"properties-reader": "^3.0.1",
|
|
44
44
|
"ssh-remote-port-forward": "^1.0.4",
|
|
45
45
|
"tar-fs": "^3.1.2",
|
|
46
|
-
"tmp": "^0.2.
|
|
47
|
-
"undici": "^7.
|
|
46
|
+
"tmp": "^0.2.6",
|
|
47
|
+
"undici": "^7.25.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/archiver": "^7.0.0",
|