"""Shared-MariaDB operations: create/drop per-site database + least-priv user. SQL is piped over stdin to ``docker exec -i mariadb mariadb`` so the root password never appears in the process list; the command itself is logged redacted. """ from __future__ import annotations import os from pathlib import Path from . import naming from .config import Config, Site from .errors import CommandError from .runner import Runner def root_password(cfg: Config) -> str: val = os.environ.get("MARIADB_ROOT_PASSWORD") if val: return val p = Path(cfg.mariadb_env_file) if p.is_file(): for line in p.read_text(encoding="utf-8").splitlines(): if line.startswith("MARIADB_ROOT_PASSWORD="): return line.split("=", 1)[1].strip() raise CommandError( "MARIADB_ROOT_PASSWORD not set (env var or mariadb .env at " f"{cfg.mariadb_env_file})" ) def _exec_sql(runner: Runner, cfg: Config, sql: str) -> None: pw = "" if runner.dry_run else root_password(cfg) runner.run( ["docker", "exec", "-i", cfg.mariadb_container, "mariadb", "-uroot", f"-p{pw}"], input=sql, secret=True, ) def create_db_user(runner: Runner, cfg: Config, site: Site, password: str) -> None: db = naming.database_name(site.customer, site.site) user = naming.database_user(site.customer, site.site) sql = ( f"CREATE DATABASE IF NOT EXISTS `{db}` " "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n" f"CREATE USER IF NOT EXISTS '{user}'@'%' IDENTIFIED BY '{password}';\n" f"ALTER USER '{user}'@'%' IDENTIFIED BY '{password}';\n" f"GRANT ALL PRIVILEGES ON `{db}`.* TO '{user}'@'%';\n" "FLUSH PRIVILEGES;\n" ) _exec_sql(runner, cfg, sql) def drop_db_user(runner: Runner, cfg: Config, site: Site) -> None: db = naming.database_name(site.customer, site.site) user = naming.database_user(site.customer, site.site) sql = ( f"DROP USER IF EXISTS '{user}'@'%';\n" f"DROP DATABASE IF EXISTS `{db}`;\n" "FLUSH PRIVILEGES;\n" ) _exec_sql(runner, cfg, sql)