rightsize 0.1.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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +255 -0
- package/dist/backend-docker/backend.d.ts +53 -0
- package/dist/backend-docker/backend.d.ts.map +1 -0
- package/dist/backend-docker/backend.js +336 -0
- package/dist/backend-docker/backend.js.map +1 -0
- package/dist/backend-docker/client.d.ts +82 -0
- package/dist/backend-docker/client.d.ts.map +1 -0
- package/dist/backend-docker/client.js +190 -0
- package/dist/backend-docker/client.js.map +1 -0
- package/dist/backend-docker/frames.d.ts +44 -0
- package/dist/backend-docker/frames.d.ts.map +1 -0
- package/dist/backend-docker/frames.js +105 -0
- package/dist/backend-docker/frames.js.map +1 -0
- package/dist/backend-docker/index.d.ts +5 -0
- package/dist/backend-docker/index.d.ts.map +1 -0
- package/dist/backend-docker/index.js +20 -0
- package/dist/backend-docker/index.js.map +1 -0
- package/dist/backend-docker/json.d.ts +13 -0
- package/dist/backend-docker/json.d.ts.map +1 -0
- package/dist/backend-docker/json.js +47 -0
- package/dist/backend-docker/json.js.map +1 -0
- package/dist/backend-docker/labels.d.ts +11 -0
- package/dist/backend-docker/labels.d.ts.map +1 -0
- package/dist/backend-docker/labels.js +13 -0
- package/dist/backend-docker/labels.js.map +1 -0
- package/dist/backend-docker/port-conflict.d.ts +9 -0
- package/dist/backend-docker/port-conflict.d.ts.map +1 -0
- package/dist/backend-docker/port-conflict.js +12 -0
- package/dist/backend-docker/port-conflict.js.map +1 -0
- package/dist/backend-docker/provider.d.ts +22 -0
- package/dist/backend-docker/provider.d.ts.map +1 -0
- package/dist/backend-docker/provider.js +36 -0
- package/dist/backend-docker/provider.js.map +1 -0
- package/dist/backend-msb/backend.d.ts +169 -0
- package/dist/backend-msb/backend.d.ts.map +1 -0
- package/dist/backend-msb/backend.js +664 -0
- package/dist/backend-msb/backend.js.map +1 -0
- package/dist/backend-msb/commands.d.ts +28 -0
- package/dist/backend-msb/commands.d.ts.map +1 -0
- package/dist/backend-msb/commands.js +67 -0
- package/dist/backend-msb/commands.js.map +1 -0
- package/dist/backend-msb/exec-tunnel.d.ts +33 -0
- package/dist/backend-msb/exec-tunnel.d.ts.map +1 -0
- package/dist/backend-msb/exec-tunnel.js +163 -0
- package/dist/backend-msb/exec-tunnel.js.map +1 -0
- package/dist/backend-msb/follow-replay.d.ts +14 -0
- package/dist/backend-msb/follow-replay.d.ts.map +1 -0
- package/dist/backend-msb/follow-replay.js +20 -0
- package/dist/backend-msb/follow-replay.js.map +1 -0
- package/dist/backend-msb/image-cache.d.ts +25 -0
- package/dist/backend-msb/image-cache.d.ts.map +1 -0
- package/dist/backend-msb/image-cache.js +27 -0
- package/dist/backend-msb/image-cache.js.map +1 -0
- package/dist/backend-msb/index.d.ts +6 -0
- package/dist/backend-msb/index.d.ts.map +1 -0
- package/dist/backend-msb/index.js +21 -0
- package/dist/backend-msb/index.js.map +1 -0
- package/dist/backend-msb/invoke.d.ts +24 -0
- package/dist/backend-msb/invoke.d.ts.map +1 -0
- package/dist/backend-msb/invoke.js +73 -0
- package/dist/backend-msb/invoke.js.map +1 -0
- package/dist/backend-msb/ls-json.d.ts +23 -0
- package/dist/backend-msb/ls-json.d.ts.map +1 -0
- package/dist/backend-msb/ls-json.js +113 -0
- package/dist/backend-msb/ls-json.js.map +1 -0
- package/dist/backend-msb/network-links.d.ts +7 -0
- package/dist/backend-msb/network-links.d.ts.map +1 -0
- package/dist/backend-msb/network-links.js +31 -0
- package/dist/backend-msb/network-links.js.map +1 -0
- package/dist/backend-msb/platform.d.ts +29 -0
- package/dist/backend-msb/platform.d.ts.map +1 -0
- package/dist/backend-msb/platform.js +128 -0
- package/dist/backend-msb/platform.js.map +1 -0
- package/dist/backend-msb/port-conflict.d.ts +10 -0
- package/dist/backend-msb/port-conflict.d.ts.map +1 -0
- package/dist/backend-msb/port-conflict.js +16 -0
- package/dist/backend-msb/port-conflict.js.map +1 -0
- package/dist/backend-msb/provider.d.ts +10 -0
- package/dist/backend-msb/provider.d.ts.map +1 -0
- package/dist/backend-msb/provider.js +40 -0
- package/dist/backend-msb/provider.js.map +1 -0
- package/dist/backend-msb/provisioner.d.ts +19 -0
- package/dist/backend-msb/provisioner.d.ts.map +1 -0
- package/dist/backend-msb/provisioner.js +332 -0
- package/dist/backend-msb/provisioner.js.map +1 -0
- package/dist/backend-msb/reaper.d.ts +10 -0
- package/dist/backend-msb/reaper.d.ts.map +1 -0
- package/dist/backend-msb/reaper.js +73 -0
- package/dist/backend-msb/reaper.js.map +1 -0
- package/dist/core/backend.d.ts +98 -0
- package/dist/core/backend.d.ts.map +1 -0
- package/dist/core/backend.js +2 -0
- package/dist/core/backend.js.map +1 -0
- package/dist/core/backends.d.ts +37 -0
- package/dist/core/backends.d.ts.map +1 -0
- package/dist/core/backends.js +127 -0
- package/dist/core/backends.js.map +1 -0
- package/dist/core/cleanup.d.ts +37 -0
- package/dist/core/cleanup.d.ts.map +1 -0
- package/dist/core/cleanup.js +86 -0
- package/dist/core/cleanup.js.map +1 -0
- package/dist/core/errors.d.ts +51 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +66 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/free-ports.d.ts +23 -0
- package/dist/core/free-ports.d.ts.map +1 -0
- package/dist/core/free-ports.js +63 -0
- package/dist/core/free-ports.js.map +1 -0
- package/dist/core/generic-container.d.ts +145 -0
- package/dist/core/generic-container.d.ts.map +1 -0
- package/dist/core/generic-container.js +385 -0
- package/dist/core/generic-container.js.map +1 -0
- package/dist/core/model.d.ts +60 -0
- package/dist/core/model.d.ts.map +1 -0
- package/dist/core/model.js +2 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/mountable-file.d.ts +25 -0
- package/dist/core/mountable-file.d.ts.map +1 -0
- package/dist/core/mountable-file.js +64 -0
- package/dist/core/mountable-file.js.map +1 -0
- package/dist/core/network.d.ts +58 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/network.js +84 -0
- package/dist/core/network.js.map +1 -0
- package/dist/core/run-id.d.ts +18 -0
- package/dist/core/run-id.d.ts.map +1 -0
- package/dist/core/run-id.js +18 -0
- package/dist/core/run-id.js.map +1 -0
- package/dist/core/wait.d.ts +59 -0
- package/dist/core/wait.d.ts.map +1 -0
- package/dist/core/wait.js +239 -0
- package/dist/core/wait.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/arango.d.ts +14 -0
- package/dist/modules/arango.d.ts.map +1 -0
- package/dist/modules/arango.js +37 -0
- package/dist/modules/arango.js.map +1 -0
- package/dist/modules/clickhouse.d.ts +37 -0
- package/dist/modules/clickhouse.d.ts.map +1 -0
- package/dist/modules/clickhouse.js +69 -0
- package/dist/modules/clickhouse.js.map +1 -0
- package/dist/modules/flink.d.ts +65 -0
- package/dist/modules/flink.d.ts.map +1 -0
- package/dist/modules/flink.js +125 -0
- package/dist/modules/flink.js.map +1 -0
- package/dist/modules/floci.d.ts +46 -0
- package/dist/modules/floci.d.ts.map +1 -0
- package/dist/modules/floci.js +62 -0
- package/dist/modules/floci.js.map +1 -0
- package/dist/modules/index.d.ts +31 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +31 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/kafka.d.ts +11 -0
- package/dist/modules/kafka.d.ts.map +1 -0
- package/dist/modules/kafka.js +41 -0
- package/dist/modules/kafka.js.map +1 -0
- package/dist/modules/keycloak.d.ts +40 -0
- package/dist/modules/keycloak.d.ts.map +1 -0
- package/dist/modules/keycloak.js +67 -0
- package/dist/modules/keycloak.js.map +1 -0
- package/dist/modules/mariadb.d.ts +56 -0
- package/dist/modules/mariadb.d.ts.map +1 -0
- package/dist/modules/mariadb.js +89 -0
- package/dist/modules/mariadb.js.map +1 -0
- package/dist/modules/memcached.d.ts +24 -0
- package/dist/modules/memcached.d.ts.map +1 -0
- package/dist/modules/memcached.js +105 -0
- package/dist/modules/memcached.js.map +1 -0
- package/dist/modules/mongodb.d.ts +19 -0
- package/dist/modules/mongodb.d.ts.map +1 -0
- package/dist/modules/mongodb.js +69 -0
- package/dist/modules/mongodb.js.map +1 -0
- package/dist/modules/mysql.d.ts +61 -0
- package/dist/modules/mysql.d.ts.map +1 -0
- package/dist/modules/mysql.js +101 -0
- package/dist/modules/mysql.js.map +1 -0
- package/dist/modules/neo4j.d.ts +60 -0
- package/dist/modules/neo4j.d.ts.map +1 -0
- package/dist/modules/neo4j.js +83 -0
- package/dist/modules/neo4j.js.map +1 -0
- package/dist/modules/pinot.d.ts +30 -0
- package/dist/modules/pinot.d.ts.map +1 -0
- package/dist/modules/pinot.js +49 -0
- package/dist/modules/pinot.js.map +1 -0
- package/dist/modules/postgres.d.ts +29 -0
- package/dist/modules/postgres.d.ts.map +1 -0
- package/dist/modules/postgres.js +73 -0
- package/dist/modules/postgres.js.map +1 -0
- package/dist/modules/rabbitmq.d.ts +46 -0
- package/dist/modules/rabbitmq.d.ts.map +1 -0
- package/dist/modules/rabbitmq.js +68 -0
- package/dist/modules/rabbitmq.js.map +1 -0
- package/dist/modules/redis.d.ts +15 -0
- package/dist/modules/redis.d.ts.map +1 -0
- package/dist/modules/redis.js +24 -0
- package/dist/modules/redis.js.map +1 -0
- package/dist/modules/redpanda.d.ts +27 -0
- package/dist/modules/redpanda.d.ts.map +1 -0
- package/dist/modules/redpanda.js +58 -0
- package/dist/modules/redpanda.js.map +1 -0
- package/dist/modules/spring-cloud-config.d.ts +18 -0
- package/dist/modules/spring-cloud-config.d.ts.map +1 -0
- package/dist/modules/spring-cloud-config.js +28 -0
- package/dist/modules/spring-cloud-config.js.map +1 -0
- package/dist/modules/wiremock.d.ts +21 -0
- package/dist/modules/wiremock.d.ts.map +1 -0
- package/dist/modules/wiremock.js +32 -0
- package/dist/modules/wiremock.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import { Wait } from "../core/wait.js";
|
|
3
|
+
const HTTP_PORT = 8123;
|
|
4
|
+
const NATIVE_PORT = 9000;
|
|
5
|
+
/**
|
|
6
|
+
* A single-node ClickHouse container, ready-checked via `/ping` (returns
|
|
7
|
+
* the literal body `Ok.` on 200 — `Wait.forHttp`'s status-code check is all
|
|
8
|
+
* that's needed here). Exposes both the HTTP interface (8123, what the
|
|
9
|
+
* helpers here use) and the native protocol port (9000) for consumers that
|
|
10
|
+
* want a native-protocol client instead.
|
|
11
|
+
*
|
|
12
|
+
* `CLICKHOUSE_USER`/`CLICKHOUSE_PASSWORD`/`CLICKHOUSE_DB` (verified against
|
|
13
|
+
* the entrypoint's own logging: `create new user 'test' instead 'default'`,
|
|
14
|
+
* `create database 'test'`) replace the image's default `default` user
|
|
15
|
+
* entirely — once set, the unauthenticated `default` user's usual
|
|
16
|
+
* passwordless access is gone, and every query needs the configured
|
|
17
|
+
* credentials.
|
|
18
|
+
*/
|
|
19
|
+
export class ClickHouseContainer extends GenericContainer {
|
|
20
|
+
usernameState = "test";
|
|
21
|
+
passwordState = "test";
|
|
22
|
+
databaseState = "test";
|
|
23
|
+
constructor(image = "clickhouse/clickhouse-server:25.8") {
|
|
24
|
+
super(image);
|
|
25
|
+
this.withExposedPorts(HTTP_PORT, NATIVE_PORT)
|
|
26
|
+
.withEnv("CLICKHOUSE_USER", this.usernameState)
|
|
27
|
+
.withEnv("CLICKHOUSE_PASSWORD", this.passwordState)
|
|
28
|
+
.withEnv("CLICKHOUSE_DB", this.databaseState)
|
|
29
|
+
.waitingFor(Wait.forHttp("/ping").forPort(HTTP_PORT));
|
|
30
|
+
// No withMemoryLimit override: measured ~524MB resident at rest well
|
|
31
|
+
// under msb's default ~450MB-and-up microVM sizing headroom in
|
|
32
|
+
// practice (verified booting and answering /ping on msb's default).
|
|
33
|
+
}
|
|
34
|
+
static async start(image = "clickhouse/clickhouse-server:25.8") {
|
|
35
|
+
return (await new ClickHouseContainer(image).start());
|
|
36
|
+
}
|
|
37
|
+
/** Overrides `CLICKHOUSE_USER` (default `test`). */
|
|
38
|
+
withUsername(username) {
|
|
39
|
+
this.usernameState = username;
|
|
40
|
+
return this.withEnv("CLICKHOUSE_USER", username);
|
|
41
|
+
}
|
|
42
|
+
/** Overrides `CLICKHOUSE_PASSWORD` (default `test`). */
|
|
43
|
+
withPassword(password) {
|
|
44
|
+
this.passwordState = password;
|
|
45
|
+
return this.withEnv("CLICKHOUSE_PASSWORD", password);
|
|
46
|
+
}
|
|
47
|
+
/** Overrides `CLICKHOUSE_DB` (default `test`). */
|
|
48
|
+
withDatabase(database) {
|
|
49
|
+
this.databaseState = database;
|
|
50
|
+
return this.withEnv("CLICKHOUSE_DB", database);
|
|
51
|
+
}
|
|
52
|
+
/** The configured user (default `test`). */
|
|
53
|
+
get username() {
|
|
54
|
+
return this.usernameState;
|
|
55
|
+
}
|
|
56
|
+
/** The configured password (default `test`). */
|
|
57
|
+
get password() {
|
|
58
|
+
return this.passwordState;
|
|
59
|
+
}
|
|
60
|
+
/** The configured database name (default `test`). */
|
|
61
|
+
get databaseName() {
|
|
62
|
+
return this.databaseState;
|
|
63
|
+
}
|
|
64
|
+
/** The HTTP interface's base URL for the running container. */
|
|
65
|
+
get httpUrl() {
|
|
66
|
+
return `http://${this.host}:${this.getMappedPort(HTTP_PORT)}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=clickhouse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../src/modules/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,mBAAoB,SAAQ,gBAAgB;IAC/C,aAAa,GAAG,MAAM,CAAC;IACvB,aAAa,GAAG,MAAM,CAAC;IACvB,aAAa,GAAG,MAAM,CAAC;IAE/B,YAAY,KAAK,GAAG,mCAAmC;QACrD,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC;aAC1C,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,aAAa,CAAC;aAC9C,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,aAAa,CAAC;aAClD,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,aAAa,CAAC;aAC5C,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACxD,qEAAqE;QACrE,+DAA+D;QAC/D,oEAAoE;IACtE,CAAC;IAED,MAAM,CAAU,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,mCAAmC;QACrE,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAwB,CAAC;IAC/E,CAAC;IAED,oDAAoD;IACpD,YAAY,CAAC,QAAgB;QAC3B,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,wDAAwD;IACxD,YAAY,CAAC,QAAgB;QAC3B,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IAED,kDAAkD;IAClD,YAAY,CAAC,QAAgB;QAC3B,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;IAED,4CAA4C;IAC5C,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,gDAAgD;IAChD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,qDAAqD;IACrD,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,+DAA+D;IAC/D,IAAI,OAAO;QACT,OAAO,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;IAChE,CAAC;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import type { SandboxBackend } from "../core/backend.js";
|
|
3
|
+
/**
|
|
4
|
+
* A Flink JobManager container (REST 8081, RPC 6123), with an optional
|
|
5
|
+
* companion TaskManager on Docker only — see `withTaskManager()`.
|
|
6
|
+
*
|
|
7
|
+
* `FLINK_PROPERTIES` must set `jobmanager.rpc.address` on the JobManager
|
|
8
|
+
* itself (not just the TaskManager): the JobManager's own Pekko/Akka actor
|
|
9
|
+
* system binds its external address from this property too, and the image's
|
|
10
|
+
* default (`localhost`) doesn't resolve to anything reachable from a sibling
|
|
11
|
+
* container. Both roles get `withMemoryLimit(1024)` — verified booting
|
|
12
|
+
* cleanly on this machine at that floor for a bare JobManager and for the
|
|
13
|
+
* two-container topology on docker.
|
|
14
|
+
*
|
|
15
|
+
* ### `withTaskManager()` — docker-only (KNOWN, adjudicated msb limitation)
|
|
16
|
+
*
|
|
17
|
+
* A real Flink session cluster needs a TaskManager to actually run
|
|
18
|
+
* anything; it registers with the JobManager over a **persistent
|
|
19
|
+
* bidirectional Pekko/Akka RPC connection**. msb's network-link emulation
|
|
20
|
+
* (`ExecTunnel`) only carries **client-speaks-first, one-exchange-at-a-time**
|
|
21
|
+
* traffic through an in-guest `nc` listener — and the `flink` image ships
|
|
22
|
+
* neither `nc` nor `busybox` at all (confirmed: `command -v nc` and
|
|
23
|
+
* `command -v busybox` both exit 127 in `flink:1.20.5`), so the tunnel's
|
|
24
|
+
* `installNetworkLinks` step can't even probe for the listener it would
|
|
25
|
+
* need, let alone carry Pekko's long-lived duplex traffic if it somehow
|
|
26
|
+
* could. `withTaskManager()` therefore throws `UnsupportedByBackendError`
|
|
27
|
+
* from `containerIsStarting()` — the instant `start()` has resolved the msb
|
|
28
|
+
* backend, before `ensureNetwork`, port allocation, or the JobManager
|
|
29
|
+
* container is created/booted at all — rather than attempting a boot that
|
|
30
|
+
* would hang or fail deep inside Flink's own registration timeout. On
|
|
31
|
+
* docker, the same builder produces a real two-container session cluster on
|
|
32
|
+
* a shared `Network`, and the TaskManager registers successfully (verified:
|
|
33
|
+
* `/taskmanagers` reports one registered TM with a live heartbeat).
|
|
34
|
+
*/
|
|
35
|
+
export declare class FlinkContainer extends GenericContainer {
|
|
36
|
+
private readonly imageTag;
|
|
37
|
+
private taskManagerRequested;
|
|
38
|
+
private taskManagerContainer;
|
|
39
|
+
private taskManagerNetwork;
|
|
40
|
+
private ownedNetwork;
|
|
41
|
+
constructor(image?: string);
|
|
42
|
+
static start(image?: string): Promise<FlinkContainer>;
|
|
43
|
+
/**
|
|
44
|
+
* Requests a companion TaskManager, started on a shared `Network` once
|
|
45
|
+
* the JobManager is ready. Docker only — see the class doc for why msb
|
|
46
|
+
* can't carry this topology; calling `start()` after this on msb throws
|
|
47
|
+
* `UnsupportedByBackendError` from `containerIsStarting()`, before any
|
|
48
|
+
* boot work (network, ports, or the JobManager container itself) begins.
|
|
49
|
+
*/
|
|
50
|
+
withTaskManager(): this;
|
|
51
|
+
/**
|
|
52
|
+
* Fires the instant `start()` resolves a backend — before `ensureNetwork`,
|
|
53
|
+
* port allocation, or the JobManager container is created/booted at all.
|
|
54
|
+
* Rejecting an msb + withTaskManager() combination here, instead of in
|
|
55
|
+
* `containerIsStarted()` after the JobManager has already fully booted,
|
|
56
|
+
* means the caller never pays for (or has to clean up after) a boot this
|
|
57
|
+
* backend was never going to be able to complete.
|
|
58
|
+
*/
|
|
59
|
+
protected containerIsStarting(backend: SandboxBackend): Promise<void>;
|
|
60
|
+
protected containerIsStarted(): Promise<void>;
|
|
61
|
+
stop(): Promise<void>;
|
|
62
|
+
/** The REST base URL for the running JobManager. */
|
|
63
|
+
get restUrl(): string;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=flink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flink.d.ts","sourceRoot":"","sources":["../../src/modules/flink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAIhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,cAAe,SAAQ,gBAAgB;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,YAAY,CAAsB;gBAE9B,KAAK,SAAiB;WAWZ,KAAK,CAAC,KAAK,SAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;IAI5E;;;;;;OAMG;IACH,eAAe,IAAI,IAAI;IASvB;;;;;;;OAOG;cACsB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;cAU3D,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYpC,oDAAoD;IACpD,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import { Network } from "../core/network.js";
|
|
3
|
+
import { Wait } from "../core/wait.js";
|
|
4
|
+
import { UnsupportedByBackendError } from "../core/errors.js";
|
|
5
|
+
const RPC_PORT = 6123;
|
|
6
|
+
const REST_PORT = 8081;
|
|
7
|
+
const JOBMANAGER_MEMORY_MB = 1024;
|
|
8
|
+
const TASKMANAGER_MEMORY_MB = 1024;
|
|
9
|
+
const JOBMANAGER_ALIAS = "flink-jobmanager";
|
|
10
|
+
/**
|
|
11
|
+
* A Flink JobManager container (REST 8081, RPC 6123), with an optional
|
|
12
|
+
* companion TaskManager on Docker only — see `withTaskManager()`.
|
|
13
|
+
*
|
|
14
|
+
* `FLINK_PROPERTIES` must set `jobmanager.rpc.address` on the JobManager
|
|
15
|
+
* itself (not just the TaskManager): the JobManager's own Pekko/Akka actor
|
|
16
|
+
* system binds its external address from this property too, and the image's
|
|
17
|
+
* default (`localhost`) doesn't resolve to anything reachable from a sibling
|
|
18
|
+
* container. Both roles get `withMemoryLimit(1024)` — verified booting
|
|
19
|
+
* cleanly on this machine at that floor for a bare JobManager and for the
|
|
20
|
+
* two-container topology on docker.
|
|
21
|
+
*
|
|
22
|
+
* ### `withTaskManager()` — docker-only (KNOWN, adjudicated msb limitation)
|
|
23
|
+
*
|
|
24
|
+
* A real Flink session cluster needs a TaskManager to actually run
|
|
25
|
+
* anything; it registers with the JobManager over a **persistent
|
|
26
|
+
* bidirectional Pekko/Akka RPC connection**. msb's network-link emulation
|
|
27
|
+
* (`ExecTunnel`) only carries **client-speaks-first, one-exchange-at-a-time**
|
|
28
|
+
* traffic through an in-guest `nc` listener — and the `flink` image ships
|
|
29
|
+
* neither `nc` nor `busybox` at all (confirmed: `command -v nc` and
|
|
30
|
+
* `command -v busybox` both exit 127 in `flink:1.20.5`), so the tunnel's
|
|
31
|
+
* `installNetworkLinks` step can't even probe for the listener it would
|
|
32
|
+
* need, let alone carry Pekko's long-lived duplex traffic if it somehow
|
|
33
|
+
* could. `withTaskManager()` therefore throws `UnsupportedByBackendError`
|
|
34
|
+
* from `containerIsStarting()` — the instant `start()` has resolved the msb
|
|
35
|
+
* backend, before `ensureNetwork`, port allocation, or the JobManager
|
|
36
|
+
* container is created/booted at all — rather than attempting a boot that
|
|
37
|
+
* would hang or fail deep inside Flink's own registration timeout. On
|
|
38
|
+
* docker, the same builder produces a real two-container session cluster on
|
|
39
|
+
* a shared `Network`, and the TaskManager registers successfully (verified:
|
|
40
|
+
* `/taskmanagers` reports one registered TM with a live heartbeat).
|
|
41
|
+
*/
|
|
42
|
+
export class FlinkContainer extends GenericContainer {
|
|
43
|
+
imageTag;
|
|
44
|
+
taskManagerRequested = false;
|
|
45
|
+
taskManagerContainer;
|
|
46
|
+
taskManagerNetwork;
|
|
47
|
+
ownedNetwork;
|
|
48
|
+
constructor(image = "flink:1.20.5") {
|
|
49
|
+
super(image);
|
|
50
|
+
this.imageTag = image;
|
|
51
|
+
this.withExposedPorts(RPC_PORT, REST_PORT)
|
|
52
|
+
.withEnv("FLINK_PROPERTIES", `jobmanager.rpc.address: ${JOBMANAGER_ALIAS}`)
|
|
53
|
+
.withCommand("jobmanager")
|
|
54
|
+
.withMemoryLimit(JOBMANAGER_MEMORY_MB)
|
|
55
|
+
.withNetworkAliases(JOBMANAGER_ALIAS)
|
|
56
|
+
.waitingFor(Wait.forHttp("/overview").forPort(REST_PORT).withStartupTimeout(120_000));
|
|
57
|
+
}
|
|
58
|
+
static async start(image = "flink:1.20.5") {
|
|
59
|
+
return (await new FlinkContainer(image).start());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Requests a companion TaskManager, started on a shared `Network` once
|
|
63
|
+
* the JobManager is ready. Docker only — see the class doc for why msb
|
|
64
|
+
* can't carry this topology; calling `start()` after this on msb throws
|
|
65
|
+
* `UnsupportedByBackendError` from `containerIsStarting()`, before any
|
|
66
|
+
* boot work (network, ports, or the JobManager container itself) begins.
|
|
67
|
+
*/
|
|
68
|
+
withTaskManager() {
|
|
69
|
+
this.taskManagerRequested = true;
|
|
70
|
+
const net = Network.newNetwork();
|
|
71
|
+
this.ownedNetwork = net;
|
|
72
|
+
this.taskManagerNetwork = net;
|
|
73
|
+
this.withNetwork(net);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fires the instant `start()` resolves a backend — before `ensureNetwork`,
|
|
78
|
+
* port allocation, or the JobManager container is created/booted at all.
|
|
79
|
+
* Rejecting an msb + withTaskManager() combination here, instead of in
|
|
80
|
+
* `containerIsStarted()` after the JobManager has already fully booted,
|
|
81
|
+
* means the caller never pays for (or has to clean up after) a boot this
|
|
82
|
+
* backend was never going to be able to complete.
|
|
83
|
+
*/
|
|
84
|
+
async containerIsStarting(backend) {
|
|
85
|
+
if (this.taskManagerRequested && backend.name === "microsandbox") {
|
|
86
|
+
throw new UnsupportedByBackendError("Flink TaskManager registration", backend.name, "the flink image ships no nc/busybox for the network-link exec-tunnel, and TaskManager registration is a persistent bidirectional RPC the tunnel can't carry anyway — run this with RIGHTSIZE_BACKEND=docker instead");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async containerIsStarted() {
|
|
90
|
+
if (!this.taskManagerRequested) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const backend = this.currentBackend();
|
|
94
|
+
const network = this.taskManagerNetwork;
|
|
95
|
+
if (network === undefined) {
|
|
96
|
+
throw new Error("withTaskManager() requires a network, but none was set");
|
|
97
|
+
}
|
|
98
|
+
const taskManager = new GenericContainer(this.imageTag)
|
|
99
|
+
.withEnv("FLINK_PROPERTIES", `jobmanager.rpc.address: ${JOBMANAGER_ALIAS}`)
|
|
100
|
+
.withCommand("taskmanager")
|
|
101
|
+
.withMemoryLimit(TASKMANAGER_MEMORY_MB)
|
|
102
|
+
.withNetwork(network)
|
|
103
|
+
// Use the SAME backend this JobManager actually resolved (test
|
|
104
|
+
// doubles and an explicit RIGHTSIZE_BACKEND override must both carry
|
|
105
|
+
// through to the companion container, not re-resolve independently).
|
|
106
|
+
.withBackend(backend);
|
|
107
|
+
this.taskManagerContainer = await taskManager.start();
|
|
108
|
+
}
|
|
109
|
+
async stop() {
|
|
110
|
+
if (this.taskManagerContainer !== undefined) {
|
|
111
|
+
await this.taskManagerContainer.stop();
|
|
112
|
+
this.taskManagerContainer = undefined;
|
|
113
|
+
}
|
|
114
|
+
await super.stop();
|
|
115
|
+
if (this.ownedNetwork !== undefined) {
|
|
116
|
+
await this.ownedNetwork.close();
|
|
117
|
+
this.ownedNetwork = undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** The REST base URL for the running JobManager. */
|
|
121
|
+
get restUrl() {
|
|
122
|
+
return `http://${this.host}:${this.getMappedPort(REST_PORT)}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=flink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flink.js","sourceRoot":"","sources":["../../src/modules/flink.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
/**
|
|
3
|
+
* A [floci.io](https://floci.io) cloud emulator — one native Quarkus image
|
|
4
|
+
* per cloud provider, each speaking that provider's REST APIs against an
|
|
5
|
+
* in-memory backing store. One module type covers all three variants; pick
|
|
6
|
+
* one via the `FlociContainer.aws()`/`.azure()`/`.gcp()` factory functions
|
|
7
|
+
* (there is no bare `new FlociContainer(...)` or `static start()` — each
|
|
8
|
+
* factory pins the provider's own image and guest port, so it's the only
|
|
9
|
+
* entry point) and call `.start()` on the result, e.g. `await
|
|
10
|
+
* FlociContainer.aws().start()`.
|
|
11
|
+
*
|
|
12
|
+
* ### Readiness — `/health` works uniformly, unlike the AWS-flavored `/_localstack/health`
|
|
13
|
+
*
|
|
14
|
+
* All three variants answer a plain `GET /health` with `200` and a small
|
|
15
|
+
* JSON status body the moment the embedded Quarkus HTTP listener is up
|
|
16
|
+
* (verified against real boots of `floci/floci:1.5.30`,
|
|
17
|
+
* `floci/floci-az:0.8.0`, and `floci/floci-gcp:0.4.0`) — pinned as the one
|
|
18
|
+
* wait path that works across all three; no log-wait fallback was needed.
|
|
19
|
+
*
|
|
20
|
+
* ### No signing needed — verified against the AWS variant's S3 surface
|
|
21
|
+
*
|
|
22
|
+
* The AWS variant's S3-shaped REST endpoints accept unsigned requests with
|
|
23
|
+
* no `Authorization` header at all: `PUT /<bucket>` (create-bucket), `PUT
|
|
24
|
+
* /<bucket>/<key>` (put-object), and `GET /<bucket>/<key>` (get-object) all
|
|
25
|
+
* round-trip successfully with a bare HTTP client call — no SigV4, no AWS
|
|
26
|
+
* SDK dependency required.
|
|
27
|
+
*
|
|
28
|
+
* ### Memory — tiny, no ladder needed
|
|
29
|
+
*
|
|
30
|
+
* All three images are native (GraalVM) Quarkus binaries; a real boot
|
|
31
|
+
* settles under 150MiB RSS, and each variant boots and answers `/health`
|
|
32
|
+
* under msb's default microVM RAM with no `withMemoryLimit` override.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FlociContainer extends GenericContainer {
|
|
35
|
+
private readonly guestPort;
|
|
36
|
+
private constructor();
|
|
37
|
+
/** The AWS emulator (`floci/floci:1.5.30`), guest port 4566 — S3, DynamoDB, SQS, etc. */
|
|
38
|
+
static aws(image?: string): FlociContainer;
|
|
39
|
+
/** The Azure emulator (`floci/floci-az:0.8.0`), guest port 4577. */
|
|
40
|
+
static azure(image?: string): FlociContainer;
|
|
41
|
+
/** The GCP emulator (`floci/floci-gcp:0.4.0`), guest port 4588. */
|
|
42
|
+
static gcp(image?: string): FlociContainer;
|
|
43
|
+
/** This variant's REST endpoint — the base URL for every emulated API call. */
|
|
44
|
+
get endpointUrl(): string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=floci.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floci.d.ts","sourceRoot":"","sources":["../../src/modules/floci.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAOhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,cAAe,SAAQ,gBAAgB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO;IAMP,yFAAyF;IACzF,MAAM,CAAC,GAAG,CAAC,KAAK,SAAuB,GAAG,cAAc;IAIxD,oEAAoE;IACpE,MAAM,CAAC,KAAK,CAAC,KAAK,SAAyB,GAAG,cAAc;IAI5D,mEAAmE;IACnE,MAAM,CAAC,GAAG,CAAC,KAAK,SAA0B,GAAG,cAAc;IAI3D,+EAA+E;IAC/E,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import { Wait } from "../core/wait.js";
|
|
3
|
+
const AWS_PORT = 4566;
|
|
4
|
+
const AZURE_PORT = 4577;
|
|
5
|
+
const GCP_PORT = 4588;
|
|
6
|
+
/**
|
|
7
|
+
* A [floci.io](https://floci.io) cloud emulator — one native Quarkus image
|
|
8
|
+
* per cloud provider, each speaking that provider's REST APIs against an
|
|
9
|
+
* in-memory backing store. One module type covers all three variants; pick
|
|
10
|
+
* one via the `FlociContainer.aws()`/`.azure()`/`.gcp()` factory functions
|
|
11
|
+
* (there is no bare `new FlociContainer(...)` or `static start()` — each
|
|
12
|
+
* factory pins the provider's own image and guest port, so it's the only
|
|
13
|
+
* entry point) and call `.start()` on the result, e.g. `await
|
|
14
|
+
* FlociContainer.aws().start()`.
|
|
15
|
+
*
|
|
16
|
+
* ### Readiness — `/health` works uniformly, unlike the AWS-flavored `/_localstack/health`
|
|
17
|
+
*
|
|
18
|
+
* All three variants answer a plain `GET /health` with `200` and a small
|
|
19
|
+
* JSON status body the moment the embedded Quarkus HTTP listener is up
|
|
20
|
+
* (verified against real boots of `floci/floci:1.5.30`,
|
|
21
|
+
* `floci/floci-az:0.8.0`, and `floci/floci-gcp:0.4.0`) — pinned as the one
|
|
22
|
+
* wait path that works across all three; no log-wait fallback was needed.
|
|
23
|
+
*
|
|
24
|
+
* ### No signing needed — verified against the AWS variant's S3 surface
|
|
25
|
+
*
|
|
26
|
+
* The AWS variant's S3-shaped REST endpoints accept unsigned requests with
|
|
27
|
+
* no `Authorization` header at all: `PUT /<bucket>` (create-bucket), `PUT
|
|
28
|
+
* /<bucket>/<key>` (put-object), and `GET /<bucket>/<key>` (get-object) all
|
|
29
|
+
* round-trip successfully with a bare HTTP client call — no SigV4, no AWS
|
|
30
|
+
* SDK dependency required.
|
|
31
|
+
*
|
|
32
|
+
* ### Memory — tiny, no ladder needed
|
|
33
|
+
*
|
|
34
|
+
* All three images are native (GraalVM) Quarkus binaries; a real boot
|
|
35
|
+
* settles under 150MiB RSS, and each variant boots and answers `/health`
|
|
36
|
+
* under msb's default microVM RAM with no `withMemoryLimit` override.
|
|
37
|
+
*/
|
|
38
|
+
export class FlociContainer extends GenericContainer {
|
|
39
|
+
guestPort;
|
|
40
|
+
constructor(image, port) {
|
|
41
|
+
super(image);
|
|
42
|
+
this.guestPort = port;
|
|
43
|
+
this.withExposedPorts(port).waitingFor(Wait.forHttp("/health").forPort(port));
|
|
44
|
+
}
|
|
45
|
+
/** The AWS emulator (`floci/floci:1.5.30`), guest port 4566 — S3, DynamoDB, SQS, etc. */
|
|
46
|
+
static aws(image = "floci/floci:1.5.30") {
|
|
47
|
+
return new FlociContainer(image, AWS_PORT);
|
|
48
|
+
}
|
|
49
|
+
/** The Azure emulator (`floci/floci-az:0.8.0`), guest port 4577. */
|
|
50
|
+
static azure(image = "floci/floci-az:0.8.0") {
|
|
51
|
+
return new FlociContainer(image, AZURE_PORT);
|
|
52
|
+
}
|
|
53
|
+
/** The GCP emulator (`floci/floci-gcp:0.4.0`), guest port 4588. */
|
|
54
|
+
static gcp(image = "floci/floci-gcp:0.4.0") {
|
|
55
|
+
return new FlociContainer(image, GCP_PORT);
|
|
56
|
+
}
|
|
57
|
+
/** This variant's REST endpoint — the base URL for every emulated API call. */
|
|
58
|
+
get endpointUrl() {
|
|
59
|
+
return `http://${this.host}:${this.getMappedPort(this.guestPort)}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=floci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floci.js","sourceRoot":"","sources":["../../src/modules/floci.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC;AACtB,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,QAAQ,GAAG,IAAI,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,cAAe,SAAQ,gBAAgB;IACjC,SAAS,CAAS;IAEnC,YAAoB,KAAa,EAAE,IAAY;QAC7C,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,yFAAyF;IACzF,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,oBAAoB;QACrC,OAAO,IAAI,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,oEAAoE;IACpE,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,sBAAsB;QACzC,OAAO,IAAI,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC/C,CAAC;IAED,mEAAmE;IACnE,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,uBAAuB;QACxC,OAAO,IAAI,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,+EAA+E;IAC/E,IAAI,WAAW;QACb,OAAO,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;IACrE,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rightsize/modules` — eighteen preconfigured containers.
|
|
3
|
+
*
|
|
4
|
+
* Each module is a `GenericContainer` subclass with sensible defaults (image
|
|
5
|
+
* tag, exposed ports, wait strategy, and any memory floor its image needs)
|
|
6
|
+
* plus typed connection helpers, so `await using redis = await RedisContainer.start()`
|
|
7
|
+
* is immediately usable without hand-assembling a `GenericContainer`. Every
|
|
8
|
+
* builder method from the base class (`withEnv`, `withExposedPorts`,
|
|
9
|
+
* `waitingFor`, …) remains available on every module.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
export { RedisContainer } from "./redis.js";
|
|
14
|
+
export { MemcachedContainer, MemcachedRespondsStrategy } from "./memcached.js";
|
|
15
|
+
export { ArangoContainer } from "./arango.js";
|
|
16
|
+
export { MongoDBContainer } from "./mongodb.js";
|
|
17
|
+
export { RedpandaContainer } from "./redpanda.js";
|
|
18
|
+
export { KafkaContainer } from "./kafka.js";
|
|
19
|
+
export { SpringCloudConfigContainer } from "./spring-cloud-config.js";
|
|
20
|
+
export { PostgresContainer } from "./postgres.js";
|
|
21
|
+
export { MySQLContainer } from "./mysql.js";
|
|
22
|
+
export { PinotContainer } from "./pinot.js";
|
|
23
|
+
export { RabbitMQContainer } from "./rabbitmq.js";
|
|
24
|
+
export { MariaDBContainer } from "./mariadb.js";
|
|
25
|
+
export { FlinkContainer } from "./flink.js";
|
|
26
|
+
export { WireMockContainer } from "./wiremock.js";
|
|
27
|
+
export { KeycloakContainer } from "./keycloak.js";
|
|
28
|
+
export { ClickHouseContainer } from "./clickhouse.js";
|
|
29
|
+
export { Neo4jContainer } from "./neo4j.js";
|
|
30
|
+
export { FlociContainer } from "./floci.js";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/modules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rightsize/modules` — eighteen preconfigured containers.
|
|
3
|
+
*
|
|
4
|
+
* Each module is a `GenericContainer` subclass with sensible defaults (image
|
|
5
|
+
* tag, exposed ports, wait strategy, and any memory floor its image needs)
|
|
6
|
+
* plus typed connection helpers, so `await using redis = await RedisContainer.start()`
|
|
7
|
+
* is immediately usable without hand-assembling a `GenericContainer`. Every
|
|
8
|
+
* builder method from the base class (`withEnv`, `withExposedPorts`,
|
|
9
|
+
* `waitingFor`, …) remains available on every module.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
export { RedisContainer } from "./redis.js";
|
|
14
|
+
export { MemcachedContainer, MemcachedRespondsStrategy } from "./memcached.js";
|
|
15
|
+
export { ArangoContainer } from "./arango.js";
|
|
16
|
+
export { MongoDBContainer } from "./mongodb.js";
|
|
17
|
+
export { RedpandaContainer } from "./redpanda.js";
|
|
18
|
+
export { KafkaContainer } from "./kafka.js";
|
|
19
|
+
export { SpringCloudConfigContainer } from "./spring-cloud-config.js";
|
|
20
|
+
export { PostgresContainer } from "./postgres.js";
|
|
21
|
+
export { MySQLContainer } from "./mysql.js";
|
|
22
|
+
export { PinotContainer } from "./pinot.js";
|
|
23
|
+
export { RabbitMQContainer } from "./rabbitmq.js";
|
|
24
|
+
export { MariaDBContainer } from "./mariadb.js";
|
|
25
|
+
export { FlinkContainer } from "./flink.js";
|
|
26
|
+
export { WireMockContainer } from "./wiremock.js";
|
|
27
|
+
export { KeycloakContainer } from "./keycloak.js";
|
|
28
|
+
export { ClickHouseContainer } from "./clickhouse.js";
|
|
29
|
+
export { Neo4jContainer } from "./neo4j.js";
|
|
30
|
+
export { FlociContainer } from "./floci.js";
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/modules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import type { ContainerSpec } from "../core/model.js";
|
|
3
|
+
/** A single-node Kafka broker (KRaft mode, no ZooKeeper). */
|
|
4
|
+
export declare class KafkaContainer extends GenericContainer {
|
|
5
|
+
constructor(image?: string);
|
|
6
|
+
static start(image?: string): Promise<KafkaContainer>;
|
|
7
|
+
protected customizeSpec(spec: ContainerSpec, mapped: (guest: number) => number): ContainerSpec;
|
|
8
|
+
/** The `PLAINTEXT://` bootstrap-servers address for the running broker. */
|
|
9
|
+
get bootstrapServers(): string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=kafka.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafka.d.ts","sourceRoot":"","sources":["../../src/modules/kafka.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAItD,6DAA6D;AAC7D,qBAAa,cAAe,SAAQ,gBAAgB;gBACtC,KAAK,SAAuB;WAoBlB,KAAK,CAAC,KAAK,SAAuB,GAAG,OAAO,CAAC,cAAc,CAAC;cAM/D,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,aAAa;IAOvG,2EAA2E;IAC3E,IAAI,gBAAgB,IAAI,MAAM,CAE7B;CACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
import { Wait } from "../core/wait.js";
|
|
3
|
+
const GUEST_PORT = 9092;
|
|
4
|
+
/** A single-node Kafka broker (KRaft mode, no ZooKeeper). */
|
|
5
|
+
export class KafkaContainer extends GenericContainer {
|
|
6
|
+
constructor(image = "apache/kafka:4.0.0") {
|
|
7
|
+
super(image);
|
|
8
|
+
this.withExposedPorts(GUEST_PORT)
|
|
9
|
+
.withEnv("KAFKA_NODE_ID", "1")
|
|
10
|
+
.withEnv("KAFKA_PROCESS_ROLES", "broker,controller")
|
|
11
|
+
.withEnv("KAFKA_CONTROLLER_QUORUM_VOTERS", "1@localhost:9091")
|
|
12
|
+
.withEnv("KAFKA_LISTENERS", "PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9091")
|
|
13
|
+
.withEnv("KAFKA_CONTROLLER_LISTENER_NAMES", "CONTROLLER")
|
|
14
|
+
.withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT")
|
|
15
|
+
.withEnv("KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR", "1")
|
|
16
|
+
.withEnv("KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS", "0")
|
|
17
|
+
// The apache/kafka image defaults KAFKA_HEAP_OPTS to -Xmx1G, which
|
|
18
|
+
// exceeds the microsandbox microVM's default RAM and aborts the JVM
|
|
19
|
+
// ("insufficient memory"). A single-node KRaft dev broker runs
|
|
20
|
+
// comfortably in a 256M heap; harmless on the Docker backend, which
|
|
21
|
+
// is not memory-constrained here.
|
|
22
|
+
.withEnv("KAFKA_HEAP_OPTS", "-Xmx256M -Xms256M")
|
|
23
|
+
.waitingFor(Wait.forLogMessage(".*Kafka Server started.*"));
|
|
24
|
+
}
|
|
25
|
+
static async start(image = "apache/kafka:4.0.0") {
|
|
26
|
+
return (await new KafkaContainer(image).start());
|
|
27
|
+
}
|
|
28
|
+
// Rewrites the advertised listener to carry the mapped host port; see
|
|
29
|
+
// RedpandaContainer.customizeSpec for why this needs the mapped callback.
|
|
30
|
+
customizeSpec(spec, mapped) {
|
|
31
|
+
return {
|
|
32
|
+
...spec,
|
|
33
|
+
env: [...spec.env, ["KAFKA_ADVERTISED_LISTENERS", `PLAINTEXT://127.0.0.1:${mapped(GUEST_PORT)}`]],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The `PLAINTEXT://` bootstrap-servers address for the running broker. */
|
|
37
|
+
get bootstrapServers() {
|
|
38
|
+
return `PLAINTEXT://${this.host}:${this.getMappedPort(GUEST_PORT)}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=kafka.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafka.js","sourceRoot":"","sources":["../../src/modules/kafka.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAGvC,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,6DAA6D;AAC7D,MAAM,OAAO,cAAe,SAAQ,gBAAgB;IAClD,YAAY,KAAK,GAAG,oBAAoB;QACtC,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC;aAC9B,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;aAC7B,OAAO,CAAC,qBAAqB,EAAE,mBAAmB,CAAC;aACnD,OAAO,CAAC,gCAAgC,EAAE,kBAAkB,CAAC;aAC7D,OAAO,CAAC,iBAAiB,EAAE,oDAAoD,CAAC;aAChF,OAAO,CAAC,iCAAiC,EAAE,YAAY,CAAC;aACxD,OAAO,CAAC,sCAAsC,EAAE,0CAA0C,CAAC;aAC3F,OAAO,CAAC,wCAAwC,EAAE,GAAG,CAAC;aACtD,OAAO,CAAC,wCAAwC,EAAE,GAAG,CAAC;YACvD,mEAAmE;YACnE,oEAAoE;YACpE,+DAA+D;YAC/D,oEAAoE;YACpE,kCAAkC;aACjC,OAAO,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;aAC/C,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,CAAU,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,oBAAoB;QACtD,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAmB,CAAC;IACrE,CAAC;IAED,sEAAsE;IACtE,0EAA0E;IACvD,aAAa,CAAC,IAAmB,EAAE,MAAiC;QACrF,OAAO;YACL,GAAG,IAAI;YACP,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,4BAA4B,EAAE,yBAAyB,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;SAClG,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,IAAI,gBAAgB;QAClB,OAAO,eAAe,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;IACtE,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GenericContainer } from "../core/generic-container.js";
|
|
2
|
+
/**
|
|
3
|
+
* A single-node Keycloak container started with `start-dev` (an
|
|
4
|
+
* in-memory/dev-mode boot — no external database wiring needed for tests).
|
|
5
|
+
*
|
|
6
|
+
* Two pins here are version-sensitive and were verified against 26.0.8
|
|
7
|
+
* specifically:
|
|
8
|
+
*
|
|
9
|
+
* - **Env var names**: 26.x renamed the bootstrap-admin variables to
|
|
10
|
+
* `KC_BOOTSTRAP_ADMIN_USERNAME`/`KC_BOOTSTRAP_ADMIN_PASSWORD` (older
|
|
11
|
+
* releases used `KEYCLOAK_ADMIN`/`KEYCLOAK_ADMIN_PASSWORD`, which 26.x
|
|
12
|
+
* does not recognize). Confirmed by the boot log: `KC-SERVICES0077:
|
|
13
|
+
* Created temporary admin user with username admin`.
|
|
14
|
+
* - **Health lives on the management port, not the app port.** 26.x splits
|
|
15
|
+
* HTTP (8080) from a separate management interface (9000, confirmed by
|
|
16
|
+
* the boot log: "Management interface listening on http://0.0.0.0:9000")
|
|
17
|
+
* that serves `/health/ready`; `KC_HEALTH_ENABLED=true` is required for
|
|
18
|
+
* that endpoint to exist at all.
|
|
19
|
+
*
|
|
20
|
+
* A four-JVM-adjacent Quarkus boot (Keycloak on Quarkus, with Infinispan +
|
|
21
|
+
* an embedded H2) fits comfortably in `withMemoryLimit(1024)` (measured
|
|
22
|
+
* ~55% utilization at rest) — set by default here.
|
|
23
|
+
*/
|
|
24
|
+
export declare class KeycloakContainer extends GenericContainer {
|
|
25
|
+
private adminUsernameState;
|
|
26
|
+
private adminPasswordState;
|
|
27
|
+
constructor(image?: string);
|
|
28
|
+
static start(image?: string): Promise<KeycloakContainer>;
|
|
29
|
+
/** Overrides `KC_BOOTSTRAP_ADMIN_USERNAME` (default `admin`). */
|
|
30
|
+
withAdminUsername(username: string): this;
|
|
31
|
+
/** Overrides `KC_BOOTSTRAP_ADMIN_PASSWORD` (default `admin`). */
|
|
32
|
+
withAdminPassword(password: string): this;
|
|
33
|
+
/** The configured bootstrap admin username (default `admin`). */
|
|
34
|
+
get adminUsername(): string;
|
|
35
|
+
/** The configured bootstrap admin password (default `admin`). */
|
|
36
|
+
get adminPassword(): string;
|
|
37
|
+
/** The auth server's base URL (the app HTTP port, 8080). */
|
|
38
|
+
get authServerUrl(): string;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=keycloak.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.d.ts","sourceRoot":"","sources":["../../src/modules/keycloak.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAOhE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;IACrD,OAAO,CAAC,kBAAkB,CAAW;IACrC,OAAO,CAAC,kBAAkB,CAAW;gBAEzB,KAAK,SAAmC;WAW9B,KAAK,CAAC,KAAK,SAAmC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIjG,iEAAiE;IACjE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKzC,iEAAiE;IACjE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKzC,iEAAiE;IACjE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,iEAAiE;IACjE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,4DAA4D;IAC5D,IAAI,aAAa,IAAI,MAAM,CAE1B;CACF"}
|