tracksolid_timescale_grafan.../run_migrations.py

89 lines
2.3 KiB
Python
Raw Normal View History

"""
run_migrations.py Idempotent SQL migration runner for Docker init.
Uses psql (not psycopg2) so each statement runs independently
one error doesn't roll back the entire file.
"""
import os
import subprocess
import sys
import psycopg2
DATABASE_URL = os.environ["DATABASE_URL"]
MIGRATIONS = [
"02_tracksolid_full_schema_rev.sql",
"03_webhook_schema_migration.sql",
]
CRITICAL_TABLES = [
"tracksolid.devices",
"tracksolid.api_token_cache",
"tracksolid.ingestion_log",
"tracksolid.live_positions",
"tracksolid.position_history",
"tracksolid.trips",
"tracksolid.alarms",
"tracksolid.obd_readings",
]
def run_file(path, filename):
"""Execute a SQL file via psql. Returns True on success."""
print(f"Running {filename}...")
result = subprocess.run(
["psql", DATABASE_URL, "-f", path],
capture_output=True, text=True,
)
# psql prints errors to stderr but continues by default
errors = [l for l in result.stderr.splitlines() if "ERROR:" in l]
if errors:
for e in errors:
print(f" WARN: {e.strip()}")
return False
print(f" OK: {filename}")
return True
def verify_schema():
"""Verify critical tables exist. Exit 1 if not — prevents services from starting."""
print("Verifying schema...")
conn = psycopg2.connect(DATABASE_URL)
cur = conn.cursor()
missing = []
for table in CRITICAL_TABLES:
schema, name = table.split(".")
cur.execute(
"SELECT 1 FROM information_schema.tables WHERE table_schema=%s AND table_name=%s",
(schema, name),
)
if not cur.fetchone():
missing.append(table)
cur.close()
conn.close()
if missing:
print(f"FATAL: Missing critical tables: {', '.join(missing)}")
print("Schema bootstrap failed. Services cannot start.")
sys.exit(1)
print(" All critical tables verified.")
def main():
print("=== Database Migration Runner ===")
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
run_file(path, sql_file)
verify_schema()
print("Migrations complete.")
if __name__ == "__main__":
main()