From f5e7a37c8d0fce3df6dbbb850bae6182eac9dabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Mon, 10 Aug 2026 18:27:20 +0800 Subject: [PATCH] feat(database): unify recipe host ports and add Consul, Elasticsearch --- deploy/database/README.md | 4 +- deploy/database/README.zh-CN.md | 4 +- deploy/database/RECIPE_TEMPLATE.md | 4 +- deploy/database/clickhouse/24.8/compose.yaml | 2 +- deploy/database/clickhouse/24.8/recipe.json | 3 +- deploy/database/consul/2.0.2/compose.yaml | 26 +++ deploy/database/consul/2.0.2/init/README.md | 3 + deploy/database/consul/2.0.2/recipe.json | 14 ++ .../database/elasticsearch/6.8/compose.yaml | 21 +++ .../database/elasticsearch/6.8/init/README.md | 3 + deploy/database/elasticsearch/6.8/recipe.json | 14 ++ deploy/database/etcd/3.7/compose.yaml | 4 +- deploy/database/etcd/3.7/recipe.json | 3 +- deploy/database/kafka/4.3/compose.yaml | 4 +- deploy/database/kafka/4.3/recipe.json | 3 +- deploy/database/mariadb/10.11/compose.yaml | 2 +- deploy/database/mariadb/10.11/recipe.json | 3 +- deploy/database/mongodb/5.0/compose.yaml | 2 +- deploy/database/mongodb/5.0/recipe.json | 3 +- deploy/database/mongodb/8.2/compose.yaml | 2 +- deploy/database/mongodb/8.2/recipe.json | 3 +- deploy/database/mysql/5.7/compose.yaml | 2 +- deploy/database/mysql/5.7/recipe.json | 3 +- deploy/database/mysql/8.4/compose.yaml | 2 +- deploy/database/mysql/8.4/recipe.json | 3 +- deploy/database/nacos/2.5/compose.yaml | 6 +- deploy/database/nacos/2.5/recipe.json | 3 +- deploy/database/nacos/3.2/compose.yaml | 10 +- deploy/database/nacos/3.2/recipe.json | 7 +- deploy/database/postgresql/14.23/compose.yaml | 2 +- deploy/database/postgresql/14.23/recipe.json | 3 +- deploy/database/postgresql/17.4/compose.yaml | 2 +- deploy/database/postgresql/17.4/recipe.json | 3 +- deploy/database/pulsar/4.2/compose.yaml | 4 +- deploy/database/pulsar/4.2/recipe.json | 3 +- deploy/database/qdrant/1.8/compose.yaml | 4 +- deploy/database/qdrant/1.8/recipe.json | 3 +- deploy/database/redis/3.0.7/compose.yaml | 2 +- deploy/database/redis/3.0.7/recipe.json | 3 +- deploy/database/redis/7.4/compose.yaml | 2 +- deploy/database/redis/7.4/recipe.json | 3 +- deploy/database/rnacos/0.8/compose.yaml | 6 +- deploy/database/rnacos/0.8/recipe.json | 3 +- deploy/database/zookeeper/3.9/compose.yaml | 2 +- deploy/database/zookeeper/3.9/recipe.json | 3 +- docs/content/docs/database-lab.cn.mdx | 78 ++++---- docs/content/docs/database-lab.mdx | 78 ++++---- scripts/database-env.mjs | 175 +++++++++++++++++- scripts/database-env.test.mjs | 115 +++++++++++- 49 files changed, 522 insertions(+), 135 deletions(-) create mode 100644 deploy/database/consul/2.0.2/compose.yaml create mode 100644 deploy/database/consul/2.0.2/init/README.md create mode 100644 deploy/database/consul/2.0.2/recipe.json create mode 100644 deploy/database/elasticsearch/6.8/compose.yaml create mode 100644 deploy/database/elasticsearch/6.8/init/README.md create mode 100644 deploy/database/elasticsearch/6.8/recipe.json diff --git a/deploy/database/README.md b/deploy/database/README.md index cc2f5f7f81..ead2d09f65 100644 --- a/deploy/database/README.md +++ b/deploy/database/README.md @@ -11,13 +11,13 @@ make db-verify DB=postgresql@17.4 make db-down DB=postgresql@17.4 ``` -Every network recipe uses the standard database port plus one on the host. The default password is `123456`, and the default database is `dbx`; Redis uses DB 0 and the `dbx:` key prefix. Container names follow `dbx--`. Versions of the same database share the default host port, so use `DB_PORT` when running them concurrently. `DB_PORT` and `DB_PASSWORD` override the default host port and password. Ports bind to `127.0.0.1` by default. To allow remote access explicitly, set `DB_BIND_ADDRESS=0.0.0.0`, choose a strong `DB_PASSWORD`, and protect the host with firewall rules. `make db-reset` deletes the named volume and therefore requires `CONFIRM=1`: +Each product has a dedicated `101xx`-`115xx` default host-port range, so checked-in recipes can run together without using common service ports or colliding across versions. The default password is `123456`, and the default database is `dbx`; Redis uses DB 0 and the `dbx:` key prefix. Container names follow `dbx--`. `DB_PORT` and `DB_PASSWORD` override the primary host port and password; service-specific variables override auxiliary ports. Ports bind to `127.0.0.1` by default. To allow remote access explicitly, set `DB_BIND_ADDRESS=0.0.0.0`, choose a strong `DB_PASSWORD`, and protect the host with firewall rules. `make db-reset` deletes the named volume and therefore requires `CONFIRM=1`: ```bash make db-reset DB=redis@7.4 CONFIRM=1 ``` -The primary Make targets are `db-list`, `db`, `db-verify`, `db-down`, `db-reset`, and `db-check`. Run `make db` to print one copyable start command for every recipe. `make db-completion` prints Bash, Zsh, and PowerShell completion setup; the scripts in `completion/` dynamically complete recipe selectors. The targets avoid POSIX-shell conditionals and work with GNU Make in PowerShell, Git Bash, or WSL. For diagnosis, use the lower-level `pnpm db:env -- info|status|logs|shell ` commands. +The primary Make targets are `db-list`, `db`, `db-verify`, `db-down`, `db-reset`, and `db-check`. `make db-list` groups recipe versions by product and shows each container-to-host default port mapping, image, and supported platform. Run `make db` to print one copyable start command for every recipe. For connection types supported by DBX, a completed startup also prints a prefilled `dbx://connection/new` link; on macOS with DBX Desktop installed, run `open ''` to open the new-connection dialog. The link can contain a password, so do not save it in shared terminal history, logs, or tickets. Recipes with no compatible DBX connection type explicitly report that no deep link is available. `make db-completion` prints Bash, Zsh, and PowerShell completion setup; the scripts in `completion/` dynamically complete recipe selectors. The targets avoid POSIX-shell conditionals and work with GNU Make in PowerShell, Git Bash, or WSL. For diagnosis, use the lower-level `pnpm db:env -- info|status|logs|shell ` commands. ## Recipe layout diff --git a/deploy/database/README.zh-CN.md b/deploy/database/README.zh-CN.md index 90f27a272f..00f90a143a 100644 --- a/deploy/database/README.zh-CN.md +++ b/deploy/database/README.zh-CN.md @@ -11,13 +11,13 @@ make db-verify DB=postgresql@17.4 make db-down DB=postgresql@17.4 ``` -所有网络数据库默认使用标准端口 `+1` 的宿主端口,默认密码统一为 `123456`,默认数据库名为 `dbx`;Redis 使用 DB 0 和 `dbx:` 键前缀。容器名统一为 `dbx--`。同一种数据库的不同版本共用默认宿主端口,并行启动时请使用 `DB_PORT` 覆盖其中一个端口。`DB_PORT` 和 `DB_PASSWORD` 可以覆盖默认主机端口和密码。端口默认仅绑定 `127.0.0.1`;如需远程访问,必须显式设置 `DB_BIND_ADDRESS=0.0.0.0`,同时使用强密码和防火墙限制访问。`make db-reset` 会删除命名卷及其数据,因此必须显式传入 `CONFIRM=1`: +每个产品都有专属的 `101xx` 至 `115xx` 默认宿主端口段,因此所有已提交配方可同时启动,且不会占用常见服务端口或在版本间冲突。默认密码统一为 `123456`,默认数据库名为 `dbx`;Redis 使用 DB 0 和 `dbx:` 键前缀。容器名统一为 `dbx--`。`DB_PORT` 和 `DB_PASSWORD` 可覆盖主连接端口和密码;辅助端口使用各服务原有的专属环境变量覆盖。端口默认仅绑定 `127.0.0.1`;如需远程访问,必须显式设置 `DB_BIND_ADDRESS=0.0.0.0`,同时使用强密码和防火墙限制访问。`make db-reset` 会删除命名卷及其数据,因此必须显式传入 `CONFIRM=1`: ```bash make db-reset DB=redis@7.4 CONFIRM=1 ``` -主要 Make 目标为 `db-list`、`db`、`db-verify`、`db-down`、`db-reset` 和 `db-check`。执行 `make db` 会为每个配方输出一条可直接复制的启动命令。`make db-completion` 会输出 Bash、Zsh 和 PowerShell 补全加载命令,`completion/` 中的脚本会动态补全配方选择器。目标不再依赖 POSIX Shell 条件语法,可在 PowerShell、Git Bash 或 WSL 中通过 GNU Make 使用。诊断时可使用底层命令 `pnpm db:env -- info|status|logs|shell `。 +主要 Make 目标为 `db-list`、`db`、`db-verify`、`db-down`、`db-reset` 和 `db-check`。`make db-list` 会按产品归并展示各版本,并列出每项默认的容器到宿主机端口映射、镜像和支持平台。执行 `make db` 会为每个配方输出一条可直接复制的启动命令;对于 DBX 已支持的连接类型,启动完成后还会输出预填的 `dbx://connection/new` 链接,可在已安装 DBX 桌面端的 macOS 上通过 `open '<链接>'` 打开新建连接窗口。该链接可能包含密码,请不要将其写入共享终端历史、日志或工单。没有对应 DBX 连接类型的配方会显示深链不可用。`make db-completion` 会输出 Bash、Zsh 和 PowerShell 补全加载命令,`completion/` 中的脚本会动态补全配方选择器。目标不再依赖 POSIX Shell 条件语法,可在 PowerShell、Git Bash 或 WSL 中通过 GNU Make 使用。诊断时可使用底层命令 `pnpm db:env -- info|status|logs|shell `。 ## 配方结构 diff --git a/deploy/database/RECIPE_TEMPLATE.md b/deploy/database/RECIPE_TEMPLATE.md index 0318cc2099..7d07d07521 100644 --- a/deploy/database/RECIPE_TEMPLATE.md +++ b/deploy/database/RECIPE_TEMPLATE.md @@ -1,8 +1,8 @@ # Database Recipe Template -Create `deploy/database///` with `recipe.json`, `compose.yaml`, and an `init/` directory. Set `defaultPort` in `recipe.json` to the native service port; the connection port and Compose `DB_PORT` fallback must both be `defaultPort + 1`. +Create `deploy/database///` with `recipe.json`, `compose.yaml`, and an `init/` directory. Set `defaultPort` to the native service port, then declare every default host mapping in `hostPorts`. `connection.port` and the Compose `DB_PORT` fallback must both match `hostPorts.DB_PORT`. -Every recipe must use a pinned image, default its host binding to `${DB_BIND_ADDRESS:-127.0.0.1}`, use the standard port plus one by default, set `container_name` to `dbx--`, use password `123456`, and initialize database `dbx`. Redis-like services without named databases use DB 0 and the `dbx:` key prefix. +Every recipe must use a pinned image, default its host binding to `${DB_BIND_ADDRESS:-127.0.0.1}`, allocate unique host ports from its product's `101xx`-`115xx` range, set `container_name` to `dbx--`, use password `123456`, and initialize database `dbx`. Redis-like services without named databases use DB 0 and the `dbx:` key prefix. Add both a non-interactive `smoke.steps` command and an interactive `shell` command to `recipe.json`, then run: diff --git a/deploy/database/clickhouse/24.8/compose.yaml b/deploy/database/clickhouse/24.8/compose.yaml index b60ff25e4f..8663e41c09 100644 --- a/deploy/database/clickhouse/24.8/compose.yaml +++ b/deploy/database/clickhouse/24.8/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-clickhouse-24.8 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-8124}:8123" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10600}:8123" environment: CLICKHOUSE_DB: dbx CLICKHOUSE_USER: default diff --git a/deploy/database/clickhouse/24.8/recipe.json b/deploy/database/clickhouse/24.8/recipe.json index 70dd273129..48f31dfb54 100644 --- a/deploy/database/clickhouse/24.8/recipe.json +++ b/deploy/database/clickhouse/24.8/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 8123, - "connection": { "host": "127.0.0.1", "port": 8124, "username": "default", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10600, "username": "default", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10600 }, "shell": ["clickhouse-client", "--user", "default", "--password", "${DB_PASSWORD}", "--database", "dbx"], "smoke": { "steps": [{ "name": "query initialized row", "command": ["clickhouse-client", "--user", "default", "--password", "${DB_PASSWORD}", "--database", "dbx", "--query", "SELECT note FROM dbx_smoke WHERE id = 1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/consul/2.0.2/compose.yaml b/deploy/database/consul/2.0.2/compose.yaml new file mode 100644 index 0000000000..f3f035faa7 --- /dev/null +++ b/deploy/database/consul/2.0.2/compose.yaml @@ -0,0 +1,26 @@ +services: + database: + image: docker.cnb.cool/znb/images/consul:2.0.2 + container_name: dbx-consul-2.0.2 + restart: unless-stopped + command: + - agent + - -dev + - -client=0.0.0.0 + - -ui + - -data-dir=/consul/data + ports: + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10900}:8500" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${CONSUL_GRPC_PORT:-10901}:8502" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${CONSUL_DNS_PORT:-10902}:8600/tcp" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${CONSUL_DNS_PORT:-10902}:8600/udp" + volumes: + - data:/consul/data + healthcheck: + test: ["CMD", "consul", "members", "-http-addr=http://127.0.0.1:8500"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 10s +volumes: + data: diff --git a/deploy/database/consul/2.0.2/init/README.md b/deploy/database/consul/2.0.2/init/README.md new file mode 100644 index 0000000000..2dcfe864be --- /dev/null +++ b/deploy/database/consul/2.0.2/init/README.md @@ -0,0 +1,3 @@ +# Consul development data + +The Consul development agent starts without ACL authentication. `make db-verify DB=consul@2.0.2` writes and reads the `dbx/smoke` KV value. diff --git a/deploy/database/consul/2.0.2/recipe.json b/deploy/database/consul/2.0.2/recipe.json new file mode 100644 index 0000000000..4faa2633ce --- /dev/null +++ b/deploy/database/consul/2.0.2/recipe.json @@ -0,0 +1,14 @@ +{ + "database": "consul", + "name": "Consul", + "version": "2.0.2", + "displayVersion": "2.0.2", + "image": "docker.cnb.cool/znb/images/consul:2.0.2", + "platforms": ["linux/amd64", "linux/arm64"], + "service": "database", + "defaultPort": 8500, + "connection": { "host": "127.0.0.1", "port": 10900, "authentication": "none", "database": "dbx", "grpcPort": 10901, "dnsPort": 10902 }, + "hostPorts": { "DB_PORT": 10900, "CONSUL_GRPC_PORT": 10901, "CONSUL_DNS_PORT": 10902 }, + "shell": ["consul", "members", "-http-addr=http://127.0.0.1:8500"], + "smoke": { "steps": [{ "name": "write a Consul KV smoke value", "command": ["consul", "kv", "put", "-http-addr=http://127.0.0.1:8500", "dbx/smoke", "DBX smoke"], "expect": "Success! Data written" }, { "name": "read the Consul KV smoke value", "command": ["consul", "kv", "get", "-http-addr=http://127.0.0.1:8500", "dbx/smoke"], "expect": "DBX smoke" }] } +} diff --git a/deploy/database/elasticsearch/6.8/compose.yaml b/deploy/database/elasticsearch/6.8/compose.yaml new file mode 100644 index 0000000000..1a5891f562 --- /dev/null +++ b/deploy/database/elasticsearch/6.8/compose.yaml @@ -0,0 +1,21 @@ +services: + database: + image: docker.cnb.cool/znb/images/elasticsearch:6.8 + container_name: dbx-elasticsearch-6.8 + restart: always + ports: + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11500}:9200" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${ELASTICSEARCH_TRANSPORT_PORT:-11501}:9300" + environment: + discovery.type: single-node + xpack.ml.enabled: "false" + volumes: + - data:/usr/share/elasticsearch/data + healthcheck: + test: ["CMD-SHELL", "curl --fail --silent 'http://127.0.0.1:9200/_cluster/health?wait_for_status=yellow&timeout=1s' >/dev/null"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 20s +volumes: + data: diff --git a/deploy/database/elasticsearch/6.8/init/README.md b/deploy/database/elasticsearch/6.8/init/README.md new file mode 100644 index 0000000000..29eabfca8a --- /dev/null +++ b/deploy/database/elasticsearch/6.8/init/README.md @@ -0,0 +1,3 @@ +# Elasticsearch 6.8 smoke data + +`make db-verify DB=elasticsearch@6.8` indexes and reads the `dbx-smoke` document. Elasticsearch runs in single-node development mode without authentication. diff --git a/deploy/database/elasticsearch/6.8/recipe.json b/deploy/database/elasticsearch/6.8/recipe.json new file mode 100644 index 0000000000..189ca7268e --- /dev/null +++ b/deploy/database/elasticsearch/6.8/recipe.json @@ -0,0 +1,14 @@ +{ + "database": "elasticsearch", + "name": "Elasticsearch", + "version": "6.8", + "displayVersion": "6.8", + "image": "docker.cnb.cool/znb/images/elasticsearch:6.8", + "platforms": ["linux/amd64", "linux/arm64"], + "service": "database", + "defaultPort": 9200, + "connection": { "host": "127.0.0.1", "port": 11500, "authentication": "none", "database": "dbx", "transportPort": 11501 }, + "hostPorts": { "DB_PORT": 11500, "ELASTICSEARCH_TRANSPORT_PORT": 11501 }, + "shell": ["bash"], + "smoke": { "steps": [{ "name": "index a smoke document", "command": ["bash", "-c", "curl --fail --silent --show-error -X PUT -H 'Content-Type: application/json' -d '{\"message\":\"DBX smoke\"}' 'http://127.0.0.1:9200/dbx-smoke/_doc/1?refresh=true'"], "expect": "\"result\"" }, { "name": "read the smoke document", "command": ["bash", "-c", "curl --fail --silent --show-error http://127.0.0.1:9200/dbx-smoke/_doc/1"], "expect": "DBX smoke" }] } +} diff --git a/deploy/database/etcd/3.7/compose.yaml b/deploy/database/etcd/3.7/compose.yaml index d69c47313c..9074a293f5 100644 --- a/deploy/database/etcd/3.7/compose.yaml +++ b/deploy/database/etcd/3.7/compose.yaml @@ -18,8 +18,8 @@ services: - --logger=zap - --log-outputs=stderr ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-2380}:2379" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${ETCD_PEER_PORT:-2381}:2380" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10700}:2379" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${ETCD_PEER_PORT:-10701}:2380" volumes: - data:/etcd-data healthcheck: diff --git a/deploy/database/etcd/3.7/recipe.json b/deploy/database/etcd/3.7/recipe.json index 2d04c8d02d..b90f9f149b 100644 --- a/deploy/database/etcd/3.7/recipe.json +++ b/deploy/database/etcd/3.7/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 2379, - "connection": { "host": "127.0.0.1", "port": 2380, "username": "root", "password": "123456", "database": "dbx", "peerPort": 2381 }, + "connection": { "host": "127.0.0.1", "port": 10700, "username": "root", "password": "123456", "database": "dbx", "peerPort": 10701 }, + "hostPorts": { "DB_PORT": 10700, "ETCD_PEER_PORT": 10701 }, "bootstrap": { "check": { "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "--user", "root:${DB_PASSWORD}", "auth", "status"], "expect": "Authentication Status: true" }, "steps": [ diff --git a/deploy/database/kafka/4.3/compose.yaml b/deploy/database/kafka/4.3/compose.yaml index 4e0f5c95d7..c5444b6fee 100644 --- a/deploy/database/kafka/4.3/compose.yaml +++ b/deploy/database/kafka/4.3/compose.yaml @@ -4,12 +4,12 @@ services: container_name: dbx-kafka-4.3 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-9093}:9092" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11300}:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_LISTENERS: EXTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094,INTERNAL://0.0.0.0:9095 - KAFKA_ADVERTISED_LISTENERS: EXTERNAL://${KAFKA_ADVERTISED_HOST:-localhost}:${DB_PORT:-9093},INTERNAL://database:9095 + KAFKA_ADVERTISED_LISTENERS: EXTERNAL://${KAFKA_ADVERTISED_HOST:-localhost}:${DB_PORT:-11300},INTERNAL://database:9095 KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT diff --git a/deploy/database/kafka/4.3/recipe.json b/deploy/database/kafka/4.3/recipe.json index fbc680c590..097a062a04 100644 --- a/deploy/database/kafka/4.3/recipe.json +++ b/deploy/database/kafka/4.3/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 9092, - "connection": { "host": "127.0.0.1", "port": 9093, "authentication": "none", "database": "dbx", "internalPort": 9095 }, + "connection": { "host": "127.0.0.1", "port": 11300, "authentication": "none", "database": "dbx", "internalPort": 9095 }, + "hostPorts": { "DB_PORT": 11300 }, "shell": ["/bin/bash"], "smoke": { "steps": [{ "name": "create, produce, and consume a smoke message", "command": ["/bin/bash", "-ec", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server database:9095 --create --if-not-exists --topic dbx-smoke --partitions 1 --replication-factor 1; printf 'DBX smoke\\n' | /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server database:9095 --topic dbx-smoke; /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server database:9095 --topic dbx-smoke --from-beginning --max-messages 1 --timeout-ms 10000"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/mariadb/10.11/compose.yaml b/deploy/database/mariadb/10.11/compose.yaml index 7c9086b16d..51ccfbd0c5 100644 --- a/deploy/database/mariadb/10.11/compose.yaml +++ b/deploy/database/mariadb/10.11/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-mariadb-10.11 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-3307}:3306" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10200}:3306" environment: MARIADB_ROOT_PASSWORD: "${DB_PASSWORD:-123456}" MARIADB_DATABASE: dbx diff --git a/deploy/database/mariadb/10.11/recipe.json b/deploy/database/mariadb/10.11/recipe.json index 176509a2d7..97060434d1 100644 --- a/deploy/database/mariadb/10.11/recipe.json +++ b/deploy/database/mariadb/10.11/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 3306, - "connection": { "host": "127.0.0.1", "port": 3307, "username": "root", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10200, "username": "root", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10200 }, "shell": ["mariadb", "-uroot", "-p${DB_PASSWORD}", "-Ddbx"], "smoke": { "steps": [{ "name": "query initialized row", "command": ["mariadb", "-uroot", "-p${DB_PASSWORD}", "-Ddbx", "-Nse", "SELECT note FROM dbx_smoke WHERE id=1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/mongodb/5.0/compose.yaml b/deploy/database/mongodb/5.0/compose.yaml index 62fdc4f812..cfb417686f 100644 --- a/deploy/database/mongodb/5.0/compose.yaml +++ b/deploy/database/mongodb/5.0/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-mongodb-5.0 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-27018}:27017" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10400}:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: "${DB_PASSWORD:-123456}" diff --git a/deploy/database/mongodb/5.0/recipe.json b/deploy/database/mongodb/5.0/recipe.json index bee1bc4a4d..66b780f747 100644 --- a/deploy/database/mongodb/5.0/recipe.json +++ b/deploy/database/mongodb/5.0/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 27017, - "connection": { "host": "127.0.0.1", "port": 27018, "username": "root", "password": "123456", "database": "dbx", "authSource": "admin" }, + "connection": { "host": "127.0.0.1", "port": 10400, "username": "root", "password": "123456", "database": "dbx", "authSource": "admin" }, + "hostPorts": { "DB_PORT": 10400 }, "shell": ["mongosh", "--quiet", "--host", "127.0.0.1", "--port", "27017", "--username", "root", "--password", "${DB_PASSWORD}", "--authenticationDatabase", "admin", "dbx"], "smoke": { "steps": [{ "name": "query initialized document over TCP", "command": ["mongosh", "--quiet", "--host", "127.0.0.1", "--port", "27017", "--username", "root", "--password", "${DB_PASSWORD}", "--authenticationDatabase", "admin", "dbx", "--eval", "db.dbx_smoke.findOne({_id: 1}).note"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/mongodb/8.2/compose.yaml b/deploy/database/mongodb/8.2/compose.yaml index f0189ac3e6..b715c02190 100644 --- a/deploy/database/mongodb/8.2/compose.yaml +++ b/deploy/database/mongodb/8.2/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-mongodb-8.2 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-27018}:27017" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10401}:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: "${DB_PASSWORD:-123456}" diff --git a/deploy/database/mongodb/8.2/recipe.json b/deploy/database/mongodb/8.2/recipe.json index a3d2896731..67c7e671d9 100644 --- a/deploy/database/mongodb/8.2/recipe.json +++ b/deploy/database/mongodb/8.2/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 27017, - "connection": { "host": "127.0.0.1", "port": 27018, "username": "root", "password": "123456", "database": "dbx", "authSource": "admin" }, + "connection": { "host": "127.0.0.1", "port": 10401, "username": "root", "password": "123456", "database": "dbx", "authSource": "admin" }, + "hostPorts": { "DB_PORT": 10401 }, "shell": ["mongosh", "--quiet", "--host", "127.0.0.1", "--port", "27017", "--username", "root", "--password", "${DB_PASSWORD}", "--authenticationDatabase", "admin", "dbx"], "smoke": { "steps": [{ "name": "query initialized document over TCP", "command": ["mongosh", "--quiet", "--host", "127.0.0.1", "--port", "27017", "--username", "root", "--password", "${DB_PASSWORD}", "--authenticationDatabase", "admin", "dbx", "--eval", "db.dbx_smoke.findOne({_id: 1}).note"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/mysql/5.7/compose.yaml b/deploy/database/mysql/5.7/compose.yaml index de3356b429..1c2cef3071 100644 --- a/deploy/database/mysql/5.7/compose.yaml +++ b/deploy/database/mysql/5.7/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-mysql-5.7 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-3307}:3306" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10100}:3306" environment: MYSQL_ROOT_PASSWORD: "${DB_PASSWORD:-123456}" MYSQL_DATABASE: dbx diff --git a/deploy/database/mysql/5.7/recipe.json b/deploy/database/mysql/5.7/recipe.json index 280824ca69..6956f4d309 100644 --- a/deploy/database/mysql/5.7/recipe.json +++ b/deploy/database/mysql/5.7/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 3306, - "connection": { "host": "127.0.0.1", "port": 3307, "username": "root", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10100, "username": "root", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10100 }, "shell": ["mysql", "-uroot", "-p${DB_PASSWORD}", "-Ddbx"], "smoke": { "steps": [{ "name": "query initialized row", "command": ["mysql", "-uroot", "-p${DB_PASSWORD}", "-Ddbx", "-Nse", "SELECT note FROM dbx_smoke WHERE id=1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/mysql/8.4/compose.yaml b/deploy/database/mysql/8.4/compose.yaml index f56d08dbe6..e007433631 100644 --- a/deploy/database/mysql/8.4/compose.yaml +++ b/deploy/database/mysql/8.4/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-mysql-8.4 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-3307}:3306" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10101}:3306" environment: MYSQL_ROOT_PASSWORD: "${DB_PASSWORD:-123456}" MYSQL_DATABASE: dbx diff --git a/deploy/database/mysql/8.4/recipe.json b/deploy/database/mysql/8.4/recipe.json index 400f39bf1c..e009fda6dc 100644 --- a/deploy/database/mysql/8.4/recipe.json +++ b/deploy/database/mysql/8.4/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 3306, - "connection": { "host": "127.0.0.1", "port": 3307, "username": "root", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10101, "username": "root", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10101 }, "shell": ["mysql", "-uroot", "-p${DB_PASSWORD}", "-Ddbx"], "smoke": { "steps": [{ "name": "query initialized row", "command": ["mysql", "-uroot", "-p${DB_PASSWORD}", "-Ddbx", "-Nse", "SELECT note FROM dbx_smoke WHERE id=1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/nacos/2.5/compose.yaml b/deploy/database/nacos/2.5/compose.yaml index 15334cb128..95f4837051 100644 --- a/deploy/database/nacos/2.5/compose.yaml +++ b/deploy/database/nacos/2.5/compose.yaml @@ -4,9 +4,9 @@ services: container_name: dbx-nacos-2.5 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-8849}:8848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_GRPC_PORT:-9849}:9848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_RAFT_PORT:-9850}:9849" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11000}:8848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_GRPC_PORT:-11001}:9848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_RAFT_PORT:-11002}:9849" environment: MODE: standalone PREFER_HOST_MODE: hostname diff --git a/deploy/database/nacos/2.5/recipe.json b/deploy/database/nacos/2.5/recipe.json index 24ee317cb5..f3a88c961c 100644 --- a/deploy/database/nacos/2.5/recipe.json +++ b/deploy/database/nacos/2.5/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 8848, - "connection": { "host": "127.0.0.1", "port": 8849, "username": "nacos", "password": "123456", "database": "dbx", "namespace": "public" }, + "connection": { "host": "127.0.0.1", "port": 11000, "username": "nacos", "password": "123456", "database": "dbx", "namespace": "public", "grpcPort": 11001, "raftPort": 11002 }, + "hostPorts": { "DB_PORT": 11000, "NACOS_GRPC_PORT": 11001, "NACOS_RAFT_PORT": 11002 }, "bootstrap": { "check": { "command": ["curl", "--fail", "--silent", "--show-error", "--request", "POST", "http://127.0.0.1:8848/nacos/v1/auth/login", "--data-urlencode", "username=nacos", "--data-urlencode", "password=${DB_PASSWORD}"], "expect": "accessToken" }, "steps": [{ "name": "initialize administrator password", "command": ["sh", "-ec", "curl --fail --silent --show-error --request POST --data-urlencode \"password=$1\" http://127.0.0.1:8848/nacos/v1/auth/users/admin >/dev/null; curl --fail --silent --show-error --request POST http://127.0.0.1:8848/nacos/v1/auth/login --data-urlencode username=nacos --data-urlencode \"password=$1\" | grep -q '\"accessToken\"'; printf 'administrator initialized\\n'", "sh", "${DB_PASSWORD}"], "expect": "administrator initialized" }] diff --git a/deploy/database/nacos/3.2/compose.yaml b/deploy/database/nacos/3.2/compose.yaml index ebca1c2189..bb4769fd20 100644 --- a/deploy/database/nacos/3.2/compose.yaml +++ b/deploy/database/nacos/3.2/compose.yaml @@ -1,13 +1,13 @@ services: database: - image: docker.cnb.cool/znb/images/nacos-server:v3.2.2 + image: docker.cnb.cool/znb/images/nacos-server:v3.2.3 container_name: dbx-nacos-3.2 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_CONSOLE_PORT:-8010}:8080" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-8849}:8848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_GRPC_PORT:-9849}:9848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_RAFT_PORT:-9850}:9849" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_CONSOLE_PORT:-11004}:8080" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11003}:8848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_GRPC_PORT:-11005}:9848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${NACOS_RAFT_PORT:-11006}:9849" environment: MODE: standalone PREFER_HOST_MODE: hostname diff --git a/deploy/database/nacos/3.2/recipe.json b/deploy/database/nacos/3.2/recipe.json index f356fafab1..2cc1516e81 100644 --- a/deploy/database/nacos/3.2/recipe.json +++ b/deploy/database/nacos/3.2/recipe.json @@ -1,13 +1,14 @@ { "database": "nacos", "name": "Nacos", - "version": "3.2.2", + "version": "3.2.3", "displayVersion": "3.2", - "image": "docker.cnb.cool/znb/images/nacos-server:v3.2.2", + "image": "docker.cnb.cool/znb/images/nacos-server:v3.2.3", "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 8848, - "connection": { "host": "127.0.0.1", "port": 8849, "username": "nacos", "password": "123456", "database": "dbx", "namespace": "public", "consolePort": 8010 }, + "connection": { "host": "127.0.0.1", "port": 11003, "username": "nacos", "password": "123456", "database": "dbx", "namespace": "public", "consolePort": 11004, "grpcPort": 11005, "raftPort": 11006 }, + "hostPorts": { "DB_PORT": 11003, "NACOS_CONSOLE_PORT": 11004, "NACOS_GRPC_PORT": 11005, "NACOS_RAFT_PORT": 11006 }, "bootstrap": { "check": { "command": ["curl", "--fail", "--silent", "--show-error", "--request", "POST", "http://127.0.0.1:8848/nacos/v3/auth/user/login", "--data-urlencode", "username=nacos", "--data-urlencode", "password=${DB_PASSWORD}"], "expect": "accessToken" }, "steps": [{ "name": "initialize administrator password", "command": ["sh", "-ec", "curl --fail --silent --show-error --request POST --data-urlencode \"password=$1\" http://127.0.0.1:8848/nacos/v3/auth/user/admin >/dev/null; curl --fail --silent --show-error --request POST http://127.0.0.1:8848/nacos/v3/auth/user/login --data-urlencode username=nacos --data-urlencode \"password=$1\" | grep -q '\"accessToken\"'; printf 'administrator initialized\\n'", "sh", "${DB_PASSWORD}"], "expect": "administrator initialized" }] diff --git a/deploy/database/postgresql/14.23/compose.yaml b/deploy/database/postgresql/14.23/compose.yaml index ab2aeb61f6..d7ba9f7d0b 100644 --- a/deploy/database/postgresql/14.23/compose.yaml +++ b/deploy/database/postgresql/14.23/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-postgresql-14.23 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-5433}:5432" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10300}:5432" environment: POSTGRES_PASSWORD: "${DB_PASSWORD:-123456}" POSTGRES_DB: dbx diff --git a/deploy/database/postgresql/14.23/recipe.json b/deploy/database/postgresql/14.23/recipe.json index 7a4fa2b403..71becb759c 100644 --- a/deploy/database/postgresql/14.23/recipe.json +++ b/deploy/database/postgresql/14.23/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 5432, - "connection": { "host": "127.0.0.1", "port": 5433, "username": "postgres", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10300, "username": "postgres", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10300 }, "shell": ["env", "PGPASSWORD=${DB_PASSWORD}", "psql", "-h", "127.0.0.1", "-p", "5432", "-U", "postgres", "-d", "dbx"], "smoke": { "steps": [{ "name": "query initialized row over TCP", "command": ["env", "PGPASSWORD=${DB_PASSWORD}", "psql", "-h", "127.0.0.1", "-p", "5432", "-U", "postgres", "-d", "dbx", "-Atc", "SELECT note FROM dbx_smoke WHERE id=1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/postgresql/17.4/compose.yaml b/deploy/database/postgresql/17.4/compose.yaml index f10187f77b..4c121b98c4 100644 --- a/deploy/database/postgresql/17.4/compose.yaml +++ b/deploy/database/postgresql/17.4/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-postgresql-17.4 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-5433}:5432" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10301}:5432" environment: POSTGRES_PASSWORD: "${DB_PASSWORD:-123456}" POSTGRES_DB: dbx diff --git a/deploy/database/postgresql/17.4/recipe.json b/deploy/database/postgresql/17.4/recipe.json index 1bca2c0bf7..836bb160db 100644 --- a/deploy/database/postgresql/17.4/recipe.json +++ b/deploy/database/postgresql/17.4/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 5432, - "connection": { "host": "127.0.0.1", "port": 5433, "username": "postgres", "password": "123456", "database": "dbx" }, + "connection": { "host": "127.0.0.1", "port": 10301, "username": "postgres", "password": "123456", "database": "dbx" }, + "hostPorts": { "DB_PORT": 10301 }, "shell": ["env", "PGPASSWORD=${DB_PASSWORD}", "psql", "-h", "127.0.0.1", "-p", "5432", "-U", "postgres", "-d", "dbx"], "smoke": { "steps": [{ "name": "query initialized row over TCP", "command": ["env", "PGPASSWORD=${DB_PASSWORD}", "psql", "-h", "127.0.0.1", "-p", "5432", "-U", "postgres", "-d", "dbx", "-Atc", "SELECT note FROM dbx_smoke WHERE id=1"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/pulsar/4.2/compose.yaml b/deploy/database/pulsar/4.2/compose.yaml index bacf412d5b..cf025bdf9f 100644 --- a/deploy/database/pulsar/4.2/compose.yaml +++ b/deploy/database/pulsar/4.2/compose.yaml @@ -5,8 +5,8 @@ services: restart: always command: ["bin/pulsar", "standalone", "--advertised-address", "localhost"] ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-6651}:6650" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${PULSAR_WEB_PORT:-8081}:8080" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11400}:6650" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${PULSAR_WEB_PORT:-11401}:8080" volumes: - data:/pulsar/data - conf:/pulsar/conf diff --git a/deploy/database/pulsar/4.2/recipe.json b/deploy/database/pulsar/4.2/recipe.json index 8cbbd592dd..040791c53d 100644 --- a/deploy/database/pulsar/4.2/recipe.json +++ b/deploy/database/pulsar/4.2/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 6650, - "connection": { "host": "127.0.0.1", "port": 6651, "authentication": "none", "database": "dbx", "namespace": "public/default", "webPort": 8081 }, + "connection": { "host": "127.0.0.1", "port": 11400, "authentication": "none", "database": "dbx", "namespace": "public/default", "webPort": 11401 }, + "hostPorts": { "DB_PORT": 11400, "PULSAR_WEB_PORT": 11401 }, "shell": ["/bin/bash"], "smoke": { "steps": [{ "name": "produce a smoke message", "command": ["bin/pulsar-client", "produce", "persistent://public/default/dbx-smoke", "--messages", "DBX smoke"], "expect": "1 messages successfully produced" }] } } diff --git a/deploy/database/qdrant/1.8/compose.yaml b/deploy/database/qdrant/1.8/compose.yaml index ce8436d916..f2d0ed01c6 100644 --- a/deploy/database/qdrant/1.8/compose.yaml +++ b/deploy/database/qdrant/1.8/compose.yaml @@ -4,8 +4,8 @@ services: container_name: dbx-qdrant-1.8 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-6334}:6333" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${QDRANT_GRPC_PORT:-6335}:6334" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11200}:6333" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${QDRANT_GRPC_PORT:-11201}:6334" environment: QDRANT__SERVICE__API_KEY: "${DB_PASSWORD:-123456}" volumes: diff --git a/deploy/database/qdrant/1.8/recipe.json b/deploy/database/qdrant/1.8/recipe.json index 6a4dc77bfb..abeec40a4d 100644 --- a/deploy/database/qdrant/1.8/recipe.json +++ b/deploy/database/qdrant/1.8/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 6333, - "connection": { "host": "127.0.0.1", "port": 6334, "username": "", "password": "123456", "database": "dbx", "grpcPort": 6335 }, + "connection": { "host": "127.0.0.1", "port": 11200, "username": "", "password": "123456", "database": "dbx", "grpcPort": 11201 }, + "hostPorts": { "DB_PORT": 11200, "QDRANT_GRPC_PORT": 11201 }, "shell": ["bash"], "smoke": { "steps": [{ "name": "reject an unauthenticated collection request", "command": ["bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/6333; printf 'GET /collections HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; cat <&3"], "expect": "403 Forbidden" }, { "name": "query collections with the configured API key", "command": ["bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/6333; printf 'GET /collections HTTP/1.1\\r\\nHost: localhost\\r\\napi-key: %s\\r\\nConnection: close\\r\\n\\r\\n' \"$1\" >&3; cat <&3", "bash", "${DB_PASSWORD}"], "expect": "collections" }] } } diff --git a/deploy/database/redis/3.0.7/compose.yaml b/deploy/database/redis/3.0.7/compose.yaml index e1aee938fe..6c15ba4c47 100644 --- a/deploy/database/redis/3.0.7/compose.yaml +++ b/deploy/database/redis/3.0.7/compose.yaml @@ -9,7 +9,7 @@ services: # Keep the password in container env so CMD-SHELL can quote it safely. REDIS_PASSWORD: "${DB_PASSWORD:-123456}" ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-6380}:6379" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10500}:6379" volumes: - data:/data healthcheck: diff --git a/deploy/database/redis/3.0.7/recipe.json b/deploy/database/redis/3.0.7/recipe.json index 3a3421e025..b3687f8f4b 100644 --- a/deploy/database/redis/3.0.7/recipe.json +++ b/deploy/database/redis/3.0.7/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64"], "service": "database", "defaultPort": 6379, - "connection": { "host": "127.0.0.1", "port": 6380, "password": "123456", "database": 0 }, + "connection": { "host": "127.0.0.1", "port": 10500, "password": "123456", "database": 0 }, + "hostPorts": { "DB_PORT": 10500 }, "shell": ["redis-cli", "-a", "${DB_PASSWORD}"], "smoke": { "steps": [{ "name": "seed smoke key", "command": ["redis-cli", "-a", "${DB_PASSWORD}", "SET", "dbx:smoke", "DBX smoke"], "expect": "OK" }, { "name": "read smoke key", "command": ["redis-cli", "-a", "${DB_PASSWORD}", "GET", "dbx:smoke"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/redis/7.4/compose.yaml b/deploy/database/redis/7.4/compose.yaml index 545f94c604..e1fa8b3fc3 100644 --- a/deploy/database/redis/7.4/compose.yaml +++ b/deploy/database/redis/7.4/compose.yaml @@ -8,7 +8,7 @@ services: # Keep the password in container env so CMD-SHELL can quote it safely. REDIS_PASSWORD: "${DB_PASSWORD:-123456}" ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-6380}:6379" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10501}:6379" volumes: - data:/data healthcheck: diff --git a/deploy/database/redis/7.4/recipe.json b/deploy/database/redis/7.4/recipe.json index 6aab1d9e96..1aab0c8e88 100644 --- a/deploy/database/redis/7.4/recipe.json +++ b/deploy/database/redis/7.4/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 6379, - "connection": { "host": "127.0.0.1", "port": 6380, "username": "default", "password": "123456", "database": 0 }, + "connection": { "host": "127.0.0.1", "port": 10501, "username": "default", "password": "123456", "database": 0 }, + "hostPorts": { "DB_PORT": 10501 }, "shell": ["redis-cli", "-a", "${DB_PASSWORD}"], "smoke": { "steps": [{ "name": "seed smoke key", "command": ["redis-cli", "-a", "${DB_PASSWORD}", "SET", "dbx:smoke", "DBX smoke"], "expect": "OK" }, { "name": "read smoke key", "command": ["redis-cli", "-a", "${DB_PASSWORD}", "GET", "dbx:smoke"], "expect": "DBX smoke" }] } } diff --git a/deploy/database/rnacos/0.8/compose.yaml b/deploy/database/rnacos/0.8/compose.yaml index e6bd9bfb49..110a954d1a 100644 --- a/deploy/database/rnacos/0.8/compose.yaml +++ b/deploy/database/rnacos/0.8/compose.yaml @@ -3,9 +3,9 @@ services: image: docker.cnb.cool/znb/images/rnacos:v0.8.5 container_name: dbx-rnacos-0.8 ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-8849}:8848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${RNACOS_GRPC_PORT:-9849}:9848" - - "${DB_BIND_ADDRESS:-127.0.0.1}:${RNACOS_CONSOLE_PORT:-10849}:10848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-11100}:8848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${RNACOS_GRPC_PORT:-11101}:9848" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${RNACOS_CONSOLE_PORT:-11102}:10848" volumes: - data:/io environment: diff --git a/deploy/database/rnacos/0.8/recipe.json b/deploy/database/rnacos/0.8/recipe.json index d8ffbce7af..42a2b46609 100644 --- a/deploy/database/rnacos/0.8/recipe.json +++ b/deploy/database/rnacos/0.8/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 8848, - "connection": { "host": "127.0.0.1", "port": 8849, "username": "admin", "password": "123456", "database": "dbx", "namespace": "public", "grpcPort": 9849, "consolePort": 10849 }, + "connection": { "host": "127.0.0.1", "port": 11100, "username": "admin", "password": "123456", "database": "dbx", "namespace": "public", "grpcPort": 11101, "consolePort": 11102 }, + "hostPorts": { "DB_PORT": 11100, "RNACOS_GRPC_PORT": 11101, "RNACOS_CONSOLE_PORT": 11102 }, "shell": ["bash"], "smoke": { "steps": [{ "name": "check the HTTP health endpoint", "command": ["bash", "-ec", "exec 3<>/dev/tcp/127.0.0.1/8848; printf 'GET /health HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; cat <&3"], "expect": "success" }] } } diff --git a/deploy/database/zookeeper/3.9/compose.yaml b/deploy/database/zookeeper/3.9/compose.yaml index e60eb960d0..4bba8583b0 100644 --- a/deploy/database/zookeeper/3.9/compose.yaml +++ b/deploy/database/zookeeper/3.9/compose.yaml @@ -4,7 +4,7 @@ services: container_name: dbx-zookeeper-3.9 restart: always ports: - - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-2182}:2181" + - "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-10800}:2181" volumes: - data:/data - datalog:/datalog diff --git a/deploy/database/zookeeper/3.9/recipe.json b/deploy/database/zookeeper/3.9/recipe.json index 238f94c71a..11d74a7f5b 100644 --- a/deploy/database/zookeeper/3.9/recipe.json +++ b/deploy/database/zookeeper/3.9/recipe.json @@ -7,7 +7,8 @@ "platforms": ["linux/amd64", "linux/arm64"], "service": "database", "defaultPort": 2181, - "connection": { "host": "127.0.0.1", "port": 2182, "username": "root", "password": "123456", "database": "dbx", "authScheme": "digest" }, + "connection": { "host": "127.0.0.1", "port": 10800, "username": "root", "password": "123456", "database": "dbx", "authScheme": "digest" }, + "hostPorts": { "DB_PORT": 10800 }, "bootstrap": { "check": { "command": ["sh", "-ec", "printf 'addauth digest root:%s\\nget /dbx\\nquit\\n' \"$1\" | zkCli.sh -server 127.0.0.1:2181", "sh", "${DB_PASSWORD}"], "expect": "DBX smoke" }, "steps": [{ "name": "create Digest-protected smoke node", "command": ["sh", "-ec", "printf 'addauth digest root:%s\\ncreate /dbx \"DBX smoke\" auth:root:%s:cdrwa\\nquit\\n' \"$1\" \"$1\" | zkCli.sh -server 127.0.0.1:2181", "sh", "${DB_PASSWORD}"], "expect": "Created /dbx" }] diff --git a/docs/content/docs/database-lab.cn.mdx b/docs/content/docs/database-lab.cn.mdx index 4c29ed5693..186bb2a9b6 100644 --- a/docs/content/docs/database-lab.cn.mdx +++ b/docs/content/docs/database-lab.cn.mdx @@ -9,63 +9,77 @@ description: 为 DBX 开发与验证启动可重复创建的本地数据库实 ## 可用配方 -| 数据库 | 版本目录 | Docker Hub 官方镜像 | Compose 使用的 CNB 镜像 | 默认宿主端口 | 容器名 | +| 数据库 | 版本目录 | 官方上游镜像 | Compose 使用的 CNB 镜像 | 默认宿主端口 | 容器名 | | --- | --- | --- | --- | ---: | --- | -| ClickHouse | `clickhouse/24.8` | `clickhouse/clickhouse-server:24.8.14.39` | `docker.cnb.cool/znb/images/clickhouse-server:24.8.14.39` | `8124` | `dbx-clickhouse-24.8` | -| etcd | `etcd/3.7` | `gcr.io/etcd-development/etcd:v3.7.0` | `docker.cnb.cool/znb/images/etcd:v3.7.0` | `2380` | `dbx-etcd-3.7` | -| Kafka | `kafka/4.3` | `apache/kafka:4.3.1` | `docker.cnb.cool/znb/images/kafka:4.3.1` | `9093` | `dbx-kafka-4.3` | -| MariaDB | `mariadb/10.11` | `mariadb:10.11.11` | `docker.cnb.cool/znb/images/mariadb:10.11.11` | `3307` | `dbx-mariadb-10.11` | -| MongoDB | `mongodb/5.0` | `mongo:5.0.5` | `docker.cnb.cool/znb/images/mongo:5.0.5` | `27018` | `dbx-mongodb-5.0` | -| MongoDB | `mongodb/8.2` | `mongo:8.2.3-noble` | `docker.cnb.cool/znb/images/mongo:8.2.3-noble` | `27018` | `dbx-mongodb-8.2` | -| MySQL | `mysql/5.7` | `mysql:5.7.44` | `docker.cnb.cool/znb/images/mysql:5.7.44` | `3307` | `dbx-mysql-5.7` | -| MySQL | `mysql/8.4` | `mysql:8.4.6` | `docker.cnb.cool/znb/images/mysql:8.4.6` | `3307` | `dbx-mysql-8.4` | -| Nacos | `nacos/2.5` | `nacos/nacos-server:v2.5.2` | `docker.cnb.cool/znb/images/nacos-server:v2.5.2` | `8849` | `dbx-nacos-2.5` | -| Nacos | `nacos/3.2` | `nacos/nacos-server:v3.2.2` | `docker.cnb.cool/znb/images/nacos-server:v3.2.2` | `8849` | `dbx-nacos-3.2` | -| PostgreSQL | `postgresql/14.23` | `postgres:14.23` | `docker.cnb.cool/znb/images/postgres:14.23` | `5433` | `dbx-postgresql-14.23` | -| PostgreSQL | `postgresql/17.4` | `postgres:17.4` | `docker.cnb.cool/znb/images/postgres:17.4` | `5433` | `dbx-postgresql-17.4` | -| Pulsar | `pulsar/4.2` | `apachepulsar/pulsar:4.2.3` | `docker.cnb.cool/znb/images/pulsar:4.2.3` | `6651` | `dbx-pulsar-4.2` | -| Qdrant | `qdrant/1.8` | `qdrant/qdrant:v1.8.3` | `docker.cnb.cool/znb/images/qdrant:v1.8.3` | `6334` | `dbx-qdrant-1.8` | -| Redis | `redis/3.0.7` | `redis:3.0.7-alpine` | `docker.cnb.cool/znb/images/redis:3.0.7-alpine` | `6380` | `dbx-redis-3.0.7` | -| Redis | `redis/7.4` | `redis:7.4.9-alpine` | `docker.cnb.cool/znb/images/redis:7.4.9-alpine` | `6380` | `dbx-redis-7.4` | -| r-nacos | `rnacos/0.8` | `qingpan/rnacos:v0.8.5` | `docker.cnb.cool/znb/images/rnacos:v0.8.5` | `8849` | `dbx-rnacos-0.8` | -| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `2182` | `dbx-zookeeper-3.9` | +| ClickHouse | `clickhouse/24.8` | `clickhouse/clickhouse-server:24.8.14.39` | `docker.cnb.cool/znb/images/clickhouse-server:24.8.14.39` | `10600` | `dbx-clickhouse-24.8` | +| Consul | `consul/2.0.2` | `hashicorp/consul:2.0.2` | `docker.cnb.cool/znb/images/consul:2.0.2` | `10900` | `dbx-consul-2.0.2` | +| Elasticsearch | `elasticsearch/6.8` | `docker.elastic.co/elasticsearch/elasticsearch:6.8` | `docker.cnb.cool/znb/images/elasticsearch:6.8` | `11500` | `dbx-elasticsearch-6.8` | +| etcd | `etcd/3.7` | `gcr.io/etcd-development/etcd:v3.7.0` | `docker.cnb.cool/znb/images/etcd:v3.7.0` | `10700` | `dbx-etcd-3.7` | +| Kafka | `kafka/4.3` | `apache/kafka:4.3.1` | `docker.cnb.cool/znb/images/kafka:4.3.1` | `11300` | `dbx-kafka-4.3` | +| MariaDB | `mariadb/10.11` | `mariadb:10.11.11` | `docker.cnb.cool/znb/images/mariadb:10.11.11` | `10200` | `dbx-mariadb-10.11` | +| MongoDB | `mongodb/5.0` | `mongo:5.0.5` | `docker.cnb.cool/znb/images/mongo:5.0.5` | `10400` | `dbx-mongodb-5.0` | +| MongoDB | `mongodb/8.2` | `mongo:8.2.3-noble` | `docker.cnb.cool/znb/images/mongo:8.2.3-noble` | `10401` | `dbx-mongodb-8.2` | +| MySQL | `mysql/5.7` | `mysql:5.7.44` | `docker.cnb.cool/znb/images/mysql:5.7.44` | `10100` | `dbx-mysql-5.7` | +| MySQL | `mysql/8.4` | `mysql:8.4.6` | `docker.cnb.cool/znb/images/mysql:8.4.6` | `10101` | `dbx-mysql-8.4` | +| Nacos | `nacos/2.5` | `nacos/nacos-server:v2.5.2` | `docker.cnb.cool/znb/images/nacos-server:v2.5.2` | `11000` | `dbx-nacos-2.5` | +| Nacos | `nacos/3.2` | `nacos/nacos-server:v3.2.3` | `docker.cnb.cool/znb/images/nacos-server:v3.2.3` | `11003` | `dbx-nacos-3.2` | +| PostgreSQL | `postgresql/14.23` | `postgres:14.23` | `docker.cnb.cool/znb/images/postgres:14.23` | `10300` | `dbx-postgresql-14.23` | +| PostgreSQL | `postgresql/17.4` | `postgres:17.4` | `docker.cnb.cool/znb/images/postgres:17.4` | `10301` | `dbx-postgresql-17.4` | +| Pulsar | `pulsar/4.2` | `apachepulsar/pulsar:4.2.3` | `docker.cnb.cool/znb/images/pulsar:4.2.3` | `11400` | `dbx-pulsar-4.2` | +| Qdrant | `qdrant/1.8` | `qdrant/qdrant:v1.8.3` | `docker.cnb.cool/znb/images/qdrant:v1.8.3` | `11200` | `dbx-qdrant-1.8` | +| Redis | `redis/3.0.7` | `redis:3.0.7-alpine` | `docker.cnb.cool/znb/images/redis:3.0.7-alpine` | `10500` | `dbx-redis-3.0.7` | +| Redis | `redis/7.4` | `redis:7.4.9-alpine` | `docker.cnb.cool/znb/images/redis:7.4.9-alpine` | `10501` | `dbx-redis-7.4` | +| r-nacos | `rnacos/0.8` | `qingpan/rnacos:v0.8.5` | `docker.cnb.cool/znb/images/rnacos:v0.8.5` | `11100` | `dbx-rnacos-0.8` | +| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `10800` | `dbx-zookeeper-3.9` | -所有网络数据库的默认宿主端口都比标准端口大 `1`,并且该约定由配方校验器强制检查。默认密码统一为 `123456`,默认数据库名为 `dbx`。Redis 不支持命名数据库,因此使用 DB 0,并以 `dbx:` 作为冒烟键前缀。Compose 默认使用 CNB 镜像列;Docker Hub 列保留官方上游地址,便于核对来源或直接拉取。Redis 3.0.7 仅支持 amd64;在 arm64 主机上启动时会单独提醒,Docker 将使用模拟运行。其余配方均支持 amd64 和 arm64。 +每个产品使用专属的 `101xx` 至 `115xx` 宿主端口段,校验器会验证全部声明的默认映射位于正确范围且全局唯一。默认密码统一为 `123456`,默认数据库名为 `dbx`。Redis 不支持命名数据库,因此使用 DB 0,并以 `dbx:` 作为冒烟键前缀。Compose 默认使用 CNB 镜像列;官方上游镜像列保留原始镜像地址,便于核对来源或直接拉取。Redis 3.0.7 仅支持 amd64;在 arm64 主机上启动时会单独提醒,Docker 将使用模拟运行。其余配方均支持 amd64 和 arm64。 -Nacos 使用管理员用户名 `nacos`、密码 `123456` 和默认 `public` 命名空间。V3 配方额外在 `http://127.0.0.1:8010` 暴露 Web 控制台;V2 控制台使用主端口 `8849`。如需并行运行两个 Nacos 版本,请为其中一个版本设置 `NACOS_CONSOLE_PORT`、`NACOS_GRPC_PORT` 和 `NACOS_RAFT_PORT`。 +Nacos 使用管理员用户名 `nacos`、密码 `123456` 和默认 `public` 命名空间。V2 的 HTTP、gRPC 和 Raft 宿主端口为 `11000`、`11001`、`11002`;V3 的 HTTP、Web 控制台、gRPC 和 Raft 宿主端口为 `11003`、`11004`、`11005`、`11006`。两个版本默认即可并行运行。 ```bash -DB_PORT=8818 NACOS_CONSOLE_PORT=8010 NACOS_GRPC_PORT=9818 NACOS_RAFT_PORT=9819 make db DB=nacos@3.2 +DB_PORT=11030 NACOS_CONSOLE_PORT=11031 NACOS_GRPC_PORT=11032 NACOS_RAFT_PORT=11033 make db DB=nacos@3.2 ``` -r-nacos 使用管理员账号 `admin` 和密码 `123456`。DBX 配方的 HTTP、gRPC 和 Web 控制台宿主端口默认分别为 `8849`、`9849`、`10849`;上游单机 Compose 使用 `8848:8848`、`9848:9848`、`10848:10848`。若需避开本机端口冲突,可使用以下自定义宿主端口映射(不是上游默认值): +r-nacos 使用管理员账号 `admin` 和密码 `123456`。DBX 配方的 HTTP、gRPC 和 Web 控制台宿主端口默认分别为 `11100`、`11101`、`11102`;上游单机 Compose 使用 `8848:8848`、`9848:9848`、`10848:10848`。 ```bash -DB_PORT=3848 RNACOS_GRPC_PORT=3748 RNACOS_CONSOLE_PORT=3048 make db DB=rnacos@0.8 +DB_PORT=11130 RNACOS_GRPC_PORT=11131 RNACOS_CONSOLE_PORT=11132 make db DB=rnacos@0.8 ``` -etcd 会创建密码为 `123456` 的 `root` 用户、授予 root 角色并开启认证。默认客户端和 peer 主机端口为 `2380`、`2381`。如需使用上游 Docker 命令中的端口,可执行: +etcd 会创建密码为 `123456` 的 `root` 用户、授予 root 角色并开启认证。默认客户端和 peer 主机端口为 `10700`、`10701`。如需使用上游 Docker 命令中的端口,可执行: ```bash DB_PORT=2379 ETCD_PEER_PORT=2380 make db DB=etcd@3.7 ``` -Qdrant 将 `123456` 作为管理员 API Key。在 DBX 中请将用户名留空,并把 API Key 填入密码字段,驱动会通过 HTTP 请求头 `api-key` 发送。HTTP 和 gRPC 默认主机端口分别为 `6334`、`6335`。如需使用上游 Docker 命令中的端口,可执行: +Qdrant 将 `123456` 作为管理员 API Key。在 DBX 中请将用户名留空,并把 API Key 填入密码字段,驱动会通过 HTTP 请求头 `api-key` 发送。HTTP 和 gRPC 默认主机端口分别为 `11200`、`11201`。如需使用上游 Docker 命令中的端口,可执行: ```bash DB_PORT=6333 QDRANT_GRPC_PORT=6334 make db DB=qdrant@1.8 ``` -ZooKeeper 使用 Digest 凭据 `root` / `123456` 保护 `/dbx` 节点。默认主机端口为 `2182`;设置 `DB_PORT=2181` 即可匹配上游 Docker 命令。ZooKeeper 的 Digest ACL 模型保护单个节点,并不提供全局登录开关。 +ZooKeeper 使用 Digest 凭据 `root` / `123456` 保护 `/dbx` 节点。默认主机端口为 `10800`;设置 `DB_PORT=2181` 即可匹配上游 Docker 命令。ZooKeeper 的 Digest ACL 模型保护单个节点,并不提供全局登录开关。 Kafka 和 Pulsar 均为刻意保持未认证的单节点开发配方:Kafka 使用 PLAINTEXT,Pulsar 使用 standalone,因此不要将它们暴露到远程网络。Kafka 如需使用常用端口,可执行 `DB_PORT=9092 make db DB=kafka@4.3`;Pulsar 使用常用端口可执行 `DB_PORT=6650 PULSAR_WEB_PORT=8080 make db DB=pulsar@4.2`。 Redis 3.0.7 和 Redis 7.4.9 用于覆盖兼容性的两个端点。Redis 3 早于 ACL、RESP3、Streams 及许多现代命令,Redis 7 则覆盖当前协议与命令集。对这个实验室而言,同时保留 6.2 和 7.4 所提供的兼容性覆盖更小。 -同一数据库的不同版本默认会共用宿主机端口。通常一次启动一个版本;需要并行对比时,为其中一个版本指定其他端口: +Consul 为单节点开发模式,使用 `agent -dev -client=0.0.0.0 -ui` 启动,不启用 ACL。HTTP API 与 UI、gRPC 和 DNS 的默认宿主端口分别为 `10900`、`10901` 和 `10902`;DNS 同时开放 TCP 和 UDP。以下命令可使用本地常用端口 `8500`、`8502` 和 `8600`: ```bash -DB_PORT=13307 make db DB=mysql@5.7 +DB_PORT=8500 CONSUL_GRPC_PORT=8502 CONSUL_DNS_PORT=8600 make db DB=consul@2.0.2 +``` + +Elasticsearch 6.8 以未认证的单节点开发集群运行,并禁用机器学习功能。HTTP API 和 transport 默认宿主端口为 `11500`、`11501`;以下命令可使用上游端口 `9200`、`9300`: + +```bash +DB_PORT=9200 ELASTICSEARCH_TRANSPORT_PORT=9300 make db DB=elasticsearch@6.8 +``` + +所有已提交配方都有唯一的默认宿主端口分配,因此版本和产品可默认并行运行。仅在本地规范需要不同端口时使用 `DB_PORT`: + +```bash +DB_PORT=10150 make db DB=mysql@5.7 make db DB=mysql@8.4 ``` @@ -79,15 +93,17 @@ make db-list make db DB=postgresql@17.4 make db-verify DB=postgresql@17.4 make db DB=rnacos@0.8 +make db DB=consul@2.0.2 +make db DB=elasticsearch@6.8 ``` -不带参数执行 `make db` 会输出当前全部支持的、可直接复制启动的命令,以及可选参数。传入 `DB=product@version` 后,`db` 会等待 Compose 健康检查通过并输出 DBX 连接字段。`db-verify` 还会在数据库容器中执行配方中的冒烟命令,并校验预期输出;提交数据库相关修复前推荐执行此命令。 +不带参数执行 `make db` 会输出当前全部支持的、可直接复制启动的命令,以及可选参数。传入 `DB=product@version` 后,`db` 会等待 Compose 健康检查通过并输出 DBX 连接字段;对于 DBX 已支持的连接类型,还会输出预填完成的 `dbx://connection/new` 深链。复制该链接到浏览器或终端执行 `open '<链接>'`,即可打开桌面端的新建连接窗口。使用前请先安装并启动一次 DBX 桌面端以注册 `dbx://` 协议。深链可能包含密码,因此不要将其写入共享终端历史、日志或工单。尚未具备 DBX 连接类型的配方会明确显示深链不可用,例如 Consul、Nacos、Kafka、Pulsar 和 r-nacos。`db-verify` 还会在数据库容器中执行配方中的冒烟命令,并校验预期输出;提交数据库相关修复前推荐执行此命令。 ## 命令和变量 | Make 目标 | 用途 | | --- | --- | -| `make db-list` | 列出数据库版本、镜像和支持的平台 | +| `make db-list` | 列出数据库版本、容器到宿主机端口映射、镜像和支持的平台 | | `make db DB=product@version` | 创建、等待环境就绪并输出连接字段 | | `make db-verify DB=product@version` | 启动并运行冒烟检查 | | `make db-down DB=product@version` | 停止环境但保留数据卷 | diff --git a/docs/content/docs/database-lab.mdx b/docs/content/docs/database-lab.mdx index 518ffc5354..0730150bd8 100644 --- a/docs/content/docs/database-lab.mdx +++ b/docs/content/docs/database-lab.mdx @@ -9,63 +9,77 @@ The Database Test Lab provides small, pinned Docker Compose environments for tes ## Available recipes -| Database | Version directory | Official Docker Hub image | CNB image used by Compose | Default host port | Container name | +| Database | Version directory | Official upstream image | CNB image used by Compose | Default host port | Container name | | --- | --- | --- | --- | ---: | --- | -| ClickHouse | `clickhouse/24.8` | `clickhouse/clickhouse-server:24.8.14.39` | `docker.cnb.cool/znb/images/clickhouse-server:24.8.14.39` | `8124` | `dbx-clickhouse-24.8` | -| etcd | `etcd/3.7` | `gcr.io/etcd-development/etcd:v3.7.0` | `docker.cnb.cool/znb/images/etcd:v3.7.0` | `2380` | `dbx-etcd-3.7` | -| Kafka | `kafka/4.3` | `apache/kafka:4.3.1` | `docker.cnb.cool/znb/images/kafka:4.3.1` | `9093` | `dbx-kafka-4.3` | -| MariaDB | `mariadb/10.11` | `mariadb:10.11.11` | `docker.cnb.cool/znb/images/mariadb:10.11.11` | `3307` | `dbx-mariadb-10.11` | -| MongoDB | `mongodb/5.0` | `mongo:5.0.5` | `docker.cnb.cool/znb/images/mongo:5.0.5` | `27018` | `dbx-mongodb-5.0` | -| MongoDB | `mongodb/8.2` | `mongo:8.2.3-noble` | `docker.cnb.cool/znb/images/mongo:8.2.3-noble` | `27018` | `dbx-mongodb-8.2` | -| MySQL | `mysql/5.7` | `mysql:5.7.44` | `docker.cnb.cool/znb/images/mysql:5.7.44` | `3307` | `dbx-mysql-5.7` | -| MySQL | `mysql/8.4` | `mysql:8.4.6` | `docker.cnb.cool/znb/images/mysql:8.4.6` | `3307` | `dbx-mysql-8.4` | -| Nacos | `nacos/2.5` | `nacos/nacos-server:v2.5.2` | `docker.cnb.cool/znb/images/nacos-server:v2.5.2` | `8849` | `dbx-nacos-2.5` | -| Nacos | `nacos/3.2` | `nacos/nacos-server:v3.2.2` | `docker.cnb.cool/znb/images/nacos-server:v3.2.2` | `8849` | `dbx-nacos-3.2` | -| PostgreSQL | `postgresql/14.23` | `postgres:14.23` | `docker.cnb.cool/znb/images/postgres:14.23` | `5433` | `dbx-postgresql-14.23` | -| PostgreSQL | `postgresql/17.4` | `postgres:17.4` | `docker.cnb.cool/znb/images/postgres:17.4` | `5433` | `dbx-postgresql-17.4` | -| Pulsar | `pulsar/4.2` | `apachepulsar/pulsar:4.2.3` | `docker.cnb.cool/znb/images/pulsar:4.2.3` | `6651` | `dbx-pulsar-4.2` | -| Qdrant | `qdrant/1.8` | `qdrant/qdrant:v1.8.3` | `docker.cnb.cool/znb/images/qdrant:v1.8.3` | `6334` | `dbx-qdrant-1.8` | -| Redis | `redis/3.0.7` | `redis:3.0.7-alpine` | `docker.cnb.cool/znb/images/redis:3.0.7-alpine` | `6380` | `dbx-redis-3.0.7` | -| Redis | `redis/7.4` | `redis:7.4.9-alpine` | `docker.cnb.cool/znb/images/redis:7.4.9-alpine` | `6380` | `dbx-redis-7.4` | -| r-nacos | `rnacos/0.8` | `qingpan/rnacos:v0.8.5` | `docker.cnb.cool/znb/images/rnacos:v0.8.5` | `8849` | `dbx-rnacos-0.8` | -| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `2182` | `dbx-zookeeper-3.9` | +| ClickHouse | `clickhouse/24.8` | `clickhouse/clickhouse-server:24.8.14.39` | `docker.cnb.cool/znb/images/clickhouse-server:24.8.14.39` | `10600` | `dbx-clickhouse-24.8` | +| Consul | `consul/2.0.2` | `hashicorp/consul:2.0.2` | `docker.cnb.cool/znb/images/consul:2.0.2` | `10900` | `dbx-consul-2.0.2` | +| Elasticsearch | `elasticsearch/6.8` | `docker.elastic.co/elasticsearch/elasticsearch:6.8` | `docker.cnb.cool/znb/images/elasticsearch:6.8` | `11500` | `dbx-elasticsearch-6.8` | +| etcd | `etcd/3.7` | `gcr.io/etcd-development/etcd:v3.7.0` | `docker.cnb.cool/znb/images/etcd:v3.7.0` | `10700` | `dbx-etcd-3.7` | +| Kafka | `kafka/4.3` | `apache/kafka:4.3.1` | `docker.cnb.cool/znb/images/kafka:4.3.1` | `11300` | `dbx-kafka-4.3` | +| MariaDB | `mariadb/10.11` | `mariadb:10.11.11` | `docker.cnb.cool/znb/images/mariadb:10.11.11` | `10200` | `dbx-mariadb-10.11` | +| MongoDB | `mongodb/5.0` | `mongo:5.0.5` | `docker.cnb.cool/znb/images/mongo:5.0.5` | `10400` | `dbx-mongodb-5.0` | +| MongoDB | `mongodb/8.2` | `mongo:8.2.3-noble` | `docker.cnb.cool/znb/images/mongo:8.2.3-noble` | `10401` | `dbx-mongodb-8.2` | +| MySQL | `mysql/5.7` | `mysql:5.7.44` | `docker.cnb.cool/znb/images/mysql:5.7.44` | `10100` | `dbx-mysql-5.7` | +| MySQL | `mysql/8.4` | `mysql:8.4.6` | `docker.cnb.cool/znb/images/mysql:8.4.6` | `10101` | `dbx-mysql-8.4` | +| Nacos | `nacos/2.5` | `nacos/nacos-server:v2.5.2` | `docker.cnb.cool/znb/images/nacos-server:v2.5.2` | `11000` | `dbx-nacos-2.5` | +| Nacos | `nacos/3.2` | `nacos/nacos-server:v3.2.3` | `docker.cnb.cool/znb/images/nacos-server:v3.2.3` | `11003` | `dbx-nacos-3.2` | +| PostgreSQL | `postgresql/14.23` | `postgres:14.23` | `docker.cnb.cool/znb/images/postgres:14.23` | `10300` | `dbx-postgresql-14.23` | +| PostgreSQL | `postgresql/17.4` | `postgres:17.4` | `docker.cnb.cool/znb/images/postgres:17.4` | `10301` | `dbx-postgresql-17.4` | +| Pulsar | `pulsar/4.2` | `apachepulsar/pulsar:4.2.3` | `docker.cnb.cool/znb/images/pulsar:4.2.3` | `11400` | `dbx-pulsar-4.2` | +| Qdrant | `qdrant/1.8` | `qdrant/qdrant:v1.8.3` | `docker.cnb.cool/znb/images/qdrant:v1.8.3` | `11200` | `dbx-qdrant-1.8` | +| Redis | `redis/3.0.7` | `redis:3.0.7-alpine` | `docker.cnb.cool/znb/images/redis:3.0.7-alpine` | `10500` | `dbx-redis-3.0.7` | +| Redis | `redis/7.4` | `redis:7.4.9-alpine` | `docker.cnb.cool/znb/images/redis:7.4.9-alpine` | `10501` | `dbx-redis-7.4` | +| r-nacos | `rnacos/0.8` | `qingpan/rnacos:v0.8.5` | `docker.cnb.cool/znb/images/rnacos:v0.8.5` | `11100` | `dbx-rnacos-0.8` | +| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `10800` | `dbx-zookeeper-3.9` | -Every network recipe uses a host port one greater than the database standard port, and the recipe validator enforces that contract. The default password is `123456`, and the default database is `dbx`. Redis has no named databases, so it uses DB 0 with a `dbx:` smoke-key prefix. Compose uses the CNB image column; the Docker Hub column preserves the official upstream reference for provenance and direct pulls. Redis 3.0.7 is amd64-only; on arm64 hosts the command prints an architecture warning and Docker uses emulation. Every other recipe supports both amd64 and arm64. +Each product uses a dedicated `101xx`-`115xx` host-port range, and the validator verifies that every declared default mapping is in the correct range and globally unique. The default password is `123456`, and the default database is `dbx`. Redis has no named databases, so it uses DB 0 with a `dbx:` smoke-key prefix. Compose uses the CNB image column; the official upstream image column preserves the original image reference for provenance and direct pulls. Redis 3.0.7 is amd64-only; on arm64 hosts the command prints an architecture warning and Docker uses emulation. Every other recipe supports both amd64 and arm64. -Nacos uses the `nacos` administrator, password `123456`, and its default `public` namespace. Its V3 recipe also exposes the web console at `http://127.0.0.1:8010`; V2 serves its console on the main `8849` port. Set `NACOS_CONSOLE_PORT`, `NACOS_GRPC_PORT`, and `NACOS_RAFT_PORT` when running an Nacos version alongside another one. +Nacos uses the `nacos` administrator, password `123456`, and its default `public` namespace. V2 uses HTTP, gRPC, and Raft host ports `11000`, `11001`, and `11002`; V3 uses HTTP `11003`, web console `11004`, gRPC `11005`, and Raft `11006`. The two versions can run together by default. ```bash -DB_PORT=8818 NACOS_CONSOLE_PORT=8010 NACOS_GRPC_PORT=9818 NACOS_RAFT_PORT=9819 make db DB=nacos@3.2 +DB_PORT=11030 NACOS_CONSOLE_PORT=11031 NACOS_GRPC_PORT=11032 NACOS_RAFT_PORT=11033 make db DB=nacos@3.2 ``` -r-nacos uses the `admin` administrator and password `123456`. The DBX recipe defaults its HTTP, gRPC, and web-console host ports to `8849`, `9849`, and `10849`; the upstream standalone Compose file publishes `8848:8848`, `9848:9848`, and `10848:10848`. To avoid local port conflicts, use this custom host-port mapping (not the upstream defaults): +r-nacos uses the `admin` administrator and password `123456`. The DBX recipe defaults its HTTP, gRPC, and web-console host ports to `11100`, `11101`, and `11102`; the upstream standalone Compose file publishes `8848:8848`, `9848:9848`, and `10848:10848`. ```bash -DB_PORT=3848 RNACOS_GRPC_PORT=3748 RNACOS_CONSOLE_PORT=3048 make db DB=rnacos@0.8 +DB_PORT=11130 RNACOS_GRPC_PORT=11131 RNACOS_CONSOLE_PORT=11132 make db DB=rnacos@0.8 ``` -etcd creates the `root` user with password `123456`, grants the root role, then enables authentication. Its default client and peer ports are `2380` and `2381`. To use the upstream Docker command's ports, run: +etcd creates the `root` user with password `123456`, grants the root role, then enables authentication. Its default client and peer ports are `10700` and `10701`. To use the upstream Docker command's ports, run: ```bash DB_PORT=2379 ETCD_PEER_PORT=2380 make db DB=etcd@3.7 ``` -Qdrant uses `123456` as its administrator API key. In DBX, leave the username empty and enter the key as the password so the driver sends it in the `api-key` HTTP header. Its HTTP and gRPC ports default to `6334` and `6335`. To use the ports in the upstream Docker command, run: +Qdrant uses `123456` as its administrator API key. In DBX, leave the username empty and enter the key as the password so the driver sends it in the `api-key` HTTP header. Its HTTP and gRPC ports default to `11200` and `11201`. To use the ports in the upstream Docker command, run: ```bash DB_PORT=6333 QDRANT_GRPC_PORT=6334 make db DB=qdrant@1.8 ``` -ZooKeeper uses Digest credentials `root` / `123456` to protect the `/dbx` node. Its default host port is `2182`; use `DB_PORT=2181` to match the upstream Docker command. ZooKeeper's Digest ACL model protects individual nodes rather than providing a global login switch. +ZooKeeper uses Digest credentials `root` / `123456` to protect the `/dbx` node. Its default host port is `10800`; use `DB_PORT=2181` to match the upstream Docker command. ZooKeeper's Digest ACL model protects individual nodes rather than providing a global login switch. Kafka and Pulsar are deliberately unauthenticated single-node development recipes. Kafka uses PLAINTEXT and Pulsar uses standalone mode, so do not expose either service remotely. Use `DB_PORT=9092 make db DB=kafka@4.3` for Kafka's usual port, and `DB_PORT=6650 PULSAR_WEB_PORT=8080 make db DB=pulsar@4.2` for Pulsar's usual ports. Redis 3.0.7 and Redis 7.4.9 intentionally cover the compatibility endpoints. Redis 3 predates ACLs, RESP3, Streams, and many modern commands, while Redis 7 covers the current protocol and command set. Keeping 6.2 and 7.4 together would provide less compatibility coverage for this lab. -Versions of the same product intentionally share that default host port. Start them one at a time, or give one version another port when comparing versions concurrently: +Consul runs as a single-node development agent with `agent -dev -client=0.0.0.0 -ui` and no ACLs. Its HTTP API and UI, gRPC, and DNS host ports default to `10900`, `10901`, and `10902`; DNS is published over both TCP and UDP. Use the following command to match the usual local ports `8500`, `8502`, and `8600`: ```bash -DB_PORT=13307 make db DB=mysql@5.7 +DB_PORT=8500 CONSUL_GRPC_PORT=8502 CONSUL_DNS_PORT=8600 make db DB=consul@2.0.2 +``` + +Elasticsearch 6.8 runs as an unauthenticated single-node development cluster with machine-learning features disabled. Its HTTP API and transport host ports default to `11500` and `11501`; use the following command to match the upstream ports `9200` and `9300`: + +```bash +DB_PORT=9200 ELASTICSEARCH_TRANSPORT_PORT=9300 make db DB=elasticsearch@6.8 +``` + +Every checked-in recipe has a unique default host-port allocation, so versions and products can run concurrently. Use `DB_PORT` only when a local policy requires a different host port: + +```bash +DB_PORT=10150 make db DB=mysql@5.7 make db DB=mysql@8.4 ``` @@ -79,15 +93,17 @@ make db-list make db DB=postgresql@17.4 make db-verify DB=postgresql@17.4 make db DB=rnacos@0.8 +make db DB=consul@2.0.2 +make db DB=elasticsearch@6.8 ``` -`make db` without parameters prints every currently supported copy-and-paste start command, plus optional parameters. With `DB=product@version`, it waits for the Compose health check and prints the DBX connection fields. `db-verify` also runs the recipe's smoke command inside the database container and checks its expected output. It is the preferred command before reporting a database-specific fix. +`make db` without parameters prints every currently supported copy-and-paste start command, plus optional parameters. With `DB=product@version`, it waits for the Compose health check and prints the DBX connection fields. For database types supported by DBX, it also prints a prefilled `dbx://connection/new` deep link. Copy the link into a browser or run `open ''` to open the new-connection dialog in DBX Desktop. Install and open DBX Desktop once before use so the operating system registers the `dbx://` protocol. The deep link can contain a password, so do not save it in shared terminal history, logs, or tickets. Recipes without a compatible DBX connection type explicitly report that no deep link is available, including Consul, Nacos, Kafka, Pulsar, and r-nacos. `db-verify` also runs the recipe's smoke command inside the database container and checks its expected output. It is the preferred command before reporting a database-specific fix. ## Commands and variables | Make target | Purpose | | --- | --- | -| `make db-list` | List database versions, images, and supported platforms | +| `make db-list` | List database versions, container-to-host port mappings, images, and supported platforms | | `make db DB=product@version` | Create, wait for, and print the connection fields | | `make db-verify DB=product@version` | Start and run smoke checks | | `make db-down DB=product@version` | Stop the environment while keeping its data volume | diff --git a/scripts/database-env.mjs b/scripts/database-env.mjs index a92a1f693d..e26b29213d 100644 --- a/scripts/database-env.mjs +++ b/scripts/database-env.mjs @@ -10,6 +10,35 @@ export const DEFAULT_RECIPES_ROOT = join(REPO_ROOT, 'deploy', 'database'); const DEFAULT_MAKEFILE_PATH = join(REPO_ROOT, 'Makefile'); const DEFAULT_PASSWORD = '123456'; const DEFAULT_DATABASE = 'dbx'; +const DEFAULT_HOST_PORT_RANGES = { + mysql: [10100, 10199], + mariadb: [10200, 10299], + postgresql: [10300, 10399], + mongodb: [10400, 10499], + redis: [10500, 10599], + clickhouse: [10600, 10699], + etcd: [10700, 10799], + zookeeper: [10800, 10899], + consul: [10900, 10999], + nacos: [11000, 11099], + rnacos: [11100, 11199], + qdrant: [11200, 11299], + kafka: [11300, 11399], + pulsar: [11400, 11499], + elasticsearch: [11500, 11599], +}; +const DBX_DEEP_LINK_TYPES = { + clickhouse: 'clickhouse', + elasticsearch: 'elasticsearch', + etcd: 'etcd', + mariadb: 'mariadb', + mongodb: 'mongodb', + mysql: 'mysql', + postgresql: 'postgres', + qdrant: 'qdrant', + redis: 'redis', + zookeeper: 'zookeeper', +}; export function discoverRecipes(root = DEFAULT_RECIPES_ROOT) { if (!existsSync(root)) return []; @@ -45,6 +74,22 @@ export function formatTable(headers, rows) { return [headers, ...rows].map(renderRow).join('\n'); } +export function formatBorderedTable(headers, rows, groupByFirstColumn = false) { + const widths = headers.map((header, index) => Math.max( + header.length, + ...rows.map((row) => String(row[index] ?? '').length), + )); + const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`; + const renderRow = (row) => `| ${row.map((value, index) => String(value ?? '').padEnd(widths[index])).join(' | ')} |`; + const body = []; + for (const [index, row] of rows.entries()) { + body.push(renderRow(row)); + if (groupByFirstColumn && index < rows.length - 1 && rows[index + 1][0] !== '') body.push(border); + } + + return [border, renderRow(headers), border, ...body, border].join('\n'); +} + export function discoverMakeTargets(makefilePath = DEFAULT_MAKEFILE_PATH) { if (!existsSync(makefilePath)) return []; const source = readFileSync(makefilePath, 'utf8').replace(/\\\r?\n/g, ' '); @@ -119,18 +164,95 @@ export function expectedContainerName(recipe) { return `dbx-${recipe.database}-${recipe.displayVersion}`.toLowerCase().replace(/[^a-z0-9_.-]+/g, '-'); } +export function defaultHostPortMappings(compose) { + const mappings = []; + const pattern = /^\s*-\s*["']?\$\{DB_BIND_ADDRESS:-127\.0\.0\.1\}:\$\{([A-Z][A-Z0-9_]*):-([0-9]+)\}:([0-9]+)(?:\/(tcp|udp))?["']?\s*$/; + for (const line of compose.split(/\r?\n/)) { + const match = line.match(pattern); + if (!match) continue; + mappings.push({ environment: match[1], hostPort: Number(match[2]), containerPort: Number(match[3]), protocol: match[4] || 'tcp' }); + } + return mappings; +} + +export function recipePortMappings(recipe) { + const composePath = join(recipe.directory, 'compose.yaml'); + if (!existsSync(composePath)) return ''; + const mappings = defaultHostPortMappings(readFileSync(composePath, 'utf8')).sort((left, right) => ( + left.hostPort - right.hostPort + || left.containerPort - right.containerPort + || left.protocol.localeCompare(right.protocol) + )); + return mappings.map((mapping) => { + const sharesPortWithAnotherProtocol = mappings.some((candidate) => ( + candidate !== mapping + && candidate.containerPort === mapping.containerPort + && candidate.hostPort === mapping.hostPort + && candidate.protocol !== mapping.protocol + )); + const sourcePort = sharesPortWithAnotherProtocol + ? `${mapping.containerPort}/${mapping.protocol}` + : mapping.containerPort; + return `${sourcePort} -> ${mapping.hostPort}`; + }).join(', '); +} + +export function databaseListRows(recipes) { + let previousDatabase = null; + return recipes.map((recipe) => { + const database = recipe.database === previousDatabase ? '' : recipe.name; + previousDatabase = recipe.database; + return [database, recipe.displayVersion, recipePortMappings(recipe), recipe.image, recipe.platforms.join(',')]; + }); +} + +function validateHostPorts(recipe) { + const errors = []; + const hostPorts = recipe.hostPorts; + if (!hostPorts || typeof hostPorts !== 'object' || Array.isArray(hostPorts)) return ['hostPorts must be a non-empty object']; + const entries = Object.entries(hostPorts); + if (entries.length === 0) return ['hostPorts must be a non-empty object']; + const [start, end] = DEFAULT_HOST_PORT_RANGES[recipe.database] ?? []; + if (!start) { + errors.push(`database ${recipe.database} has no allocated default host port range`); + } + for (const [environment, port] of entries) { + if (!/^[A-Z][A-Z0-9_]*$/.test(environment)) errors.push(`hostPorts key ${environment} must be an environment variable name`); + if (!Number.isInteger(port) || port < start || port > end) { + errors.push(`hostPorts.${environment} must be an integer in the ${start}-${end} range`); + } + } + if (new Set(entries.map(([, port]) => port)).size !== entries.length) errors.push('hostPorts must not reuse a port within a recipe'); + if (recipe.connection?.port !== hostPorts.DB_PORT) errors.push('connection.port must match hostPorts.DB_PORT'); + return errors; +} + +export function validateHostPortAllocation(recipes) { + const errors = []; + const owners = new Map(); + for (const recipe of recipes) { + for (const [environment, port] of Object.entries(recipe.hostPorts ?? {})) { + if (!Number.isInteger(port)) continue; + const owner = `${recipeSelector(recipe)} (${environment})`; + const existing = owners.get(port); + if (existing) errors.push(`host port ${port} is allocated to both ${existing} and ${owner}`); + else owners.set(port, owner); + } + } + return errors; +} + export function validateRecipe(recipe) { const errors = []; - for (const field of ['database', 'version', 'displayVersion', 'service', 'connection', 'smoke', 'shell', 'defaultPort']) { + for (const field of ['database', 'version', 'displayVersion', 'service', 'connection', 'hostPorts', 'smoke', 'shell', 'defaultPort']) { if (recipe[field] === undefined) errors.push(`missing ${field}`); } if (!recipe.image || !/^[^\s]+:[^\s]+$/.test(recipe.image)) errors.push('image must be a pinned image reference'); if (recipe.connection?.host !== '127.0.0.1') errors.push('connection.host must be 127.0.0.1'); if (!Number.isInteger(recipe.defaultPort) || recipe.defaultPort < 1 || recipe.defaultPort > 65534) { errors.push('defaultPort must be an integer between 1 and 65534'); - } else if (recipe.connection?.port !== recipe.defaultPort + 1) { - errors.push('connection.port must be defaultPort + 1'); } + errors.push(...validateHostPorts(recipe)); if (recipe.connection?.authentication === 'none') { if (recipe.connection.password !== undefined) errors.push('unauthenticated recipes must not declare connection.password'); } else if (recipe.connection?.password !== DEFAULT_PASSWORD) errors.push(`connection.password must be ${DEFAULT_PASSWORD}`); @@ -162,8 +284,18 @@ export function validateRecipe(recipe) { errors.push('compose.yaml ports must default every mapping to 127.0.0.1 via DB_BIND_ADDRESS'); } if (!serviceHasNamedVolume(compose, recipe.service)) errors.push('target service must mount a named volume'); - if (!compose.includes(`\${DB_PORT:-${recipe.connection?.port}}:${recipe.defaultPort}`)) { - errors.push('compose.yaml must default DB_PORT to connection.port and target defaultPort'); + const portMappings = defaultHostPortMappings(compose); + const mappedPorts = new Map(); + for (const mapping of portMappings) { + const previousPort = mappedPorts.get(mapping.environment); + if (previousPort !== undefined && previousPort !== mapping.hostPort) { + errors.push(`compose.yaml uses conflicting defaults for ${mapping.environment}`); + } + mappedPorts.set(mapping.environment, mapping.hostPort); + } + const configuredPorts = Object.entries(recipe.hostPorts ?? {}); + if (mappedPorts.size !== configuredPorts.length || configuredPorts.some(([environment, port]) => mappedPorts.get(environment) !== port)) { + errors.push('compose.yaml port defaults must match recipe hostPorts'); } } const initDirectory = join(recipe.directory, 'init'); @@ -289,6 +421,24 @@ export function expandSmokeCommand(command, recipe, environment = process.env) { return command.map((value) => value.replace(/\$\{(DB_PASSWORD|DB_PORT)\}/g, (_, name) => values[name])); } +export function dbxConnectionDeepLink(recipe, environment = process.env) { + const type = DBX_DEEP_LINK_TYPES[recipe.database]; + if (!type) return null; + + const { connection } = recipe; + const params = new URLSearchParams({ + type, + name: `${recipe.name} ${recipe.version} (local)`, + host: connection.host, + port: environment.DB_PORT || String(connection.port), + database: String(connection.database), + }); + if (connection.username) params.set('user', connection.username); + if (connection.password) params.set('password', environment.DB_PASSWORD || connection.password); + if (connection.urlParams) params.set('url_params', connection.urlParams); + return `dbx://connection/new?${params}`; +} + function ensureBootstrap(recipe) { if (!recipe.bootstrap) return; // Keep one-shot setup synchronous after Compose health checks so --wait cannot return before credentials exist. @@ -310,6 +460,9 @@ function printConnection(recipe) { if (process.env.DB_PASSWORD) connection.password = process.env.DB_PASSWORD; console.log(`${recipe.name} (${recipe.version})`); for (const [key, value] of Object.entries(connection)) console.log(`${key}: ${value}`); + const deepLink = dbxConnectionDeepLink(recipe); + if (deepLink) console.log(`DBX connection link: ${deepLink}`); + else console.log(`DBX connection link: unavailable (DBX has no compatible ${recipe.name} connection type)`); if (recipe.notes) console.log(`notes: ${recipe.notes}`); const warning = architectureWarning(recipe); if (warning) console.warn(`warning: ${warning}`); @@ -317,6 +470,11 @@ function printConnection(recipe) { function checkRecipes(recipes) { let failed = false; + const allocationErrors = validateHostPortAllocation(recipes); + if (allocationErrors.length > 0) { + failed = true; + for (const error of allocationErrors) console.error(`FAIL host port allocation: ${error}`); + } for (const recipe of recipes) { const errors = validateRecipe(recipe); if (errors.length) { @@ -368,9 +526,10 @@ export function main(argv = process.argv.slice(2)) { const recipes = discoverRecipes(); if (command === 'list') { - console.log(formatTable( - ['DATABASE', 'VERSION', 'IMAGE', 'PLATFORMS'], - recipes.map((recipe) => [recipe.database, recipe.displayVersion, recipe.image, recipe.platforms.join(',')]), + console.log(formatBorderedTable( + ['DATABASE', 'VERSION', 'PORTS (CONTAINER -> HOST)', 'IMAGE', 'PLATFORMS'], + databaseListRows(recipes), + true, )); return; } diff --git a/scripts/database-env.test.mjs b/scripts/database-env.test.mjs index 33b82389ca..5888a5d3ea 100644 --- a/scripts/database-env.test.mjs +++ b/scripts/database-env.test.mjs @@ -4,19 +4,20 @@ import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { spawnSync } from 'node:child_process'; -import { DEFAULT_RECIPES_ROOT, allPortMappingsDefaultToLoopback, architectureWarning, assertResetConfirmed, discoverMakeTargets, discoverRecipes, expandSmokeCommand, expectedContainerName, formatTable, parseDatabaseSelection, platformForArchitecture, recipeSelector, resolveRecipe, serviceHasNamedVolume, validateRecipe, validateRenderedCompose } from './database-env.mjs'; +import { DEFAULT_RECIPES_ROOT, allPortMappingsDefaultToLoopback, architectureWarning, assertResetConfirmed, databaseListRows, dbxConnectionDeepLink, defaultHostPortMappings, discoverMakeTargets, discoverRecipes, expandSmokeCommand, expectedContainerName, formatBorderedTable, formatTable, parseDatabaseSelection, platformForArchitecture, recipeSelector, resolveRecipe, serviceHasNamedVolume, validateHostPortAllocation, validateRecipe, validateRenderedCompose } from './database-env.mjs'; const bashAvailable = spawnSync('bash', ['--version'], { stdio: 'ignore' }).status === 0; const zshAvailable = spawnSync('zsh', ['--version'], { stdio: 'ignore' }).status === 0; const dockerComposeAvailable = spawnSync('docker', ['compose', 'version'], { stdio: 'ignore' }).status === 0; function fixture(database, version) { + const hostPort = { mysql: 10100, postgresql: 10300, redis: 10500 }[database] ?? 10100; const root = mkdtempSync(join(tmpdir(), 'dbx-db-env-')); const directory = join(root, database, version); mkdirSync(join(directory, 'init'), { recursive: true }); writeFileSync(join(directory, 'init', '001-smoke.txt'), 'fixture initialization'); - writeFileSync(join(directory, 'recipe.json'), JSON.stringify({ database, version, displayVersion: version, name: database, image: 'test:1', platforms: ['linux/amd64'], service: 'database', defaultPort: 1234, connection: { host: '127.0.0.1', port: 1235, password: '123456', database: database === 'redis' ? 0 : 'dbx' }, smoke: { steps: [{ name: 'smoke', command: ['true'], expect: 'true' }] }, shell: ['true'] })); - writeFileSync(join(directory, 'compose.yaml'), `services:\n database:\n image: test:1\n platform: linux/amd64\n container_name: dbx-${database}-${version}\n ports:\n - "\${DB_BIND_ADDRESS:-127.0.0.1}:\${DB_PORT:-1235}:1234"\n volumes:\n - data:/var/lib/database\n healthcheck:\n test: ["CMD", "true"]\nvolumes:\n data:\n`); + writeFileSync(join(directory, 'recipe.json'), JSON.stringify({ database, version, displayVersion: version, name: database, image: 'test:1', platforms: ['linux/amd64'], service: 'database', defaultPort: 1234, connection: { host: '127.0.0.1', port: hostPort, password: '123456', database: database === 'redis' ? 0 : 'dbx' }, hostPorts: { DB_PORT: hostPort }, smoke: { steps: [{ name: 'smoke', command: ['true'], expect: 'true' }] }, shell: ['true'] })); + writeFileSync(join(directory, 'compose.yaml'), `services:\n database:\n image: test:1\n platform: linux/amd64\n container_name: dbx-${database}-${version}\n ports:\n - "\${DB_BIND_ADDRESS:-127.0.0.1}:\${DB_PORT:-${hostPort}}:1234"\n volumes:\n - data:/var/lib/database\n healthcheck:\n test: ["CMD", "true"]\nvolumes:\n data:\n`); return root; } @@ -59,6 +60,35 @@ test('formats lists as left-aligned tables with dynamic column widths', () => { ); }); +test('formats bordered tables with header and group separators', () => { + assert.equal( + formatBorderedTable( + ['DATABASE', 'VERSION'], + [['MySQL', '5.7'], ['', '8.4'], ['PostgreSQL', '17.4']], + true, + ), + '+------------+---------+\n' + + '| DATABASE | VERSION |\n' + + '+------------+---------+\n' + + '| MySQL | 5.7 |\n' + + '| | 8.4 |\n' + + '+------------+---------+\n' + + '| PostgreSQL | 17.4 |\n' + + '+------------+---------+', + ); +}); + +test('groups database versions and shows every container-to-host port mapping', () => { + const rows = databaseListRows(discoverRecipes()); + const mysql57 = rows.find((row) => row[1] === '5.7'); + const mysql84 = rows.find((row) => row[1] === '8.4'); + const consul = rows.find((row) => row[0] === 'Consul'); + + assert.deepEqual(mysql57.slice(0, 3), ['MySQL', '5.7', '3306 -> 10100']); + assert.deepEqual(mysql84.slice(0, 3), ['', '8.4', '3306 -> 10101']); + assert.equal(consul[2], '8500 -> 10900, 8502 -> 10901, 8600/tcp -> 10902, 8600/udp -> 10902'); +}); + test('discovers all public Make targets for shell completion', () => { const targets = discoverMakeTargets(); for (const target of ['help', 'build', 'test', 'docs', 'db', 'db-verify', 'db-completion']) { @@ -140,6 +170,8 @@ test('uses the approved CNB mirror images and matching recipe versions', () => { ); assert.deepEqual(recipes, { 'clickhouse@24.8': { version: '24.8.14.39', image: 'docker.cnb.cool/znb/images/clickhouse-server:24.8.14.39', platforms: ['linux/amd64', 'linux/arm64'] }, + 'consul@2.0.2': { version: '2.0.2', image: 'docker.cnb.cool/znb/images/consul:2.0.2', platforms: ['linux/amd64', 'linux/arm64'] }, + 'elasticsearch@6.8': { version: '6.8', image: 'docker.cnb.cool/znb/images/elasticsearch:6.8', platforms: ['linux/amd64', 'linux/arm64'] }, 'etcd@3.7': { version: '3.7.0', image: 'docker.cnb.cool/znb/images/etcd:v3.7.0', platforms: ['linux/amd64', 'linux/arm64'] }, 'kafka@4.3': { version: '4.3.1', image: 'docker.cnb.cool/znb/images/kafka:4.3.1', platforms: ['linux/amd64', 'linux/arm64'] }, 'mariadb@10.11': { version: '10.11.11', image: 'docker.cnb.cool/znb/images/mariadb:10.11.11', platforms: ['linux/amd64', 'linux/arm64'] }, @@ -148,7 +180,7 @@ test('uses the approved CNB mirror images and matching recipe versions', () => { 'mysql@5.7': { version: '5.7.44', image: 'docker.cnb.cool/znb/images/mysql:5.7.44', platforms: ['linux/amd64', 'linux/arm64'] }, 'mysql@8.4': { version: '8.4.6', image: 'docker.cnb.cool/znb/images/mysql:8.4.6', platforms: ['linux/amd64', 'linux/arm64'] }, 'nacos@2.5': { version: '2.5.2', image: 'docker.cnb.cool/znb/images/nacos-server:v2.5.2', platforms: ['linux/amd64', 'linux/arm64'] }, - 'nacos@3.2': { version: '3.2.2', image: 'docker.cnb.cool/znb/images/nacos-server:v3.2.2', platforms: ['linux/amd64', 'linux/arm64'] }, + 'nacos@3.2': { version: '3.2.3', image: 'docker.cnb.cool/znb/images/nacos-server:v3.2.3', platforms: ['linux/amd64', 'linux/arm64'] }, 'postgresql@14.23': { version: '14.23', image: 'docker.cnb.cool/znb/images/postgres:14.23', platforms: ['linux/amd64', 'linux/arm64'] }, 'postgresql@17.4': { version: '17.4', image: 'docker.cnb.cool/znb/images/postgres:17.4', platforms: ['linux/amd64', 'linux/arm64'] }, 'pulsar@4.2': { version: '4.2.3', image: 'docker.cnb.cool/znb/images/pulsar:4.2.3', platforms: ['linux/amd64', 'linux/arm64'] }, @@ -181,8 +213,8 @@ test('configures r-nacos with its admin account and console port', () => { const recipe = discoverRecipes().find((item) => recipeSelector(item) === 'rnacos@0.8'); assert.equal(recipe.connection.username, 'admin'); assert.equal(recipe.connection.password, '123456'); - assert.equal(recipe.connection.grpcPort, 9849); - assert.equal(recipe.connection.consolePort, 10849); + assert.equal(recipe.connection.grpcPort, 11101); + assert.equal(recipe.connection.consolePort, 11102); const compose = readFileSync(join(recipe.directory, 'compose.yaml'), 'utf8'); assert.match(compose, /RNACOS_CONSOLE_PORT/); @@ -257,6 +289,46 @@ test('smoke commands use password and port overrides without invoking a shell', ); }); +test('generates DBX deep links from effective recipe connection values', () => { + const recipe = discoverRecipes().find((item) => recipeSelector(item) === 'postgresql@17.4'); + assert.ok(recipe); + + const link = dbxConnectionDeepLink(recipe, { DB_PORT: '15433', DB_PASSWORD: 'p@ss & word' }); + assert.ok(link); + const params = new URL(link).searchParams; + assert.equal(params.get('type'), 'postgres'); + assert.equal(params.get('name'), 'PostgreSQL 17.4 (local)'); + assert.equal(params.get('host'), '127.0.0.1'); + assert.equal(params.get('port'), '15433'); + assert.equal(params.get('user'), 'postgres'); + assert.equal(params.get('password'), 'p@ss & word'); + assert.equal(params.get('database'), 'dbx'); +}); + +test('prints a DBX deep link only for compatible connection types', () => { + const repoRoot = join(DEFAULT_RECIPES_ROOT, '..', '..'); + const postgres = spawnSync(process.execPath, ['scripts/database-env.mjs', 'info', 'postgresql', '17.4'], { + cwd: repoRoot, + encoding: 'utf8', + }); + assert.equal(postgres.status, 0, postgres.stderr); + assert.match(postgres.stdout, /DBX connection link: dbx:\/\/connection\/new\?type=postgres&/); + + const elasticsearch = spawnSync(process.execPath, ['scripts/database-env.mjs', 'info', 'elasticsearch', '6.8'], { + cwd: repoRoot, + encoding: 'utf8', + }); + assert.equal(elasticsearch.status, 0, elasticsearch.stderr); + assert.match(elasticsearch.stdout, /DBX connection link: dbx:\/\/connection\/new\?type=elasticsearch&/); + + const consul = spawnSync(process.execPath, ['scripts/database-env.mjs', 'info', 'consul', '2.0.2'], { + cwd: repoRoot, + encoding: 'utf8', + }); + assert.equal(consul.status, 0, consul.stderr); + assert.match(consul.stdout, /DBX connection link: unavailable \(DBX has no compatible Consul connection type\)/); +}); + test('MongoDB commands pass reserved-character passwords as a distinct argument', () => { const password = 'p@ss/word#with?reserved&characters'; for (const recipe of discoverRecipes().filter((item) => item.database === 'mongodb')) { @@ -355,10 +427,35 @@ test('allows recipes that explicitly declare no authentication', () => { assert.deepEqual(validateRecipe(recipe), []); }); -test('requires every recipe port to be one greater than its database default port', () => { +test('requires the primary connection port to match the allocated DB_PORT', () => { const [recipe] = discoverRecipes(fixture('postgresql', '17.4')); - recipe.connection.port = 5432; - assert.match(validateRecipe(recipe).join('; '), /connection.port must be defaultPort \+ 1/); + recipe.connection.port = 10301; + assert.match(validateRecipe(recipe).join('; '), /connection.port must match hostPorts.DB_PORT/); +}); + +test('requires Compose port defaults to match the declared host-port allocation', () => { + const [recipe] = discoverRecipes(fixture('postgresql', '17.4')); + const composePath = join(recipe.directory, 'compose.yaml'); + writeFileSync(composePath, readFileSync(composePath, 'utf8').replace('DB_PORT:-10300', 'DB_PORT:-10301')); + assert.match(validateRecipe(recipe).join('; '), /compose.yaml port defaults must match recipe hostPorts/); +}); + +test('allocates unique default host ports across checked-in recipes', () => { + const recipes = discoverRecipes(); + assert.deepEqual(validateHostPortAllocation(recipes), []); + const duplicate = structuredClone(recipes[0]); + duplicate.hostPorts = { DB_PORT: recipes[1].hostPorts.DB_PORT }; + assert.match(validateHostPortAllocation([recipes[1], duplicate]).join('; '), /host port .* is allocated to both/); +}); + +test('parses every declared default host-port mapping, including shared UDP and TCP DNS', () => { + const consul = discoverRecipes().find((recipe) => recipeSelector(recipe) === 'consul@2.0.2'); + assert.ok(consul); + const mappings = defaultHostPortMappings(readFileSync(join(consul.directory, 'compose.yaml'), 'utf8')); + assert.deepEqual(mappings.filter((mapping) => mapping.environment === 'CONSUL_DNS_PORT'), [ + { environment: 'CONSUL_DNS_PORT', hostPort: 10902, containerPort: 8600, protocol: 'tcp' }, + { environment: 'CONSUL_DNS_PORT', hostPort: 10902, containerPort: 8600, protocol: 'udp' }, + ]); }); test('reports missing smoke structures without throwing a TypeError', () => {