67 lines
1.7 KiB
Python
67 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()
|