- New run_migrations.py: executes 02_*.sql and 03_*.sql in order - New db_migrate service: runs once before all other services start - All services now depend on db_migrate (service_completed_successfully) - Tolerates re-deploy: catches errors from already-existing objects Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
66 lines
1.7 KiB
Python
66 lines
1.7 KiB
Python
"""
|
|
run_migrations.py — Idempotent SQL migration runner for Docker init service.
|
|
Executes each .sql migration file in order using psycopg2.
|
|
Tolerates re-run errors (e.g. "policy already exists") so deploys are safe.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import psycopg2
|
|
|
|
DATABASE_URL = os.environ["DATABASE_URL"]
|
|
|
|
MIGRATIONS = [
|
|
"02_tracksolid_full_schema_rev.sql",
|
|
"03_webhook_schema_migration.sql",
|
|
]
|
|
|
|
|
|
def run_file(conn, path, filename):
|
|
"""Execute a SQL file. Returns True on success, False on error."""
|
|
with open(path) as f:
|
|
sql = f.read()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(sql)
|
|
print(f" OK: {filename}")
|
|
return True
|
|
except psycopg2.Error as e:
|
|
msg = (e.pgerror or str(e)).strip().split("\n")[0]
|
|
print(f" WARN: {filename}: {msg}")
|
|
# Connection is now in error state — must reset
|
|
conn.close()
|
|
return False
|
|
|
|
|
|
def main():
|
|
print("=== Database Migration Runner ===")
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
conn.autocommit = True
|
|
|
|
warnings = 0
|
|
for sql_file in MIGRATIONS:
|
|
path = os.path.join("/app", sql_file)
|
|
if not os.path.exists(path):
|
|
print(f" SKIP: {sql_file} (not found)")
|
|
continue
|
|
|
|
print(f"Running {sql_file}...")
|
|
ok = run_file(conn, path, sql_file)
|
|
if not ok:
|
|
warnings += 1
|
|
# Reconnect for the next file
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
conn.autocommit = True
|
|
|
|
conn.close()
|
|
|
|
if warnings:
|
|
print(f"Completed with {warnings} warning(s) (expected on re-deploy).")
|
|
else:
|
|
print("All migrations applied cleanly.")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|