From 2f5d2721ac5eeb1a082bc18d09d8e17394546136 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 16 Oct 2026 20:43:48 +0300 Subject: [PATCH] feat(dx): querya-testdb- images with the seeds baked in (#1487, part 3b) Dockerfile.testdb per database (postgres, mysql 8.0 native password, mongo, clickhouse, redis), built and smoke-tested on pull requests, pushed with the release version and latest after a Release. --- .github/workflows/testdb-images.yml | 145 ++++++++++++++++++++++++++++ docker/clickhouse/Dockerfile.testdb | 9 ++ docker/mongo/Dockerfile.testdb | 8 ++ docker/mysql/Dockerfile.testdb | 11 +++ docker/postgres/Dockerfile.testdb | 9 ++ docker/redis/Dockerfile.testdb | 10 ++ docker/redis/testdb-entrypoint.sh | 12 +++ 7 files changed, 204 insertions(+) create mode 100644 .github/workflows/testdb-images.yml create mode 100644 docker/clickhouse/Dockerfile.testdb create mode 100644 docker/mongo/Dockerfile.testdb create mode 100644 docker/mysql/Dockerfile.testdb create mode 100644 docker/postgres/Dockerfile.testdb create mode 100644 docker/redis/Dockerfile.testdb create mode 100755 docker/redis/testdb-entrypoint.sh diff --git a/.github/workflows/testdb-images.yml b/.github/workflows/testdb-images.yml new file mode 100644 index 00000000..c99fdd1b --- /dev/null +++ b/.github/workflows/testdb-images.yml @@ -0,0 +1,145 @@ +name: Test database images + +# Publishes ghcr.io/queryahub/querya-testdb-: (and :latest): the +# docker/ seeds baked into the official database images, for extension +# authors (no bind mounts). A database is built when docker//Dockerfile.testdb +# exists, so adding one appears here without editing this file. +# +# Pull requests only build and smoke-test. `workflow_run`, not `release`: the +# release is created with GITHUB_TOKEN, and events made by that token do not +# start other workflows. +on: + pull_request: + paths: + - docker/** + - .github/workflows/testdb-images.yml + workflow_run: + workflows: [Release] + types: [completed] + workflow_dispatch: + inputs: + version: + description: Querya Desktop release the images are tagged with (e.g. 0.4.19) + required: true + latest: + description: Also tag the images as latest + type: boolean + default: true + +permissions: + contents: read + packages: write + +jobs: + plan: + name: Plan + runs-on: ubuntu-latest + if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' + outputs: + databases: ${{ steps.dbs.outputs.databases }} + push: ${{ steps.v.outputs.push }} + version: ${{ steps.v.outputs.version }} + latest: ${{ steps.v.outputs.latest }} + steps: + - uses: actions/checkout@v4 + + - name: Databases with a Dockerfile.testdb + id: dbs + shell: bash + run: | + set -euo pipefail + list=$(for f in docker/*/Dockerfile.testdb; do basename "$(dirname "$f")"; done | jq -R . | jq -sc .) + echo "databases=$list" >> "$GITHUB_OUTPUT" + + - name: Resolve the version + id: v + shell: bash + run: | + set -euo pipefail + case "${{ github.event_name }}" in + workflow_dispatch) VERSION="${{ inputs.version }}"; LATEST="${{ inputs.latest }}" ;; + workflow_run) VERSION="${{ github.event.workflow_run.head_branch }}"; LATEST=true ;; + *) VERSION=""; LATEST=false ;; + esac + VERSION="${VERSION#v}" + if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "push=true" >> "$GITHUB_OUTPUT" + else + [ -z "$VERSION" ] || echo "::notice::'$VERSION' is not a release tag, images are only built" + echo "push=false" >> "$GITHUB_OUTPUT" + VERSION=dev + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "latest=$LATEST" >> "$GITHUB_OUTPUT" + + image: + name: querya-testdb-${{ matrix.db }} + needs: plan + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + db: ${{ fromJson(needs.plan.outputs.databases) }} + env: + IMAGE: ghcr.io/queryahub/querya-testdb-${{ matrix.db }} + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + if: needs.plan.outputs.push == 'true' + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build (loaded locally for the smoke test) + uses: docker/build-push-action@v6 + with: + context: docker/${{ matrix.db }} + file: docker/${{ matrix.db }}/Dockerfile.testdb + load: true + tags: querya-testdb:test + cache-from: type=gha,scope=testdb-${{ matrix.db }} + cache-to: type=gha,mode=max,scope=testdb-${{ matrix.db }} + + - name: Smoke test (seeds are in, no bind mounts) + shell: bash + run: | + set -euo pipefail + docker run -d --name testdb querya-testdb:test + for i in $(seq 1 60); do + s=$(docker inspect -f '{{.State.Health.Status}}' testdb) + [ "$s" = healthy ] && break + sleep 3 + done + [ "$s" = healthy ] || { docker logs testdb; echo "::error::not healthy: $s"; exit 1; } + + case "${{ matrix.db }}" in + postgres) out=$(docker exec testdb psql -U querya -d querya -Atc "SELECT count(*) FROM shop.customers") ;; + mysql) out=$(docker exec testdb mysql -uquerya -pquerya querya -Nse "SELECT count(*) FROM customers") ;; + mongo) out=$(docker exec testdb mongosh --quiet -u querya -p querya --authenticationDatabase admin querya --eval "db.users.countDocuments()") ;; + clickhouse) out=$(docker exec testdb clickhouse-client --user querya --password querya --query "SELECT count() FROM querya.customers") ;; + redis) out=$(docker exec testdb redis-cli GET querya:seed:marker) ;; + *) echo "::error::no smoke test for ${{ matrix.db }}"; exit 1 ;; + esac + echo "seed check: $out" + # A seeded table or key is a positive number; an empty database is 0 or empty. + [[ "$out" =~ ^[1-9][0-9]*$ ]] || { docker logs testdb; echo "::error::seed missing"; exit 1; } + + - name: Push + if: needs.plan.outputs.push == 'true' + uses: docker/build-push-action@v6 + with: + context: docker/${{ matrix.db }} + file: docker/${{ matrix.db }}/Dockerfile.testdb + push: true + cache-from: type=gha,scope=testdb-${{ matrix.db }} + tags: | + ${{ env.IMAGE }}:${{ needs.plan.outputs.version }} + ${{ needs.plan.outputs.latest == 'true' && format('{0}:latest', env.IMAGE) || '' }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.description=Querya test database (${{ matrix.db }}) with the Querya seeds baked in + org.opencontainers.image.licenses=MIT diff --git a/docker/clickhouse/Dockerfile.testdb b/docker/clickhouse/Dockerfile.testdb new file mode 100644 index 00000000..d2e55514 --- /dev/null +++ b/docker/clickhouse/Dockerfile.testdb @@ -0,0 +1,9 @@ +# querya-testdb-clickhouse: ClickHouse with the Querya seeds, no bind mounts. +FROM clickhouse/clickhouse-server:24.8 +ENV CLICKHOUSE_DB=querya \ + CLICKHOUSE_USER=querya \ + CLICKHOUSE_PASSWORD=querya \ + CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 +COPY init/ /docker-entrypoint-initdb.d/ +HEALTHCHECK --interval=5s --timeout=5s --retries=20 --start-period=40s \ + CMD clickhouse-client --user querya --password querya --query 'SELECT 1' diff --git a/docker/mongo/Dockerfile.testdb b/docker/mongo/Dockerfile.testdb new file mode 100644 index 00000000..4abfa9ed --- /dev/null +++ b/docker/mongo/Dockerfile.testdb @@ -0,0 +1,8 @@ +# querya-testdb-mongo: MongoDB with the Querya seeds, no bind mounts. +# Auth source is admin; the init script runs on the first start. +FROM mongo:7 +ENV MONGO_INITDB_ROOT_USERNAME=querya \ + MONGO_INITDB_ROOT_PASSWORD=querya +COPY init/ /docker-entrypoint-initdb.d/ +HEALTHCHECK --interval=5s --timeout=5s --retries=20 --start-period=30s \ + CMD mongosh --quiet -u querya -p querya --authenticationDatabase admin --eval "db.adminCommand('ping').ok" diff --git a/docker/mysql/Dockerfile.testdb b/docker/mysql/Dockerfile.testdb new file mode 100644 index 00000000..44a0f7c3 --- /dev/null +++ b/docker/mysql/Dockerfile.testdb @@ -0,0 +1,11 @@ +# querya-testdb-mysql: MySQL 8.0 with mysql_native_password (what CI runs), so +# a client without TLS can connect. The init scripts run on the first start. +FROM mysql:8.0 +ENV MYSQL_ROOT_PASSWORD=querya \ + MYSQL_DATABASE=querya \ + MYSQL_USER=querya \ + MYSQL_PASSWORD=querya +COPY init/ /docker-entrypoint-initdb.d/ +CMD ["--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"] +HEALTHCHECK --interval=5s --timeout=5s --retries=20 --start-period=30s \ + CMD mysqladmin ping -h 127.0.0.1 -uquerya -pquerya diff --git a/docker/postgres/Dockerfile.testdb b/docker/postgres/Dockerfile.testdb new file mode 100644 index 00000000..91199edc --- /dev/null +++ b/docker/postgres/Dockerfile.testdb @@ -0,0 +1,9 @@ +# querya-testdb-postgres: PostgreSQL with the Querya seeds, no bind mounts. +# The init scripts run on the first start of an empty data directory. +FROM postgres:16-alpine +ENV POSTGRES_USER=querya \ + POSTGRES_PASSWORD=querya \ + POSTGRES_DB=querya +COPY init/ /docker-entrypoint-initdb.d/ +HEALTHCHECK --interval=5s --timeout=5s --retries=12 \ + CMD pg_isready -U querya -d querya diff --git a/docker/redis/Dockerfile.testdb b/docker/redis/Dockerfile.testdb new file mode 100644 index 00000000..8ea2ad49 --- /dev/null +++ b/docker/redis/Dockerfile.testdb @@ -0,0 +1,10 @@ +# querya-testdb-redis: Redis seeded with the Querya keys on every start. +# Redis has no init directory, so the entrypoint starts the server, runs +# seed.sh against it and then keeps serving. +FROM redis:7-alpine +COPY seed.sh /seed.sh +COPY testdb-entrypoint.sh /testdb-entrypoint.sh +RUN chmod +x /testdb-entrypoint.sh +ENTRYPOINT ["/testdb-entrypoint.sh"] +HEALTHCHECK --interval=5s --timeout=3s --retries=12 \ + CMD redis-cli ping | grep -q PONG diff --git a/docker/redis/testdb-entrypoint.sh b/docker/redis/testdb-entrypoint.sh new file mode 100755 index 00000000..c96b6acc --- /dev/null +++ b/docker/redis/testdb-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Starts redis-server, seeds it once it answers, then waits for the server so +# that a stop signal reaches it and the container exits with its status. +set -eu + +redis-server --save "" --appendonly no "$@" & +SERVER=$! +trap 'kill -TERM "$SERVER" 2>/dev/null' TERM INT + +REDIS_HOST=127.0.0.1 REDIS_PORT=6379 sh /seed.sh + +wait "$SERVER"