testcontainers 12.0.2 → 12.0.4

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DockerContainerClient = void 0;
4
4
  const stream_1 = require("stream");
5
+ const promises_1 = require("stream/promises");
5
6
  const common_1 = require("../../../common");
6
7
  class DockerContainerClient {
7
8
  dockerode;
@@ -216,11 +217,18 @@ class DockerContainerClient {
216
217
  };
217
218
  processStream(stdoutStream, stdoutChunks);
218
219
  processStream(stderrStream, stderrChunks);
219
- await new Promise((res, rej) => {
220
- stream.on("end", res);
221
- stream.on("error", rej);
222
- });
223
- stream.destroy();
220
+ try {
221
+ await new Promise((res, rej) => {
222
+ stream.on("end", res);
223
+ stream.on("error", rej);
224
+ });
225
+ stdoutStream.end();
226
+ stderrStream.end();
227
+ await Promise.all([(0, promises_1.finished)(stdoutStream), (0, promises_1.finished)(stderrStream)]);
228
+ }
229
+ finally {
230
+ stream.destroy();
231
+ }
224
232
  const inspectResult = await exec.inspect();
225
233
  const exitCode = inspectResult.ExitCode ?? -1;
226
234
  const output = outputChunks.join("");
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GenericContainer = void 0;
7
- const archiver_1 = require("archiver");
7
+ const archiver_1 = __importDefault(require("archiver"));
8
8
  const async_lock_1 = __importDefault(require("async-lock"));
9
9
  const fs_1 = require("fs");
10
10
  const common_1 = require("../common");
@@ -186,7 +186,7 @@ class GenericContainer {
186
186
  }
187
187
  }
