merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit addffb91d12cea6a821f754dc29c7d4506a8855c
parent bc0c87b0a481bdee371ff75a47dd6bd035fe7789
Author: Florian Dold <dold@taler.net>
Date:   Mon,  7 Sep 2026 22:45:07 +0200

merchantdb: run migration tests when invoked as root

Launch PostgreSQL server processes as the postgres account when the
migration test runs as root. Keep SQL clients as the caller so they can
read the checkout, and retain the private socket and temporary cluster
cleanup.

Fail root execution when prerequisites are missing instead of silently
skipping migration coverage.

Diffstat:
Msrc/backenddb/test_order_sequence_migrations.py | 31+++++++++++++++++++++++--------
1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/src/backenddb/test_order_sequence_migrations.py b/src/backenddb/test_order_sequence_migrations.py @@ -28,6 +28,7 @@ the contract insertion check does not exercise the HTTP claim endpoint. from contextlib import contextmanager import os from pathlib import Path +import pwd import shutil import subprocess import sys @@ -50,22 +51,35 @@ def postgres_cluster(bindir): """Keep all test data in a disposable server, accessible only by Unix socket.""" with tempfile.TemporaryDirectory(prefix="merchant-seq-", dir="/tmp") as tmp: data = Path(tmp) / "data" + server_options = {"cwd": tmp} + if os.geteuid() == 0: + # CI runs as root, but PostgreSQL requires an unprivileged server. + # Keep SQL clients as the caller so they can read the checkout. + try: + account = pwd.getpwnam("postgres") + except KeyError: + raise RuntimeError("Root execution requires the postgres account") from None + if account.pw_uid == 0: + raise RuntimeError("The postgres account must be unprivileged") + os.chown(tmp, account.pw_uid, account.pw_gid) + server_options.update(user=account.pw_uid, group=account.pw_gid, + extra_groups=[]) env = {key: value for key, value in os.environ.items() if not key.startswith("PG")} env.update(PGHOST=tmp, PGPORT="5432", PGUSER="postgres", PGOPTIONS="-c client_min_messages=warning") run([str(bindir / "initdb"), "-D", str(data), "-A", "trust", - "-U", "postgres", "--no-locale"], env=env) + "-U", "postgres", "--no-locale"], env=env, **server_options) try: run([str(bindir / "pg_ctl"), "-D", str(data), "-l", str(Path(tmp) / "server.log"), "-o", f"-F -k {tmp} -c listen_addresses=''", "-w", "start"], - env=env) + env=env, **server_options) yield env finally: if (data / "postmaster.pid").exists(): run([str(bindir / "pg_ctl"), "-D", str(data), "-m", "immediate", - "-w", "stop"], env=env) + "-w", "stop"], env=env, **server_options) class Database: @@ -329,14 +343,15 @@ class OrderSequenceMigrations(unittest.TestCase): def main(): source, build = (Path(arg).resolve() for arg in sys.argv[1:]) + # Missing CI prerequisites must not silently disable migration coverage. + unavailable_status = 1 if os.geteuid() == 0 else 77 if not shutil.which("pg_config"): print("PostgreSQL server tools unavailable") - return 77 + return unavailable_status bindir = Path(run(["pg_config", "--bindir"])) - if os.geteuid() == 0 or not all((bindir / tool).exists() for tool in - ("initdb", "pg_ctl", "psql")): - print("Need PostgreSQL server tools and an unprivileged user") - return 77 + if not all((bindir / tool).exists() for tool in ("initdb", "pg_ctl", "psql")): + print("PostgreSQL server tools unavailable") + return unavailable_status with postgres_cluster(bindir) as env: admin = Database("template1", bindir, env, build)