testcontainers 8.7.2 → 8.8.1-beta.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/README.md +10 -2
- package/dist/docker/docker-client.js +9 -6
- package/dist/generic-container/generic-container.d.ts +1 -0
- package/dist/generic-container/generic-container.js +3 -0
- package/dist/modules/arangodb/arangodb-container.js +3 -3
- package/dist/modules/elasticsearch/elasticsearch-container.js +1 -1
- package/dist/modules/kafka/kafka-container.d.ts +25 -0
- package/dist/modules/kafka/kafka-container.js +64 -3
- package/dist/modules/mysql/mysql-container.js +2 -1
- package/dist/modules/neo4j/neo4j-container.js +1 -1
- package/dist/modules/postgresql/postgresql-container.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,18 +18,26 @@ npm i -D testcontainers
|
|
|
18
18
|
|
|
19
19
|
The following environment variables are supported:
|
|
20
20
|
|
|
21
|
+
### Logs
|
|
22
|
+
|
|
21
23
|
- `DEBUG=testcontainers` Enable testcontainers logs
|
|
22
24
|
- `DEBUG=testcontainers:containers` Enable container logs
|
|
23
25
|
- `DEBUG=testcontainers*` Enable all logs
|
|
26
|
+
|
|
27
|
+
### Docker
|
|
28
|
+
|
|
24
29
|
- `DOCKER_HOST=tcp://docker:2375` Sets the URL of the docker daemon
|
|
25
30
|
- `DOCKER_TLS_VERIFY=1` When set to `1`, enables TLS communication with the docker
|
|
26
31
|
daemon
|
|
27
32
|
- `DOCKER_CERT_PATH=/some/path` Configures the path to the `ca.pem`, `cert.pem`, and `key.pem` files used for TLS
|
|
28
|
-
verification
|
|
29
|
-
- `DOCKER_CONFIG=/some/path` Configures the path to the `config.json
|
|
33
|
+
verification
|
|
34
|
+
- `DOCKER_CONFIG=/some/path` Configures the path to the `config.json`
|
|
30
35
|
- `TESTCONTAINERS_HOST_OVERRIDE=docker.svc.local` Docker's host on which ports are exposed
|
|
31
36
|
- `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock` Path to Docker's socket. Used by [ryuk](#ryuk) and
|
|
32
37
|
other [auxiliary containers](#auxiliary-containers) that need to perform Docker actions
|
|
38
|
+
|
|
39
|
+
### Testcontainers
|
|
40
|
+
|
|
33
41
|
- `TESTCONTAINERS_RYUK_PRIVILEGED=true` Run [ryuk](#ryuk) as a privileged container
|
|
34
42
|
- `TESTCONTAINERS_RYUK_DISABLED=true` Disable [ryuk](#ryuk)
|
|
35
43
|
- `RYUK_CONTAINER_IMAGE=registry.mycompany.com/mirror/ryuk:0.3.0` Custom image for [ryuk](#ryuk)
|
|
@@ -60,18 +60,21 @@ class ConfigurationStrategy {
|
|
|
60
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
61
61
|
const { DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH } = process.env;
|
|
62
62
|
const dockerOptions = {};
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const { pathname, hostname, port } = new url_1.URL(DOCKER_HOST);
|
|
64
|
+
if (hostname !== "") {
|
|
65
|
+
dockerOptions.host = hostname;
|
|
66
|
+
dockerOptions.port = port;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
dockerOptions.socketPath = pathname;
|
|
70
|
+
}
|
|
68
71
|
if (DOCKER_TLS_VERIFY === "1" && DOCKER_CERT_PATH !== undefined) {
|
|
69
72
|
dockerOptions.ca = yield fs_1.promises.readFile(path_1.default.resolve(DOCKER_CERT_PATH, "ca.pem"));
|
|
70
73
|
dockerOptions.cert = yield fs_1.promises.readFile(path_1.default.resolve(DOCKER_CERT_PATH, "cert.pem"));
|
|
71
74
|
dockerOptions.key = yield fs_1.promises.readFile(path_1.default.resolve(DOCKER_CERT_PATH, "key.pem"));
|
|
72
75
|
}
|
|
73
76
|
return {
|
|
74
|
-
uri:
|
|
77
|
+
uri: DOCKER_HOST,
|
|
75
78
|
dockerode: new dockerode_1.default(dockerOptions),
|
|
76
79
|
};
|
|
77
80
|
});
|
|
@@ -39,6 +39,7 @@ export declare class GenericContainer implements TestContainer {
|
|
|
39
39
|
private reuseContainer;
|
|
40
40
|
private startContainer;
|
|
41
41
|
protected postStart?(container: StartedTestContainer, inspectResult: InspectResult, boundPorts: BoundPorts): Promise<void>;
|
|
42
|
+
protected get hasExposedPorts(): boolean;
|
|
42
43
|
withCmd(cmd: Command[]): this;
|
|
43
44
|
withName(name: ContainerName): this;
|
|
44
45
|
withLabels(labels: Labels): this;
|
|
@@ -13,7 +13,7 @@ exports.StartedArangoContainer = exports.ArangoDBContainer = void 0;
|
|
|
13
13
|
const __1 = require("../..");
|
|
14
14
|
const uuid_1 = require("../../uuid");
|
|
15
15
|
const abstract_started_container_1 = require("../abstract-started-container");
|
|
16
|
-
const
|
|
16
|
+
const ARANGODB_PORT = 8529;
|
|
17
17
|
const USERNAME = "root";
|
|
18
18
|
class ArangoDBContainer extends __1.GenericContainer {
|
|
19
19
|
constructor(image = "arangodb:3.7.13", password = new uuid_1.RandomUuid().nextUuid()) {
|
|
@@ -29,7 +29,7 @@ class ArangoDBContainer extends __1.GenericContainer {
|
|
|
29
29
|
start: { get: () => super.start }
|
|
30
30
|
});
|
|
31
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
-
this.withExposedPorts(
|
|
32
|
+
this.withExposedPorts(...(this.hasExposedPorts ? this.ports : [ARANGODB_PORT]))
|
|
33
33
|
.withWaitStrategy(__1.Wait.forLogMessage("Have fun!"))
|
|
34
34
|
.withEnv("ARANGO_ROOT_PASSWORD", this.password)
|
|
35
35
|
.withStartupTimeout(120000);
|
|
@@ -43,7 +43,7 @@ class StartedArangoContainer extends abstract_started_container_1.AbstractStarte
|
|
|
43
43
|
super(startedTestContainer);
|
|
44
44
|
this.password = password;
|
|
45
45
|
this.host = this.startedTestContainer.getHost();
|
|
46
|
-
this.port = this.startedTestContainer.getMappedPort(
|
|
46
|
+
this.port = this.startedTestContainer.getMappedPort(ARANGODB_PORT);
|
|
47
47
|
}
|
|
48
48
|
getTcpUrl() {
|
|
49
49
|
return `tcp://${this.host}:${this.port}/`;
|
|
@@ -22,7 +22,7 @@ class ElasticsearchContainer extends __1.GenericContainer {
|
|
|
22
22
|
start: { get: () => super.start }
|
|
23
23
|
});
|
|
24
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
this.withExposedPorts(ELASTIC_SEARCH_HTTP_PORT)
|
|
25
|
+
this.withExposedPorts(...(this.hasExposedPorts ? this.ports : [ELASTIC_SEARCH_HTTP_PORT]))
|
|
26
26
|
.withEnv("discovery.type", "single-node")
|
|
27
27
|
.withStartupTimeout(120000);
|
|
28
28
|
return new StartedElasticsearchContainer(yield _super.start.call(this));
|
|
@@ -6,17 +6,42 @@ import { AbstractStartedContainer } from "../abstract-started-container";
|
|
|
6
6
|
import { Host } from "../../docker/types";
|
|
7
7
|
import { InspectResult } from "../../docker/functions/container/inspect-container";
|
|
8
8
|
export declare const KAFKA_IMAGE = "confluentinc/cp-kafka:5.5.4";
|
|
9
|
+
interface SaslSslListenerOptions {
|
|
10
|
+
sasl: SaslOptions;
|
|
11
|
+
port: number;
|
|
12
|
+
keystore: PKCS12CertificateStore;
|
|
13
|
+
truststore?: PKCS12CertificateStore;
|
|
14
|
+
}
|
|
15
|
+
interface SaslOptions {
|
|
16
|
+
mechanism: "SCRAM-SHA-256" | "SCRAM-SHA-512";
|
|
17
|
+
user: User;
|
|
18
|
+
}
|
|
19
|
+
interface User {
|
|
20
|
+
name: string;
|
|
21
|
+
password: string;
|
|
22
|
+
}
|
|
23
|
+
interface PKCS12CertificateStore {
|
|
24
|
+
content: Parameters<GenericContainer["withCopyContentToContainer"]>[0];
|
|
25
|
+
passphrase: string;
|
|
26
|
+
}
|
|
9
27
|
export declare class KafkaContainer extends GenericContainer {
|
|
10
28
|
private readonly uuid;
|
|
11
29
|
private isZooKeeperProvided;
|
|
12
30
|
private zooKeeperHost?;
|
|
13
31
|
private zooKeeperPort?;
|
|
32
|
+
private saslSslConfig?;
|
|
14
33
|
constructor(image?: string);
|
|
15
34
|
withZooKeeper(host: Host, port: Port): this;
|
|
35
|
+
withSaslSslListener(options: SaslSslListenerOptions): this;
|
|
16
36
|
protected preStart(): Promise<void>;
|
|
17
37
|
start(): Promise<StartedKafkaContainer>;
|
|
18
38
|
protected postStart(container: StartedTestContainer, inspectResult: InspectResult, boundPorts: BoundPorts): Promise<void>;
|
|
39
|
+
private addPlaintextAndSecureListener;
|
|
40
|
+
private addPlaintextListener;
|
|
41
|
+
private updateAdvertisedListeners;
|
|
42
|
+
private createUser;
|
|
19
43
|
}
|
|
20
44
|
export declare class StartedKafkaContainer extends AbstractStartedContainer {
|
|
21
45
|
constructor(startedTestContainer: StartedTestContainer);
|
|
22
46
|
}
|
|
47
|
+
export {};
|
|
@@ -13,7 +13,6 @@ exports.StartedKafkaContainer = exports.KafkaContainer = exports.KAFKA_IMAGE = v
|
|
|
13
13
|
const generic_container_1 = require("../../generic-container/generic-container");
|
|
14
14
|
const uuid_1 = require("../../uuid");
|
|
15
15
|
const abstract_started_container_1 = require("../abstract-started-container");
|
|
16
|
-
const docker_client_1 = require("../../docker/docker-client");
|
|
17
16
|
const KAFKA_PORT = 9093;
|
|
18
17
|
const KAFKA_BROKER_PORT = 9092;
|
|
19
18
|
const DEFAULT_ZOOKEEPER_PORT = 2181;
|
|
@@ -25,7 +24,6 @@ class KafkaContainer extends generic_container_1.GenericContainer {
|
|
|
25
24
|
this.isZooKeeperProvided = false;
|
|
26
25
|
this.withExposedPorts(KAFKA_PORT)
|
|
27
26
|
.withStartupTimeout(180000)
|
|
28
|
-
.withEnv("KAFKA_LISTENERS", `PLAINTEXT://0.0.0.0:${KAFKA_PORT},BROKER://0.0.0.0:${KAFKA_BROKER_PORT}`)
|
|
29
27
|
.withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT")
|
|
30
28
|
.withEnv("KAFKA_INTER_BROKER_LISTENER_NAME", "BROKER")
|
|
31
29
|
.withEnv("KAFKA_BROKER_ID", "1")
|
|
@@ -42,10 +40,20 @@ class KafkaContainer extends generic_container_1.GenericContainer {
|
|
|
42
40
|
this.zooKeeperPort = port;
|
|
43
41
|
return this;
|
|
44
42
|
}
|
|
43
|
+
withSaslSslListener(options) {
|
|
44
|
+
this.saslSslConfig = options;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
45
47
|
preStart() {
|
|
46
48
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
49
|
const network = this.networkMode && this.networkAliases.length > 0 ? this.networkAliases[0] : "localhost";
|
|
48
50
|
this.withEnv("KAFKA_ADVERTISED_LISTENERS", `BROKER://${network}:${KAFKA_BROKER_PORT}`);
|
|
51
|
+
if (this.saslSslConfig) {
|
|
52
|
+
this.addPlaintextAndSecureListener(this.saslSslConfig);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.addPlaintextListener();
|
|
56
|
+
}
|
|
49
57
|
let command = "#!/bin/bash\n";
|
|
50
58
|
if (this.isZooKeeperProvided) {
|
|
51
59
|
this.withEnv("KAFKA_ZOOKEEPER_CONNECT", `${this.zooKeeperHost}:${this.zooKeeperPort}`);
|
|
@@ -74,9 +82,41 @@ class KafkaContainer extends generic_container_1.GenericContainer {
|
|
|
74
82
|
});
|
|
75
83
|
}
|
|
76
84
|
postStart(container, inspectResult, boundPorts) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
yield this.updateAdvertisedListeners(container, inspectResult, boundPorts);
|
|
87
|
+
if (this.saslSslConfig) {
|
|
88
|
+
yield this.createUser(container, this.saslSslConfig.sasl);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
addPlaintextAndSecureListener({ port, sasl, keystore, truststore }) {
|
|
93
|
+
this.withEnv("KAFKA_LISTENERS", `SECURE://0.0.0.0:${port},PLAINTEXT://0.0.0.0:${KAFKA_PORT},BROKER://0.0.0.0:${KAFKA_BROKER_PORT}`)
|
|
94
|
+
.withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SECURE:SASL_SSL")
|
|
95
|
+
.withEnv("KAFKA_SSL_PROTOCOL", "TLSv1.2")
|
|
96
|
+
.withCopyContentToContainer(keystore.content, "/etc/kafka/secrets/server.keystore.pfx")
|
|
97
|
+
.withEnv("KAFKA_SSL_KEYSTORE_LOCATION", "/etc/kafka/secrets/server.keystore.pfx")
|
|
98
|
+
.withEnv("KAFKA_SSL_KEYSTORE_PASSWORD", keystore.passphrase)
|
|
99
|
+
.withEnv("KAFKA_SSL_KEYSTORE_TYPE", "PKCS12")
|
|
100
|
+
.withEnv("KAFKA_SASL_ENABLED_MECHANISMS", sasl.mechanism)
|
|
101
|
+
.withEnv(`KAFKA_LISTENER_NAME_SECURE_${sasl.mechanism}_SASL_JAAS_CONFIG`, "org.apache.kafka.common.security.scram.ScramLoginModule required;")
|
|
102
|
+
.addExposedPorts(KAFKA_PORT, port);
|
|
103
|
+
if (truststore) {
|
|
104
|
+
this.withCopyContentToContainer(truststore.content, "/etc/kafka/secrets/server.truststore.pfx")
|
|
105
|
+
.withEnv("KAFKA_SSL_TRUSTSTORE_LOCATION", "/etc/kafka/secrets/server.truststore.pfx")
|
|
106
|
+
.withEnv("KAFKA_SSL_TRUSTSTORE_PASSWORD", truststore.passphrase)
|
|
107
|
+
.withEnv("KAFKA_SSL_TRUSTSTORE_TYPE", "PKCS12");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
addPlaintextListener() {
|
|
111
|
+
this.withEnv("KAFKA_LISTENERS", `PLAINTEXT://0.0.0.0:${KAFKA_PORT},BROKER://0.0.0.0:${KAFKA_BROKER_PORT}`).addExposedPorts(KAFKA_PORT);
|
|
112
|
+
}
|
|
113
|
+
updateAdvertisedListeners(container, inspectResult, boundPorts) {
|
|
77
114
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
115
|
const brokerAdvertisedListener = `BROKER://${inspectResult.hostname}:${KAFKA_BROKER_PORT}`;
|
|
79
|
-
|
|
116
|
+
let bootstrapServers = `PLAINTEXT://${container.getHost()}:${boundPorts.getBinding(KAFKA_PORT)}`;
|
|
117
|
+
if (this.saslSslConfig) {
|
|
118
|
+
bootstrapServers = `${bootstrapServers},SECURE://${container.getHost()}:${boundPorts.getBinding(this.saslSslConfig.port)}`;
|
|
119
|
+
}
|
|
80
120
|
const { output, exitCode } = yield container.exec([
|
|
81
121
|
"kafka-configs",
|
|
82
122
|
"--alter",
|
|
@@ -94,6 +134,27 @@ class KafkaContainer extends generic_container_1.GenericContainer {
|
|
|
94
134
|
}
|
|
95
135
|
});
|
|
96
136
|
}
|
|
137
|
+
createUser(container, { user: { name, password }, mechanism }) {
|
|
138
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
+
const { output, exitCode } = yield container.exec([
|
|
140
|
+
"kafka-configs",
|
|
141
|
+
"--alter",
|
|
142
|
+
// At the time of writing kafka-configs displays a warning stating that the 'zookeeper' flag is deprecated in favor of 'bootstrap-server'.
|
|
143
|
+
// Unfortunately, 'bootstrap-server' can only be used to set quotas and not to create a user.
|
|
144
|
+
"--zookeeper",
|
|
145
|
+
this.env["KAFKA_ZOOKEEPER_CONNECT"],
|
|
146
|
+
"--entity-type",
|
|
147
|
+
"users",
|
|
148
|
+
"--entity-name",
|
|
149
|
+
`${name}`,
|
|
150
|
+
"--add-config",
|
|
151
|
+
`${mechanism}=[password=${password}]`,
|
|
152
|
+
]);
|
|
153
|
+
if (exitCode !== 0) {
|
|
154
|
+
throw new Error(`Kafka container configuration failed with exit code ${exitCode}: ${output}`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
97
158
|
}
|
|
98
159
|
exports.KafkaContainer = KafkaContainer;
|
|
99
160
|
class StartedKafkaContainer extends abstract_started_container_1.AbstractStartedContainer {
|
|
@@ -13,6 +13,7 @@ exports.StartedMySqlContainer = exports.MySqlContainer = void 0;
|
|
|
13
13
|
const generic_container_1 = require("../../generic-container/generic-container");
|
|
14
14
|
const uuid_1 = require("../../uuid");
|
|
15
15
|
const abstract_started_container_1 = require("../abstract-started-container");
|
|
16
|
+
const MYSQL_PORT = 3306;
|
|
16
17
|
class MySqlContainer extends generic_container_1.GenericContainer {
|
|
17
18
|
constructor(image = "mysql:8.0.26") {
|
|
18
19
|
super(image);
|
|
@@ -42,7 +43,7 @@ class MySqlContainer extends generic_container_1.GenericContainer {
|
|
|
42
43
|
start: { get: () => super.start }
|
|
43
44
|
});
|
|
44
45
|
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
-
this.withExposedPorts(
|
|
46
|
+
this.withExposedPorts(...(this.hasExposedPorts ? this.ports : [MYSQL_PORT]))
|
|
46
47
|
.withEnv("MYSQL_DATABASE", this.database)
|
|
47
48
|
.withEnv("MYSQL_ROOT_PASSWORD", this.rootPassword)
|
|
48
49
|
.withEnv("MYSQL_USER", this.username)
|
|
@@ -39,7 +39,7 @@ class Neo4jContainer extends __1.GenericContainer {
|
|
|
39
39
|
start: { get: () => super.start }
|
|
40
40
|
});
|
|
41
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
this.withExposedPorts(BOLT_PORT, HTTP_PORT)
|
|
42
|
+
this.withExposedPorts(...(this.hasExposedPorts ? this.ports : [BOLT_PORT, HTTP_PORT]))
|
|
43
43
|
.withWaitStrategy(__1.Wait.forLogMessage("Started."))
|
|
44
44
|
.withEnv("NEO4J_AUTH", `${USERNAME}/${this.password}`)
|
|
45
45
|
.withStartupTimeout(120000);
|
|
@@ -13,6 +13,7 @@ exports.StartedPostgreSqlContainer = exports.PostgreSqlContainer = void 0;
|
|
|
13
13
|
const generic_container_1 = require("../../generic-container/generic-container");
|
|
14
14
|
const uuid_1 = require("../../uuid");
|
|
15
15
|
const abstract_started_container_1 = require("../abstract-started-container");
|
|
16
|
+
const POSTGRES_PORT = 5432;
|
|
16
17
|
class PostgreSqlContainer extends generic_container_1.GenericContainer {
|
|
17
18
|
constructor(image = "postgres:13.3-alpine") {
|
|
18
19
|
super(image);
|
|
@@ -37,7 +38,7 @@ class PostgreSqlContainer extends generic_container_1.GenericContainer {
|
|
|
37
38
|
start: { get: () => super.start }
|
|
38
39
|
});
|
|
39
40
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
-
this.withExposedPorts(
|
|
41
|
+
this.withExposedPorts(...(this.hasExposedPorts ? this.ports : [POSTGRES_PORT]))
|
|
41
42
|
.withEnv("POSTGRES_DB", this.database)
|
|
42
43
|
.withEnv("POSTGRES_USER", this.username)
|
|
43
44
|
.withEnv("POSTGRES_PASSWORD", this.password)
|