Files

60 lines
2.3 KiB
Python

"""Run before `alembic upgrade`: when there are database changes to apply, ask the backup container for a
fresh dump and wait for it, so every upgrade can be rolled back to the moment just before it.
The api stops (and Docker retries it) if the backup fails, unless SKIP_PRE_UPGRADE_BACKUP=true.
"""
import os
import sys
import time
import uuid
from pathlib import Path
from alembic.config import Config
from alembic.runtime.migration import MigrationContext
from alembic.script import ScriptDirectory
from .db import engine
REQUESTS = Path(os.getenv("BACKUP_REQUEST_DIR", "/requests"))
TIMEOUT = int(os.getenv("PRE_UPGRADE_BACKUP_TIMEOUT", "900"))
def pending() -> tuple[str | None, str]:
head = ScriptDirectory.from_config(Config("alembic.ini")).get_current_head()
with engine.connect() as conn:
current = MigrationContext.configure(conn).get_current_revision()
return current, head
def main() -> int:
current, head = pending()
if current is None or current == head:
return 0 # new database, or nothing to migrate
if os.getenv("SKIP_PRE_UPGRADE_BACKUP", "").lower() == "true":
print(f"pre-upgrade backup skipped ({current} -> {head})", flush=True)
return 0
if not REQUESTS.is_dir():
print(f"pre-upgrade backup: {REQUESTS} not mounted, cannot ask the backup container", file=sys.stderr, flush=True)
return 1
rid = time.strftime("%Y%m%d%H%M%S") + "-" + uuid.uuid4().hex[:6]
(REQUESTS / f"{rid}.request").write_text(f"database upgrade {current} -> {head}\n")
print(f"database upgrade {current} -> {head}: waiting for the backup container to take a backup", flush=True)
done = REQUESTS / f"{rid}.done"
deadline = time.time() + TIMEOUT
while time.time() < deadline:
if done.exists():
answer = done.read_text().strip() # the backup container clears old answers itself
if answer == "ok":
print("pre-upgrade backup written", flush=True)
return 0
print("pre-upgrade backup FAILED; not upgrading. See: docker compose logs backup", file=sys.stderr, flush=True)
return 1
time.sleep(2)
print("pre-upgrade backup did not answer in time; not upgrading. Is the backup container running?",
file=sys.stderr, flush=True)
return 1
if __name__ == "__main__":
sys.exit(main())