#!/usr/bin/env bash # Reusable podman/docker test database tool for postgres, mssql and mysql. # Usage: dbtest.sh [args] set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$HERE/../.." && pwd)" TIMEOUT="${DBTEST_TIMEOUT:-120}" ALL_DBS=(postgres mssql mysql) usage() { cat < [args] commands: up start container, wait until ready, run init down stop and remove container restart down + up status running state and connection string env print 'export VAR=conn' (use: eval "\$(dbtest.sh env postgres)") logs container logs shell interactive client exec run a SQL file against the database test [pkgs] up, run go tests with conn env set, down (keep with DBTEST_KEEP=1) list supported databases dbs: ${ALL_DBS[*]} env: DBTEST_RUNTIME=podman|docker DBTEST_TIMEOUT=secs DBTEST_KEEP=1 DBTEST_GOFLAGS=... USAGE } die() { echo "error: $*" >&2; exit 1; } log() { echo "[dbtest] $*" >&2; } detect_runtime() { if [ -n "${DBTEST_RUNTIME:-}" ]; then echo "$DBTEST_RUNTIME"; return; fi if command -v podman >/dev/null 2>&1; then echo podman elif command -v docker >/dev/null 2>&1; then echo docker else die "neither podman nor docker is installed"; fi } RUNTIME="$(detect_runtime)" rt() { "$RUNTIME" "$@"; } load_db() { local f="$HERE/dbs/${1:-}.sh" [ -f "$f" ] || die "unknown db '${1:-}' (supported: ${ALL_DBS[*]})" # shellcheck disable=SC1090 source "$f" } is_running() { [ "$(rt inspect -f '{{.State.Running}}' "$DB_CONTAINER" 2>/dev/null || true)" = "true" ]; } cmd_up() { if is_running; then log "$DB_NAME already running" else rt rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true log "starting $DB_NAME ($DB_IMAGE) on port $DB_PORT using $RUNTIME" rt run -d --name "$DB_CONTAINER" "${DB_ENV[@]}" \ -p "$DB_PORT:$DB_INTERNAL_PORT" \ -v "$DB_INIT_MOUNT:ro,Z" "$DB_IMAGE" >/dev/null fi log "waiting for $DB_NAME (max ${TIMEOUT}s)" local i=0 until db_ready; do i=$((i + 1)) if [ "$i" -ge "$TIMEOUT" ]; then rt logs --tail 50 "$DB_CONTAINER" >&2 || true die "$DB_NAME did not become ready" fi sleep 1 done db_post_init log "$DB_NAME ready: $DB_CONN_VAR=$DB_CONN" } cmd_down() { rt rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true log "$DB_NAME removed" } cmd_status() { if is_running; then echo "$DB_NAME: running ($DB_CONTAINER, port $DB_PORT)"; else echo "$DB_NAME: stopped"; fi echo "$DB_CONN_VAR=$DB_CONN" } cmd_test() { local pkgs="${*:-$DB_DEFAULT_PKGS}" [ -n "$pkgs" ] || die "no test packages for $DB_NAME; pass packages as arguments" cmd_up [ -n "${DBTEST_KEEP:-}" ] || trap cmd_down EXIT export "$DB_CONN_VAR=$DB_CONN" cd "$ROOT" # shellcheck disable=SC2086 go test -count=1 ${DBTEST_GOFLAGS:-} $pkgs } cmd="${1:-}" case "$cmd" in ""|-h|--help|help) usage; exit 0 ;; list) printf '%s\n' "${ALL_DBS[@]}"; exit 0 ;; esac shift target="${1:-}" [ -n "$target" ] || die "missing " shift || true run_one() { load_db "$1" shift case "$cmd" in up) cmd_up ;; down) cmd_down ;; restart) cmd_down; cmd_up ;; status) cmd_status ;; env) echo "export $DB_CONN_VAR='$DB_CONN'" ;; logs) rt logs "$DB_CONTAINER" ;; shell) db_shell "$@" ;; exec) [ -f "${1:-}" ] || die "usage: exec "; db_exec_file "$1" ;; test) cmd_test "$@" ;; *) usage; die "unknown command '$cmd'" ;; esac } if [ "$target" = "all" ]; then case "$cmd" in up|down|restart|status|env) ;; *) die "'all' is only valid for up/down/restart/status/env" ;; esac for d in "${ALL_DBS[@]}"; do (run_one "$d"); done else run_one "$target" "$@" fi