
* root: move database calls from ready() to dedicated startup signal Signed-off-by: Jens Langhammer <jens@goauthentik.io> * optimise gunicorn startup to only do DB code in one worker Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always use 2 workers in compose Signed-off-by: Jens Langhammer <jens@goauthentik.io> * send startup signals for test runner Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove k8s import that isn't really needed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ci: bump nested actions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix @reconcile_app not triggering reconcile due to changed functions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * connect startup with uid Signed-off-by: Jens Langhammer <jens@goauthentik.io> * adjust some log levels Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove internal healthcheck we didn't really use it to do anything, and we shouldn't have to since the live/ready probes are handled by django anyways and so the container runtime will restart the server if needed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add setproctitle for gunicorn and celery process titles Signed-off-by: Jens Langhammer <jens@goauthentik.io> * configure structlog early to use it Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Revert "configure structlog early to use it" This reverts commit 16778fdbbca0f5c474d376c2f85c6f8032c06044. * Revert "adjust some log levels" This reverts commit a129f7ab6aecf27f1206aea1ad8384ce897b74ad. Signed-off-by: Jens Langhammer <jens@goauthentik.io> # Conflicts: # authentik/root/settings.py * optimize startup to not spawn a bunch of one-off processes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * idk why this shows up Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
"""authentik Blueprints app"""
|
|
|
|
from collections.abc import Callable
|
|
from importlib import import_module
|
|
from inspect import ismethod
|
|
|
|
from django.apps import AppConfig
|
|
from django.db import DatabaseError, InternalError, ProgrammingError
|
|
from structlog.stdlib import BoundLogger, get_logger
|
|
|
|
from authentik.root.signals import startup
|
|
|
|
|
|
class ManagedAppConfig(AppConfig):
|
|
"""Basic reconciliation logic for apps"""
|
|
|
|
logger: BoundLogger
|
|
|
|
RECONCILE_GLOBAL_CATEGORY: str = "global"
|
|
RECONCILE_TENANT_CATEGORY: str = "tenant"
|
|
|
|
def __init__(self, app_name: str, *args, **kwargs) -> None:
|
|
super().__init__(app_name, *args, **kwargs)
|
|
self.logger = get_logger().bind(app_name=app_name)
|
|
|
|
def ready(self) -> None:
|
|
self.import_related()
|
|
startup.connect(self._on_startup_callback, dispatch_uid=self.label)
|
|
return super().ready()
|
|
|
|
def _on_startup_callback(self, sender, **_):
|
|
self._reconcile_global()
|
|
self._reconcile_tenant()
|
|
|
|
def import_related(self):
|
|
"""Automatically import related modules which rely on just being imported
|
|
to register themselves (mainly django signals and celery tasks)"""
|
|
|
|
def import_relative(rel_module: str):
|
|
try:
|
|
module_name = f"{self.name}.{rel_module}"
|
|
import_module(module_name)
|
|
self.logger.info("Imported related module", module=module_name)
|
|
except ModuleNotFoundError:
|
|
pass
|
|
|
|
import_relative("checks")
|
|
import_relative("tasks")
|
|
import_relative("signals")
|
|
|
|
def import_module(self, path: str):
|
|
"""Load module"""
|
|
import_module(path)
|
|
|
|
def _reconcile(self, prefix: str) -> None:
|
|
for meth_name in dir(self):
|
|
meth = getattr(self, meth_name)
|
|
if not ismethod(meth):
|
|
continue
|
|
category = getattr(meth, "_authentik_managed_reconcile", None)
|
|
if category != prefix:
|
|
continue
|
|
name = meth_name.replace(prefix, "")
|
|
try:
|
|
self.logger.debug("Starting reconciler", name=name)
|
|
meth()
|
|
self.logger.debug("Successfully reconciled", name=name)
|
|
except (DatabaseError, ProgrammingError, InternalError) as exc:
|
|
self.logger.warning("Failed to run reconcile", name=name, exc=exc)
|
|
|
|
@staticmethod
|
|
def reconcile_tenant(func: Callable):
|
|
"""Mark a function to be called on startup (for each tenant)"""
|
|
func._authentik_managed_reconcile = ManagedAppConfig.RECONCILE_TENANT_CATEGORY
|
|
return func
|
|
|
|
@staticmethod
|
|
def reconcile_global(func: Callable):
|
|
"""Mark a function to be called on startup (globally)"""
|
|
func._authentik_managed_reconcile = ManagedAppConfig.RECONCILE_GLOBAL_CATEGORY
|
|
return func
|
|
|
|
def _reconcile_tenant(self) -> None:
|
|
"""reconcile ourselves for tenanted methods"""
|
|
from authentik.tenants.models import Tenant
|
|
|
|
try:
|
|
tenants = list(Tenant.objects.filter(ready=True))
|
|
except (DatabaseError, ProgrammingError, InternalError) as exc:
|
|
self.logger.debug("Failed to get tenants to run reconcile", exc=exc)
|
|
return
|
|
for tenant in tenants:
|
|
with tenant:
|
|
self._reconcile(self.RECONCILE_TENANT_CATEGORY)
|
|
|
|
def _reconcile_global(self) -> None:
|
|
"""
|
|
reconcile ourselves for global methods.
|
|
Used for signals, tasks, etc. Database queries should not be made in here.
|
|
"""
|
|
from django_tenants.utils import get_public_schema_name, schema_context
|
|
|
|
with schema_context(get_public_schema_name()):
|
|
self._reconcile(self.RECONCILE_GLOBAL_CATEGORY)
|
|
|
|
|
|
class AuthentikBlueprintsConfig(ManagedAppConfig):
|
|
"""authentik Blueprints app"""
|
|
|
|
name = "authentik.blueprints"
|
|
label = "authentik_blueprints"
|
|
verbose_name = "authentik Blueprints"
|
|
default = True
|
|
|
|
@ManagedAppConfig.reconcile_global
|
|
def load_blueprints_v1_tasks(self):
|
|
"""Load v1 tasks"""
|
|
self.import_module("authentik.blueprints.v1.tasks")
|
|
|
|
@ManagedAppConfig.reconcile_tenant
|
|
def blueprints_discovery(self):
|
|
"""Run blueprint discovery"""
|
|
from authentik.blueprints.v1.tasks import blueprints_discovery, clear_failed_blueprints
|
|
|
|
blueprints_discovery.delay()
|
|
clear_failed_blueprints.delay()
|
|
|
|
def import_models(self):
|
|
super().import_models()
|
|
self.import_module("authentik.blueprints.v1.meta.apply_blueprint")
|