2020-09-09 22:14:59 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
"""System Migration handler"""
|
|
|
|
from importlib.util import module_from_spec, spec_from_file_location
|
|
|
|
from inspect import getmembers, isclass
|
2023-10-06 11:51:23 +00:00
|
|
|
from os import environ, system
|
2020-09-09 22:14:59 +00:00
|
|
|
from pathlib import Path
|
|
|
|
from typing import Any
|
|
|
|
|
2023-10-06 11:51:23 +00:00
|
|
|
from psycopg import Connection, Cursor, connect
|
2021-01-01 14:39:43 +00:00
|
|
|
from structlog.stdlib import get_logger
|
2020-09-09 22:14:59 +00:00
|
|
|
|
2020-12-05 21:08:42 +00:00
|
|
|
from authentik.lib.config import CONFIG
|
2020-09-09 22:14:59 +00:00
|
|
|
|
|
|
|
LOGGER = get_logger()
|
2021-04-18 12:47:50 +00:00
|
|
|
ADV_LOCK_UID = 1000
|
2021-10-04 21:14:16 +00:00
|
|
|
LOCKED = False
|
2020-09-09 22:14:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BaseMigration:
|
|
|
|
"""Base System Migration"""
|
|
|
|
|
2023-10-06 11:51:23 +00:00
|
|
|
cur: Cursor
|
|
|
|
con: Connection
|
2020-09-09 22:14:59 +00:00
|
|
|
|
|
|
|
def __init__(self, cur: Any, con: Any):
|
|
|
|
self.cur = cur
|
|
|
|
self.con = con
|
|
|
|
|
2023-10-06 11:51:23 +00:00
|
|
|
def system_crit(self, command: str):
|
|
|
|
"""Run system command"""
|
|
|
|
LOGGER.debug("Running system_crit command", command=command)
|
|
|
|
retval = system(command) # nosec
|
|
|
|
if retval != 0:
|
|
|
|
raise Exception("Migration error")
|
|
|
|
|
|
|
|
def fake_migration(self, *app_migration: tuple[str, str]):
|
|
|
|
for app, migration in app_migration:
|
|
|
|
self.system_crit(f"./manage.py migrate {app} {migration} --fake")
|
|
|
|
|
2020-09-09 22:14:59 +00:00
|
|
|
def needs_migration(self) -> bool:
|
|
|
|
"""Return true if Migration needs to be run"""
|
|
|
|
return False
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Run the actual migration"""
|
|
|
|
|
|
|
|
|
2021-10-04 21:14:16 +00:00
|
|
|
def wait_for_lock():
|
|
|
|
"""lock an advisory lock to prevent multiple instances from migrating at once"""
|
|
|
|
LOGGER.info("waiting to acquire database lock")
|
|
|
|
curr.execute("SELECT pg_advisory_lock(%s)", (ADV_LOCK_UID,))
|
|
|
|
# pylint: disable=global-statement
|
|
|
|
global LOCKED
|
|
|
|
LOCKED = True
|
|
|
|
|
|
|
|
|
|
|
|
def release_lock():
|
|
|
|
"""Release database lock"""
|
|
|
|
if not LOCKED:
|
|
|
|
return
|
|
|
|
curr.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))
|
|
|
|
|
|
|
|
|
2020-09-09 22:14:59 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
conn = connect(
|
2023-07-19 21:13:22 +00:00
|
|
|
dbname=CONFIG.get("postgresql.name"),
|
|
|
|
user=CONFIG.get("postgresql.user"),
|
|
|
|
password=CONFIG.get("postgresql.password"),
|
|
|
|
host=CONFIG.get("postgresql.host"),
|
2023-07-31 17:34:59 +00:00
|
|
|
port=CONFIG.get_int("postgresql.port"),
|
2023-07-19 21:13:22 +00:00
|
|
|
sslmode=CONFIG.get("postgresql.sslmode"),
|
|
|
|
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
|
|
|
sslcert=CONFIG.get("postgresql.sslcert"),
|
|
|
|
sslkey=CONFIG.get("postgresql.sslkey"),
|
2020-09-09 22:14:59 +00:00
|
|
|
)
|
|
|
|
curr = conn.cursor()
|
2021-04-18 12:47:50 +00:00
|
|
|
try:
|
2021-08-03 15:45:16 +00:00
|
|
|
for migration in Path(__file__).parent.absolute().glob("system_migrations/*.py"):
|
2021-04-18 12:47:50 +00:00
|
|
|
spec = spec_from_file_location("lifecycle.system_migrations", migration)
|
2022-09-06 22:23:25 +00:00
|
|
|
if not spec:
|
|
|
|
continue
|
2021-04-18 12:47:50 +00:00
|
|
|
mod = module_from_spec(spec)
|
|
|
|
spec.loader.exec_module(mod)
|
2020-09-09 22:14:59 +00:00
|
|
|
|
2021-04-18 12:47:50 +00:00
|
|
|
for name, sub in getmembers(mod, isclass):
|
|
|
|
if name != "Migration":
|
|
|
|
continue
|
|
|
|
migration = sub(curr, conn)
|
|
|
|
if migration.needs_migration():
|
2021-10-04 21:14:16 +00:00
|
|
|
wait_for_lock()
|
2021-04-18 12:47:50 +00:00
|
|
|
LOGGER.info("Migration needs to be applied", migration=sub)
|
|
|
|
migration.run()
|
|
|
|
LOGGER.info("Migration finished applying", migration=sub)
|
2021-10-04 21:14:16 +00:00
|
|
|
release_lock()
|
2021-04-18 12:47:50 +00:00
|
|
|
LOGGER.info("applying django migrations")
|
2023-10-06 11:51:23 +00:00
|
|
|
environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
2021-10-04 21:14:16 +00:00
|
|
|
wait_for_lock()
|
2021-04-18 12:47:50 +00:00
|
|
|
try:
|
|
|
|
from django.core.management import execute_from_command_line
|
|
|
|
except ImportError as exc:
|
|
|
|
raise ImportError(
|
|
|
|
"Couldn't import Django. Are you sure it's installed and "
|
|
|
|
"available on your PYTHONPATH environment variable? Did you "
|
|
|
|
"forget to activate a virtual environment?"
|
|
|
|
) from exc
|
|
|
|
execute_from_command_line(["", "migrate"])
|
|
|
|
finally:
|
2021-10-04 21:14:16 +00:00
|
|
|
release_lock()
|