188
188
  async createArchiveToCopyToContainer() {
189
- const tar = new archiver_1.TarArchive();
189
+ const tar = (0, archiver_1.default)("tar");
190
190
  const filesToCopyWithStats = await Promise.all(this.filesToCopy.map(async (fileToCopy) => ({
191
191
  ...fileToCopy,
192
192
  stats: await fs_1.promises.stat(fileToCopy.source),
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.StartedGenericContainer = void 0;
7
- const archiver_1 = require("archiver");
7
+ const archiver_1 = __importDefault(require("archiver"));
8
8
  const async_lock_1 = __importDefault(require("async-lock"));
9
9
  const fs_1 = require("fs");
10
10
  const common_1 = require("../common");
@@ -151,7 +151,7 @@ class StartedGenericContainer {
151
151
  async copyFilesToContainer(filesToCopy) {
152
152
  common_1.log.debug(`Copying files to container...`, { containerId: this.container.id });
153
153
  const client = await (0, container_runtime_1.getContainerRuntimeClient)();
154
- const tar = new archiver_1.TarArchive();
154
+ const tar = (0, archiver_1.default)("tar");
155
155
  const filesToCopyWithStats = await Promise.all(filesToCopy.map(async (fileToCopy) => ({
156
156
  ...fileToCopy,
157
157
  stats: await fs_1.promises.stat(fileToCopy.source),
@@ -164,7 +164,7 @@ class StartedGenericContainer {
164
164
  async copyDirectoriesToContainer(directoriesToCopy) {
165
165
  common_1.log.debug(`Copying directories to container...`, { containerId: this.container.id });
166
166
  const client = await (0, container_runtime_1.getContainerRuntimeClient)();
167
- const tar = new archiver_1.TarArchive();
167
+ const tar = (0, archiver_1.default)("tar");
168
168
  directoriesToCopy.forEach(({ source, target }) => tar.directory(source, target));
169
169
  tar.finalize();
170
170
  await client.container.putArchive(this.container, tar, "/");
@@ -173,7 +173,7 @@ class StartedGenericContainer {
173
173
  async copyContentToContainer(contentsToCopy) {
174
174
  common_1.log.debug(`Copying content to container...`, { containerId: this.container.id });
175
175
  const client = await (0, container_runtime_1.getContainerRuntimeClient)();
176
- const tar = new archiver_1.TarArchive();
176
+ const tar = (0, archiver_1.default)("tar");
177
177
  contentsToCopy.forEach(({ content, target, mode }) => tar.append(content, { name: target, mode: mode }));
178
178
  tar.finalize();
179
179
  await client.container.putArchive(this.container, tar, "/");
@@ -9,7 +9,7 @@ const reaper_1 = require("../reaper/reaper");
9
9
  const labels_1 = require("../utils/labels");
10
10
  exports.SSHD_IMAGE = process.env["SSHD_CONTAINER_IMAGE"]
11
11
  ? container_runtime_1.ImageName.fromString(process.env["SSHD_CONTAINER_IMAGE"]).string
12
- : container_runtime_1.ImageName.fromString("testcontainers/sshd:1.3.0").string;
12
+ : container_runtime_1.ImageName.fromString("testcontainers/sshd:1.4.0").string;
13
13
  class PortForwarder {
14
14
  sshConnection;
15
15
  containerId;
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "12.0.2";
1
+ export declare const LIB_VERSION = "12.0.4";
package/build/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIB_VERSION = void 0;
4
- exports.LIB_VERSION = "12.0.2";
4
+ exports.LIB_VERSION = "12.0.4";
@@ -23,10 +23,10 @@ export declare class HttpWaitStrategy extends AbstractWaitStrategy {
23
23
  [key: string]: string;
24
24
  }): this;
25
25
  withBasicCredentials(username: string, password: string): this;
26
- withReadTimeout(startupTimeoutMs: number): this;
26
+ withReadTimeout(readTimeoutMs: number): this;
27
27
  usingTls(): this;
28
28
  allowInsecure(): this;
29
29
  waitUntilReady(container: Dockerode.Container, boundPorts: BoundPorts): Promise<void>;
30
30
  private handleContainerExit;
31
- private getAgent;
31
+ private createInsecureAgent;
32
32
  }
@@ -47,8 +47,8 @@ class HttpWaitStrategy extends wait_strategy_1.AbstractWaitStrategy {
47
47
  this.headers = { ...this.headers, Authorization: `Basic ${base64Encoded}` };
48
48
  return this;
49
49
  }
50
- withReadTimeout(startupTimeoutMs) {
51
- this.readTimeoutMs = startupTimeoutMs;
50
+ withReadTimeout(readTimeoutMs) {
51
+ this.readTimeoutMs = readTimeoutMs;
52
52
  return this;
53
53
  }
54
54
  usingTls() {
@@ -65,50 +65,60 @@ class HttpWaitStrategy extends wait_strategy_1.AbstractWaitStrategy {
65
65
  let containerExited = false;
66
66
  const client = await (0, container_runtime_1.getContainerRuntimeClient)();
67
67
  const { abortOnContainerExit } = this.options;
68
- await new common_1.IntervalRetry(this.readTimeoutMs).retryUntil(async () => {
69
- try {
70
- const url = `${this.protocol}://${client.info.containerRuntime.host}:${boundPorts.getBinding(this.port)}${this.path}`;
71
- if (abortOnContainerExit) {
72
- const containerStatus = (await client.container.inspect(container)).State.Status;
73
- if (containerStatus === exitStatus) {
74
- containerExited = true;
75
- return;
68
+ // Scoped per invocation: one strategy instance can drive concurrent waits, so a shared
69
+ // agent would let one finished wait destroy a dispatcher another is still using.
70
+ const agent = this.createInsecureAgent();
71
+ try {
72
+ await new common_1.IntervalRetry(this.readTimeoutMs).retryUntil(async () => {
73
+ try {
74
+ const url = `${this.protocol}://${client.info.containerRuntime.host}:${boundPorts.getBinding(this.port)}${this.path}`;
75
+ if (abortOnContainerExit) {
76
+ const containerStatus = (await client.container.inspect(container)).State.Status;
77
+ if (containerStatus === exitStatus) {
78
+ containerExited = true;
79
+ return;
80
+ }
76
81
  }
82
+ return (0, undici_response_parser_1.undiciResponseToFetchResponse)(await (0, undici_1.request)(url, {
83
+ method: this.method,
84
+ signal: AbortSignal.timeout(this.readTimeoutMs),
85
+ headers: this.headers,
86
+ dispatcher: agent,
87
+ }));
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }, async (response) => {
93
+ if (abortOnContainerExit && containerExited) {
94
+ return true;
95
+ }
96
+ if (response === undefined) {
97
+ return false;
77
98
  }
78
- return (0, undici_response_parser_1.undiciResponseToFetchResponse)(await (0, undici_1.request)(url, {
79
- method: this.method,
80
- signal: AbortSignal.timeout(this.readTimeoutMs),
81
- headers: this.headers,
82
- dispatcher: this.getAgent(),
83
- }));
84
- }
85
- catch {
86
- return undefined;
87
- }
88
- }, async (response) => {
89
- if (abortOnContainerExit && containerExited) {
90
- return true;
91
- }
92
- if (response === undefined) {
93
- return false;
94
- }
95
- else if (!this.predicates.length) {
96
- return response.ok;
97
- }
98
- else {
99
- for (const predicate of this.predicates) {
100
- const result = await predicate(response);
101
- if (!result) {
102
- return false;
99
+ else if (!this.predicates.length) {
100
+ return response.ok;
101
+ }
102
+ else {
103
+ for (const predicate of this.predicates) {
104
+ const result = await predicate(response);
105
+ if (!result) {
106
+ return false;
107
+ }
103
108
  }
109
+ return true;
104
110
  }
105
- return true;
106
- }
107
- }, () => {
108
- const message = `URL ${this.path} not accessible after ${this.startupTimeoutMs}ms`;
109
- common_1.log.error(message, { containerId: container.id });
110
- throw new Error(message);
111
- }, this.startupTimeoutMs);
111
+ }, () => {
112
+ const message = `URL ${this.path} not accessible after ${this.startupTimeoutMs}ms`;
113
+ common_1.log.error(message, { containerId: container.id });
114
+ throw new Error(message);
115
+ }, this.startupTimeoutMs);
116
+ }
117
+ finally {
118
+ // Force-close: status-only predicates never read the body, so a graceful close() could
119
+ // hang waiting on unreleased connections. Nothing left to drain once the wait is done.
120
+ await agent?.destroy();
121
+ }
112
122
  if (abortOnContainerExit && containerExited) {
113
123
  return this.handleContainerExit(container);
114
124
  }
@@ -132,14 +142,15 @@ class HttpWaitStrategy extends wait_strategy_1.AbstractWaitStrategy {
132
142
  common_1.log.error(message, { containerId: container.id });
133
143
  throw new Error(message);
134
144
  }
135
- getAgent() {
136
- if (this._allowInsecure) {
137
- return new undici_1.Agent({
138
- connect: {
139
- rejectUnauthorized: false,
140
- },
141
- });
145
+ createInsecureAgent() {
146
+ if (!this._allowInsecure) {
147
+ return undefined;
142
148
  }
149
+ return new undici_1.Agent({
150
+ connect: {
151
+ rejectUnauthorized: false,
152
+ },
153
+ });
143
154
  }
144
155
  }
145
156
  exports.HttpWaitStrategy = HttpWaitStrategy;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testcontainers",
3
- "version": "12.0.2",
3
+ "version": "12.0.4",
4
4
  "author": "Cristian Greco",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -32,22 +32,22 @@
32
32
  "dependencies": {
33
33
  "@balena/dockerignore": "^1.0.2",
34
34
  "@types/dockerode": "^4.0.1",
35
- "archiver": "^8.0.0",
35
+ "archiver": "^7.0.1",
36
36
  "async-lock": "^1.4.1",
37
37
  "byline": "^5.0.0",
38
38
  "debug": "^4.4.3",
39
39
  "docker-compose": "^1.4.2",
40
40
  "dockerode": "^5.0.0",
41
- "get-port": "^7.2.0",
41
+ "get-port": "^5.1.1",
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
46
  "tmp": "^0.2.7",
47
- "undici": "^8.3.0"
47
+ "undici": "^8.5.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/archiver": "^8.0.0",
50
+ "@types/archiver": "^7.0.0",
51
51
  "@types/async-lock": "^1.4.2",
52
52
  "@types/byline": "^4.2.36",
53
53
  "@types/debug": "^4.1.13",