feat(database): add Nacos Pulsar Qdrant and ZooKeeper recipes
This commit is contained in:
parent
48dc2dee88
commit
21652bc797
|
|
@ -0,0 +1,31 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/etcd:v3.7.0
|
||||
container_name: dbx-etcd-3.7
|
||||
command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name=s1
|
||||
- --data-dir=/etcd-data
|
||||
- --listen-client-urls=http://0.0.0.0:2379
|
||||
- --advertise-client-urls=http://0.0.0.0:2379
|
||||
- --listen-peer-urls=http://0.0.0.0:2380
|
||||
- --initial-advertise-peer-urls=http://0.0.0.0:2380
|
||||
- --initial-cluster=s1=http://0.0.0.0:2380
|
||||
- --initial-cluster-token=tkn
|
||||
- --initial-cluster-state=new
|
||||
- --log-level=info
|
||||
- --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"
|
||||
volumes:
|
||||
- data:/etcd-data
|
||||
healthcheck:
|
||||
test: ["CMD", "/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "endpoint", "health"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# etcd 3.7 initialization
|
||||
|
||||
After the server is healthy, the recipe runner creates `root` with password `123456` (or `DB_PASSWORD`), grants it the `root` role, and enables etcd authentication. The named volume keeps the initialized authentication state.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"database": "etcd",
|
||||
"name": "etcd",
|
||||
"version": "3.7.0",
|
||||
"displayVersion": "3.7",
|
||||
"image": "docker.cnb.cool/znb/images/etcd:v3.7.0",
|
||||
"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 },
|
||||
"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": [
|
||||
{ "name": "create root user", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "user", "add", "root:${DB_PASSWORD}"], "expect": "User root created" },
|
||||
{ "name": "create root role", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "role", "add", "root"], "expect": "Role root created" },
|
||||
{ "name": "grant root role", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "user", "grant-role", "root", "root"], "expect": "Role root is granted to user root" },
|
||||
{ "name": "enable authentication", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "auth", "enable"], "expect": "Authentication Enabled" }
|
||||
]
|
||||
},
|
||||
"shell": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "--user", "root:${DB_PASSWORD}"],
|
||||
"smoke": { "steps": [{ "name": "write authenticated smoke key", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "--user", "root:${DB_PASSWORD}", "put", "dbx:smoke", "DBX smoke"], "expect": "OK" }, { "name": "read authenticated smoke key", "command": ["/usr/local/bin/etcdctl", "--endpoints=http://127.0.0.1:2379", "--user", "root:${DB_PASSWORD}", "get", "dbx:smoke"], "expect": "DBX smoke" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/kafka:4.3.1
|
||||
container_name: dbx-kafka-4.3
|
||||
ports:
|
||||
- "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-9093}: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_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
|
||||
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@database:9094
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
|
||||
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
|
||||
KAFKA_NUM_PARTITIONS: 3
|
||||
volumes:
|
||||
- data:/var/lib/kafka/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server database:9095 >/dev/null"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 20s
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Kafka 4.3 smoke data
|
||||
|
||||
Kafka runs in a single-node KRaft configuration. The `verify` command creates the `dbx-smoke` topic, publishes `DBX smoke`, and consumes it through the internal broker listener.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"database": "kafka",
|
||||
"name": "Kafka",
|
||||
"version": "4.3.1",
|
||||
"displayVersion": "4.3",
|
||||
"image": "docker.cnb.cool/znb/images/kafka:4.3.1",
|
||||
"platforms": ["linux/amd64", "linux/arm64"],
|
||||
"service": "database",
|
||||
"defaultPort": 9092,
|
||||
"connection": { "host": "127.0.0.1", "port": 9093, "authentication": "none", "database": "dbx", "internalPort": 9095 },
|
||||
"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" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/nacos-server:v2.5.2
|
||||
container_name: dbx-nacos-2.5
|
||||
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"
|
||||
environment:
|
||||
MODE: standalone
|
||||
PREFER_HOST_MODE: hostname
|
||||
NACOS_AUTH_SYSTEM_TYPE: nacos
|
||||
NACOS_AUTH_ENABLE: "true"
|
||||
NACOS_AUTH_TOKEN: "bXlDdXN0b21TZWNyZXRLZXlGb3JOYWNvc1YzMTBNb3JlVGhhbjMyQ2hhcnM="
|
||||
NACOS_AUTH_IDENTITY_KEY: dbx_nacos_identity
|
||||
NACOS_AUTH_IDENTITY_VALUE: dbx_nacos_identity_value
|
||||
JVM_XMS: 256m
|
||||
JVM_XMX: 256m
|
||||
JVM_XMN: 128m
|
||||
volumes:
|
||||
- data:/home/nacos/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl --fail --silent http://localhost:8848/nacos/v1/console/health/readiness | grep -q OK"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 60
|
||||
start_period: 20s
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Nacos 2.5 initialization
|
||||
|
||||
After the Nacos service becomes healthy, the database environment command synchronously sets the first-run `nacos` administrator password to `123456` (or `DB_PASSWORD`) through the authentication API. Later starts verify those credentials without overwriting the account. The named data volume preserves the initialized account.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"database": "nacos",
|
||||
"name": "Nacos",
|
||||
"version": "2.5.2",
|
||||
"displayVersion": "2.5",
|
||||
"image": "docker.cnb.cool/znb/images/nacos-server:v2.5.2",
|
||||
"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" },
|
||||
"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" }]
|
||||
},
|
||||
"shell": ["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}"],
|
||||
"smoke": { "steps": [{ "name": "log in with initialized administrator credentials", "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" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/nacos-server:v3.2.2
|
||||
container_name: dbx-nacos-3.2
|
||||
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"
|
||||
environment:
|
||||
MODE: standalone
|
||||
PREFER_HOST_MODE: hostname
|
||||
NACOS_AUTH_SYSTEM_TYPE: nacos
|
||||
NACOS_AUTH_ENABLE: "true"
|
||||
NACOS_AUTH_ADMIN_ENABLE: "true"
|
||||
NACOS_AUTH_CONSOLE_ENABLE: "true"
|
||||
NACOS_AUTH_TOKEN: "bXlDdXN0b21TZWNyZXRLZXlGb3JOYWNvc1YzMTBNb3JlVGhhbjMyQ2hhcnM="
|
||||
NACOS_AUTH_IDENTITY_KEY: dbx_nacos_identity
|
||||
NACOS_AUTH_IDENTITY_VALUE: dbx_nacos_identity_value
|
||||
JVM_XMS: 256m
|
||||
JVM_XMX: 256m
|
||||
JVM_XMN: 128m
|
||||
volumes:
|
||||
- data:/home/nacos/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "test \"$$(curl --silent --output /dev/null --write-out '%{http_code}' http://localhost:8848/nacos/v3/auth/user/admin)\" = 405"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 60
|
||||
start_period: 20s
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Nacos 3.2 initialization
|
||||
|
||||
After the Nacos service becomes healthy, the database environment command synchronously sets the first-run `nacos` administrator password to `123456` (or `DB_PASSWORD`) through the authentication API. Later starts verify those credentials without overwriting the account. The named data volume preserves the initialized account.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"database": "nacos",
|
||||
"name": "Nacos",
|
||||
"version": "3.2.2",
|
||||
"displayVersion": "3.2",
|
||||
"image": "docker.cnb.cool/znb/images/nacos-server:v3.2.2",
|
||||
"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 },
|
||||
"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" }]
|
||||
},
|
||||
"shell": ["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}"],
|
||||
"smoke": { "steps": [{ "name": "log in with initialized administrator credentials", "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" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/pulsar:4.2.3
|
||||
container_name: dbx-pulsar-4.2
|
||||
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"
|
||||
volumes:
|
||||
- data:/pulsar/data
|
||||
- conf:/pulsar/conf
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bin/pulsar-admin brokers healthcheck"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 60
|
||||
start_period: 30s
|
||||
volumes:
|
||||
data:
|
||||
conf:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Pulsar 4.2 smoke data
|
||||
|
||||
Pulsar standalone creates the `public/default` namespace automatically. The `verify` command publishes `DBX smoke` to `persistent://public/default/dbx-smoke`.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"database": "pulsar",
|
||||
"name": "Pulsar",
|
||||
"version": "4.2.3",
|
||||
"displayVersion": "4.2",
|
||||
"image": "docker.cnb.cool/znb/images/pulsar:4.2.3",
|
||||
"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 },
|
||||
"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" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/qdrant:v1.8.3
|
||||
container_name: dbx-qdrant-1.8
|
||||
ports:
|
||||
- "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-6334}:6333"
|
||||
- "${DB_BIND_ADDRESS:-127.0.0.1}:${QDRANT_GRPC_PORT:-6335}:6334"
|
||||
environment:
|
||||
QDRANT__SERVICE__API_KEY: "${DB_PASSWORD:-123456}"
|
||||
volumes:
|
||||
- data:/qdrant/storage
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -c '</dev/tcp/127.0.0.1/6333'"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Qdrant 1.8 smoke data
|
||||
|
||||
Qdrant stores its vectors in the named `data` volume. The `verify` command sends an authenticated HTTP request using the configured admin API key (`123456` by default).
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"database": "qdrant",
|
||||
"name": "Qdrant",
|
||||
"version": "1.8.3",
|
||||
"displayVersion": "1.8",
|
||||
"image": "docker.cnb.cool/znb/images/qdrant:v1.8.3",
|
||||
"platforms": ["linux/amd64", "linux/arm64"],
|
||||
"service": "database",
|
||||
"defaultPort": 6333,
|
||||
"connection": { "host": "127.0.0.1", "port": 6334, "username": "", "password": "123456", "database": "dbx", "grpcPort": 6335 },
|
||||
"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" }] }
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
services:
|
||||
database:
|
||||
image: docker.cnb.cool/znb/images/zookeeper:3.9.5
|
||||
container_name: dbx-zookeeper-3.9
|
||||
ports:
|
||||
- "${DB_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-2182}:2181"
|
||||
volumes:
|
||||
- data:/data
|
||||
- datalog:/datalog
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "zkServer.sh status | grep -q 'Mode: standalone'"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 15s
|
||||
volumes:
|
||||
data:
|
||||
datalog:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# ZooKeeper 3.9 initialization
|
||||
|
||||
The recipe runner creates `/dbx` with a Digest ACL for `root` and password `123456` (or `DB_PASSWORD`). ZooKeeper does not have a global username/password login switch; clients must authenticate with `addauth digest root:<password>` before accessing this protected node.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"database": "zookeeper",
|
||||
"name": "ZooKeeper",
|
||||
"version": "3.9.5",
|
||||
"displayVersion": "3.9",
|
||||
"image": "docker.cnb.cool/znb/images/zookeeper:3.9.5",
|
||||
"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" },
|
||||
"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" }]
|
||||
},
|
||||
"shell": ["zkCli.sh", "-server", "127.0.0.1:2181"],
|
||||
"smoke": { "steps": [{ "name": "reject anonymous access to protected node", "command": ["sh", "-ec", "printf 'get /dbx\\nquit\\n' | zkCli.sh -server 127.0.0.1:2181 || true"], "expect": "Insufficient permission" }, { "name": "read protected node with Digest credentials", "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" }] }
|
||||
}
|
||||
|
|
@ -12,18 +12,47 @@ description: 为 DBX 开发与验证启动可重复创建的本地数据库实
|
|||
| 数据库 | 版本目录 | Docker Hub 官方镜像 | 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` |
|
||||
| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `2182` | `dbx-zookeeper-3.9` |
|
||||
|
||||
所有网络数据库的默认宿主端口都比标准端口大 `1`,并且该约定由配方校验器强制检查。默认密码统一为 `123456`,默认数据库名为 `dbx`。Redis 不支持命名数据库,因此使用 DB 0,并以 `dbx:` 作为冒烟键前缀。Compose 默认使用 CNB 镜像列;Docker Hub 列保留官方上游地址,便于核对来源或直接拉取。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`。
|
||||
|
||||
```bash
|
||||
DB_PORT=8818 NACOS_CONSOLE_PORT=8010 NACOS_GRPC_PORT=9818 NACOS_RAFT_PORT=9819 make db DB=nacos@3.2
|
||||
```
|
||||
|
||||
etcd 会创建密码为 `123456` 的 `root` 用户、授予 root 角色并开启认证。默认客户端和 peer 主机端口为 `2380`、`2381`。如需使用上游 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 命令中的端口,可执行:
|
||||
|
||||
```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 模型保护单个节点,并不提供全局登录开关。
|
||||
|
||||
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 所提供的兼容性覆盖更小。
|
||||
|
||||
同一数据库的不同版本默认会共用宿主机端口。通常一次启动一个版本;需要并行对比时,为其中一个版本指定其他端口:
|
||||
|
|
|
|||
|
|
@ -12,18 +12,47 @@ The Database Test Lab provides small, pinned Docker Compose environments for tes
|
|||
| Database | Version directory | Official Docker Hub 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` |
|
||||
| ZooKeeper | `zookeeper/3.9` | `zookeeper:3.9.5` | `docker.cnb.cool/znb/images/zookeeper:3.9.5` | `2182` | `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.
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
DB_PORT=8818 NACOS_CONSOLE_PORT=8010 NACOS_GRPC_PORT=9818 NACOS_RAFT_PORT=9819 make db DB=nacos@3.2
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```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:
|
||||
|
||||
```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.
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@ export function recipeSelector(recipe) {
|
|||
return `${recipe.database}@${recipe.displayVersion}`;
|
||||
}
|
||||
|
||||
export function formatTable(headers, rows) {
|
||||
const widths = headers.map((header, index) => Math.max(
|
||||
header.length,
|
||||
...rows.map((row) => String(row[index] ?? '').length),
|
||||
));
|
||||
const renderRow = (row) => row.map((value, index) => {
|
||||
const text = String(value ?? '');
|
||||
return index === row.length - 1 ? text : text.padEnd(widths[index]);
|
||||
}).join(' ');
|
||||
|
||||
return [headers, ...rows].map(renderRow).join('\n');
|
||||
}
|
||||
|
||||
export function discoverMakeTargets(makefilePath = DEFAULT_MAKEFILE_PATH) {
|
||||
if (!existsSync(makefilePath)) return [];
|
||||
const source = readFileSync(makefilePath, 'utf8').replace(/\\\r?\n/g, ' ');
|
||||
|
|
@ -118,7 +131,9 @@ export function validateRecipe(recipe) {
|
|||
} else if (recipe.connection?.port !== recipe.defaultPort + 1) {
|
||||
errors.push('connection.port must be defaultPort + 1');
|
||||
}
|
||||
if (recipe.connection?.password !== DEFAULT_PASSWORD) errors.push(`connection.password must be ${DEFAULT_PASSWORD}`);
|
||||
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}`);
|
||||
if (recipe.database === 'redis' ? recipe.connection?.database !== 0 : recipe.connection?.database !== DEFAULT_DATABASE) {
|
||||
errors.push(`connection.database must be ${recipe.database === 'redis' ? '0 for Redis' : DEFAULT_DATABASE}`);
|
||||
}
|
||||
|
|
@ -159,6 +174,17 @@ export function validateRecipe(recipe) {
|
|||
if (typeof step.expect !== 'string' || step.expect.length === 0) errors.push('every smoke step needs an expected output');
|
||||
}
|
||||
if (!Array.isArray(recipe.shell) || recipe.shell.length === 0) errors.push('shell must be a non-empty command array');
|
||||
if (recipe.bootstrap !== undefined) {
|
||||
if (!recipe.bootstrap || typeof recipe.bootstrap !== 'object') errors.push('bootstrap must be an object');
|
||||
if (!recipe.bootstrap?.check || !Array.isArray(recipe.bootstrap.check.command) || recipe.bootstrap.check.command.length === 0 || typeof recipe.bootstrap.check.expect !== 'string' || recipe.bootstrap.check.expect.length === 0) {
|
||||
errors.push('bootstrap.check needs a command and expected output');
|
||||
}
|
||||
if (!Array.isArray(recipe.bootstrap?.steps) || recipe.bootstrap.steps.length === 0) errors.push('bootstrap.steps must not be empty');
|
||||
for (const step of recipe.bootstrap?.steps ?? []) {
|
||||
if (!step.name || !Array.isArray(step.command) || step.command.length === 0) errors.push('every bootstrap step needs a name and command');
|
||||
if (typeof step.expect !== 'string' || step.expect.length === 0) errors.push('every bootstrap step needs an expected output');
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +275,12 @@ function runCompose(recipe, args, options) {
|
|||
return run('docker', composeArgs(recipe, ...args), options);
|
||||
}
|
||||
|
||||
function tryRunCompose(recipe, args) {
|
||||
const result = spawnSync('docker', composeArgs(recipe, ...args), { cwd: REPO_ROOT, env: process.env, encoding: 'utf8', stdio: 'pipe' });
|
||||
if (result.error) throw result.error;
|
||||
return { ok: result.status === 0, output: `${result.stdout ?? ''}${result.stderr ?? ''}` };
|
||||
}
|
||||
|
||||
export function expandSmokeCommand(command, recipe, environment = process.env) {
|
||||
const values = {
|
||||
DB_PASSWORD: environment.DB_PASSWORD || recipe.connection.password,
|
||||
|
|
@ -257,6 +289,21 @@ export function expandSmokeCommand(command, recipe, environment = process.env) {
|
|||
return command.map((value) => value.replace(/\$\{(DB_PASSWORD|DB_PORT)\}/g, (_, name) => values[name]));
|
||||
}
|
||||
|
||||
function ensureBootstrap(recipe) {
|
||||
if (!recipe.bootstrap) return;
|
||||
// Keep one-shot setup synchronous after Compose health checks so --wait cannot return before credentials exist.
|
||||
const checkCommand = expandSmokeCommand(recipe.bootstrap.check.command, recipe);
|
||||
const check = tryRunCompose(recipe, ['exec', '-T', recipe.service, ...checkCommand]);
|
||||
if (check.ok && check.output.includes(recipe.bootstrap.check.expect)) return;
|
||||
|
||||
for (const step of recipe.bootstrap.steps) {
|
||||
const command = expandSmokeCommand(step.command, recipe);
|
||||
const output = runCompose(recipe, ['exec', '-T', recipe.service, ...command], { capture: true });
|
||||
if (!output.includes(step.expect)) throw new Error(`Bootstrap check did not contain expected text: ${step.expect}\n${output}`);
|
||||
console.log(`OK ${step.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printConnection(recipe) {
|
||||
const connection = { ...recipe.connection };
|
||||
if (process.env.DB_PORT) connection.port = Number(process.env.DB_PORT);
|
||||
|
|
@ -321,8 +368,10 @@ export function main(argv = process.argv.slice(2)) {
|
|||
const recipes = discoverRecipes();
|
||||
|
||||
if (command === 'list') {
|
||||
console.log('DATABASE\tVERSION\tIMAGE\tPLATFORMS');
|
||||
for (const recipe of recipes) console.log(`${recipe.database}\t${recipe.displayVersion}\t${recipe.image}\t${recipe.platforms.join(',')}`);
|
||||
console.log(formatTable(
|
||||
['DATABASE', 'VERSION', 'IMAGE', 'PLATFORMS'],
|
||||
recipes.map((recipe) => [recipe.database, recipe.displayVersion, recipe.image, recipe.platforms.join(',')]),
|
||||
));
|
||||
return;
|
||||
}
|
||||
if (command === 'quick-start') return printQuickStart(recipes);
|
||||
|
|
@ -349,6 +398,7 @@ export function main(argv = process.argv.slice(2)) {
|
|||
case 'start':
|
||||
case 'up':
|
||||
runCompose(recipe, ['up', '-d', '--wait']);
|
||||
ensureBootstrap(recipe);
|
||||
printConnection(recipe);
|
||||
break;
|
||||
case 'status':
|
||||
|
|
@ -362,6 +412,7 @@ export function main(argv = process.argv.slice(2)) {
|
|||
break;
|
||||
case 'verify':
|
||||
runCompose(recipe, ['up', '-d', '--wait']);
|
||||
ensureBootstrap(recipe);
|
||||
for (const step of recipe.smoke.steps) {
|
||||
const command = expandSmokeCommand(step.command, recipe);
|
||||
const output = runCompose(recipe, ['exec', '-T', recipe.service, ...command], { capture: true });
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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, parseDatabaseSelection, platformForArchitecture, recipeSelector, resolveRecipe, serviceHasNamedVolume, validateRecipe, validateRenderedCompose } from './database-env.mjs';
|
||||
import { DEFAULT_RECIPES_ROOT, allPortMappingsDefaultToLoopback, architectureWarning, assertResetConfirmed, discoverMakeTargets, discoverRecipes, expandSmokeCommand, expectedContainerName, formatTable, parseDatabaseSelection, platformForArchitecture, recipeSelector, resolveRecipe, serviceHasNamedVolume, validateRecipe, validateRenderedCompose } from './database-env.mjs';
|
||||
|
||||
const bashAvailable = spawnSync('bash', ['--version'], { stdio: 'ignore' }).status === 0;
|
||||
const zshAvailable = spawnSync('zsh', ['--version'], { stdio: 'ignore' }).status === 0;
|
||||
|
|
@ -44,6 +44,21 @@ test('formats a recipe as a copyable compact selector', () => {
|
|||
assert.equal(recipeSelector({ database: 'mysql', displayVersion: '8.4' }), 'mysql@8.4');
|
||||
});
|
||||
|
||||
test('formats lists as left-aligned tables with dynamic column widths', () => {
|
||||
assert.equal(
|
||||
formatTable(
|
||||
['DATABASE', 'VERSION', 'IMAGE'],
|
||||
[
|
||||
['etcd', '3.7', 'etcd:v3.7.0'],
|
||||
['clickhouse', '24.8', 'clickhouse-server:24.8.14.39'],
|
||||
],
|
||||
),
|
||||
'DATABASE VERSION IMAGE\n'
|
||||
+ 'etcd 3.7 etcd:v3.7.0\n'
|
||||
+ 'clickhouse 24.8 clickhouse-server:24.8.14.39',
|
||||
);
|
||||
});
|
||||
|
||||
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']) {
|
||||
|
|
@ -125,19 +140,78 @@ 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'] },
|
||||
'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'] },
|
||||
'mongodb@5.0': { version: '5.0.5', image: 'docker.cnb.cool/znb/images/mongo:5.0.5', platforms: ['linux/amd64', 'linux/arm64'] },
|
||||
'mongodb@8.2': { version: '8.2.3', image: 'docker.cnb.cool/znb/images/mongo:8.2.3-noble', platforms: ['linux/amd64', 'linux/arm64'] },
|
||||
'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'] },
|
||||
'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'] },
|
||||
'qdrant@1.8': { version: '1.8.3', image: 'docker.cnb.cool/znb/images/qdrant:v1.8.3', platforms: ['linux/amd64', 'linux/arm64'] },
|
||||
'redis@3.0.7': { version: '3.0.7', image: 'docker.cnb.cool/znb/images/redis:3.0.7-alpine', platforms: ['linux/amd64'] },
|
||||
'redis@7.4': { version: '7.4.9', image: 'docker.cnb.cool/znb/images/redis:7.4.9-alpine', platforms: ['linux/amd64', 'linux/arm64'] },
|
||||
'zookeeper@3.9': { version: '3.9.5', image: 'docker.cnb.cool/znb/images/zookeeper:3.9.5', platforms: ['linux/amd64', 'linux/arm64'] },
|
||||
});
|
||||
assert.equal(discoverRecipes().find((recipe) => recipeSelector(recipe) === 'redis@3.0.7').connection.username, undefined);
|
||||
});
|
||||
|
||||
test('initializes both Nacos versions with the shared administrator credentials', () => {
|
||||
for (const recipe of discoverRecipes().filter((item) => item.database === 'nacos')) {
|
||||
assert.equal(recipe.connection.username, 'nacos');
|
||||
assert.equal(recipe.connection.password, '123456');
|
||||
assert.equal(recipe.connection.namespace, 'public');
|
||||
assert.equal(recipe.bootstrap.check.expect, 'accessToken');
|
||||
assert.equal(recipe.bootstrap.steps.length, 1);
|
||||
assert.match(recipe.bootstrap.steps[0].command.join(' '), /administrator initialized/);
|
||||
|
||||
const compose = readFileSync(join(recipe.directory, 'compose.yaml'), 'utf8');
|
||||
assert.doesNotMatch(compose, /^ initialize:/m);
|
||||
assert.doesNotMatch(compose, /tail -f \/dev\/null/);
|
||||
assert.deepEqual(validateRecipe(recipe), []);
|
||||
}
|
||||
});
|
||||
|
||||
test('configures Qdrant with the shared API key', () => {
|
||||
const recipe = discoverRecipes().find((item) => recipeSelector(item) === 'qdrant@1.8');
|
||||
assert.equal(recipe.connection.username, '');
|
||||
assert.equal(recipe.connection.password, '123456');
|
||||
assert.equal(recipe.connection.apiKey, undefined);
|
||||
});
|
||||
|
||||
test('bootstraps etcd with the shared root credentials', () => {
|
||||
const recipe = discoverRecipes().find((item) => recipeSelector(item) === 'etcd@3.7');
|
||||
assert.equal(recipe.connection.username, 'root');
|
||||
assert.equal(recipe.connection.password, '123456');
|
||||
assert.ok(recipe.bootstrap);
|
||||
});
|
||||
|
||||
test('bootstraps ZooKeeper with the shared Digest credentials', () => {
|
||||
const recipe = discoverRecipes().find((item) => recipeSelector(item) === 'zookeeper@3.9');
|
||||
assert.equal(recipe.connection.username, 'root');
|
||||
assert.equal(recipe.connection.password, '123456');
|
||||
assert.equal(recipe.connection.authScheme, 'digest');
|
||||
});
|
||||
|
||||
test('keeps existing authenticated bootstrap recipes valid', () => {
|
||||
for (const selector of ['etcd@3.7', 'zookeeper@3.9']) {
|
||||
const recipe = discoverRecipes().find((item) => recipeSelector(item) === selector);
|
||||
assert.deepEqual(validateRecipe(recipe), []);
|
||||
}
|
||||
});
|
||||
|
||||
test('marks the unauthenticated Kafka and Pulsar development recipes explicitly', () => {
|
||||
for (const selector of ['kafka@4.3', 'pulsar@4.2']) {
|
||||
const recipe = discoverRecipes().find((item) => recipeSelector(item) === selector);
|
||||
assert.equal(recipe.connection.authentication, 'none');
|
||||
assert.equal(recipe.connection.password, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test('start without DB prints every copyable database command', () => {
|
||||
const repoRoot = join(DEFAULT_RECIPES_ROOT, '..', '..');
|
||||
const result = spawnSync(process.execPath, ['scripts/database-env.mjs', 'start'], {
|
||||
|
|
@ -260,6 +334,13 @@ test('requires the shared default password and database name', () => {
|
|||
assert.match(validateRecipe(recipe).join('; '), /connection.database must be dbx/);
|
||||
});
|
||||
|
||||
test('allows recipes that explicitly declare no authentication', () => {
|
||||
const [recipe] = discoverRecipes(fixture('postgresql', '17.4'));
|
||||
recipe.connection.authentication = 'none';
|
||||
delete recipe.connection.password;
|
||||
assert.deepEqual(validateRecipe(recipe), []);
|
||||
});
|
||||
|
||||
test('requires every recipe port to be one greater than its database default port', () => {
|
||||
const [recipe] = discoverRecipes(fixture('postgresql', '17.4'));
|
||||
recipe.connection.port = 5432;
|
||||
|
|
|
|||
Loading…
Reference in New Issue