63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Startup guards: the VISUAL target must match STACK, and insecure LDAP is never silent."""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from .settings import Settings
|
||
|
|
|
||
|
|
log = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
_TARGET_FIELDS = {"server", "database", "profile"}
|
||
|
|
|
||
|
|
|
||
|
|
class StackGuardError(Exception):
|
||
|
|
"""The configuration must not start."""
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class GuardResult:
|
||
|
|
target: str # e.g. "VSQL\DEV / PESCOINCDEV (DEV)"
|
||
|
|
production: bool
|
||
|
|
explicit: bool
|
||
|
|
warnings: list[str]
|
||
|
|
|
||
|
|
|
||
|
|
def load_visual_settings() -> Any:
|
||
|
|
"""visualkit's own Settings (VISUAL_* variables). Tests monkeypatch this."""
|
||
|
|
from visualkit.config import Settings as VisualSettings
|
||
|
|
|
||
|
|
return VisualSettings()
|
||
|
|
|
||
|
|
|
||
|
|
def check_stack(settings: Settings, visual: Any | None = None) -> GuardResult:
|
||
|
|
"""Raise StackGuardError when STACK and the VISUAL target disagree."""
|
||
|
|
if visual is None:
|
||
|
|
visual = load_visual_settings()
|
||
|
|
production = bool(visual.is_production)
|
||
|
|
explicit = bool(_TARGET_FIELDS & set(getattr(visual, "model_fields_set", set())))
|
||
|
|
target = f"{visual.server} / {visual.database} ({'PRODUCTION' if production else 'DEV'})"
|
||
|
|
warnings: list[str] = []
|
||
|
|
if settings.stack != "prod" and production:
|
||
|
|
raise StackGuardError(f"STACK={settings.stack} but the VISUAL target is production: {target}")
|
||
|
|
if settings.stack == "prod" and not production:
|
||
|
|
if not explicit:
|
||
|
|
raise StackGuardError(
|
||
|
|
"STACK=prod but no VISUAL target is set (VisualKit would default to DEV); "
|
||
|
|
"set VISUAL_PROFILE=prod or VISUAL_SERVER and VISUAL_DATABASE"
|
||
|
|
)
|
||
|
|
warnings.append(f"STACK=prod reads a non-production VISUAL target: {target}")
|
||
|
|
if settings.ldap_allow_insecure:
|
||
|
|
warnings.append(f"LDAP_ALLOW_INSECURE=true on STACK={settings.stack}: LDAP may run without TLS")
|
||
|
|
return GuardResult(target, production, explicit, warnings)
|
||
|
|
|
||
|
|
|
||
|
|
def startup_checks(settings: Settings) -> GuardResult:
|
||
|
|
"""Run at app and worker start: raise on a bad stack, log every warning."""
|
||
|
|
result = check_stack(settings)
|
||
|
|
for w in result.warnings:
|
||
|
|
log.warning(w)
|
||
|
|
return result
|