blueprints: watch blueprints directory and trigger tasks (#4309)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
5b68942b23
commit
bd56922a2f
|
@ -10,6 +10,13 @@ from django.utils.text import slugify
|
|||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog.stdlib import get_logger
|
||||
from watchdog.events import (
|
||||
FileCreatedEvent,
|
||||
FileModifiedEvent,
|
||||
FileSystemEvent,
|
||||
FileSystemEventHandler,
|
||||
)
|
||||
from watchdog.observers import Observer
|
||||
from yaml import load
|
||||
from yaml.error import YAMLError
|
||||
|
||||
|
@ -32,6 +39,7 @@ from authentik.lib.config import CONFIG
|
|||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
_file_watcher_started = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -45,6 +53,39 @@ class BlueprintFile:
|
|||
meta: Optional[BlueprintMetadata] = field(default=None)
|
||||
|
||||
|
||||
def start_blueprint_watcher():
|
||||
"""Start blueprint watcher, if it's not running already."""
|
||||
# This function might be called twice since it's called on celery startup
|
||||
# pylint: disable=global-statement
|
||||
global _file_watcher_started
|
||||
if _file_watcher_started:
|
||||
return
|
||||
observer = Observer()
|
||||
observer.schedule(BlueprintEventHandler(), CONFIG.y("blueprints_dir"), recursive=True)
|
||||
observer.start()
|
||||
_file_watcher_started = True
|
||||
|
||||
|
||||
class BlueprintEventHandler(FileSystemEventHandler):
|
||||
"""Event handler for blueprint events"""
|
||||
|
||||
def on_any_event(self, event: FileSystemEvent):
|
||||
if not isinstance(event, (FileCreatedEvent, FileModifiedEvent)):
|
||||
return
|
||||
if event.is_directory:
|
||||
return
|
||||
if isinstance(event, FileCreatedEvent):
|
||||
LOGGER.debug("new blueprint file created, starting discovery")
|
||||
blueprints_discover.delay()
|
||||
if isinstance(event, FileModifiedEvent):
|
||||
path = Path(event.src_path)
|
||||
root = Path(CONFIG.y("blueprints_dir")).absolute()
|
||||
rel_path = str(path.relative_to(root))
|
||||
for instance in BlueprintInstance.objects.filter(path=rel_path):
|
||||
LOGGER.debug("modified blueprint file, starting apply", instance=instance)
|
||||
apply_blueprint.delay(instance.pk.hex)
|
||||
|
||||
|
||||
@CELERY_APP.task(
|
||||
throws=(DatabaseError, ProgrammingError, InternalError),
|
||||
)
|
||||
|
@ -60,8 +101,7 @@ def blueprints_find():
|
|||
"""Find blueprints and return valid ones"""
|
||||
blueprints = []
|
||||
root = Path(CONFIG.y("blueprints_dir"))
|
||||
for file in root.glob("**/*.yaml"):
|
||||
path = Path(file)
|
||||
for path in root.glob("**/*.yaml"):
|
||||
LOGGER.debug("found blueprint", path=str(path))
|
||||
with open(path, "r", encoding="utf-8") as blueprint_file:
|
||||
try:
|
||||
|
|
|
@ -99,6 +99,9 @@ def worker_ready_hook(*args, **kwargs):
|
|||
task.delay()
|
||||
except ProgrammingError as exc:
|
||||
LOGGER.warning("Startup task failed", task=task, exc=exc)
|
||||
from authentik.blueprints.v1.tasks import start_blueprint_watcher
|
||||
|
||||
start_blueprint_watcher()
|
||||
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
|
|
|
@ -436,6 +436,7 @@ _LOGGING_HANDLER_MAP = {
|
|||
"asyncio": "WARNING",
|
||||
"redis": "WARNING",
|
||||
"silk": "INFO",
|
||||
"fsevents": "WARNING",
|
||||
}
|
||||
for handler_name, level in _LOGGING_HANDLER_MAP.items():
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
|
|
4629
poetry.lock
generated
4629
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -155,6 +155,7 @@ webauthn = "*"
|
|||
wsproto = "*"
|
||||
xmlsec = "*"
|
||||
zxcvbn = "*"
|
||||
watchdog = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
bandit = "*"
|
||||
|
|
|
@ -13,6 +13,11 @@ Blueprints offer a new way to template, automate and distribute authentik config
|
|||
Blueprints are yaml files, whose format is described further in [File structure](./v1/structure). Blueprints can be applied in one of two ways:
|
||||
|
||||
- As a Blueprint instance, which is a YAML file mounted into the authentik (worker) container. This file is read and applied regularly (every 60 minutes). Multiple instances can be created for a single blueprint file, and instances can be given context key:value attributes to configure the blueprint.
|
||||
|
||||
:::info
|
||||
Starting with authentik 2022.12.1, authentik listens for file modification/creation events in the blueprint directory and will automatically trigger a discovery when a new blueprint file is created, and trigger a blueprint apply when a file is modified.
|
||||
:::
|
||||
|
||||
- As a Flow import, which is a YAML file uploaded via the Browser/API. This file is validated and applied directly after being uploaded, but is not further monitored/applied.
|
||||
|
||||
Starting with authentik 2022.8, blueprints are used to manage authentik default flows and other system objects. These blueprints can be disabled/replaced with custom blueprints in certain circumstances.
|
||||
|
|
Reference in a new issue